From 20ccbdd11c0c5e6010f0a0bbeaa382410ec69ec7 Mon Sep 17 00:00:00 2001 From: Stephan Yelle Date: Thu, 3 Jul 2025 13:48:49 -0400 Subject: [PATCH] Update Ensure-RealBitLockerKey.ps1 --- Ensure-RealBitLockerKey.ps1 | 157 +++++++++++++++++++----------------- 1 file changed, 85 insertions(+), 72 deletions(-) diff --git a/Ensure-RealBitLockerKey.ps1 b/Ensure-RealBitLockerKey.ps1 index 82c2cc2..0f3d50d 100644 --- a/Ensure-RealBitLockerKey.ps1 +++ b/Ensure-RealBitLockerKey.ps1 @@ -1,41 +1,9 @@ -<## -.SYNOPSIS -Ensures a valid BitLocker recovery key is present for one or all volumes, optionally re-encrypting and auditing. -.DESCRIPTION -Advanced cmdlet to manage BitLocker recovery keys. Supports: - • Targeting system drive only (default) or all fixed disks with -AllDisks - • Adding a fresh RecoveryPassword protector (fast path) - • Full decrypt-and-reencrypt cycle (clean-slate path) - • Persisting audit output to registry or RMM UDF fields -Supports ShouldProcess for safe execution and returns a PSObject detailing actions. -.PARAMETER MountPoint -Drive letter (e.g. 'C:') to target. Ignored when -AllDisks is used. -.PARAMETER AllDisks -Switch to target all fixed disk volumes instead of a single MountPoint. -.PARAMETER FullReencrypt -Switch to perform a full decrypt-and-re-encrypt cycle. -.PARAMETER PersistToRegistry -Switch to persist the new recovery key and audit string to registry/UDF. -.PARAMETER UDFNumber -RMM UDF index to write audit string when PersistToRegistry is used. -.PARAMETER RegistryBasePath -Registry path for persisting keys. Defaults to HKLM:\SOFTWARE\CentraStage. -.EXAMPLE -# System drive only (default) -Ensure-RealBitLockerKey -.EXAMPLE -# All fixed disks -Ensure-RealBitLockerKey -AllDisks -.EXAMPLE -# Full reencrypt on all disks and write audit to UDF2 -Ensure-RealBitLockerKey -AllDisks -FullReencrypt -PersistToRegistry -UDFNumber 2 -#> function Ensure-RealBitLockerKey { [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] param( [Parameter(ParameterSetName='Single', Position=0)] [ValidatePattern('^[A-Z]:$')] - [string]$MountPoint = $env:SystemDrive, + [string]$MountPoint = $env:SystemDrive, [Parameter(ParameterSetName='All')] [switch]$AllDisks, @@ -53,13 +21,16 @@ function Ensure-RealBitLockerKey { if ($AllDisks) { $targets = Get-WmiObject -Query "SELECT DeviceID FROM Win32_LogicalDisk WHERE DriveType=3" | Select-Object -ExpandProperty DeviceID - } else { + } + else { $targets = @($MountPoint) } $results = @() foreach ($d in $targets) { - if (-not $PSCmdlet.ShouldProcess("Volume $d","Ensure recovery key")) { continue } + if (-not $PSCmdlet.ShouldProcess("Volume $d","Ensure recovery key")) { + continue + } # Key regex $keyPattern = '^\d{6}(-\d{6}){7}$' @@ -69,67 +40,109 @@ function Ensure-RealBitLockerKey { Select-Object -Last 1 -ExpandProperty RecoveryPassword if ($curKey -match $keyPattern) { - $results += [PSCustomObject]@{MountPoint=$d;Action='None';RecoveryKey=$curKey;AuditString=$null} + $results += [PSCustomObject]@{ + MountPoint = $d + Action = 'None' + RecoveryKey = $curKey + AuditString = $null + } continue } # Provision new key if ($FullReencrypt) { Disable-BitLocker -MountPoint $d -ErrorAction Stop - while ((Get-BitLockerVolume -MountPoint $d).VolumeStatus -ne 'FullyDecrypted') { Start-Sleep 15 } - Enable-BitLocker -MountPoint $d -UsedSpaceOnly -SkipHardwareTest -TpmProtector -RecoveryPasswordProtector -ErrorAction Stop + while ((Get-BitLockerVolume -MountPoint $d).VolumeStatus -ne 'FullyDecrypted') { + Start-Sleep 15 + } + Enable-BitLocker ` + -MountPoint $d ` + -UsedSpaceOnly ` + -SkipHardwareTest ` + -TpmProtector ` + -RecoveryPasswordProtector ` + -ErrorAction Stop $action = 'FullReencrypt' - } else { + } + else { $vol.KeyProtector | - Where KeyProtectorType -eq 'RecoveryPassword' | + Where-Object KeyProtectorType -eq 'RecoveryPassword' | ForEach-Object { Remove-BitLockerKeyProtector -MountPoint $d -KeyProtectorId $_.KeyProtectorId } Add-BitLockerKeyProtector -MountPoint $d -RecoveryPasswordProtector -ErrorAction Stop $action = 'AddProtector' } - # Get new key + # Grab the newly provisioned key $newKey = (Get-BitLockerVolume -MountPoint $d).KeyProtector | Where-Object KeyProtectorType -eq 'RecoveryPassword' | Select-Object -Last 1 -ExpandProperty RecoveryPassword $auditString = $null if ($PersistToRegistry) { - # Build audit - # Powershell 7 - #$alert = $env:usrAlert -match 'true' ? 'Notice' : 'Status' - - # powershell 5 - if ($env:usrAlert -match 'true') { - $alert = 'Notice' - } else { - $alert = 'Status ...' - } - - $t = Get-Tpm; $mask = (if ($t.TpmPresent) { 1 } else { 0 }) + (if ($t.TpmReady) { 6 } else { 0 })# PoSH 7 $mask = ($t.TpmPresent?1:0) + ($t.TpmReady?6:0) - - switch ($mask) {0{'Absent'};1{'Disabled'};3{'Deactivated'};7{'Active'};default{'Error'}} | Out-Null - $drives=$AllDisks?($targets):(,$d) - $diskStatus='' - foreach ($x in $drives) { - $stat=(Get-BitLockerVolume -MountPoint $x).VolumeStatus - switch ($stat) { - 'FullyEncrypted' {$code='ENCPASS'} - 'EncryptionInProgress' {$code='ENCPASS'} - 'DecryptionInProgress' {$code='ENCFAIL'} - 'EncryptionSuspended' {$code='ENCFAIL'} - 'DecryptionSuspended' {$code='ENCFAIL'} - 'EncryptingWipeInProgress'{$code='ENCFAIL'} - default {$code='ENCFAIL'} - } - $diskStatus += "/$x`$code" + # -- Replace PowerShell 7 ternary with if/else -- + if ($env:usrAlert -match 'true') { + $alert = 'Notice' } - $diskStatus = $diskStatus.TrimStart('/') + else { + $alert = 'Status' + } + + # -- Build TPM mask -- + $t = Get-Tpm + $mask = (if ($t.TpmPresent) { 1 } else { 0 }) + ` + (if ($t.TpmReady) { 6 } else { 0 }) + + # Capture and discard the switch output + switch ($mask) { + 0 { $state = 'Absent'; break } + 1 { $state = 'Disabled'; break } + 3 { $state = 'Deactivated'; break } + 7 { $state = 'Active'; break } + Default { $state = 'Error' } + } + $null = $state + + # -- Replace PowerShell 7 “? :” for drives list -- + if ($AllDisks) { + $drives = $targets + } + else { + $drives = ,$d + } + + # Build disk-status string + $diskStatus = '' + foreach ($x in $drives) { + $stat = (Get-BitLockerVolume -MountPoint $x).VolumeStatus + switch ($stat) { + 'FullyEncrypted' { $code = 'ENCPASS'; break } + 'EncryptionInProgress' { $code = 'ENCPASS'; break } + 'DecryptionInProgress' { $code = 'ENCFAIL'; break } + 'EncryptionSuspended' { $code = 'ENCFAIL'; break } + 'DecryptionSuspended' { $code = 'ENCFAIL'; break } + 'EncryptingWipeInProgress' { $code = 'ENCFAIL'; break } + Default { $code = 'ENCFAIL' } + } + $diskStatus += "/$x`$$code" + } + $diskStatus = $diskStatus.TrimStart('/') $auditString = "TPM: $mask | DISKS: $diskStatus | RECOVERY: $newKey" - New-ItemProperty -Path $RegistryBasePath -Name "Custom$UDFNumber" -Value $auditString -Force | Out-Null + + # Persist to registry/UDF + New-ItemProperty ` + -Path $RegistryBasePath ` + -Name "Custom$UDFNumber" ` + -Value $auditString ` + -Force | Out-Null } - $results += [PSCustomObject]@{MountPoint=$d;Action=$action;RecoveryKey=$newKey;AuditString=$auditString} + $results += [PSCustomObject]@{ + MountPoint = $d + Action = $action + RecoveryKey = $newKey + AuditString = $auditString + } } return $results