Update Ensure-RealBitLockerKey.ps1

This commit is contained in:
2025-07-03 13:48:49 -04:00
parent eb924f7cdf
commit 20ccbdd11c

View File

@@ -1,35 +1,3 @@
<##
.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 { function Ensure-RealBitLockerKey {
[CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')]
param( param(
@@ -53,13 +21,16 @@ function Ensure-RealBitLockerKey {
if ($AllDisks) { if ($AllDisks) {
$targets = Get-WmiObject -Query "SELECT DeviceID FROM Win32_LogicalDisk WHERE DriveType=3" | $targets = Get-WmiObject -Query "SELECT DeviceID FROM Win32_LogicalDisk WHERE DriveType=3" |
Select-Object -ExpandProperty DeviceID Select-Object -ExpandProperty DeviceID
} else { }
else {
$targets = @($MountPoint) $targets = @($MountPoint)
} }
$results = @() $results = @()
foreach ($d in $targets) { 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 # Key regex
$keyPattern = '^\d{6}(-\d{6}){7}$' $keyPattern = '^\d{6}(-\d{6}){7}$'
@@ -69,67 +40,109 @@ function Ensure-RealBitLockerKey {
Select-Object -Last 1 -ExpandProperty RecoveryPassword Select-Object -Last 1 -ExpandProperty RecoveryPassword
if ($curKey -match $keyPattern) { 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 continue
} }
# Provision new key # Provision new key
if ($FullReencrypt) { if ($FullReencrypt) {
Disable-BitLocker -MountPoint $d -ErrorAction Stop Disable-BitLocker -MountPoint $d -ErrorAction Stop
while ((Get-BitLockerVolume -MountPoint $d).VolumeStatus -ne 'FullyDecrypted') { Start-Sleep 15 } while ((Get-BitLockerVolume -MountPoint $d).VolumeStatus -ne 'FullyDecrypted') {
Enable-BitLocker -MountPoint $d -UsedSpaceOnly -SkipHardwareTest -TpmProtector -RecoveryPasswordProtector -ErrorAction Stop Start-Sleep 15
}
Enable-BitLocker `
-MountPoint $d `
-UsedSpaceOnly `
-SkipHardwareTest `
-TpmProtector `
-RecoveryPasswordProtector `
-ErrorAction Stop
$action = 'FullReencrypt' $action = 'FullReencrypt'
} else { }
else {
$vol.KeyProtector | $vol.KeyProtector |
Where KeyProtectorType -eq 'RecoveryPassword' | Where-Object KeyProtectorType -eq 'RecoveryPassword' |
ForEach-Object { Remove-BitLockerKeyProtector -MountPoint $d -KeyProtectorId $_.KeyProtectorId } ForEach-Object { Remove-BitLockerKeyProtector -MountPoint $d -KeyProtectorId $_.KeyProtectorId }
Add-BitLockerKeyProtector -MountPoint $d -RecoveryPasswordProtector -ErrorAction Stop Add-BitLockerKeyProtector -MountPoint $d -RecoveryPasswordProtector -ErrorAction Stop
$action = 'AddProtector' $action = 'AddProtector'
} }
# Get new key # Grab the newly provisioned key
$newKey = (Get-BitLockerVolume -MountPoint $d).KeyProtector | $newKey = (Get-BitLockerVolume -MountPoint $d).KeyProtector |
Where-Object KeyProtectorType -eq 'RecoveryPassword' | Where-Object KeyProtectorType -eq 'RecoveryPassword' |
Select-Object -Last 1 -ExpandProperty RecoveryPassword Select-Object -Last 1 -ExpandProperty RecoveryPassword
$auditString = $null $auditString = $null
if ($PersistToRegistry) { if ($PersistToRegistry) {
# Build audit
# Powershell 7
#$alert = $env:usrAlert -match 'true' ? 'Notice' : 'Status'
# powershell 5 # -- Replace PowerShell 7 ternary with if/else --
if ($env:usrAlert -match 'true') { if ($env:usrAlert -match 'true') {
$alert = 'Notice' $alert = 'Notice'
} else { }
$alert = 'Status ...' 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) # -- 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
switch ($mask) {0{'Absent'};1{'Disabled'};3{'Deactivated'};7{'Active'};default{'Error'}} | Out-Null # -- Replace PowerShell 7 “? :” for drives list --
$drives=$AllDisks?($targets):(,$d) if ($AllDisks) {
$drives = $targets
}
else {
$drives = ,$d
}
# Build disk-status string
$diskStatus = '' $diskStatus = ''
foreach ($x in $drives) { foreach ($x in $drives) {
$stat = (Get-BitLockerVolume -MountPoint $x).VolumeStatus $stat = (Get-BitLockerVolume -MountPoint $x).VolumeStatus
switch ($stat) { switch ($stat) {
'FullyEncrypted' {$code='ENCPASS'} 'FullyEncrypted' { $code = 'ENCPASS'; break }
'EncryptionInProgress' {$code='ENCPASS'} 'EncryptionInProgress' { $code = 'ENCPASS'; break }
'DecryptionInProgress' {$code='ENCFAIL'} 'DecryptionInProgress' { $code = 'ENCFAIL'; break }
'EncryptionSuspended' {$code='ENCFAIL'} 'EncryptionSuspended' { $code = 'ENCFAIL'; break }
'DecryptionSuspended' {$code='ENCFAIL'} 'DecryptionSuspended' { $code = 'ENCFAIL'; break }
'EncryptingWipeInProgress'{$code='ENCFAIL'} 'EncryptingWipeInProgress' { $code = 'ENCFAIL'; break }
default {$code='ENCFAIL'} Default { $code = 'ENCFAIL' }
} }
$diskStatus += "/$x`$code" $diskStatus += "/$x`$$code"
} }
$diskStatus = $diskStatus.TrimStart('/') $diskStatus = $diskStatus.TrimStart('/')
$auditString = "TPM: $mask | DISKS: $diskStatus | RECOVERY: $newKey" $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 return $results