Run a BitLocker escrow audit across a fleet and it's easy to feel good about a 100% GREEN result. But GREEN in that kind of audit only means one thing: a recovery key record exists somewhere in Entra ID or Active Directory for that device. It says nothing about whether that specific record is the key that would actually unlock the drive today. Those are two different claims, and the gap between them is exactly where a "compliant" fleet can still lose data on the one day it matters.
This post is a direct follow-up to the BitLocker recovery key escrow audit already on this site. That audit answers "does a key exist?" This one answers a sharper question: "is the key that exists actually the one protecting this drive right now?" — and gives you a script to check.
The problem — existence isn't currency
A BitLocker recovery key isn't a single, permanent secret tied to a device forever. It's tied to a specific key protector — one instance of a protector object, generated at a specific point in time, identified by its own GUID (KeyProtectorId). When that protector gets replaced — by a re-image, a protector rotation, a TPM reset, or BitLocker being suspended and re-enabled — the volume gets a new key protector with a new ID. The old escrowed record doesn't get deleted. It just sits in Entra ID or AD, permanently, describing a protector that no longer exists on any drive.
Run the escrow audit against that device and it still reports GREEN — a key record exists, dated, timestamped, present. It's just not the key you'd actually need if that drive locked tomorrow.
Why it happens — key protectors rotate, escrow records don't self-clean
Several ordinary, well-documented events on a device mint a new local key protector without anyone doing anything wrong:
- Re-imaging or OS reinstall. A fresh install means a fresh
Enable-BitLockerrun, which means a brand-new key protector and a brand-newKeyProtectorId— even if the device name and Entra device object stay the same. - TPM reset or firmware update that clears the TPM. Some TPM-owner-clear operations force BitLocker to suspend and regenerate protectors on resume.
- Manual protector rotation. Running
Remove-BitLockerKeyProtectorfollowed byAdd-BitLockerKeyProtector— a legitimate, sometimes-necessary maintenance action — always produces a new ID. - Drive replacement. A swapped disk with BitLocker re-enabled is, from BitLocker's perspective, an entirely new volume with entirely new protectors.
None of these events reach into Entra ID or Active Directory and remove the old record. Recovery key escrow is additive by design — Microsoft's own guidance is that you should back up a new protector when one is created, not that old ones get pruned. That's the right default for not losing history, but it also means an escrow store accumulates dead records over a device's lifetime, and nothing forces a re-escrow after most of the events above unless your process explicitly does it.
How to verify — compare the local protector ID against what's actually escrowed
The only way to know if an escrowed key is current is to compare it against what the device itself says is protecting the drive right now. Locally, Get-BitLockerVolume returns a KeyProtector array, and each entry in that array has its own KeyProtectorId:
On the cloud side, Microsoft Graph's bitlockerRecoveryKey resource stores each escrowed key with its own id, plus the deviceId it was backed up from and a createdDateTime. Listing the escrowed keys for a device and comparing their IDs against the live local KeyProtectorId for the RecoveryPassword protector tells you, directly, whether the record on file is current.
bitlockerRecoveryKey.id is guaranteed to equal the local KeyProtectorId GUID byte-for-byte in every case. In practice, backing up a protector with BackupToAAD-BitLockerKeyProtector -KeyProtectorId is how the ID gets there in the first place, so a match is the expected outcome — but the script below treats a non-match as "needs manual review," not an automatic hard failure, precisely because that exact guarantee isn't spelled out in the docs.The fix — re-escrow on drift, and check re-imaging workflows specifically
- Confirm you're looking at the RecoveryPassword protector specifically — a volume can have multiple protector types (Tpm, RecoveryPassword, Password), and only the RecoveryPassword one is what gets escrowed for recovery purposes.
- Re-escrow the current protector immediately:
BackupToAAD-BitLockerKeyProtector -MountPoint "C:" -KeyProtectorId $BLV.KeyProtector[1].KeyProtectorId(for Entra ID), orBackup-BitLockerKeyProtectorfor on-prem AD DS. - If this is a re-imaging pipeline issue rather than a one-off, add the backup command as a mandatory step immediately after
Enable-BitLockerin the provisioning script or task sequence — don't rely on Intune's encryption policy alone to catch devices that were encrypted outside its assignment. - Old, stale records don't need to be deleted to fix this — a device having an old dead record alongside a current one is harmless. The risk is only in not having a current one.
Proof it worked — a currency check, not a claim of "we tested every key"
Be precise about what this actually proves. Confirming that a local KeyProtectorId matches an escrowed record's ID proves the right record is on file. It does not prove that record would successfully decrypt the volume in a real recovery scenario — that would require an actual offline unlock test against a genuinely locked volume, which isn't something you can safely automate across a live fleet. Treat a real unlock test as a lab-only exercise on a spare device, not something this script claims to do at scale. What the script below does is the honest, safe, at-scale version: it tells you whether the record on file is even the current one, which is the far more common failure mode.
Test-BitLockerRecoveryKeyCurrency.ps1 is free and open source. It's a genuinely read-only check — no writes to BitLocker, Intune, or Entra ID.
Get-BitLockerVolume on the device itself, plus Graph read access (BitLockerKey.ReadBasic.All) to list that device's escrowed records — it can't be run purely centrally from a Graph-only context, because the "what's actually protecting this drive right now" half of the comparison only exists on the device.