Retrieve Bitlocker keys stored in AzureAD with PowerShell

Sample output

Bitlocker keys can be stored in Active Directory and in Azure Active Directory too – but querying the latter is a bit trickier than usual. The following script will export all Bitlocker recovery keys (from your Azure Active Directory tenant) to an HTML table.

TL;DR
1. Ensure that you meet the following prerequisites:
– you have adequate rights in AzureAD (Global Admin for example 🙂 )
– the following PowerShell modules are installed: AzureRM, AzureAD
2. Run the script below (make sure that the path is valid).
– the script will prompt for AzureAD credentials twice, it is normal (scipt explained later)

The scipt itself:

$exportFile = "C:\TEMP\BitLockerReport.html"

#Install-Module AzureRM
Import-Module AzureRM.Profile
Login-AzureRmAccount

#Prepare Context - REQUIRES TENANT ADMIN
$context = Get-AzureRmContext
    $tenantId = $context.Tenant.Id
    $refreshToken = @($context.TokenCache.ReadItems() | Where-Object {$_.tenantId -eq $tenantId -and $_.ExpiresOn -gt (Get-Date)})[0].RefreshToken
    $body = "grant_type=refresh_token&refresh_token=$($refreshToken)&resource=74658136-14ec-4630-ad9b-26e160ff0fc6"
    $apiToken = Invoke-RestMethod "https://login.windows.net/$tenantId/oauth2/token" -Method POST -Body $body -ContentType 'application/x-www-form-urlencoded'
    $header = @{
        'Authorization'          = 'Bearer ' + $apiToken.access_token
        'X-Requested-With'       = 'XMLHttpRequest'
        'x-ms-client-request-id' = [guid]::NewGuid()
        'x-ms-correlation-id'    = [guid]::NewGuid()
    }


Connect-AzureAD
$AzureADDevices = Get-AzureADDevice -all $true | ? {$_.deviceostype -eq "Windows"}

# Retrieve BitLocker keys
$deviceRecords = @()

$deviceRecords = foreach ($device in $AzureADDevices) {
        $url = "https://main.iam.ad.ext.azure.com/api/Device/$($device.objectId)"
        $deviceRecord = Invoke-RestMethod -Uri $url -Headers $header -Method Get
        $deviceRecord
    }

$Devices_BitlockerKey = $deviceRecords.Where({$_.BitlockerKey.count -ge 1})

$obj_report_Bitlocker = foreach ($device in $Devices_BitlockerKey){
    foreach ($BLKey in $device.BitlockerKey){
        [pscustomobject]@{
            DisplayName = $device.DisplayName
            driveType = $BLKey.drivetype
            keyID = $BLKey.keyIdentifier
            recoveryKey = $BLKey.recoveryKey
            }
    }
}
#HTML report

<#-- Create HTML report --#>
$body = $null

$body += "<p><b>AzureAD Bitlocker key report</b></p>"
$body += @"
<table style=width:100% border="1">
  <tr>
    <th>Device</th>
    <th>DriveType</th>
    <th>KeyID</th>
    <th>RecoveryKey</th>
  </tr>
"@

$body +=  foreach ($obj in $obj_report_Bitlocker){
"<tr><td>" + $obj.DisplayName + " </td>"
    "<td>" + $obj.DriveType + " </td>"
    "<td>" + $obj.KeyID + " </td>"
"<td>" + $obj.RecoveryKey + "</td></tr>"
}
  
$body += "</table>"

$body > $exportFile

Explained

In the first line, $exportFile defines the output filename as a variable (it will be used at the end of the script).
$exportFile = "C:\TEMP\BitLockerReport.html"

Next, we will connect to the Azure Resource Manager (AzureRM) – to install the module, PowerShell should be running in elevated mode
Install-Module AzureRM
Import-Module AzureRM.Profile
Login-AzureRmAccount

Next, we are going to prepare the Rest API headers used to gather information. It basically consists of retrieving a RefreshToken, which is then used to create an Access Token. This token will grant access to resources (Authorization header).
$context = Get-AzureRmContext
$tenantId = $context.Tenant.Id
$refreshToken = @($context.TokenCache.ReadItems() | Where-Object {$_.tenantId -eq $tenantId -and $_.ExpiresOn -gt (Get-Date)})[0].RefreshToken
$body = "grant_type=refresh_token&refresh_token=$($refreshToken)&resource=74658136-14ec-4630-ad9b-26e160ff0fc6"
$apiToken = Invoke-RestMethod "https://login.windows.net/$tenantId/oauth2/token" -Method POST -Body $body -ContentType 'application/x-www-form-urlencoded'
$header = @{
'Authorization' = 'Bearer ' + $apiToken.access_token
'X-Requested-With' = 'XMLHttpRequest'
'x-ms-client-request-id' = [guid]::NewGuid()
'x-ms-correlation-id' = [guid]::NewGuid()
}

Next, we connect to AzureAD and query all AzureAD device with Windows operating system (Bitlocker applies only on Windows, so other devices are irrelevant):
Connect-AzureAD
$AzureADDevices = Get-AzureADDevice -all $true | ? {$_.deviceostype -eq "Windows"}

The following lines are basically the essence of the script: for each device it queries the old Azure endpoint for information (I’m sure that GraphAPI can be used as well)
$deviceRecords = @()
$deviceRecords = foreach ($device in $AzureADDevices) {
$url = "https://main.iam.ad.ext.azure.com/api/Device/$($device.objectId)"
$deviceRecord = Invoke-RestMethod -Uri $url -Headers $header -Method Get
$deviceRecord
}

And that was the only part which required the Access Token. NOTE: if you’re testing some parts of the script, make sure to run the previous block (starting with Get-AzureRMContext) as access tokens expire after 1 hour and you may experience errors.
Now, we filter out the devices that have Bitlocker information:
$Devices_BitlockerKey = $deviceRecords.Where({$_.BitlockerKey.count -ge 1})

The next block is basically creating a custom object to store the neccessary information as of my needs. The report should contain the following information on each line:
– Device DisplayName
– Drive Type
– Recovery Key ID
– Recovery Key
Because a device can have multiple key ids (OS disk, data disk, etc.), I need to loop through each device’s each BitlockerKey property, so that information can be printed to a simple table (no multivalued cells):
$obj_report_Bitlocker = foreach ($device in $Devices_BitlockerKey){
foreach ($BLKey in $device.BitlockerKey){
[pscustomobject]@{
DisplayName = $device.DisplayName
driveType = $BLKey.drivetype
keyID = $BLKey.keyIdentifier
recoveryKey = $BLKey.recoveryKey
}
}
}

Now that information is ready to be exported, we add the HTML tags to the information and export it:

$body = $null

$body += "<p><b>AzureAD Bitlocker key report</b></p>"
$body += @"
<table style=width:100% border="1">
  <tr>
    <th>Device</th>
    <th>DriveType</th>
    <th>KeyID</th>
    <th>RecoveryKey</th>
  </tr>
"@

$body +=  foreach ($obj in $obj_report_Bitlocker){
"<tr><td>" + $obj.DisplayName + " </td>"
    "<td>" + $obj.DriveType + " </td>"
    "<td>" + $obj.KeyID + " </td>"
"<td>" + $obj.RecoveryKey + "</td></tr>"
}
  
$body += "</table>"

$body > $exportFile

And that’s it 🙂

Leave a Reply