ANAVEM
Reference
Languagefr
How to Export BitLocker Recovery Keys from Active Directory Using PowerShell

How to Export BitLocker Recovery Keys from Active Directory Using PowerShell

Create and run a PowerShell script to extract BitLocker recovery keys from Active Directory and export them to CSV with computer names, recovery passwords, and timestamps.

Emanuel DE ALMEIDAEmanuel DE ALMEIDA
March 17, 2026 15 min 10
mediumbitlocker 8 steps 15 min

Why Export BitLocker Recovery Keys from Active Directory?

BitLocker recovery keys stored in Active Directory represent a critical security asset that requires proper management and accessibility. When users forget their BitLocker passwords or encounter system issues, these recovery keys become the only way to access encrypted data. However, manually searching through Active Directory Users and Computers for individual recovery keys is time-consuming and inefficient, especially in large enterprise environments.

What Makes PowerShell the Best Tool for BitLocker Key Management?

PowerShell's ActiveDirectory module provides direct access to the msFVE-RecoveryInformation objects where BitLocker keys are stored. Unlike GUI-based approaches, PowerShell allows you to process thousands of computer objects efficiently, extract recovery keys with associated metadata, and export everything to structured formats like CSV for analysis and reporting.

How Does BitLocker Key Storage Work in Active Directory?

When BitLocker is configured to back up recovery information to Active Directory (via Group Policy), each encrypted volume creates child objects under the computer account. These objects contain the 48-digit recovery password, recovery key ID, volume GUID, and creation timestamp. The PowerShell script leverages these attributes to create comprehensive reports that include computer names, operating systems, and key creation dates.

This tutorial walks you through creating a robust PowerShell solution that not only extracts the keys but also provides advanced filtering, reporting capabilities, and automation options for ongoing BitLocker key management in your organization.

Related: How to Force Password Changes for All Users in Microsoft 365

Related: How to Configure Automatic Session Lock via Group Policy in

Related: How to Disable NTLM Authentication Protocol in Active

Implementation Guide

Full Procedure

01

Install and Verify the ActiveDirectory PowerShell Module

First, you need to ensure the ActiveDirectory PowerShell module is available on your system. This module is part of the Remote Server Administration Tools (RSAT).

On Windows 10/11, install RSAT tools using this command:

Add-WindowsCapability -Online -Name Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0

On Windows Server, the module is typically installed by default. Verify the module is available:

Get-Module -ListAvailable -Name ActiveDirectory

Import the module and test domain connectivity:

Import-Module ActiveDirectory
Get-ADDomain

Verification: The Get-ADDomain command should return your domain information without errors. If you see domain details like DNSRoot and DomainMode, you're ready to proceed.

Pro tip: If you're running PowerShell 7.x, you might need to install the Windows PowerShell Compatibility module: Install-Module WindowsCompatibility
02

Create the BitLocker Recovery Key Export Script

Create a comprehensive PowerShell script that will search Active Directory for BitLocker recovery information. Open your favorite text editor and create a new file called Export-BitLockerKeys.ps1:

param(
    [string]$SearchBase = "",
    [string]$OutputPath = "C:\temp\BitLockerKeys.csv"
)

function Export-BitLockerKeys {
    param([string]$SearchBase, [string]$OutputPath)

    try {
        Write-Host "Starting BitLocker recovery key export..." -ForegroundColor Green

        # Ensure output directory exists
        $outputDir = Split-Path $OutputPath -Parent
        if (!(Test-Path $outputDir)) {
            New-Item -ItemType Directory -Path $outputDir -Force
            Write-Host "Created output directory: $outputDir" -ForegroundColor Yellow
        }

        # Set up search parameters
        $searchParams = @{
            Filter = "*"
            Properties = @('Name', 'DistinguishedName', 'OperatingSystem', 'LastLogonDate')
        }
        if ($SearchBase) { 
            $searchParams.SearchBase = $SearchBase
            Write-Host "Searching in OU: $SearchBase" -ForegroundColor Yellow 
        }

        # Get all computer objects
        $computers = Get-ADComputer @searchParams
        Write-Host "Found $($computers.Count) computer objects" -ForegroundColor Yellow

        $results = @()
        $counter = 0
        
        foreach ($computer in $computers) {
            $counter++
            Write-Progress -Activity "Processing computers" -Status "Processing $($computer.Name)" -PercentComplete (($counter / $computers.Count) * 100)
            
            # Search for BitLocker recovery information objects
            $recoveryInfo = Get-ADObject -Filter "objectClass -eq 'msFVE-RecoveryInformation'" -SearchBase $computer.DistinguishedName -Properties *
            
            if ($recoveryInfo) {
                foreach ($recovery in $recoveryInfo) {
                    $results += [PSCustomObject]@{
                        ComputerName = $computer.Name
                        RecoveryPassword = $recovery.'msFVE-RecoveryPassword'
                        RecoveryID = $recovery.'msFVE-RecoveryGuid'
                        CreationTime = $recovery.whenCreated
                        DistinguishedName = $computer.DistinguishedName
                        OperatingSystem = $computer.OperatingSystem
                        VolumeGUID = $recovery.'msFVE-VolumeGuid'
                        LastLogon = $computer.LastLogonDate
                    }
                }
            }
        }

        Write-Progress -Activity "Processing computers" -Completed

        if ($results.Count -gt 0) {
            $results | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8
            Write-Host "Export completed! $($results.Count) recovery keys exported to $OutputPath" -ForegroundColor Green
            Write-Host "File size: $([math]::Round((Get-Item $OutputPath).Length / 1KB, 2)) KB" -ForegroundColor Cyan
        } else {
            Write-Host "No BitLocker recovery keys found in Active Directory." -ForegroundColor Yellow
        }
    } catch {
        Write-Error "Error during export: $($_.Exception.Message)"
    }
}

# Execute the function
Export-BitLockerKeys -SearchBase $SearchBase -OutputPath $OutputPath

Save this script to a location like C:\Scripts\Export-BitLockerKeys.ps1.

Verification: Check that the file was saved correctly by running Get-Content C:\Scripts\Export-BitLockerKeys.ps1 | Select-Object -First 5 to see the first few lines.

03

Set PowerShell Execution Policy

Before running the script, you need to ensure PowerShell can execute it. Check your current execution policy:

Get-ExecutionPolicy

If it returns Restricted, you'll need to change it. Set the execution policy to allow local scripts:

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

Alternatively, for a more restrictive approach, you can bypass the policy for this specific script execution:

PowerShell.exe -ExecutionPolicy Bypass -File "C:\Scripts\Export-BitLockerKeys.ps1"

Verification: Run Get-ExecutionPolicy again to confirm the policy change, or test with a simple script like Write-Host "Test" saved as a .ps1 file.

Warning: Never set the execution policy to Unrestricted on production systems. RemoteSigned provides a good balance of security and functionality.
04

Run the Script for Entire Domain

Execute the script to export BitLocker keys from the entire domain. Navigate to your script directory and run:

cd C:\Scripts
.\Export-BitLockerKeys.ps1 -OutputPath "C:\Reports\BitLockerKeys_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"

This command will:

  • Search the entire domain for computer objects
  • Extract BitLocker recovery information for each computer
  • Create a timestamped CSV file in the Reports directory

Monitor the progress as the script processes each computer. For large domains, this may take several minutes.

Verification: Check that the CSV file was created and contains data:

$csvFile = Get-ChildItem "C:\Reports\BitLockerKeys_*.csv" | Sort-Object LastWriteTime -Descending | Select-Object -First 1
Import-Csv $csvFile.FullName | Select-Object -First 5 | Format-Table

You should see columns for ComputerName, RecoveryPassword, RecoveryID, CreationTime, and other fields.

05

Target Specific Organizational Units

For better organization and performance, you can target specific OUs instead of the entire domain. First, identify your target OU's distinguished name:

Get-ADOrganizationalUnit -Filter "Name -like '*Workstations*'" | Select-Object Name, DistinguishedName

Run the script targeting a specific OU:

.\Export-BitLockerKeys.ps1 -SearchBase "OU=Workstations,OU=Computers,DC=yourdomain,DC=com" -OutputPath "C:\Reports\BitLocker_Workstations.csv"

You can also target multiple OUs by running the script multiple times with different SearchBase parameters:

# Export from Workstations OU
.\Export-BitLockerKeys.ps1 -SearchBase "OU=Workstations,DC=yourdomain,DC=com" -OutputPath "C:\Reports\BitLocker_Workstations.csv"

# Export from Laptops OU
.\Export-BitLockerKeys.ps1 -SearchBase "OU=Laptops,DC=yourdomain,DC=com" -OutputPath "C:\Reports\BitLocker_Laptops.csv"

Verification: Compare the number of results between domain-wide and OU-specific exports:

$workstationsCount = (Import-Csv "C:\Reports\BitLocker_Workstations.csv").Count
Write-Host "Workstations OU: $workstationsCount recovery keys found" -ForegroundColor Green
Pro tip: Use Get-ADComputer -Filter * -SearchBase "OU=YourOU,DC=domain,DC=com" | Measure-Object to get a quick count of computers in an OU before running the export.
06

Analyze and Validate the Exported Data

Once you have your CSV export, it's important to analyze the data for completeness and accuracy. Load the CSV and perform basic analysis:

$bitlockerData = Import-Csv "C:\Reports\BitLockerKeys_20260316_143022.csv"

# Display summary statistics
Write-Host "Total recovery keys exported: $($bitlockerData.Count)" -ForegroundColor Green
Write-Host "Unique computers with keys: $($bitlockerData | Select-Object ComputerName -Unique | Measure-Object).Count" -ForegroundColor Cyan

# Show computers with multiple recovery keys (multiple drives or key rotations)
$multipleKeys = $bitlockerData | Group-Object ComputerName | Where-Object {$_.Count -gt 1}
if ($multipleKeys) {
    Write-Host "Computers with multiple recovery keys: $($multipleKeys.Count)" -ForegroundColor Yellow
    $multipleKeys | Select-Object Name, Count | Format-Table
}

# Check for recent key creations (last 30 days)
$recentKeys = $bitlockerData | Where-Object {[DateTime]$_.CreationTime -gt (Get-Date).AddDays(-30)}
Write-Host "Recovery keys created in last 30 days: $($recentKeys.Count)" -ForegroundColor Magenta

Validate the recovery password format (should be 48 digits in 8 groups of 6):

# Check for properly formatted recovery passwords
$invalidPasswords = $bitlockerData | Where-Object {$_.RecoveryPassword -notmatch '^\d{6}-\d{6}-\d{6}-\d{6}-\d{6}-\d{6}-\d{6}-\d{6}$'}
if ($invalidPasswords) {
    Write-Warning "Found $($invalidPasswords.Count) entries with invalid recovery password format"
    $invalidPasswords | Select-Object ComputerName, RecoveryPassword | Format-Table
}

Verification: Spot-check a few entries by manually looking up a computer in Active Directory Users and Computers and comparing the recovery key.

07

Create Advanced Reporting and Filtering

Enhance your BitLocker reporting with advanced filtering and formatting. Create a comprehensive report script:

# Advanced BitLocker Report Generation
$bitlockerData = Import-Csv "C:\Reports\BitLockerKeys_20260316_143022.csv"

# Create summary report
$summaryReport = @{
    'Total Recovery Keys' = $bitlockerData.Count
    'Unique Computers' = ($bitlockerData | Select-Object ComputerName -Unique).Count
    'Windows 11 Systems' = ($bitlockerData | Where-Object {$_.OperatingSystem -like "*Windows 11*"}).Count
    'Windows 10 Systems' = ($bitlockerData | Where-Object {$_.OperatingSystem -like "*Windows 10*"}).Count
    'Keys Created This Year' = ($bitlockerData | Where-Object {[DateTime]$_.CreationTime -gt (Get-Date -Month 1 -Day 1)}).Count
}

# Display summary
$summaryReport.GetEnumerator() | Sort-Object Name | Format-Table -AutoSize

# Find computers without recent logons (potential inactive systems)
$staleComputers = $bitlockerData | Where-Object {
    $_.LastLogon -and [DateTime]$_.LastLogon -lt (Get-Date).AddDays(-90)
} | Select-Object ComputerName, LastLogon, OperatingSystem | Sort-Object LastLogon

if ($staleComputers) {
    Write-Host "Computers with BitLocker keys but no logon in 90+ days: $($staleComputers.Count)" -ForegroundColor Yellow
    $staleComputers | Export-Csv "C:\Reports\BitLocker_StaleComputers.csv" -NoTypeInformation
}

# Create filtered exports by OS
$windows11Keys = $bitlockerData | Where-Object {$_.OperatingSystem -like "*Windows 11*"}
$windows10Keys = $bitlockerData | Where-Object {$_.OperatingSystem -like "*Windows 10*"}

if ($windows11Keys) {
    $windows11Keys | Export-Csv "C:\Reports\BitLocker_Windows11.csv" -NoTypeInformation
    Write-Host "Windows 11 systems exported: $($windows11Keys.Count) keys" -ForegroundColor Green
}

if ($windows10Keys) {
    $windows10Keys | Export-Csv "C:\Reports\BitLocker_Windows10.csv" -NoTypeInformation
    Write-Host "Windows 10 systems exported: $($windows10Keys.Count) keys" -ForegroundColor Green
}

Verification: Check that the filtered CSV files were created and contain the expected data:

Get-ChildItem "C:\Reports\BitLocker_*.csv" | Select-Object Name, Length, LastWriteTime | Format-Table
Warning: BitLocker recovery keys are highly sensitive. Ensure your CSV files are stored securely and access is restricted to authorized personnel only. Consider encrypting the export files or storing them on encrypted drives.
08

Schedule Automated Exports with Task Scheduler

Set up automated BitLocker key exports using Windows Task Scheduler for regular reporting. First, create a wrapper script that includes logging:

# Save as C:\Scripts\Scheduled-BitLockerExport.ps1
$logPath = "C:\Logs\BitLockerExport.log"
$reportPath = "C:\Reports\Scheduled"

# Ensure directories exist
if (!(Test-Path "C:\Logs")) { New-Item -ItemType Directory -Path "C:\Logs" -Force }
if (!(Test-Path $reportPath)) { New-Item -ItemType Directory -Path $reportPath -Force }

# Start logging
Start-Transcript -Path $logPath -Append

try {
    Write-Host "Starting scheduled BitLocker export at $(Get-Date)" -ForegroundColor Green
    
    # Run the export
    $outputFile = "$reportPath\BitLocker_Weekly_$(Get-Date -Format 'yyyyMMdd').csv"
    & "C:\Scripts\Export-BitLockerKeys.ps1" -OutputPath $outputFile
    
    # Clean up old reports (keep last 4 weeks)
    Get-ChildItem $reportPath -Filter "BitLocker_Weekly_*.csv" | 
        Where-Object {$_.LastWriteTime -lt (Get-Date).AddDays(-28)} | 
        Remove-Item -Force
    
    Write-Host "Scheduled export completed successfully" -ForegroundColor Green
} catch {
    Write-Error "Scheduled export failed: $($_.Exception.Message)"
} finally {
    Stop-Transcript
}

Create the scheduled task using PowerShell:

# Create scheduled task for weekly BitLocker export
$action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-ExecutionPolicy Bypass -File 'C:\Scripts\Scheduled-BitLockerExport.ps1'"
$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Monday -At "02:00AM"
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable

Register-ScheduledTask -TaskName "BitLocker Recovery Key Export" -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Description "Weekly export of BitLocker recovery keys from Active Directory"

Verification: Check that the task was created and test it manually:

Get-ScheduledTask -TaskName "BitLocker Recovery Key Export" | Format-List
Start-ScheduledTask -TaskName "BitLocker Recovery Key Export"

# Check the log file after a few minutes
Get-Content "C:\Logs\BitLockerExport.log" -Tail 10

Frequently Asked Questions

What permissions do I need to export BitLocker recovery keys from Active Directory?+
You need Domain Admin privileges or specifically delegated read access to computer objects and their msFVE-RecoveryInformation child objects. The account must also have read permissions on the msFVE-RecoveryPassword and msFVE-RecoveryGuid attributes. Running PowerShell as Administrator is also required for the ActiveDirectory module to function properly.
Why am I getting 'No BitLocker recovery keys found' when I know computers have BitLocker enabled?+
This typically means BitLocker keys aren't being backed up to Active Directory. Check your Group Policy settings under Computer Configuration > Administrative Templates > Windows Components > BitLocker Drive Encryption. Enable 'Store BitLocker recovery information in Active Directory Domain Services' and ensure the policy is applied to target computers. Keys are only stored when this policy is active during BitLocker enablement.
Can I export BitLocker keys from Azure AD or Microsoft Entra ID instead of on-premises Active Directory?+
Yes, but the process is different. For cloud-only or hybrid environments, use Microsoft Graph PowerShell with the Get-MgDevice cmdlet and BitLocker recovery key properties. You'll need the Device.Read.All permission and different authentication methods. The script structure changes significantly as Azure AD uses different object types and attributes than on-premises AD.
How can I verify that the exported recovery passwords are valid and properly formatted?+
Valid BitLocker recovery passwords are exactly 48 digits arranged in 8 groups of 6 digits, separated by hyphens (e.g., 123456-789012-345678-901234-567890-123456-789012-345678). Use PowerShell regex matching with the pattern '^\d{6}-\d{6}-\d{6}-\d{6}-\d{6}-\d{6}-\d{6}-\d{6}$' to validate format. You can also test a key by attempting to unlock a BitLocker drive using manage-bde -unlock C: -RecoveryPassword.
What should I do if the PowerShell script is running slowly on large domains with thousands of computers?+
Optimize performance by adding -ResultPageSize 1000 to Get-ADComputer calls, running the script during off-peak hours, and targeting specific OUs instead of the entire domain. Consider filtering computers by operating system or last logon date to reduce the search scope. For very large environments, implement parallel processing using PowerShell workflows or run separate exports for different OUs simultaneously.
Emanuel DE ALMEIDA
Written by

Emanuel DE ALMEIDA

Microsoft MCSA-certified Cloud Architect | Fortinet-focused. I modernize cloud, hybrid & on-prem infrastructure for reliability, security, performance and cost control - sharing field-tested ops & troubleshooting.

Discussion

Share your thoughts and insights

You must be logged in to comment.

Loading comments...