Skip to content

Commit 52b2927

Browse files
authored
Add VHDX encryption-at-rest audit (BitLocker) (#45)
New read-only check in BitLocker Management ([7] VHDX Encryption-at-Rest Audit) and via the VHDXEncryptionAudit CLI action: report whether each Hyper-V VM's virtual disk (VHD/VHDX) sits on a BitLocker-protected volume. Enumerates the virtual disks attached to the host's VMs, resolves the volume each lives on, and reports encrypted / unencrypted / unknown per disk plus a summary. Volumes BitLocker can't enumerate (CSV / UNC / remote) are reported as "unknown", not guessed. JSON-aware for fleet auditing. Makes no changes. Addition to 31-BitLocker (no new module). CLI actions 191 -> 192; README / Help / dist counts updated in lockstep (doc-freshness guard). 5032 tests, 0 failures; PSScriptAnalyzer clean.
1 parent 362fa93 commit 52b2927

14 files changed

Lines changed: 127 additions & 22 deletions

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ powershell -ExecutionPolicy Bypass -File Tests\pssa-check.ps1
3030

3131
## Pull Request Checklist
3232

33-
- [ ] All 5,025 tests pass (`Run-Tests.ps1` exits with code 0)
33+
- [ ] All 5,032 tests pass (`Run-Tests.ps1` exits with code 0)
3434
- [ ] PSScriptAnalyzer reports 0 errors (`pssa-check.ps1`)
3535
- [ ] Monolithic synced (`sync-to-monolithic.ps1` shows 0 parse errors)
3636
- [ ] New functions follow PowerShell verb-noun naming (`Get-`, `Set-`, `Test-`, `Show-`)

Changelog.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## v1.109.0
4+
5+
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.
6+
7+
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.
8+
39
## v1.108.0
410

511
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.

Header.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
7h3 4b1d3r
3131
3232
.VERSION
33-
1.108.0
33+
1.109.0
3434
3535
.LAST UPDATED
3636
05/23/2026
@@ -1391,7 +1391,7 @@
13911391
param(
13921392
# CLI headless mode: run a specific action without interactive menus
13931393
# Usage: RackStack.exe -Action Cleanup [-Tier Standard] [-Silent] [-OutputFormat JSON]
1394-
[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')]
1394+
[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')]
13951395
[string]$Action,
13961396

13971397
[ValidateSet('Light', 'Standard', 'Aggressive')]

Modules/00-Initialization.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ if (-not $PSCommandPath -and $script:ScriptPath) {
225225
if (-not $script:ModuleRoot -and $script:ScriptPath) {
226226
$script:ModuleRoot = [System.IO.Path]::GetDirectoryName($script:ScriptPath)
227227
}
228-
$script:ScriptVersion = "1.108.0"
228+
$script:ScriptVersion = "1.109.0"
229229
$script:ScriptStartTime = Get-Date
230230

231231
# Post-update cleanup: UpdateSelf / Rollback leave a `.pending-delete` sibling next to RackStack.exe.

Modules/31-BitLocker.ps1

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ function Show-BitLockerManagement {
178178
Write-MenuItem -Text "[4] Show Recovery Key"
179179
Write-MenuItem -Text "[5] Check Encryption Progress"
180180
Write-MenuItem -Text "[6] Verify Recovery Key Backup"
181+
Write-MenuItem -Text "[7] VHDX Encryption-at-Rest Audit"
181182
Write-OutputColor " └────────────────────────────────────────────────────────────────────────┘" -color "Info"
182183
Write-OutputColor "" -color "Info"
183184
Write-OutputColor " [B] ◄ Back" -color "Info"
@@ -492,11 +493,81 @@ function Show-BitLockerManagement {
492493
Write-OutputColor "" -color "Info"
493494
Test-BitLockerRecoveryBackup
494495
}
496+
"7" { Show-VHDXEncryptionAudit }
495497
{ $_ -eq "b" -or $_ -eq "B" } { return }
496-
default { Write-OutputColor " Invalid choice. Enter 1-6 or B." -color "Error"; Start-Sleep -Seconds 1 }
498+
default { Write-OutputColor " Invalid choice. Enter 1-7 or B." -color "Error"; Start-Sleep -Seconds 1 }
497499
}
498500

499501
Write-PressEnter
500502
}
501503
}
504+
505+
# Read-only encryption-at-rest verification: report whether each VHD/VHDX
506+
# backing a Hyper-V VM sits on a BitLocker-protected volume. No changes made.
507+
function Get-VHDXEncryptionStatus {
508+
$paths = New-Object System.Collections.Generic.List[string]
509+
try {
510+
if (Get-Command -Name Get-VM -ErrorAction SilentlyContinue) {
511+
foreach ($d in (Get-VM -ErrorAction SilentlyContinue | Get-VMHardDiskDrive -ErrorAction SilentlyContinue)) {
512+
if (-not [string]::IsNullOrWhiteSpace($d.Path)) { $paths.Add($d.Path) }
513+
}
514+
}
515+
}
516+
catch { }
517+
$unique = @($paths | Sort-Object -Unique)
518+
# Cache volume -> protection status from BitLocker once.
519+
$blByMount = @{}
520+
try { foreach ($v in (Get-BitLockerVolume -ErrorAction SilentlyContinue)) { $blByMount["$($v.MountPoint)"] = "$($v.ProtectionStatus)" } } catch { }
521+
$results = @()
522+
foreach ($p in $unique) {
523+
$root = $null
524+
try { $root = ([System.IO.Path]::GetPathRoot($p)).TrimEnd('\') } catch { }
525+
$prot = if ($root -and $blByMount.ContainsKey($root)) { $blByMount[$root] } else { "Unknown" }
526+
$results += [PSCustomObject]@{
527+
Path = $p; Volume = $root; Protection = $prot; Encrypted = ($prot -eq 'On')
528+
}
529+
}
530+
return $results
531+
}
532+
533+
# Interactive display of the VHDX encryption-at-rest audit.
534+
function Show-VHDXEncryptionAudit {
535+
Clear-Host
536+
Write-CenteredOutput "VHDX Encryption-at-Rest Audit" -color "Info"
537+
if ($null -eq (Get-Command -Name Get-BitLockerVolume -ErrorAction SilentlyContinue)) {
538+
Write-OutputColor " BitLocker is not available on this system." -color "Warning"; return
539+
}
540+
$r = @(Get-VHDXEncryptionStatus)
541+
if ($r.Count -eq 0) {
542+
Write-OutputColor " No VHD/VHDX virtual disks found (no Hyper-V VMs, or none attached)." -color "Info"; return
543+
}
544+
Write-OutputColor "" -color "Info"
545+
foreach ($item in $r) {
546+
$label = if ($item.Encrypted) { "ENCRYPTED" } elseif ($item.Protection -eq 'Off') { "UNENCRYPTED" } else { "UNKNOWN" }
547+
$c = if ($item.Encrypted) { "Success" } elseif ($item.Protection -eq 'Off') { "Error" } else { "Warning" }
548+
Write-OutputColor (" [{0}] {1}" -f $label, $item.Path) -color $c
549+
Write-OutputColor (" volume: $(if ($item.Volume) { $item.Volume } else { 'unresolved (CSV/UNC/remote)' })") -color "Debug"
550+
}
551+
$enc = @($r | Where-Object { $_.Encrypted }).Count
552+
Write-OutputColor "" -color "Info"
553+
Write-OutputColor " $enc of $($r.Count) virtual disk(s) sit on BitLocker-protected volumes." -color "Info"
554+
Write-OutputColor " 'Unknown' volumes (CSV / UNC / remote storage) are not enumerable by Get-BitLockerVolume." -color "Debug"
555+
}
556+
557+
# CLI entry: VHDXEncryptionAudit — read-only, JSON-aware.
558+
function Start-VHDXEncryptionAudit {
559+
$r = @(Get-VHDXEncryptionStatus)
560+
if ($script:CLIOutputFormat -eq 'JSON') {
561+
Write-Output (@{
562+
Tool = $script:ToolFullName; Version = $script:ScriptVersion; Action = 'VHDXEncryptionAudit'
563+
Timestamp = (Get-Date -Format "yyyy-MM-ddTHH:mm:ss"); Hostname = $env:COMPUTERNAME
564+
TotalDisks = $r.Count; EncryptedDisks = @($r | Where-Object { $_.Encrypted }).Count
565+
Disks = @($r | ForEach-Object { @{ Path = $_.Path; Volume = $_.Volume; Protection = $_.Protection; Encrypted = $_.Encrypted } })
566+
} | ConvertTo-Json -Depth 5)
567+
}
568+
else {
569+
Show-VHDXEncryptionAudit
570+
}
571+
return $true
572+
}
502573
#endregion

Modules/34-Help.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ function Search-HelpTopics {
253253
@{ 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" }
254254
@{ 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" }
255255
@{ Title = "VM Management"; Keywords = @("checkpoint", "snapshot", "export", "import", "migration", "vhd", "iso"); Description = "VM checkpoints, export/import, migration readiness, VHD health, and ISO inventory" }
256-
@{ 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." }
256+
@{ 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." }
257257
@{ 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." }
258258
@{ 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." }
259259
@{ 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." }

Modules/50-EntryPoint.ps1

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,7 @@ function Assert-Elevation {
409409
@{ Action = 'SIEMStatus'; Description = 'Show SIEM log-forwarder readiness (WEF / WinRM / agents / Arc)' }
410410
@{ Action = 'WACSetup'; Description = 'Install + configure the Windows Admin Center gateway (verified MSI, port, certificate)' }
411411
@{ Action = 'WACStatus'; Description = 'Show Windows Admin Center gateway status (service, port, listening)' }
412+
@{ Action = 'VHDXEncryptionAudit'; Description = 'Read-only: report whether each VM virtual disk sits on a BitLocker-protected volume' }
412413
@{ Action = 'Batch'; Description = 'JSON-driven full configuration' }
413414
)
414415
if ($script:CLIOutputFormat -eq 'JSON') {
@@ -2041,6 +2042,11 @@ footer{text-align:center;color:#999;font-size:12px;padding:16px}
20412042
$wacOk = Start-WACSetup
20422043
[Environment]::Exit([int](-not $wacOk))
20432044
}
2045+
'VHDXEncryptionAudit' {
2046+
# Read-only: which VM virtual disks sit on BitLocker-protected volumes (JSON-aware).
2047+
$vhdxOk = Start-VHDXEncryptionAudit
2048+
[Environment]::Exit([int](-not $vhdxOk))
2049+
}
20442050
'Batch' {
20452051
if (-not $script:CLIConfig) {
20462052
Write-OutputColor " ERROR: -Action Batch requires -Config <path>" -color "Error"

0 commit comments

Comments
 (0)