Open your Intune encryption report right now. Filter to Windows devices. Every device shows Encrypted. That's a real, true statement about the state of the disk. Now ask a harder question: for every single one of those devices, if it prompted for the 48-digit recovery key tomorrow morning, is there actually a copy of that key sitting somewhere your organisation can retrieve it from? "Encrypted" and "recoverable" are not the same claim, and the gap between them is exactly where a device becomes permanently, unrecoverably locked — not because BitLocker failed, but because it worked perfectly and nobody escrowed the key it generated.
This post explains why that gap exists — specifically the fact that BitLocker recovery keys escrow to two completely different places depending on how a device is managed, and a script that only checks one of them will confidently report 100% compliance while quietly missing every device escrowed the other way — how to manually verify a single device's key status before you trust any automation, and how to build a PowerShell audit against Microsoft Graph that tells you which devices actually have a retrievable key and which ones are one boot prompt away from a total data-loss event.
Get-BitLockerEscrowComplianceReport.ps1 is free and open source. Download it from the Imran76Awan/Daily-Tasks repository on GitHub — no sign-in required.
The problem — the recovery screen doesn't care how confident your compliance report was
Here's the exact moment this becomes real. A laptop reboots after a firmware update, or the motherboard gets replaced under warranty, or someone clears the TPM during a troubleshooting step, or a technician moves a drive from a dead machine into a loaner chassis. Windows boots, BitLocker's platform validation fails because the measurements it's checking against — PCR values tied to boot configuration, Secure Boot state, or the TPM itself — no longer match what was recorded when the volume was encrypted. Instead of Windows, the user sees a blue recovery screen with a Recovery key ID (an eight-character prefix of a longer GUID) and a prompt for the 48-digit numerical recovery password. Some builds surface this alongside a specific error code — 0xC0210000 is one you'll see referenced in this state, though the exact code varies by what tripped the validation failure, so treat any single code as illustrative rather than the only one you'll encounter.
If someone in your organisation can look up that Recovery key ID and hand back the matching 48-digit password, this is a five-minute inconvenience. If nobody can — because the key was never successfully escrowed anywhere retrievable — this is not a five-minute inconvenience. It is permanent data loss. There is no backdoor, no Microsoft support escalation, no "call this number" path that recovers a BitLocker volume without either the recovery key or the original TPM/PCR state intact. The encryption did exactly what it was designed to do. The failure is entirely upstream, in whatever process was supposed to get that key off the device and into a place your organisation controls.
The number that should worry a fleet owner is never "what percentage of devices are encrypted." Every modern BitLocker deployment reports that number in the high 90s or better, and it is largely meaningless on its own. The number that matters is "what percentage of encrypted devices have a recovery key I can actually retrieve right now" — and until you've run an audit that checks both possible escrow destinations, you don't actually know that number, even if your encryption report looks perfect.
Why it happens — two completely separate escrow destinations
This is the single most important thing to understand about auditing BitLocker recovery, and it's the part almost every "just check compliance" report gets wrong: where the recovery key ends up depends entirely on how the device is managed, and the two destinations are not visible from the same place.
1. Entra-joined / Intune-managed devices escrow their BitLocker recovery key to Microsoft Entra ID. This is queryable through Microsoft Graph.
2. Hybrid-joined / GPO-managed devices escrow their recovery key to on-premises Active Directory, stored as an
msFVE-RecoveryInformation object underneath the computer object. This is not visible to Microsoft Graph at all — it lives purely on-prem and needs a completely separate AD query to check.Those are two different systems, two different query methods, and — critically — two different places a device can fail to escrow into. A script that only queries Graph will report a perfectly clean bill of health for a hybrid fleet where half the devices are actually escrowing (or failing to escrow) into on-prem AD, and Graph simply has no visibility into that at all. The reverse is also true: an AD-only audit will completely miss a cloud-native, Entra-joined device whose key never made it up to Entra.
For the cloud/Entra path, the relevant Graph cmdlet is Get-MgInformationProtectionBitlockerRecoveryKey, available from the Microsoft.Graph.Identity.SignIns module (or Microsoft.Graph.Beta.Identity.SignIns — this endpoint has lived under the beta profile in recent SDK versions, so confirm which module version actually exposes it in your tenant before relying on the v1.0 name). It returns objects with Id, CreatedDateTime, DeviceId, and VolumeType — enough to confirm a key exists and when it landed, but not the key value itself. The permission scope for this read is BitLockerKey.ReadBasic.All, which deliberately returns metadata only. There's a higher-privilege scope, BitLockerKey.Read.All, that exposes the actual recovery key value — that scope has no business being requested for a reporting/compliance script. You are auditing whether a key exists, not retrieving it, and requesting more privilege than the task needs is exactly the kind of over-provisioned app registration that turns into its own audit finding later.
For the on-prem/GPO path, there is no Graph equivalent. The key lives in the msFVE-RecoveryInformation child object under the computer object in Active Directory, and checking it means querying AD directly — with the ActiveDirectory PowerShell module, on a device with connectivity to a domain controller, something like:
# Find the computer object first, then look for msFVE-RecoveryInformation child objects $computer = Get-ADComputer -Identity "DESKTOP-████████" Get-ADObject -Filter 'objectClass -eq "msFVE-RecoveryInformation"' ` -SearchBase $computer.DistinguishedName ` -Properties msFVE-RecoveryPassword, msFVE-RecoveryGuid, whenCreated | Select-Object Name, whenCreated
Note that this is a genuinely different script pattern than the Graph query above — different module, different auth model (Windows-integrated auth against a domain controller rather than an app registration and OAuth token), different connectivity requirement (LOS to a DC, not just internet access to Graph). Do not expect one script to cover both paths; expect two.
Get-MgDeviceManagementManagedDevice -All, checking each device's encryption state against its escrow status. I'm not fully confident of the exact boolean property name for "is this device's OS drive encrypted" on the returned managedDevice object across every module version — it may be IsEncrypted in the schema you have installed, but property surfaces on this resource type have shifted between Graph SDK releases. Don't hard-code that property name and trust it silently. The script below checks for the property's existence at runtime and warns loudly if it isn't found, rather than asserting it's there. Confirm the actual property name against your installed module version (Get-MgDeviceManagementManagedDevice -All | Get-Member is the fastest way) before you rely on it for anything more than a cross-check.One more nuance worth a mention rather than its own section: self-encrypting hardware drives (SEDs, using the eDrive/OPAL standard) that BitLocker manages via hardware encryption sometimes surface differently in reporting, and some organisations reasonably scope them out of a "software BitLocker" recovery-key audit since the key material and threat model differ slightly. If your fleet has any SEDs, confirm which encryption mode Windows actually negotiated (Get-BitLockerVolume locally reports EncryptionMethod) before assuming your audit script's escrow expectations apply identically to every device.
How to verify — check one device manually before you trust any script
Before running an audit against your entire fleet, verify the mechanism on a single known-good device in each management model. If you can't confirm a healthy device shows a key where you expect it, don't trust the script's output for the rest of the fleet.
Entra admin center — the cloud/Intune path
Selecting a specific Entra-joined device and opening its BitLocker keys tab shows every recovery key object escrowed for that device, including the volume type and the date it was created. If that list is empty for a device you know is BitLocker-encrypted, that device has no retrievable Entra-escrowed key — full stop. That's the manual version of exactly what the audit script below checks at scale.
Separately, Intune's own device reporting (Devices › Monitor › Encryption report) is useful for confirming a device believes itself encrypted, but it does not show you escrow status the way the Entra admin center's BitLocker keys tab does — it's a good first filter for "which devices are encrypted at all," not a substitute for checking whether the key actually escrowed.
On-prem Active Directory — the hybrid/GPO path
For a hybrid-joined device that escrows to AD instead of Entra, checking the equivalent state means the Get-ADObject query shown earlier, run against a real device you know is encrypted:
That last line is the whole point of this section: a device can be genuinely, fully encrypted on disk and still have zero recoverable key anywhere your organisation can reach. Confirming this gap exists on a device you can walk over to and look at is the cheapest sanity check you'll ever run, and it's what tells you the fleet-wide script below is actually measuring something real.
The fix — auditing escrow compliance with PowerShell
The script below, Get-BitLockerEscrowComplianceReport.ps1, covers the cloud/Entra escrow path end to end: it connects to Microsoft Graph, enumerates Windows devices, checks each one's Intune-reported encryption state, and cross-references whether a matching BitLocker recovery key object actually exists for that device. It reports every encrypted device with no retrievable key as a hard failure — not a warning, a failure — because that is precisely the state that turns an ordinary firmware update into permanent data loss.
Core logic — the Graph query and the escrow cross-check
# Metadata-only scope - we're auditing existence of a key, not retrieving its value Connect-MgGraph -Scopes "BitLockerKey.ReadBasic.All", "DeviceManagementManagedDevices.Read.All", "Device.Read.All" -NoWelcome # Get-MgInformationProtectionBitlockerRecoveryKey - confirm this cmdlet resolves under your # installed module (may require Microsoft.Graph.Beta if not yet promoted to v1.0 in your version) $allKeys = Get-MgInformationProtectionBitlockerRecoveryKey -All $keysByDevice = $allKeys | Group-Object DeviceId -AsHashTable $managedDevices = Get-MgDeviceManagementManagedDevice -All -Filter "operatingSystem eq 'Windows'" foreach ($device in $managedDevices) { # Defensive check - do not assume the property name; warn if it's missing rather than # silently treating an unencrypted device as compliant or vice versa if (-not ($device.PSObject.Properties.Name -contains 'IsEncrypted')) { Write-Warning "IsEncrypted property not found on managedDevice object for $($device.DeviceName) - verify schema for your module version" continue } if ($device.IsEncrypted -ne $true) { continue } # not encrypted - out of scope for this audit $hasKey = $keysByDevice.ContainsKey($device.AzureADDeviceId) if (-not $hasKey) { $status = 'RED' # encrypted, no escrowed key found in Entra - data-loss risk } else { $status = 'GREEN' } }
The full script wraps this in fail-loud error handling — any Graph connection or query failure exits with code 1 and never silently reports zero non-compliant devices — plus a colour-coded console summary and an optional -ExportCsv switch for a dated compliance record.
A realistic first run
=== Connecting to Microsoft Graph === Connected as admin@contoso.onmicrosoft.com | Tenant: ████████-████-████-████-████████████ === Enumerating managed Windows devices === Pulling Intune managed device list (Get-MgDeviceManagementManagedDevice -All)... Pulling Entra ID BitLocker recovery key records (Get-MgInformationProtectionBitlockerRecoveryKey -All)... Found 214 Windows managed devices, 187 escrowed recovery key records. === Evaluating escrow compliance === === BitLocker escrow compliance report === [FAIL] FIN-LAPTOP-19 Encrypted=True | No BitLocker key found in Entra ID | DATA-LOSS RISK [FAIL] OPS-DESKTOP-22 Encrypted=True | No BitLocker key found in Entra ID | DATA-LOSS RISK [FAIL] SALES-LAPTOP-03 Encrypted=True | No BitLocker key found in Entra ID | DATA-LOSS RISK [OK] FIN-LAPTOP-14 Encrypted=True | Key escrowed 2026-06-02 [OK] IT-LAPTOP-01 Encrypted=True | Key escrowed 2026-07-11 [OK] ...185 more devices... all GREEN Summary: 185 GREEN | 3 RED (out of 188 encrypted devices checked) ACTION NEEDED: 3 device(s) are encrypted with NO recoverable key on record in Entra ID. REMINDER: this run checked Entra ID escrow only. Hybrid-joined devices escrowing to on-prem AD are NOT covered by this script - see the separate msFVE-RecoveryInformation query. CSV exported: .\BitLockerEscrowReport_2026-07-22_0912.csv
Three devices, all reporting Encrypted=True, with no matching key object in Entra ID at all. Every one of those is currently one recovery prompt away from being unrecoverable. Note the deliberate warning at the bottom of the run — the script tells you outright that it only covers the Entra escrow path, precisely so nobody mistakes "0 RED from this script" for "0 RED across the whole fleet" when a chunk of the estate is hybrid-joined and escrowing on-prem instead.
Remediating a device with no escrowed key
Proof it worked — re-running after remediation
After forcing a fresh key backup on the three flagged devices and confirming the encryption profile assignment, give it time for the Entra sync to complete and re-run the exact same script:
=== Connecting to Microsoft Graph === Connected as admin@contoso.onmicrosoft.com | Tenant: ████████-████-████-████-████████████ === Enumerating managed Windows devices === Pulling Intune managed device list (Get-MgDeviceManagementManagedDevice -All)... Pulling Entra ID BitLocker recovery key records (Get-MgInformationProtectionBitlockerRecoveryKey -All)... Found 214 Windows managed devices, 190 escrowed recovery key records. === Evaluating escrow compliance === === BitLocker escrow compliance report === [OK] FIN-LAPTOP-19 Encrypted=True | Key escrowed 2026-07-22 [OK] OPS-DESKTOP-22 Encrypted=True | Key escrowed 2026-07-22 [OK] SALES-LAPTOP-03 Encrypted=True | Key escrowed 2026-07-22 [OK] FIN-LAPTOP-14 Encrypted=True | Key escrowed 2026-06-02 [OK] ...184 more devices... all GREEN Summary: 188 GREEN | 0 RED (out of 188 encrypted devices checked) REMINDER: this run checked Entra ID escrow only. Hybrid-joined devices escrowing to on-prem AD are NOT covered by this script - see the separate msFVE-RecoveryInformation query. CSV exported: .\BitLockerEscrowReport_2026-07-22_1544.csv
All three previously-flagged devices now show a fresh Key escrowed 2026-07-22 timestamp — confirmation that a matching recovery key object now exists in Entra ID, not just that the encryption profile is assigned. That distinction matters: assignment succeeding was never in question for these three devices; escrow actually completing is what changed. Keep the recurring reminder about on-prem AD coverage visible in every run — it's the detail that stops a clean GREEN summary from being mistaken for full fleet coverage when hybrid devices are still unaudited.
I'll audit both the Entra ID and on-prem AD escrow paths across your fleet and close the gaps before a firmware update finds them for you.