diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 27f938b..3178dff 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -30,7 +30,7 @@ powershell -ExecutionPolicy Bypass -File Tests\pssa-check.ps1
## Pull Request Checklist
-- [ ] All 5,025 tests pass (`Run-Tests.ps1` exits with code 0)
+- [ ] All 5,032 tests pass (`Run-Tests.ps1` exits with code 0)
- [ ] PSScriptAnalyzer reports 0 errors (`pssa-check.ps1`)
- [ ] Monolithic synced (`sync-to-monolithic.ps1` shows 0 parse errors)
- [ ] New functions follow PowerShell verb-noun naming (`Get-`, `Set-`, `Test-`, `Show-`)
diff --git a/Changelog.md b/Changelog.md
index 824d199..2851e0a 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,5 +1,11 @@
# Changelog
+## v1.109.0
+
+VHDX encryption-at-rest verification — added to **BitLocker Management** (`[7] VHDX Encryption-at-Rest Audit`) and via the `VHDXEncryptionAudit` CLI action. A **read-only** check that reports whether each Hyper-V VM's virtual disk (VHD/VHDX) sits on a BitLocker-protected volume — so you can confirm VM storage is encrypted at rest.
+
+It enumerates the virtual disks attached to the host's VMs, resolves the volume each one lives on, and reports **encrypted / unencrypted / unknown** per disk plus a summary count. Volumes that BitLocker can't enumerate (Cluster Shared Volumes, UNC, remote storage) are reported as "unknown" rather than guessed. `-Action VHDXEncryptionAudit -OutputFormat JSON` emits the per-disk result for fleet auditing. Makes no changes.
+
## v1.108.0
Windows Admin Center — new module (77-WindowsAdminCenter.ps1), reachable from **Roles & Features → [13] Windows Admin Center (WAC)** and via the `WACSetup` / `WACStatus` CLI actions. Installs and configures the WAC gateway on this host.
diff --git a/Header.ps1 b/Header.ps1
index ce09a70..99932c0 100644
--- a/Header.ps1
+++ b/Header.ps1
@@ -30,7 +30,7 @@
7h3 4b1d3r
.VERSION
- 1.108.0
+ 1.109.0
.LAST UPDATED
05/23/2026
@@ -1391,7 +1391,7 @@
param(
# CLI headless mode: run a specific action without interactive menus
# Usage: RackStack.exe -Action Cleanup [-Tier Standard] [-Silent] [-OutputFormat JSON]
- [ValidateSet('Cleanup', 'Debloat', 'HealthCheck', 'Batch', 'QuickScan', 'Inventory', 'DriftCheck', 'Snapshot', 'Compliance', 'Harden', 'Remediate', 'Aggregate', 'Compare', 'Export', 'Trend', 'CertCheck', 'ReportHTML', 'ListeningPorts', 'SoftwareList', 'Uptime', 'ServiceAudit', 'EventAudit', 'NetInfo', 'ScheduledExport', 'ValidateConfig', 'Watch', 'Query', 'Diff', 'Baseline', 'Alert', 'FleetScan', 'PatchStatus', 'UserAudit', 'FirewallAudit', 'TaskAudit', 'DiskAudit', 'TLSAudit', 'SMBAudit', 'DriverAudit', 'TimeAudit', 'BootAudit', 'GPOAudit', 'MemoryAudit', 'ProcessAudit', 'BackupAudit', 'ShareAudit', 'DNSAudit', 'PowerAudit', 'RegistryAudit', 'ProfileAudit', 'HyperVAudit', 'NetworkAudit', 'StorageAudit', 'FeatureAudit', 'AutoStartAudit', 'BIOSAudit', 'ClusterAudit', 'AuditPolicyAudit', 'EnvAudit', 'CrashAudit', 'LocalGroupAudit', 'WMIAudit', 'TempAudit', 'UpdatePolicyAudit', 'IISAudit', 'SSHAudit', 'BitLockerAudit', 'PrintAudit', 'CredGuardAudit', 'PortAudit', 'AntivirusAudit', 'DotNetAudit', 'RDPAudit', 'VPNAudit', 'HostsFileAudit', 'NetStatAudit', 'LicenseAudit', 'USBDeviceAudit', 'AppLockerAudit', 'EventSubAudit', 'HotfixAudit', 'SysInfoAudit', 'LogonAudit', 'ACLAudit', 'RecoveryAudit', 'ServiceAccountAudit', 'ProxyAudit', 'PendingRebootAudit', 'PageFileAudit', 'CPUAudit', 'DefenderExclusionAudit', 'KerberosAudit', 'DHCPAudit', 'NUMAAudit', 'SymlinkAudit', 'StartupScriptAudit', 'SecureChannelAudit', 'ComObjectAudit', 'FirewallLogAudit', 'ScheduledRebootAudit', 'PowerShellAudit', 'RouteTableAudit', 'TokenPrivilegeAudit', 'WindowsCapabilityAudit', 'ARPTableAudit', 'LocaleAudit', 'TaskHistoryAudit', 'NTFSAudit', 'Win11Cleanup', 'DarkMode', 'LightMode', 'iSCSIAudit', 'NICTeamAudit', 'SMBSessionAudit', 'WindowsUpdateAudit', 'ClusterQuorumAudit', 'S2DAudit', 'VirtualSwitchAudit', 'MPIOPathAudit', 'ServiceRecoveryAudit', 'VMOvercommitAudit', 'DedupAudit', 'ClusterNetworkAudit', 'ReplicaLagAudit', 'HandleLeakAudit', 'ShadowCopyAudit', 'QoSPolicyAudit', 'LiveMigrationAudit', 'DomainTrustAudit', 'DiskLatencyAudit', 'NICOffloadAudit', 'StorageTimeoutAudit', 'EventLogCapacityAudit', 'TcpSettingsAudit', 'WinRMAudit', 'ClusterHealthScore', 'VMInventoryExport', 'VMSnapshotAudit', 'StorageHealthScore', 'CSVSpaceAudit', 'SMBConnectionAudit', 'VolumeLabelAudit', 'NICErrorAudit', 'VMResourceWaste', 'HealthDashboard', 'SCCMClientAudit', 'SCOMAgentAudit', 'WACConnectivityAudit', 'AzureADAudit', 'ServerScore', 'FleetReport', 'PasswordPolicy', 'FirewallRuleAudit', 'GPResultAudit', 'DNSCacheAudit', 'TPMAudit', 'SecureBootAudit', 'TimeSkewAudit', 'NetworkProfileAudit', 'InsecureServiceAudit', 'SelfTest', 'CheckForUpdate', 'ExportLogs', 'UpdateSelf', 'Rollback', 'ScheduleUpdateCheck', 'Dashboard', 'History', 'Replay', 'AzureArcEnroll', 'DefenderEndpointOnboard', 'WSUSSetup', 'ADCSSetup', 'StorageMigrationSetup', 'GPOBackup', 'GPODrift', 'JEAList', 'NPSSetup', 'AlwaysOnVPNSetup', 'CISScan', 'SIEMSetup', 'SIEMStatus', 'WACSetup', 'WACStatus')]
+ [ValidateSet('Cleanup', 'Debloat', 'HealthCheck', 'Batch', 'QuickScan', 'Inventory', 'DriftCheck', 'Snapshot', 'Compliance', 'Harden', 'Remediate', 'Aggregate', 'Compare', 'Export', 'Trend', 'CertCheck', 'ReportHTML', 'ListeningPorts', 'SoftwareList', 'Uptime', 'ServiceAudit', 'EventAudit', 'NetInfo', 'ScheduledExport', 'ValidateConfig', 'Watch', 'Query', 'Diff', 'Baseline', 'Alert', 'FleetScan', 'PatchStatus', 'UserAudit', 'FirewallAudit', 'TaskAudit', 'DiskAudit', 'TLSAudit', 'SMBAudit', 'DriverAudit', 'TimeAudit', 'BootAudit', 'GPOAudit', 'MemoryAudit', 'ProcessAudit', 'BackupAudit', 'ShareAudit', 'DNSAudit', 'PowerAudit', 'RegistryAudit', 'ProfileAudit', 'HyperVAudit', 'NetworkAudit', 'StorageAudit', 'FeatureAudit', 'AutoStartAudit', 'BIOSAudit', 'ClusterAudit', 'AuditPolicyAudit', 'EnvAudit', 'CrashAudit', 'LocalGroupAudit', 'WMIAudit', 'TempAudit', 'UpdatePolicyAudit', 'IISAudit', 'SSHAudit', 'BitLockerAudit', 'PrintAudit', 'CredGuardAudit', 'PortAudit', 'AntivirusAudit', 'DotNetAudit', 'RDPAudit', 'VPNAudit', 'HostsFileAudit', 'NetStatAudit', 'LicenseAudit', 'USBDeviceAudit', 'AppLockerAudit', 'EventSubAudit', 'HotfixAudit', 'SysInfoAudit', 'LogonAudit', 'ACLAudit', 'RecoveryAudit', 'ServiceAccountAudit', 'ProxyAudit', 'PendingRebootAudit', 'PageFileAudit', 'CPUAudit', 'DefenderExclusionAudit', 'KerberosAudit', 'DHCPAudit', 'NUMAAudit', 'SymlinkAudit', 'StartupScriptAudit', 'SecureChannelAudit', 'ComObjectAudit', 'FirewallLogAudit', 'ScheduledRebootAudit', 'PowerShellAudit', 'RouteTableAudit', 'TokenPrivilegeAudit', 'WindowsCapabilityAudit', 'ARPTableAudit', 'LocaleAudit', 'TaskHistoryAudit', 'NTFSAudit', 'Win11Cleanup', 'DarkMode', 'LightMode', 'iSCSIAudit', 'NICTeamAudit', 'SMBSessionAudit', 'WindowsUpdateAudit', 'ClusterQuorumAudit', 'S2DAudit', 'VirtualSwitchAudit', 'MPIOPathAudit', 'ServiceRecoveryAudit', 'VMOvercommitAudit', 'DedupAudit', 'ClusterNetworkAudit', 'ReplicaLagAudit', 'HandleLeakAudit', 'ShadowCopyAudit', 'QoSPolicyAudit', 'LiveMigrationAudit', 'DomainTrustAudit', 'DiskLatencyAudit', 'NICOffloadAudit', 'StorageTimeoutAudit', 'EventLogCapacityAudit', 'TcpSettingsAudit', 'WinRMAudit', 'ClusterHealthScore', 'VMInventoryExport', 'VMSnapshotAudit', 'StorageHealthScore', 'CSVSpaceAudit', 'SMBConnectionAudit', 'VolumeLabelAudit', 'NICErrorAudit', 'VMResourceWaste', 'HealthDashboard', 'SCCMClientAudit', 'SCOMAgentAudit', 'WACConnectivityAudit', 'AzureADAudit', 'ServerScore', 'FleetReport', 'PasswordPolicy', 'FirewallRuleAudit', 'GPResultAudit', 'DNSCacheAudit', 'TPMAudit', 'SecureBootAudit', 'TimeSkewAudit', 'NetworkProfileAudit', 'InsecureServiceAudit', 'SelfTest', 'CheckForUpdate', 'ExportLogs', 'UpdateSelf', 'Rollback', 'ScheduleUpdateCheck', 'Dashboard', 'History', 'Replay', 'AzureArcEnroll', 'DefenderEndpointOnboard', 'WSUSSetup', 'ADCSSetup', 'StorageMigrationSetup', 'GPOBackup', 'GPODrift', 'JEAList', 'NPSSetup', 'AlwaysOnVPNSetup', 'CISScan', 'SIEMSetup', 'SIEMStatus', 'WACSetup', 'WACStatus', 'VHDXEncryptionAudit')]
[string]$Action,
[ValidateSet('Light', 'Standard', 'Aggressive')]
diff --git a/Modules/00-Initialization.ps1 b/Modules/00-Initialization.ps1
index 5e6f97d..8d35193 100644
--- a/Modules/00-Initialization.ps1
+++ b/Modules/00-Initialization.ps1
@@ -225,7 +225,7 @@ if (-not $PSCommandPath -and $script:ScriptPath) {
if (-not $script:ModuleRoot -and $script:ScriptPath) {
$script:ModuleRoot = [System.IO.Path]::GetDirectoryName($script:ScriptPath)
}
-$script:ScriptVersion = "1.108.0"
+$script:ScriptVersion = "1.109.0"
$script:ScriptStartTime = Get-Date
# Post-update cleanup: UpdateSelf / Rollback leave a `.pending-delete` sibling next to RackStack.exe.
diff --git a/Modules/31-BitLocker.ps1 b/Modules/31-BitLocker.ps1
index e0cb9ae..09e9c95 100644
--- a/Modules/31-BitLocker.ps1
+++ b/Modules/31-BitLocker.ps1
@@ -178,6 +178,7 @@ function Show-BitLockerManagement {
Write-MenuItem -Text "[4] Show Recovery Key"
Write-MenuItem -Text "[5] Check Encryption Progress"
Write-MenuItem -Text "[6] Verify Recovery Key Backup"
+ Write-MenuItem -Text "[7] VHDX Encryption-at-Rest Audit"
Write-OutputColor " └────────────────────────────────────────────────────────────────────────┘" -color "Info"
Write-OutputColor "" -color "Info"
Write-OutputColor " [B] ◄ Back" -color "Info"
@@ -492,11 +493,81 @@ function Show-BitLockerManagement {
Write-OutputColor "" -color "Info"
Test-BitLockerRecoveryBackup
}
+ "7" { Show-VHDXEncryptionAudit }
{ $_ -eq "b" -or $_ -eq "B" } { return }
- default { Write-OutputColor " Invalid choice. Enter 1-6 or B." -color "Error"; Start-Sleep -Seconds 1 }
+ default { Write-OutputColor " Invalid choice. Enter 1-7 or B." -color "Error"; Start-Sleep -Seconds 1 }
}
Write-PressEnter
}
}
+
+# Read-only encryption-at-rest verification: report whether each VHD/VHDX
+# backing a Hyper-V VM sits on a BitLocker-protected volume. No changes made.
+function Get-VHDXEncryptionStatus {
+ $paths = New-Object System.Collections.Generic.List[string]
+ try {
+ if (Get-Command -Name Get-VM -ErrorAction SilentlyContinue) {
+ foreach ($d in (Get-VM -ErrorAction SilentlyContinue | Get-VMHardDiskDrive -ErrorAction SilentlyContinue)) {
+ if (-not [string]::IsNullOrWhiteSpace($d.Path)) { $paths.Add($d.Path) }
+ }
+ }
+ }
+ catch { }
+ $unique = @($paths | Sort-Object -Unique)
+ # Cache volume -> protection status from BitLocker once.
+ $blByMount = @{}
+ try { foreach ($v in (Get-BitLockerVolume -ErrorAction SilentlyContinue)) { $blByMount["$($v.MountPoint)"] = "$($v.ProtectionStatus)" } } catch { }
+ $results = @()
+ foreach ($p in $unique) {
+ $root = $null
+ try { $root = ([System.IO.Path]::GetPathRoot($p)).TrimEnd('\') } catch { }
+ $prot = if ($root -and $blByMount.ContainsKey($root)) { $blByMount[$root] } else { "Unknown" }
+ $results += [PSCustomObject]@{
+ Path = $p; Volume = $root; Protection = $prot; Encrypted = ($prot -eq 'On')
+ }
+ }
+ return $results
+}
+
+# Interactive display of the VHDX encryption-at-rest audit.
+function Show-VHDXEncryptionAudit {
+ Clear-Host
+ Write-CenteredOutput "VHDX Encryption-at-Rest Audit" -color "Info"
+ if ($null -eq (Get-Command -Name Get-BitLockerVolume -ErrorAction SilentlyContinue)) {
+ Write-OutputColor " BitLocker is not available on this system." -color "Warning"; return
+ }
+ $r = @(Get-VHDXEncryptionStatus)
+ if ($r.Count -eq 0) {
+ Write-OutputColor " No VHD/VHDX virtual disks found (no Hyper-V VMs, or none attached)." -color "Info"; return
+ }
+ Write-OutputColor "" -color "Info"
+ foreach ($item in $r) {
+ $label = if ($item.Encrypted) { "ENCRYPTED" } elseif ($item.Protection -eq 'Off') { "UNENCRYPTED" } else { "UNKNOWN" }
+ $c = if ($item.Encrypted) { "Success" } elseif ($item.Protection -eq 'Off') { "Error" } else { "Warning" }
+ Write-OutputColor (" [{0}] {1}" -f $label, $item.Path) -color $c
+ Write-OutputColor (" volume: $(if ($item.Volume) { $item.Volume } else { 'unresolved (CSV/UNC/remote)' })") -color "Debug"
+ }
+ $enc = @($r | Where-Object { $_.Encrypted }).Count
+ Write-OutputColor "" -color "Info"
+ Write-OutputColor " $enc of $($r.Count) virtual disk(s) sit on BitLocker-protected volumes." -color "Info"
+ Write-OutputColor " 'Unknown' volumes (CSV / UNC / remote storage) are not enumerable by Get-BitLockerVolume." -color "Debug"
+}
+
+# CLI entry: VHDXEncryptionAudit — read-only, JSON-aware.
+function Start-VHDXEncryptionAudit {
+ $r = @(Get-VHDXEncryptionStatus)
+ if ($script:CLIOutputFormat -eq 'JSON') {
+ Write-Output (@{
+ Tool = $script:ToolFullName; Version = $script:ScriptVersion; Action = 'VHDXEncryptionAudit'
+ Timestamp = (Get-Date -Format "yyyy-MM-ddTHH:mm:ss"); Hostname = $env:COMPUTERNAME
+ TotalDisks = $r.Count; EncryptedDisks = @($r | Where-Object { $_.Encrypted }).Count
+ Disks = @($r | ForEach-Object { @{ Path = $_.Path; Volume = $_.Volume; Protection = $_.Protection; Encrypted = $_.Encrypted } })
+ } | ConvertTo-Json -Depth 5)
+ }
+ else {
+ Show-VHDXEncryptionAudit
+ }
+ return $true
+}
#endregion
\ No newline at end of file
diff --git a/Modules/34-Help.ps1 b/Modules/34-Help.ps1
index 490c740..f18b7b3 100644
--- a/Modules/34-Help.ps1
+++ b/Modules/34-Help.ps1
@@ -253,7 +253,7 @@ function Search-HelpTopics {
@{ Title = "Performance"; Keywords = @("performance", "cpu", "memory", "disk", "io", "bandwidth", "dashboard", "process"); Description = "Live performance dashboard with CPU, memory, disk I/O, and network bandwidth monitoring" }
@{ Title = "Licensing & NTP"; Keywords = @("license", "activation", "kms", "avma", "ntp", "time", "timezone", "clock"); Description = "Windows licensing status (KMS/AVMA/Retail), NTP configuration, time sync, and timezone setup" }
@{ Title = "VM Management"; Keywords = @("checkpoint", "snapshot", "export", "import", "migration", "vhd", "iso"); Description = "VM checkpoints, export/import, migration readiness, VHD health, and ISO inventory" }
- @{ Title = "CLI Actions"; Keywords = @("cli", "action", "headless", "automation", "fleet", "json", "audit", "scan", "score", "dashboard", "monitor", "policy", "sla", "netmap", "validate"); Description = "191 CLI actions for headless automation. Run -ListActions to see all. JSON output via -OutputFormat JSON. Key: ServerScore, HealthDashboard, FleetReport, CISScan, NPSSetup, AlwaysOnVPNSetup, SIEMStatus." }
+ @{ Title = "CLI Actions"; Keywords = @("cli", "action", "headless", "automation", "fleet", "json", "audit", "scan", "score", "dashboard", "monitor", "policy", "sla", "netmap", "validate"); Description = "192 CLI actions for headless automation. Run -ListActions to see all. JSON output via -OutputFormat JSON. Key: ServerScore, HealthDashboard, FleetReport, CISScan, NPSSetup, AlwaysOnVPNSetup, SIEMStatus." }
@{ Title = "SelfTest Action"; Keywords = @("selftest", "self-test", "diagnose", "diagnostic", "verify", "healthcheck", "sanity"); Description = "Internal diagnostic. -Action SelfTest checks PS version, elevation, module count, version consistency, defaults.json validity, temp path writability, FileServer reachability, and agent installer config. Exit 1 on any failure. Use -OutputFormat JSON for structured output." }
@{ Title = "Security Audits"; Keywords = @("security", "audit", "hardening", "compliance", "tls", "smb", "kerberos", "credguard", "applocker", "bitlockeraudit", "defenderexclusionaudit", "audit-policy", "secureboot", "tpm"); Description = "Security-focused CLI audits: TLSAudit, SMBAudit, KerberosAudit, CredGuardAudit, AppLockerAudit, BitLockerAudit, DefenderExclusionAudit, AuditPolicyAudit, SecureBootAudit, TPMAudit, UserAudit, LogonAudit, InsecureServiceAudit, RegistryAudit. All support -OutputFormat JSON." }
@{ Title = "Network Audits"; Keywords = @("netaudit", "dns", "firewall-audit", "firewalllog", "arp", "route", "tcp", "netstat", "dhcp", "netprofile", "winrm", "qos", "nicoffload"); Description = "Network audits: DNSAudit, DNSCacheAudit, FirewallAudit, FirewallRuleAudit, FirewallLogAudit, ARPTableAudit, RouteTableAudit, TcpSettingsAudit, NetStatAudit, DHCPAudit, NetworkProfileAudit, WinRMAudit, QoSPolicyAudit, NICOffloadAudit, NICErrorAudit, HostsFileAudit, VPNAudit, ProxyAudit." }
diff --git a/Modules/50-EntryPoint.ps1 b/Modules/50-EntryPoint.ps1
index 708b19f..f1c1d55 100644
--- a/Modules/50-EntryPoint.ps1
+++ b/Modules/50-EntryPoint.ps1
@@ -409,6 +409,7 @@ function Assert-Elevation {
@{ Action = 'SIEMStatus'; Description = 'Show SIEM log-forwarder readiness (WEF / WinRM / agents / Arc)' }
@{ Action = 'WACSetup'; Description = 'Install + configure the Windows Admin Center gateway (verified MSI, port, certificate)' }
@{ Action = 'WACStatus'; Description = 'Show Windows Admin Center gateway status (service, port, listening)' }
+ @{ Action = 'VHDXEncryptionAudit'; Description = 'Read-only: report whether each VM virtual disk sits on a BitLocker-protected volume' }
@{ Action = 'Batch'; Description = 'JSON-driven full configuration' }
)
if ($script:CLIOutputFormat -eq 'JSON') {
@@ -2041,6 +2042,11 @@ footer{text-align:center;color:#999;font-size:12px;padding:16px}
$wacOk = Start-WACSetup
[Environment]::Exit([int](-not $wacOk))
}
+ 'VHDXEncryptionAudit' {
+ # Read-only: which VM virtual disks sit on BitLocker-protected volumes (JSON-aware).
+ $vhdxOk = Start-VHDXEncryptionAudit
+ [Environment]::Exit([int](-not $vhdxOk))
+ }
'Batch' {
if (-not $script:CLIConfig) {
Write-OutputColor " ERROR: -Action Batch requires -Config
-
+