<## .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 }