diff --git a/Ensure-RealBitLockerKey.ps1 b/Ensure-RealBitLockerKey.ps1 new file mode 100644 index 0000000..e2625d3 --- /dev/null +++ b/Ensure-RealBitLockerKey.ps1 @@ -0,0 +1,125 @@ +<## +.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, + + [Parameter(ParameterSetName='All')] + [switch]$AllDisks, + + [switch]$FullReencrypt, + [switch]$PersistToRegistry, + + [Parameter(Mandatory=$true, ParameterSetName='Persist')] + [int]$UDFNumber, + + [string]$RegistryBasePath = 'HKLM:\SOFTWARE\CentraStage' + ) + + # Determine target drives + if ($AllDisks) { + $targets = Get-WmiObject -Query "SELECT DeviceID FROM Win32_LogicalDisk WHERE DriveType=3" | + Select-Object -ExpandProperty DeviceID + } else { + $targets = @($MountPoint) + } + + $results = @() + foreach ($d in $targets) { + if (-not $PSCmdlet.ShouldProcess("Volume $d","Ensure recovery key")) { continue } + + # Key regex + $keyPattern = '^\d{6}(-\d{6}){7}$' + $vol = Get-BitLockerVolume -MountPoint $d + $curKey = $vol.KeyProtector | + Where-Object KeyProtectorType -eq 'RecoveryPassword' | + Select-Object -Last 1 -ExpandProperty RecoveryPassword + + if ($curKey -match $keyPattern) { + $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 + $action = 'FullReencrypt' + } else { + $vol.KeyProtector | + Where KeyProtectorType -eq 'RecoveryPassword' | + ForEach-Object { Remove-BitLockerKeyProtector -MountPoint $d -KeyProtectorId $_.KeyProtectorId } + Add-BitLockerKeyProtector -MountPoint $d -RecoveryPasswordProtector -ErrorAction Stop + $action = 'AddProtector' + } + + # Get new key + $newKey = (Get-BitLockerVolume -MountPoint $d).KeyProtector | + Where-Object KeyProtectorType -eq 'RecoveryPassword' | + Select-Object -Last 1 -ExpandProperty RecoveryPassword + + $auditString = $null + if ($PersistToRegistry) { + # Build audit + $alert = $env:usrAlert -match 'true' ? 'Notice' : 'Status' + $t = Get-Tpm; $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" + } + $diskStatus = $diskStatus.TrimStart('/') + $auditString = "TPM: $mask | DISKS: $diskStatus | RECOVERY: $newKey" + New-ItemProperty -Path $RegistryBasePath -Name "Custom$UDFNumber" -Value $auditString -Force | Out-Null + } + + $results += [PSCustomObject]@{MountPoint=$d;Action=$action;RecoveryKey=$newKey;AuditString=$auditString} + } + + return $results +}