Worklet: Enforce BitLocker Encryption


Userlevel 7

For detailed steps on creating and scheduling a Worklet, please see this article:

 

Creating a Worklet

 

 

For an overview on Worklets, see this article:

 

 

How to Use Worklets

 

 

The Worklets

 

 

The following Evaluation and Remediation worklets can be used to enforce BitLocker encryption.

 

 

Evaluation Code

 

 

# PowerShell 4.0 and Above

# Windows 8 and later



#Get BitLocker status for All Drives

try { $encryption = Get-BitLockerVolume -ErrorAction Stop }

catch { Write-Output "Unable to determine BitLocker status" }



# Count Drives and initialize lists for later output

$numDrives = $encryption.Count

$encCount = 0

$encrypted = @()

$unencrypted = @()



# Loop through each drive and see if it is Protected or Note

# Add to the appropriate list, Encrypted or Unencrypted

foreach ($drive in $encryption) {

$encStatus = $drive.ProtectionStatus

$encInProgress = $drive.VolumeStatus

if ( ($encStatus -match 'On') -or ($encInProgress -match "EncryptionInProgress") ) {

$encrypted += $drive.MountPoint

$encCount++

} else {

$unencrypted += $drive.MountPoint

}

}



# Output drive statuses so the can be seen in the Activity Log

Write-Output "Encrypted Drives: $encrypted`n"

Write-Output "Unencrypted Drives: $unencrypted`n"



# Determine Compliant based on if the number of Encrypted

# Drives matches the number of Total Drives

if ($encCount -eq $numDrives) {

Write-Output "Compliant"

exit 0

} else {

Write-Output "Non-Compliant"

exit 1

}

 

This evaluation requests BitLocker status for all physical disk drives on the target device. It then compares the count of encrypted drives to the total number present. If all drives are encrypted then return Compliant (Exit 0). Otherwise return Non-Compliant (Exit 1).

 

 

Remediation Code

 

 

# PowerShell 4.0 and Above

# Windows 8 and later



# Define where you want your Recovery Key to be exported

# Note that this needs to be a local (non-network) drive.

$keyPath = 'C:\temp'



$toEncrypt = Get-BitLockerVolume | Where-Object { $_.VolumeStatus -match 'Decrypted' }



# Loop through each Unencrypted Drives

# Enable Bitlocker and Export their Recovery Keys

foreach ( $drive in $toEncrypt ) {

$driveLetter = $drive.MountPoint.Replace(':','')

try {

#Enable Bitlocker

Enable-BitLocker -MountPoint $driveLetter -EncryptionMethod Aes128 -RecoveryPasswordProtector | Out-Null



#Export Key and Key ID to a File

$recID = (Get-BitLockerVolume -MountPoint $driveLetter).KeyProtector.KeyProtectorID

$recKey = (Get-BitLockerVolume -MountPoint $driveLetter).KeyProtector.RecoveryPassword

Set-Content -Path "$keyPath\BitlockerRecoveryKey_$driveLetter.txt" -Force -Value "Recovery Key ID: $recID"

Add-Content -Path "$keyPath\BitlockerRecoveryKey_$driveLetter.txt" -Value "Recovery Key: $recKey"

} catch {

Write-Output "Unable to Encrypt $($drive.MountPoint)"

}

}

 

The Remediation code has one editable variable ($keyPath). Use this to define where the Recovery Key will be stored. The Recovery Key is necessary to decrypt the drive should that become necessary in the future.

 

 

This worklet initially runs a similar check as the Evaluation Code to enumerate each physical drive that is not encrypted. Using this information, it starts encryption on each of these drives and exports the Recovery Key to a text file in the previously specified location.

 


This topic has been closed for comments

12 replies

This is great. Wondering if this could be edited so that we could only check the C: drive and as well, instead of actually encrypting, simply just marking the device as non-compliant so that we could run a report and then remediate after some communications. That would be awesome.

Userlevel 7

Welcome @agranados! The drive letter limited to C should be pretty straightforward. The harder part would be gathering all the info one place. Since we’re not actually storing a key, we could instead report to the Activitly Log report as to which devices need encryption. Then you could filter the activity log by that specific policy and export that to CSV if further editing is necessary. Would that work as an approach for your use case?

Thats actually exactly what i ended up doing after some tweaking. Its just reporting on all drives vs just C: which is fine. Can easily be filtered like you said. Thanks!

Userlevel 3
Badge

Thanks for creating this worklet! I am running into an issue where the script ran the first time, created the recovery keys and went through without error on 2 test machines. Afterwards, neither of the machines drives actually got encrypted (They do have TPM enabled) and if I try running the script again it just outputs no result in reports. Its almost acting like the drives are encrypted even though they aren’t.


Any ideas on this?

Userlevel 4
Badge

Hey Guys,


So I also ran into issues with this worklet and have been racking my brain on trying to get it to work successfully. So I also had the same issues as @agranados and @JHagstrom. I wanted only C drive, and it wasn’t actually encrypting these drives (as far as I could tell). So first noticed that when running the encrypt command via nonelevated PSx86, access denied. So scriptblock must be used again!


Anyways, heres my rough draft, it works as far as I can tell, I really would like a cleaner way to output the ‘decrypting > encrypting’ activity log, but already encrypted works.


Another note: I want my bitlocker recovery key to go into the activity log so we can pull it into a csv and have a repo for it. Putting the log on the local device is near-useless, since you’ll only ever need that key if you get locked out of your harddrive (which has the only key on it? seems questionable).


Let me know what you guys think!


EDIT: Looks like this could use some error handling when TPM isn’t enabled, no output in activity log.


$scriptblock =
{
$toEncrypt = Get-BitLockerVolume | Where-Object { $_.VolumeStatus -match 'FullyDecrypted' -and $_.MountPoint -match 'c:'}
$Encrypted = Get-BitLockerVolume | Where-Object { $_.VolumeStatus -match 'FullyEncrypted' -and $_.MountPoint -match 'c:'}
# Loop through each Unencrypted Drives
# Enable Bitlocker and Export their Recovery Keys

foreach ( $drive in $toEncrypt ) {
$driveLetter = $drive.MountPoint.Replace(':','')
try {
#Enable Bitlocker
Enable-BitLocker -MountPoint 'C:' -EncryptionMethod Aes256 -SkipHardwareTest -UsedSpaceOnly -RecoveryPasswordProtector | Out-Null

#Export Key and Key ID to a File
$recID = (Get-BitLockerVolume -MountPoint $driveLetter).KeyProtector.KeyProtectorID
$recKey = (Get-BitLockerVolume -MountPoint $driveLetter).KeyProtector.RecoveryPassword
} catch {
Write-Output "Unable to find new C drive"
}
}
foreach ( $drive in $Encrypted ) {
$driveLetter = $drive.MountPoint.Replace(':','')
try {
#Export Key and Key ID to a File
$recID = (Get-BitLockerVolume -MountPoint $driveLetter).KeyProtector.KeyProtectorID
$recKey = (Get-BitLockerVolume -MountPoint $driveLetter).KeyProtector.RecoveryPassword
set-content -Path "C:\ProgramData\amagent\BitlockerRecoveryKey_c.txt" -Force -Value "Recovery Key ID: $recID"
add-content -Path "C:\ProgramData\amagent\BitlockerRecoveryKey_c.txt" -Value "Recovery Key: $recKey"
} catch {
Write-Output "Unable to find existing C drive"
}
}
}
$users = $installed64 = & "$env:SystemRoot\sysnative\WindowsPowerShell\v1.0\powershell.exe" -ExecutionPolicy Bypass -WindowStyle Hidden -NoProfile -NonInteractive -Command $scriptblock

$output = Get-Content -Path "C:\ProgramData\amagent\BitlockerRecoveryKey_c.txt"

Write-Output $output
Remove-Item -Path "C:\ProgramData\amagent\BitlockerRecoveryKey_c.txt"

Awesome! I haven’t had a chance to try your update yet, but I had a thought of getting the key into the Activity log.


Try adding a “Write-Output” line at the end of the try section of your code. As long as it completes successfully, you should see the output in the Activity log.



try {

#Export Key and Key ID to a File

$recID = (Get-BitLockerVolume -MountPoint $driveLetter).KeyProtector.KeyProtectorID

$recKey = (Get-BitLockerVolume -MountPoint $driveLetter).KeyProtector.RecoveryPassword

set-content -Path “C:\ProgramData\amagent\BitlockerRecoveryKey_c.txt” -Force -Value “Recovery Key ID: $recID”

add-content -Path “C:\ProgramData\amagent\BitlockerRecoveryKey_c.txt” -Value “Recovery Key: $recKey”



Add another line with something like this:


Write-Output "Recovery Key = $recKey, Recovery Key ID = $recID"
Userlevel 4
Badge

So Write-Outputs do not work in a scriptblock (AFAIK, I havent gotten it to work). So what I did was export it to a file in amagent folder, then escape the scriptblock and read that file and then Write-Output.

Either way, we get the necessary data regardless in my iteration above. Its just not formatted as pretty as I’d like.

Ahhhh, yes… That.


Here is a way to skip the file creation to get a variable out of a scriptblock. There is another step (change your variable at the end to your variable you want to return from within the scriptblock. In this example it is $keyAndId


Fake codeblock:


$scriptblock = {
$key = 'abcdefg'
$keyId = '123456'
$keyAndId = "Key = $key and Key ID = $keyId"

Write-host $keyAndId
}
$keyAndId = & "$env:SystemRoot\sysnative\WindowsPowerShell\v1.0\powershell.exe" -ExecutionPolicy Bypass -WindowStyle Hidden -NoProfile -NonInteractive -Command $scriptblock
exit 0

Here is the output in the activity log:

Userlevel 3
Badge

I am working on a script for bitlocker installs that sends the output of the recovery codes and attaches it to the devices tags section through the API as a solution for storing the key.


I am planning to post a separate worklet post once its done, but will try to remember to tag you guys incase you are interested 🙂

Hey, did you ever build this script?

Thanks for this script, Nic.

If I exit 1 on the Eval code and put the above Evaluation code into the Remediation Code section, the Event log just shows errors. I would like to see the result of the evaluation but as the Write-Output in that section does not appear in the Event log, is there any way to pick this up in the Remediation section? I have tried to set an Env variable but that does not work. There also doesn’t seem to be a way to see what the Eval exit code was.

Userlevel 5

I took the eval code and tweaked it. This gives you the list of encrypted and unencrypted drives in the activity log. If all drives are not encrypted, then that would have kicked off the remediation in the original worklet at the top.


Evaluation:


Exit 0

Remediation:


# PowerShell 4.0 and Above
# Windows 8 and later

#Get BitLocker status for All Drives
try { $encryption = Get-BitLockerVolume -ErrorAction Stop }
catch { Write-Output "Unable to determine BitLocker status" }

# Count Drives and initialize lists for later output
$numDrives = $encryption.Count
$encCount = 0
$encrypted = @()
$unencrypted = @()

# Loop through each drive and see if it is Protected or Note
# Add to the appropriate list, Encrypted or Unencrypted
foreach ($drive in $encryption) {
$encStatus = $drive.ProtectionStatus
$encInProgress = $drive.VolumeStatus
if ( ($encStatus -match 'On') -or ($encInProgress -match "EncryptionInProgress") ) {
$encrypted += $drive.MountPoint
$encCount++
} else {
$unencrypted += $drive.MountPoint
}
}

# Output drive statuses so the can be seen in the Activity Log
If ($encrypted) {
Write-Output "Encrypted Drives: $encrypted "
} else {
Write-Output "Encrypted Drives: None "
}

If ($unencrypted) {
Write-Output "Unencrypted Drives: $unencrypted "
} else {
Write-Output "Unencrypted Drives: None"
}