Get Intune devices with missing BitLocker keys in Azure AD

Recently, I was approached by a good friend with a question; “How do we retrieve a list of Intune managed Windows devices that are missing the BitLocker recovery key.” In other words, they needed a way to get Intune managed devices lacking an escrowed BitLocker recovery key.

Encryption report

To find Intune devices with missing BitLocker keys in Azure AD, any experienced Intune administrator would instinctively look at the Encryption report available under Devices -> Monitor. But only to find that the report blade shows the encryption status information only. And not necessarily if the BitLocker recovery key was successfully escrowed to the device in Azure AD.

Intune bitlocker report

What other options do we have to understand if any of the devices showing up as Encrypted in the above Intune reporting blade have escrowed the Bitlocker recovery key in Azure AD? At present, the only option is to query the Microsoft Graph API for the Bitlocker information, which is exactly what I’m going to describe how to accomplish in this blog post.

BitLocker resource in Graph API

Relatively recently, the Beta API for Intune in Graph received a much sought after update with a new resource named bitlockerRecoveryKey, which is great news for us.

Read more about the new “bitlockerRecoveryKey” on Microsoft Docs:
Get bitlockerRecoveryKey – Microsoft Graph beta | Microsoft Docs

If we wish to query this resource in Graph, we need to make a few adjustments. First of all, as mentioned in the docs page above, the resource only supports Delegated authentication, meaning that we’d need to authenticate using an identity, e.g., your own work account.

Delegation for reading bitlocker information from azure ad

Unfortunately for us, these permissions have not been added to the ‘Microsoft Intune PowerShell‘ enterprise application. This is available in all tenants after you’ve granted admin consent for it. This means that a custom app registration is required to query the BitLocker recovery keys using this resource from the Graph.

Custom App registration requirements

If it’s the first time you’re attempting to retrieve data from Graph API using PowerShell, you should know that a custom App registration in Azure AD will be required and used to retrieve the authentication token and also provide the desired permissions to certain parts of Graph API. I will not cover the steps required to create a custom app registration, as they’re already explained in a well written blog post from my fellow MVP Nicola Suter:

Microsoft Graph Access Token Acquisition with PowerShell explained in depth – nicolonsky tech

However, what’s not covered in Nicola’s post, is the additional configuration in terms of permissions that needs to be granted for this specific scenario we’re talking about here, covered in the next section.

API Permissions for Bitlocker

In addition to the app registration’s authentication blade, the API Permissions blade also requires some configuration. Below you’ll see that I’ve added the two permissions mentioned earlier. These are required for us to get Intune devices with missing Bitlocker keys:

Delegated access permissions for bitlocker key retreival

With the app registration created and ready to be used, there are only two things that we need to make a note of. And that’s the ‘Directory (tenant) ID‘ and ‘Application (client) ID‘ properties, available on the Overview blade of the app registration:

app registration info for the bitlocker retrieval script

With everything settled, let’s have a look at the PowerShell code required.

Calling bitlockerRecoveryKey resource with PowerShell

Before we look at the whole script used to retrieve the devices that may never have successfully escrowed a BitLocker recovery key, let’s talk about some basics first. When scripting against Graph API, we need to deal with retrieving an authentication token. In the script that we’ll talk about more below, I’m using the MSAL.PS PowerShell module from the PowerShell gallery to deal with just that. More specifically, the Get-MsalToken cmdlet from this module is what I use to retrieve the token.

By running the following and supplying the cmdlet with the ‘Directory (tenant) ID‘ and the ‘Application (client) ID‘ of the app registration either created to modified for this purpose, we get the following simple chunk of code:

$TenantID = "<tenant_id>"
$ClientID = "<client_id>"
$AccessToken = Get-MsalToken -TenantId $TenantID -ClientId $ClientID

Get-MsalToken will prompt for your credentials; please provide them when asked. When the authentication token (also known as bearer) has been retrieved, an object named Microsoft.Identity.Client.AuthenticationResult is returned and will show the following output:

access token from msal

Constructing the authentication header

With this information, we can construct a hash-table with the required information needed by Graph API. Normally, we’d use something similar to the following that we then use for the Header parameter of the Invoke-RestMethod cmdlet:

$AuthenticationHeader = @{
    "Content-Type" = "application/json"
    "Authorization" = $AccessToken.CreateAuthorizationHeader()
    "ExpiresOn" = $AccessToken.ExpiresOn.LocalDateTime

Here the $AccessToken variable that contains the authentication result that we received earlier comes into play. First of all, we define that the header will be of a content type that is ‘application/json‘. The Authentication and ExpiresOn properties are constructed using the CreateAuthorizationHeader method and LocalDateTime property, respectively.

So far, nothing super fancy, however for the calls required towards the bitlockerRecoveryKey resource, it requires that you also pass two additional parameters in the header, more specifically these two:

  • ocp-client-name
    • Any string value can be used, e.g. “My App”
  • ocp-client-version
    • Any string value as a version can be used, e.g. “1.0”

This forces us to extend the hash-table to the following:

$AuthenticationHeader = @{
    "Content-Type" = "application/json"
    "Authorization" = $AccessToken.CreateAuthorizationHeader()
    "ExpiresOn" = $AccessToken.ExpiresOn.LocalDateTime
    "ocp-client-name" = "My App"
    "ocp-client-version" = "1.0"

At this point, we’ve put together all the required pieces to successfully be able to start pulling the data from the various resources in Graph API and analyze the data for our intended scenario.

Combining everything into a functioning script

On our GitHub repository, I’ve made available the full script that helps you identify Intune managed devices that have not escrowed any BitLocker recovery keys. You can find the script here:

Intune/Get-IntuneManagedDeviceBitLockerKeyPresence.ps1 at master · MSEndpointMgr/Intune (

What this script does is to, first of all, retrieve all bitlockerRecoveryKey objects from the bitlocker/recoveryKeys resource in Graph API. Afterward, all managed devices are retrieved using the deviceManagement/managedDevices resource with a filter for Windows devices only. With all of this data available, the -notin operator can be used to determine what devices have not escrowed a recovery key.

How to use the script to get the Bitlocker information

While creating the script, I figured it could also be useful to retrieve all devices that actually have escrowed a BitLocker recovery key. So I have added a parameter named –State that accepts either ‘Present‘ or ‘NotPresent‘ as input.

Run the following command in a PowerShell console to retrieve all managed devices without an escrowed BitLocker recovery key present:

.\Get-IntuneManagedDeviceBitLockerKeyPresence.ps1 -TenantID "<tenant_id>" -ClientID "<client_id>" -State NotPresent -Verbose

In case any managed devices meet the specified criteria, they will be shown as output in a formatted table like shown below:

intune bitlocker script output

If you rather wanted to get a list of all devices that actually have escrowed, simply change the parameter input to ‘Present‘ for the State parameter.

Taking BitLocker management even further

Once we have all our BitLocker recovery keys safely stored away in Azure AD, we can take our key management to the next level. A good start is setting up True Bitlocker one-time key with Intune.

Nickolaj Andersen

Chief Technical Architect and Enterprise Mobility MVP since 2016. Nickolaj has been in the IT industry for the past 10 years specializing in Enterprise Mobility and Security, Windows devices and deployments including automation. Awarded as PowerShell Hero in 2015 by the community for his script and tools contributions. Creator of ConfigMgr Prerequisites Tool, ConfigMgr OSD FrontEnd, ConfigMgr WebService to name a few. Frequent speaker at conferences such as Microsoft Ignite, NIC Conference and IT/Dev Connections including nordic user groups.


  • Hello Nickolaj,

    Your assistance with my issue around this was invaluable. I did have to get the script scaled out to support the amount of data very large orgs may have. Your original script is good until the bitlocker data retrieval overwhelms the amount of ram available, or runs longer than the account privilege elevation window allows.

    THANK YOU for your assistance.

  • Afternoon guys,

    Always impressed by the scripts you guys come up with. I’ve had your HP Driver install from AutoPilot in production for a couple of months and it’s working very well.

    Anyway, onto todays trials!

    When running this script I’m getting the following error;

    VERBOSE: GET$filter=operatingSystem eq
    WARNING: Graph request failed with status code ‘Forbidden’. Error message: {
    “_version”: 3,
    “Message”: “Application is not authorized to perform this operation. Application must have one of the following
    scopes: DeviceManagementManagedDevices.Read.All, DeviceManagementManagedDevices.ReadWrite.All – Operation ID (for
    customer support): 00000000-0000-0000-0000-000000000000 – Activity ID: 9720f868-22ea-452d-9bcc-46d2cfe91c60 – Url:
    “CustomApiErrorPhrase”: “”,
    “RetryAfter”: null,
    “ErrorSourceService”: “”,
    “HttpHeaders”: “{}”

    My laptops are all Hybrid Azure AD joined and I’ve added DeviceManagementManagedDevices.Read.All to delegated permissions.

    Am I missing something?

    Cheers and keep up the good work.


    • OK, I got it figured out!

      It seems that I needed to clear my cached $AccessToken and run the script again. But it seems that that extra delegated permission | DeviceManagementManagedDevices.Read.All | is needed now 🙂




Categories use cookies to ensure that we give you the best experience on our website.