Having a modern, secure infrastructure in 2019 is a requirement. You should implement BitLocker to make sure that in the event of stolen laptop data is not readily extractable and implementing LAPS is a must in a fast changing IT world. But I'm not here to convince you to those two security features. I'm here to show you an easy way to backup LAPS and BitLocker. While having everything stored in Active Directory is excellent, things can get complicated when you don't have access to your Active Directory, or you restore an older version of it. You see, LAPS, for example, keeps only last Administrator password. This is great and all but what happens if you restore the machine from backup from 6 months back? Your password has already changed multiple times. During our testing of DR scenarios, we wanted to access the computer via their local Administrator credentials and we just couldn't because that password was already gone.
Below code should allow you to extract all BitLocker Keys available in Active Directory for all computers. Keep in mind it will get it all. Not only the latest one.
function Get-WinADDomainBitlocker { param( [string] $Domain = $Env:USERDNSDOMAIN, [Array] $Computers ) $Properties = @( 'Name', 'OperatingSystem', 'DistinguishedName' ) #[DateTime] $CurrentDate = Get-Date if ($null -eq $Computers) { $Computers = Get-ADComputer -Filter * -Properties $Properties -Server $Domain } foreach ($Computer in $Computers) { $Bitlockers = Get-ADObject -Filter 'objectClass -eq "msFVE-RecoveryInformation"' -SearchBase $Computer.DistinguishedName -Properties 'WhenCreated', 'msFVE-RecoveryPassword' #| Sort-Object whenCreated -Descending #| Select-Object whenCreated, msFVE-RecoveryPassword foreach ($Bitlocker in $Bitlockers) { [PSCustomObject] @{ 'Name' = $Computer.Name 'Operating System' = $Computer.'OperatingSystem' 'Bitlocker Recovery Password' = $Bitlocker.'msFVE-RecoveryPassword' 'Bitlocker When' = $Bitlocker.WhenCreated 'DistinguishedName' = $Computer.'DistinguishedName' } } } }
Usage is quite simple
$MyBitlocker = Get-WinADDomainBitlocker $MyBitlocker | Format-Table -AutoSize # Exporting to Excel # Install-Module PSWriteExcel # is required ConvertTo-Excel -FilePath 'C:\myFile.xlsx' -DataTable $MyBitlocker -AutoFilter -AutoFit -ExcelWorkSheetName 'Bitlocker Keys'
Below code is able to deliver extracted LAPS passwords for each, and every computer in Active Directory. It also shows Expiration Time and Days to Expire.
function Get-WinADDomainLAPS { [CmdletBinding()] param( [string] $Domain = $Env:USERDNSDOMAIN, [Array] $Computers ) $Properties = @( 'Name', 'OperatingSystem', 'DistinguishedName', 'ms-Mcs-AdmPwd', 'ms-Mcs-AdmPwdExpirationTime' ) [DateTime] $CurrentDate = Get-Date if ($null -eq $Computers -or $Computers.Count -eq 0) { $Computers = Get-ADComputer -Filter * -Properties $Properties } foreach ($Computer in $Computers) { [PSCustomObject] @{ 'Name' = $Computer.Name 'Operating System' = $Computer.'OperatingSystem' 'Laps Password' = $Computer.'ms-Mcs-AdmPwd' 'Laps Expire (days)' = Convert-TimeToDays -StartTime ($CurrentDate) -EndTime (Convert-ToDateTime -Timestring ($Computer.'ms-Mcs-AdmPwdExpirationTime')) 'Laps Expiration Time' = Convert-ToDateTime -Timestring ($Computer.'ms-Mcs-AdmPwdExpirationTime') 'DistinguishedName' = $Computer.'DistinguishedName' } } }
Again, usage is very similar to what you get in Bitlocker above
$Laps = Get-WinADDomainLAPS $Laps | Format-Table -AutoSize # Again using this required PSWriteExcel ConvertTo-Excel -FilePath 'C:\myFile.xlsx' -DataTable $Laps -AutoFilter -AutoFit -ExcelWorkSheetName 'LAPS'
There are two PowerShell modules I'm using that I've created and are utilized above. One is PSSharedGoods, and one is PSWriteExcel. PSSharedGoods is required for two functions Convert-TimeToDays and Convert-ToDateTime. If you don't feel like installing those feel free to use the code below. It's not strongly optimized, but it works. PSWriteExcel is responsible for creating an Excel file if you prefer to export using other, more common modules you don't need that.
function Convert-TimeToDays { [CmdletBinding()] param ( $StartTime, $EndTime, #[nullable[DateTime]] $StartTime, #[nullable[DateTime]] $EndTime, [string] $Ignore = '*1601*' ) if ($StartTime -and $EndTime) { try { if ($StartTime -notlike $Ignore -and $EndTime -notlike $Ignore) { $Days = (NEW-TIMESPAN -Start (GET-DATE) -End ($EndTime)).Days } else { $Days = $null } } catch { $Days = $null } } return $Days } function Convert-ToDateTime { [CmdletBinding()] param ( [string] $Timestring, [string] $Ignore = '*1601*' ) Try { $DateTime = ([datetime]::FromFileTime($Timestring)) } catch { $DateTime = $null } if ($null -eq $DateTime -or $DateTime -like $Ignore) { return $null } else { return $DateTime } }