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 modeInstall-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 🙂