Backing Up BitLocker Recover Key (Strikes Again!)
Here’s a one-liner for backing up the BitLocker Recovery Key for every drive attached to the machine:
ZgB1AG4AYwB0AGkAbwBuACAAQgBhAGMAawB1AHAALQBCAGkAdABMAG8AYwBrAGUAcgBSAGUAYw…
Followed by 9kb of more of the same. It’s an EncodedCommand, so we can toss this into a scheduled task. The actual encoded command is attached (thank goodness!) It outputs a .CLIXML file that contains a scalar or array of [Hashtable] with computer’s name, drive name (e.g. C:), drive id (a GUID) and the Recovery Key.
Here is the source code:
function Backup-BitLockerRecoverKey
{
param (
$Path = "$home\OneDrive\BitlockerRecovery\$env:Computername.$((Get-WmiObject -Class Win32_Computersystem).Domain).txt".ToLower()
);
trap {
if ($DebugPreference -eq 'Continue') { $Host.EnterNestedPrompt(); }
return ( Write-Warning $_.Exception.Message );
}
$ErrorActionPreference = 'SilentlyContinue';
if ( # effectively "is user running as Administrator"
(New-Object -TypeName Security.Principal.WindowsPrincipal -ArgumentList (
[Security.Principal.WindowsIdentity]::GetCurrent()
)).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
)
{
# create subfolders to path.
if (Test-Path -Path $Path)
{
$oldPath = "$($Path -replace '\.txt$').$(Get-Date -Format 'yyyy-MM-dd_HH-mm-ss').txt";
Move-Item -Path $Path -Destination $oldPath;
} # if (Test-Path -Path $Path)
elseif (!(Test-Path -Path (Split-Path -Path $Path -Parent)))
{
New-Item -Path (
Split-Path -Path $Path -Parent
) -ItemType Directory -ErrorAction SilentlyContinue -ErrorVariable errorVariable;
} # if (Test-Path -Path $Path)
if ($errorVariable) { return ( Write-Warning $errorVariable.Exception.Message ); }
Import-Module -Name Bitlocker -ErrorAction SilentlyContinue -ErrorVariable errorVariable;
if ($errorVariable) { return ( Write-Warning $errorVariable.Exception.Message ); }
Get-BitLockerVolume |
% {
$volumeData = $_;
$volumeData.KeyProtector |
? { $_.RecoveryPassword } |
% {
@{
ComputerName = $env:COMPUTERNAME;
Drive = $volumeData.MountPoint;
GUID = $_.KeyProtectorId -replace '[{}]';
Key = $_.RecoveryPassword;
};
} # $VolumeData.KeyProtector ...
} | Tee-Object -Variable recoveryKeys;
if ($recoveryKeys) { Export-Clixml -Path $Path -InputObject $recoveryKeys; }
if (Test-Path -Path $Path)
{
Write-Host -ForegroundColor Green "Created file $((Resolve-Path -Path $Path).ProviderPath)";
if ($oldPath)
{
if (!(Compare-Object -ReferenceObject (
Get-Content -Path $Path
) -DifferenceObject (
Get-Content -Path $oldPath
)))
{
Remove-Item -Path $oldPath;
Write-Verbose -Message "Backup and new file identical. Removing $oldPath";
} # if ((Get-Content -Path $Path) -eq (Get-Content -Path $oldPath))
else
{
Write-Warning -Message "'$Path' and '$oldPath' differ!";
} # if ((Get-Content -Path $Path) -eq (Get-Content -Path $oldPath))
} # if ($oldPath)
} # if (Test-Path -Path $Path)
elseif ($oldPath -and (Test-Path -Path $oldPath))
{
Write-Warning -Message "Cannot create -Path '$Path'";
Move-Item -Path $oldPath -Destination $Path;
} # if (Test-Path -Path $Path) ... else
} # if (<user is running as Administrator>)
else
{ Write-Warning -Message "Must be in a 'Run as Administrator' window."; }
} # function Backup-BitLockerRecoverKey
Backup-BitLockerRecoverKey
And here are the commands to generate that blob of text:
$bytes = [System.Text.Encoding]::Unicode.GetBytes($command)
$encodedCommand = [Convert]::ToBase64String($bytes)
One thing to note: the source code and the encoded text are different. For some reason, linefeeds don’t encode. As much as I love clearly-commented code, a comment doesn’t work when it’s all slammed into one line, which is how the decoded string (yes, STRING) comes.