Dargslan.HyperVSecurityAudit.psm1
|
<# .SYNOPSIS Audit Hyper-V VMs: Secure Boot, vTPM, Shielded VM, snapshot retention, integration components. JSON / HTML report. .DESCRIPTION Part of the Dargslan Windows Admin Tools collection. Free Cheat Sheet: https://dargslan.com/cheat-sheets/hyperv-vm-security-audit-2026 Full Guide: https://dargslan.com/blog/hyperv-vm-security-audit-powershell-2026 More tools: https://dargslan.com .LINK https://dargslan.com .LINK https://github.com/Dargslan/powershell-admin-scripts #> $script:Banner = @" +----------------------------------------------------------+ | Dargslan Hyper-V Security Audit | https://dargslan.com - Free cheat sheets & eBooks | +----------------------------------------------------------+ "@ function Get-DargslanVmSecurityState { <# .SYNOPSIS Per-VM Secure Boot, vTPM, Shielded VM and Generation state. #> [CmdletBinding()] param([string]$ComputerName = $env:COMPUTERNAME) Get-VM -ComputerName $ComputerName | ForEach-Object { $fw = Get-VMFirmware -VM $_ -ErrorAction SilentlyContinue $sec= Get-VMSecurity -VM $_ -ErrorAction SilentlyContinue [pscustomobject]@{ Host = $ComputerName VM = $_.Name Generation = $_.Generation State = $_.State SecureBoot = $fw.SecureBoot SecureBootTemplate = $fw.SecureBootTemplate TpmEnabled = $sec.TpmEnabled Shielded = $sec.Shielded EncryptState= $sec.EncryptStateAndVmMigrationTraffic } } } function Get-DargslanVmSnapshots { <# .SYNOPSIS Per-VM checkpoint count and oldest checkpoint age. #> [CmdletBinding()] param([string]$ComputerName = $env:COMPUTERNAME) Get-VM -ComputerName $ComputerName | ForEach-Object { $snaps = @(Get-VMSnapshot -VM $_) $oldest = ($snaps | Sort CreationTime | Select-Object -First 1).CreationTime [pscustomobject]@{ VM = $_.Name CheckpointCount = $snaps.Count OldestSnapshot = $oldest AgeDays = if ($oldest) { [math]::Round(((Get-Date) - $oldest).TotalDays, 1) } else { $null } } } } function Get-DargslanVmIntegrationServices { <# .SYNOPSIS Integration components state per VM. #> [CmdletBinding()] param([string]$ComputerName = $env:COMPUTERNAME) Get-VM -ComputerName $ComputerName | ForEach-Object { $ic = Get-VMIntegrationService -VM $_ [pscustomobject]@{ VM = $_.Name HeartBeat = ($ic | Where-Object Name -eq 'Heartbeat').Enabled TimeSync = ($ic | Where-Object Name -eq 'Time Synchronization').Enabled Shutdown = ($ic | Where-Object Name -eq 'Shutdown').Enabled Backup = ($ic | Where-Object Name -eq 'VSS').Enabled GuestSvc = ($ic | Where-Object Name -eq 'Guest Service Interface').Enabled } } } function Get-DargslanHyperVAuditReport { <# .SYNOPSIS Combined Hyper-V audit with PASS / WARN / FAIL verdict. #> [CmdletBinding()] param([string]$ComputerName = $env:COMPUTERNAME) $sec = @(Get-DargslanVmSecurityState -ComputerName $ComputerName) $snap = @(Get-DargslanVmSnapshots -ComputerName $ComputerName) $ic = @(Get-DargslanVmIntegrationServices -ComputerName $ComputerName) $gen2 = ($sec | Where-Object Generation -eq 2).Count $noSb = ($sec | Where-Object { $_.Generation -eq 2 -and -not $_.SecureBoot }).Count $noTp = ($sec | Where-Object { $_.Generation -eq 2 -and -not $_.TpmEnabled }).Count $oldSnaps = ($snap | Where-Object AgeDays -gt 7).Count $score = 0 if ($sec.Count -gt 0) { $score++ } if ($noSb -eq 0) { $score++ } if ($noTp -le 1) { $score++ } if ($oldSnaps -eq 0) { $score++ } $verdict = if ($score -ge 3) { 'PASS' } elseif ($score -ge 1) { 'WARN' } else { 'FAIL' } [pscustomobject]@{ Host = $ComputerName VmCount = $sec.Count Gen2Count = $gen2 NoSecureBoot = $noSb NoTpm = $noTp OldSnapshots = $oldSnaps Security = $sec Snapshots = $snap Integration = $ic Score = $score Verdict = $verdict TimeStamp = (Get-Date).ToString('s') } } function Export-DargslanHyperVAuditReport { <# .SYNOPSIS Export the Hyper-V audit to HTML and JSON. #> [CmdletBinding()] param([string]$ComputerName = $env:COMPUTERNAME, [string]$OutDir = (Join-Path $env:TEMP 'DargslanHyperVAudit')) if (-not (Test-Path $OutDir)) { New-Item -Type Directory -Path $OutDir | Out-Null } $r = Get-DargslanHyperVAuditReport -ComputerName $ComputerName $json = Join-Path $OutDir ('hyperv-' + $ComputerName + '.json') $html = Join-Path $OutDir ('hyperv-' + $ComputerName + '.html') $r | ConvertTo-Json -Depth 6 | Set-Content $json -Encoding UTF8 $body = "<h1>Hyper-V Audit - $($r.Host)</h1>" $body += "<p>Verdict: <b>$($r.Verdict)</b> ($($r.Score)/4)</p>" $body += '<h2>Security</h2>' + ($r.Security | ConvertTo-Html -Fragment) $body += '<h2>Snapshots</h2>' + ($r.Snapshots | ConvertTo-Html -Fragment) $body += '<h2>Integration</h2>' + ($r.Integration | ConvertTo-Html -Fragment) ConvertTo-Html -Body $body -Title 'Hyper-V Audit' | Set-Content $html -Encoding UTF8 [pscustomobject]@{ Json = $json; Html = $html; Verdict = $r.Verdict } } |