Description

This script reads the Bitlocker Drive Keys from a PC and backs them up to Active Directory

Source Code

#Reference
#https://www.top-password.com/blog/tag/get-bitlocker-recovery-key-from-ad-powershell/
#https://docs.microsoft.com/en-us/powershell/module/addsadministration/get-adcomputer?view=win10-ps
#https://stackoverflow.com/questions/18877580/powershell-and-the-contains-operator

#Author: Matthew Olan aka molan
#Date: March 2 2020

cls
#this script will Check AD for the local windows machines it is run on. Lookup its Bitlocker recovery Keys and then attempt to verify their local bitlocker info is backed up in AD
#in order to successfully complete for a computer the device must be online and remotely accessable by this script 
#this script required administrative access

Get-WindowsCapability -Online | Where-Object {$_.Name -like "*ActiveDirectory.DS-LDS*"} | Add-WindowsCapability -Online

$CompList = hostname

$ADObjects = @()

#for each AD computer and server lookup if it has Bitlocker Recovery Keys Stored and Retrive them

#write-host "Query AD and look for Bitlocker Keys" -forgroundColor Green

#Foreach($CL in $CompList)
#{
    $CompADSearchBase = Get-ADComputer -Identity $CompList
    #write-host $CompADSearchBase
    
    $Bitlocker_Object = Get-ADObject -Filter {objectclass -eq 'msFVE-RecoveryInformation'} -SearchBase $CompADSearchBase -Properties 'msFVE-RecoveryPassword'
    
    $Properties = @{'HostName'=$CompList; 'BitLockerInfo'=$Bitlocker_Object}
    $ADObjects = New-Object -TypeName PSObject -Property $Properties 
    
    #write-host "Computer: "$CompList " -> AD Key Count:" $Bitlocker_Object.'msFVE-RecoveryPassword'.Count
   
#}


#Collect machine info from queried machiens
#to collect machine Name, Drive Letter, Encryption State, Key ID
$PropertiesPCDISK = @{VolumeLetter=$null;DiskSize=$null;BitLockerVersion=$null;ConverstionStatus=$null;PercentEncrypted=$null;EncryptMethod=$null;ProtectionStatus=$null;LockStatus=$null;KeyID=$null;'KeyBackedUpinAD'=$null}
$Disk = New-Object PSObject -Property  $PropertiesPCDISK
$PropertiesPC = @{'HostName'=$null; 'Online_Offline'=$null; 'Disk'=$null; 'ADKeyIDInfo'=$null}
$QueriedPCList = @()
$ListPCOffline = ""

foreach($ADO in $ADObjects)
{
        #write-host "foreach ado"
        #Get Bitlocker Password ID for all computers
        #write-host $ADO.HostName  " AD Key Count: " $ADO.BitLockerInfo.Count
        <#
        #ping PC to see if it is online
        $Timeout = 100
        $Ping = New-Object System.Net.NetworkInformation.Ping
            
        $Response = ""
        $ResponseError = ""

        try
        {
            #If the ping is successfull the $Response variable becomes an object with the responce data
            #if the ping fails the $Response variable remains a string
            $Response = $Ping.Send($ADO.HostName.Trim(),$Timeout)
            #write-host "Ping: " $Response.Status -ForegroundColor Yellow
            if($Response.Status -eq "TimedOut")
            {
                $ResponseError =  "TimedOut"
            }
        }
        Catch
        {
            $ResponseError =  "TimedOut"
        }
            
        

        #Check if Host is online based on Ping Status
        #If the ping is successfull the $Response variable becomes an object with the responce data
        #if the ping fails the $Response variable remains a string
        if($Response.Status -eq "Success")
        {
        #>
        #Query the PC for its local Bitlocker Info
        $QueriedPCListSingle = New-Object PSObject -Property $PropertiesPC
        $BDE_Status =  ""
        try
        {
            #write-host "attempting to get local Bitlocker Info" -ForegroundColor Green
            $BDE_Status = manage-bde -computername $ADO.HostName -Status #manage-bde returns an array of strings
        }
        catch
        {
            #write-host "Host Online, Error getting local Bitlocker Info" -ForgroundColor Red
        }
                
        $QueriedPCListSingle.HostName = $ADO.HostName
        $QueriedPCListSingle.Online_Offline = "Online"
                
        #combine AD INfo with Search Results
        #Loop through AD info and add it to final PSObject
        $ADKeysList = @()
        foreach($t1 in $ADO.BitLockerInfo)
        {
            #This Value is the Bitlocker Password
            ##write-host "PW: " $b.'msFVE-RecoveryPassword' 
            $Var = $t1.ToString()
            #This Value is the Password ID
            #write-host "AD Key ID: " $Var.Substring($Var.IndexOf("{")+1, $Var.IndexOf("}") - $Var.IndexOf("{") - 1 ) -ForegroundColor DarkYellow
            $ADKeysList += $Var.Substring($Var.IndexOf("{")+1, $Var.IndexOf("}") - $Var.IndexOf("{") - 1 )
        }

        $QueriedPCListSingle.ADKeyIDInfo += $ADKeysList

        $DiskResults = New-Object -TypeName PSObject -Property  $PropertiesPCDISK
        $DiskResultsArray = @()
                 
        $LoopCounter = 0
        $IsEncrypted = "False"
        #Loop through Local Machine Bitlocker info and add it to the Final PSObject
        #manage-bde returns an array of strings
        foreach($e in $bde_Status)
        {
                $LoopCounter += 1
                     
                #Check if Bitlocker is enabled
                if($e.Length -gt 0)
                {
                    if($e.ToString() -Match "Percentage Encrypted: 100.0%")
                    {
                    $IsEncrypted = "True"
                    }
                    if($e.ToString() -Match "Volume")
                    { 
                    if($e.ToString() -Match ":")
                    {
                        $DiskResults.VolumeLetter = $e.Substring(7,2).Trim()
                    }
                    }

                    if($e.ToString() -Match "Size:")
                    { 
                    $DiskResults.DiskSize = $e.ToString()
                    }

                    if($e.ToString() -Match "BitLocker Version:")
                    { 
                    $DiskResults.BitLockerVersion = $e.ToString()
                    }

                    if($e.ToString() -Match "Conversion Status:")
                    { 
                    $DiskResults.ConverstionStatus = $e.ToString()
                    }

                    if($e.ToString() -Match "Percentage Encrypted:")
                    { 
                    $DiskResults.PercentEncrypted = $e.ToString()
                    }

                    if($e.ToString() -Match "Encryption Method:")
                    { 
                    $DiskResults.EncryptMethod = $e.ToString()
                    }

                    if($e.ToString() -Match "Protection Status:")
                    {
                    $DiskResults.ProtectionStatus = $e.ToString()
                    }

                    if($e.ToString() -Match "Lock Status:")
                    { 
                    $DiskResults.LockStatus = $e.ToString()
                    }
                          
                }
                      
                #$LoopCounter
                #https://www.codecademy.com/forum_questions/52dd862f9c4e9d6378000bd9
                #Use Modulos operate to check if a number is evenly divisable by itself after subtracting 16
                #subtract 16 becuase the first result in the array contains an extra 4 lines of info
                #all follow up lines appear to be 12 lines each
                #I hope MS doesn't change this or my script will break..... Badly!
                #This is the most dodgy and difficult part of this script!!
                $V1 = $LoopCounter - 16
                $V2 = $V1 % 12
                if($V2 -eq 0 -and $V1 -ge 0)
                {
                #Query for ID
                if($IsEncrypted -eq "True")
                {
                    #write-host "Query for Protectors on :" $ADO.HostName ": '" $DiskResults.VolumeLetter.Trim() "'" -ForegroundColor Green
                    $BLProtectors = manage-bde -computername $ADO.HostName -protectors -get $DiskResults.VolumeLetter.Trim()

                    #write-host "Protector Data Lines Found: " $BLProtectors.count -ForegroundColor Red 

                    $IDCount = 0
                    foreach($BLP in $BlProtectors)
                    {
                        if($BLP -match "ID: {")
                        {
                            $IDCount += 1
                            If($IDCount -eq 2) #take the 2nd ID for the password. not the first ID for the TPM
                            {
                                $DiskResults.KeyID =  $BLP.Substring($BLP.IndexOf("{")+1, $BLP.IndexOf("}") - $BLP.IndexOf("{") - 1 )
                                #write-host "Found The Key!: " $DiskResults.KeyID -ForegroundColor Yellow
                                        
                                #Check if any Keys Found backed up In AD 
                                foreach($Q1 in $ADO.BitLockerInfo)
                                {
                                    $Var = $Q1.ToString()
                                    #This Value is the Password ID
                                    $ADOKey = $Var.Substring($Var.IndexOf("{")+1, $Var.IndexOf("}") - $Var.IndexOf("{") - 1 )

                                    #write-host "Check Key Backed up"
                                    #write-host "PC Key ID: " $DiskResults.KeyID 
                                    #write-host "AD Key ID: " $ADOKey

                                    if($DiskResults.KeyID -eq $ADOKey)
                                    {
                                        #write-host $DiskResults.KeyID " Key is backed Up Correctly" -ForegroundColor Green
                                        $DiskResults.KeyBackedUpinAD = "Yes"
                                                
                                    }
                                }
                            }
                        }
                    }
                }

                        
                #write-host "Save Results" -ForegroundColor Yellow
                $DiskResults

                #Save Successfull Results to the final PSObject 
                $DiskResultsArray += $DiskResults
                $DiskResults = New-Object -TypeName PSObject -Property  $PropertiesPCDISK
                $IsEncrypted = "False"
                }
        }
               
        $QueriedPCListSingle.Disk = $DiskResultsArray
        $QueriedPCList += $QueriedPCListSingle
                
    }
<#
        elseif($ResponseError -eq "TimedOut")
        {
            #write-host "Host Offline: Skipped" -ForegroundColor Red
            #Save Error Results to the final PSObject     
            $QueriedPCListSingle = New-Object PSObject -Property $PropertiesPC
            $QueriedPCListSingle.HostName = $ADO.HostName
            $QueriedPCListSingle.Online_Offline = "Offline"

            $QueriedPCList += $QueriedPCListSingle
        }
        #write-host "`r`n"
}#>

#Searching Complete



#Format data for nice output to CSV
$FinalResult =@()
$FinalResult += "HostName," + "Online?," + "Drive Letter," + "Disk Size," + "Bit Locker Version," + "Conversion Status," + "% Encrypted," + "Encryption Method," + "Protection Status," + "Lock Status," + "KeyID," + "Key Backed Up in AD," + "AD Key Info -->"
foreach($q1 in $QueriedPCList)
{
    #Add hosts with discovered disks to the output
    foreach($q2 in $q1.Disk)
    {
        $KeyInfo = ""
        foreach($q3 in $q1.ADKeyIDInfo)
        {
            $KeyInfo += $q3 + ", "
        }
        $FinalResult += $q1.HostName + "," + $q1.Online_Offline + "," + $q2.VolumeLetter + "," + $q2.DiskSize + "," + $q2.BitLockerVersion + "," + $q2.ConverstionStatus + "," + $q2.PercentEncrypted + "," + $q2.EncryptMethod + "," + $q2.ProtectionStatus + "," + $q2.LockStatus + "," + $q2.KeyID + "," + $q2.KeyBackedUpinAD + "," + $KeyInfo
    }
    #Add hosts that failed to pass the ping test or that returned no disk results
    if($q1.Disk.Count -lt 1)
    {
        $KeyInfo = ""
        foreach($q3 in $q1.ADKeyIDInfo)
        {
            $KeyInfo += $q3 + ", "
        }
        $FinalResult += $q1.HostName + "," + $q1.Online_Offline + ","  + ","  + ","  + "," +  "," + ","  + "," +  "," + "," + ","  + ","  + $KeyInfo
    }
}



#Read each stored Host
foreach($q1 in $QueriedPCList)
{
    #write-host "Foreach q1"  $q1.Disk
    #check each discovered disk for each stored host
    foreach($d in $q1.Disk)
    {
        #write-host "Foreach d"
        #Check if the host has a Key to backup. Only backup if it isn't already backed up
        if($d.KeyID.Length -gt 1 -and $d.KeyBackedUpinAD -ne 'yes')
        {
            #Backup Bitlock Key
            #write-host "Attempting to backup Key for: " $q1.HostName -ForegroundColor Green

            $KI = "{" + $d.KeyID + "}"
            #write-host "manage-bde -computername" $q1.HostName "-protectors -adbackup" $d.VolumeLetter "-id" $KI
                
            $BackupAttempt = ""
            $BackupAttempt = manage-bde -computername $q1.HostName -protectors -adbackup $d.VolumeLetter -id $KI
                
            foreach($ba in $BackupAttempt)
            {
                #write-host $ba

                if($ba -match 'successfully')
                {
                    #write-host "Backup up Success: " +  $q1.HostName + ", " + $d.VolumeLetter + "," +  $d.KeyId + ", " + $ba
                }
                    
                if($ba -match 'ERROR')
                {
                    #write-host "Backup up Error: " +  $q1.HostName + ", " + $d.VolumeLetter + "," +  $d.KeyId + ", " + $ba
                }
            }
        }
        else
        {
            #write-host "No Keys Backed UP"
        }
    }
}