Summary: Guest blogger, Stephane van Gulick, continues his series about using Windows PowerShell and BitLocker together.
Microsoft Scripting Guy, Ed Wilson, is here. Welcome back Stephane van Gulick for the final part of his two-part series. Be sure you read PowerShell and BitLocker: Part 1 first.
Encryption operations
A lot of the following script examples come from a function I wrote called BitLockerSAK. It is a tool written in Windows PowerShell that makes BitLocker tasks easier to automate.
Finally, we arrive at the interesting part: the encryption of the drive. Don’t get me wrong—the Trusted Platform Module (TPM) operations are extremely important in the process of automating the drive encryption. Without these steps, the drive encryption might not even happen. But this is where I had the most fun in the scripting process.
Are you sitting comfortably? You might want to get a refill of coffee before we hit it. Ready? All right...let’s go!
Everything that relates to the proper encryption of the drive and that needs to be automated resides in the WMI (CIM) repository. It lies in the same Root\cimv2\Security\ namespace hierarchy as the Win32_TPM. But this time we will dive into the Win32_EncryptableVolume class.
The Win32_EncryptableVolume class contains an instance for each of the volumes that are present on the computer (for example, hard drives and USB keys).
We can look into it by using the following command, and because we generally want to encrypt the system drive, we will filter on drive C.
Using Get-CimInstance will look like this (the results are shown in green in the following image):
$CIMVolumeC = Get-CimInstance -namespace "Root\cimv2\security\MicrosoftVolumeEncryption" -ClassName "Win32_Encryptablevolume" -
Or we can use Get-WmiObject as follows for retrocompatibility (shown in red in the following image):
$WMIVolumeC= Get-WmiObject -namespace "Root\cimv2\security\MicrosoftVolumeEncryption" -ClassName "Win32_Encryptablevolume" -filter "DriveLetter = 'C:'"
As you can see, these two commands return (almost) the same results:
The only difference is that Get-WMIObject returns the instance and the system properties (they start with the double underscore “__”).
Let’s look at the properties and methods we have access to through the two methods.
Get-CIMInstance returns the following list:
Get-WMIObject returns a bunch more methods—there are so many that we cannot see them all on this screenshot:
The CIM option returns only 18 results when piped to Get-Member:
But good old Get-WMIObject returns 84 results:
Now that we have seen the methods that are available, we can start to work with them.
Key protectors
Prior to launching the encryption of a specific volume, we need to set a key protector. A key protector will protect the volume encryption key, which will protect the volume that has just been encrypted.
We can find all the key protectors that can be set by using the following code:
$EncryptionData = Get-WMIObject -Namespace "Root\cimv2\security\MicrosoftVolumeEncryption" –classname "Win32_EncryptableVolume" -Filter "DriveLetter = 'c:'"
We have a few methods available as shown in the following screenshot:
Those I have worked with the most are:
- ProtectKeyWithTPM
- ProtectKeyWithTPMAndPIN
- ProtectKeyNumericalPassword
Theoretically, we could allow any key protector on any computer. But this is something you want to control in your environment. This can be easily achieved by using a Group Policy Object (GPO).
Each key protector will deliver another encryption experience and it will need some custom scripting to make it work in your environment.
We will not go into the details of each because that would make this post even longer that what it already is. But each of the previous methods are documented on MSDN, so you can find everything that you need there.
Protection key IDs and types
We list the key protectors that are currently on one computer by using GetKeyProtectors and getKeyProtectorType from the Win32_Encryptable class. Here is the code from my BitLockerSAK function:
$BitLocker = Get-WmiObject -Namespace "Root\cimv2\Security\MicrosoftVolumeEncryption" -Class "Win32_EncryptableVolume" -Filter "DriveLetter = '$DriveLetter'"
$ProtectorIds = $BitLocker.GetKeyProtectors("0").volumekeyprotectorID
$return = @()
foreach ($ProtectorID in $ProtectorIds){
$KeyProtectorType = $BitLocker.GetKeyProtectorType($ProtectorID).KeyProtectorType
$keyType = ""
switch($KeyProtectorType){
"0"{$Keytype = "Unknown or other protector type";break}
"1"{$Keytype = "Trusted Platform Module (TPM)";break}
"2"{$Keytype = "External key";break}
"3"{$Keytype = "Numerical password";break}
"4"{$Keytype = "TPM And PIN";break}
"5"{$Keytype = "TPM And Startup Key";break}
"6"{$Keytype = "TPM And PIN And Startup Key";break}
"7"{$Keytype = "Public Key";break}
"8"{$Keytype = "Passphrase";break}
"9"{$Keytype = "TPM Certificate";break}
"10"{$Keytype = "CryptoAPI Next Generation (CNG) Protector";break}
}#endSwitch
$Properties = @{"KeyProtectorID"=$ProtectorID;"KeyProtectorType"=$Keytype}
$Return += New-Object -TypeName psobject -Property $Properties
}#EndForeach
Return $Return
This enumerates the all the existing key protectors. Based on their IDs, it will fetch their type, put it in a custom object, and return the information through the variable $return.
You will have something similar to this:
Those I have seen the most are:
- Numerical Password (return value 3)
- TPM and PIN (return value 4)
BitLocker Drive Encryption operations
Finally, we come to the part about BitLocker Drive Encryption operations...
There is one main WMI class that hosts all the encryption methods and properties of all of your drives: the Win32_EncryptableVolume. You will find this class in the Root\cimv2\security\MicrosoftVolumeEncryption namespace.
Global protection state
Prior to any encryption operations, you most likely would want to verify which state the drive is in. If it is already 100% encrypted, you will save you some time. We can get that information by using the following code:
$ProtectionState = Get-WmiObject -Namespace ROOT\CIMV2\Security\Microsoftvolumeencryption -Class Win32_encryptablevolume -Filter "DriveLetter = 'c:'"
switch ($ProtectionState.GetProtectionStatus().protectionStatus){
("0"){$return = "Unprotected"}
("1"){$return = "Protected"}
("2"){$return = "Uknowned"}
default {$return = "NoReturn"}
}
return $return
We get a value of either 0, which means the drive is unprotected or 1, which means the drive is protected.
This is a first step. If the drive is protected, you can quit the whole script logic because this means that your drive is currently 100% encrypted, and it is ready for the wild, wild west.
Encryption state and encryption percentage
If you want the see the current encryption state of your drive, you can use the following code:
$EncryptionData= Get-WmiObject -Namespace ROOT\CIMV2\Security\Microsoftvolumeencryption -Class Win32_encryptablevolume -Filter "DriveLetter = 'c:'"
$protectionState = $EncryptionData.GetConversionStatus()
$CurrentEncryptionProgress = $protectionState.EncryptionPercentage
switch ($ProtectionState.Conversionstatus){
"0" {
$Properties = @{'EncryptionState'='FullyDecrypted';'CurrentEncryptionProgress'=$CurrentEncryptionProgress}
$Return = New-Object psobject -Property $Properties
}
"1" {
$Properties = @{'EncryptionState'='FullyEncrypted';'CurrentEncryptionProgress'=$CurrentEncryptionProgress}
$Return = New-Object psobject -Property $Properties
}
"2" {
$Properties = @{'EncryptionState'='EncryptionInProgress';'CurrentEncryptionProgress'=$CurrentEncryptionProgress}
$Return = New-Object psobject -Property $Properties
}
"3" {
$Properties = @{'EncryptionState'='DecryptionInProgress';'CurrentEncryptionProgress'=$CurrentEncryptionProgress}
$Return = New-Object psobject -Property $Properties
}
"4" {
$Properties = @{'EncryptionState'='EncryptionPaused';'CurrentEncryptionProgress'=$CurrentEncryptionProgress}
$Return = New-Object psobject -Property $Properties
}
"5" {
$Properties = @{'EncryptionState'='DecryptionPaused';'CurrentEncryptionProgress'=$CurrentEncryptionProgress}
$Return = New-Object psobject -Property $Properties
}
default {
write-verbose "Couldn't retrieve an encryption state."
$Properties = @{'EncryptionState'=$false;'CurrentEncryptionProgress'=$false}
$Return = New-Object psobject -Property $Properties
}
}
return $return
The current encryption state and the current percentage of encryption of the current drive will be returned. If I launch this part of the code on my computer with elevated rights, the following results are returned:
Note In the case of decryption, the percentage represents the amount of encrypted space.
The following Visio flow chart helps us see a global overview. It shows the action and the methods that are related to these actions.
Encryption
Now that we have identified the current state of the drive, we want to start the encryption. At this state, you should already have a protection key.
If we take a peek in the MSDN documentation, ProtectKeyWithNumericalPassword, we see that the ProtectKeyWithNumericalPassword method has two parameters as input [IN], and one as output [OUT]. But both of the input parameters are optional [Optional]. This means that we can actually call this method without passing any parameters.
Note The following code will only work if you have set a GPO that allows drive protection by using TPM and PIN.
$pin = 123456
$ProtectionState = Get-WmiObject -Namespace ROOT\CIMV2\Security\Microsoftvolumeencryption -Class Win32_encryptablevolume -Filter "DriveLetter = '$DriveLetter'"
write-verbose "Launching drive encryption."
$ProtectorKey = $protectionState.ProtectKeyWithTPMAndPIN("ProtectKeyWithTPMAndPIN","",$pin)
Start-Sleep -Seconds 3
$NumericalPasswordReturn = $protectionState.ProtectKeyWithNumericalPassword()
$Return = $protectionState.Encrypt()
$returnCode = $return.returnvalue
switch ($ReturnCode) {
("0"){$message = "Operation successfully started."}
("2147942487") {$message = "The EncryptionMethod parameter is provided but is not within the known range or does not match the current Group Policy setting."}
("2150694958") {$message = "No encryption key exists for the volume"}
("2150694957") {$message = "The provided encryption method does not match that of the partially or fully encrypted volume."}
("2150694942") {$message = "The volume cannot be encrypted because this computer is configured to be part of a server cluster."}
("2150694956") {$message = "No key protectors of the type Numerical Password are specified. The Group Policy requires a backup of recovery information to Active Directory Domain Services"}
default{
$message = "An unknown status was returned by the Encryption action."
}
}
$Properties = @{'ReturnCode'=$ReturnCode;'ErrorMessage'=$message}
$Return = New-Object psobject -Property $Properties
return $return
As you can see, we use following two methods to encrypt our drive:
- ProtectKeyWithTPMandPIN
- ProtectKeyWithNumericalPassword
To protect our volume, we will use the ProtectKeyWithTPMAndPIN method. For this method, there are several parameters that we could pass, but only PIN is a required parameter.
According to the documentation, PIN accepts a user-specified personal identification string as input. This string must consist of a sequence of 4 to 20 digits or, if the "Allow enhanced PINs for startup" Group Policy is enabled, 4 to 20 letters, symbols, spaces, or numbers.
If a 0 is returned (operation successfully started), you can call the previous code and see how the encryption percentage progresses through the time.
Pause the encryption
If at any time, you want to pause the encryption, you can use the following code:
$BitLocker = Get-WmiObject -Namespace "Root\cimv2\Security\MicrosoftVolumeEncryption" -Class "Win32_EncryptableVolume" -Filter "DriveLetter = '$DriveLetter'"
$ReturnCode = $BitLocker.PauseConversion()
switch ($ReturnCode.ReturnValue){
"0"{$Return = "Paused sucessfully.";break}
"2150694912"{$Return = "The volume is locked.";Break}
default {$Return = "Uknown return code.";break}
}
return $return
Note To continue the encryption from where it was paused, simply use previous encryption code to call the encrypt() method again.
The drive encryption logic is summarized in the following Visio flow chart. It shows the actions and the methods that are related to these actions.
Decryption
In some cases, you might want or need to decrypt a drive. Again, this can be done through the Win32_EncryptableVolume WMI class with the following code:
$BitLocker = Get-WmiObject -Namespace "Root\cimv2\Security\MicrosoftVolumeEncryption" -Class "Win32_EncryptableVolume" -Filter "DriveLetter = 'c:'"
$ReturnCode = $BitLocker.Decrypt()
switch ($ReturnCode.ReturnValue){
"0"{$Return = "Uncryption started successfully.";break}
"2150694912"{$Return = "The volume is locked.";Break}
"2150694953" {$Return = "This volume cannot be decrypted because keys used to automatically unlock data volumes are available.";Break}
default {$Return = "Uknown return code.";break}
}
return $return
If the code is launched, it will start the decryption of drive C.
If you launch the encryption state code again, you will see that the decryption starts and the CurrentEncryptionProgress percentage gets closer to zero each time you launch it.
The methodology must be familiar to most of you by now. If we combine the previous code examples, we can build a logic similar to the following quite easily by using the Decrypt() method.
Global encryption logic
I have presented a lot of code, and all of these single tasks need to be done in a specific order. I have summarized all the BitLocker encryption logic in the following Visio flow chart:
If the encryption involves a TPM, the TPM also need to be activated; and therefore, some specific TPM actions need to be done. (Those details are discussed in the first post of this series.)
BitLockerSAK
The BitLocker Swiss Army Knife (BitLockerSAK) is a project I started a while ago. It started with the need to automate TPM and BitLocker encryption for one of my clients. This client didn’t have Windows PowerShell 3.0 deployed—thus no BitLocker or CIM cmdlets.
After repetitively executing Get-WMIObject calls, I thought I would simplify the complete process and combine all of this in one unique tool that would have the look and feel of the well-known Manage-bde.exe. I wrote version 1.0 in a weekend and posted it shortly after.
BitLockerSAK makes TPM and drive encryption operations through Windows PowerShell much easier than calling the different WMI methods directly. It has additional logic that will save a lot of time for those who need to script BitLocker or TPM tasks. I have used it in complex encryption scripts and in Configuration Manager configuration items to retrieve non encrypted computers, and remediate the non-compliant ones.
The following tables might look similar, but I have simplified them (especially the WMI Method section) to help you identify how to execute which encryption or TPM task according to which tool you are using.
TPM operations equivalence
The following table lists the most common TPM WMI methods (based on Win32_TPM) and their BitLockerSAK equivalents.
| WMIMethod | BitLockerSAK |
TPM Enabled | .IsEnabled().isenabled | BitLockerSAK -isTPMEnabled |
TPM Activated | .IsActivated().isactivated | BitLockerSAK -isTPMActivated |
TPM Owned | .IsOwned().Isowned | BitLockerSAK -isTPMOwned |
Take TPM OwnerShip | .ClearTpm + .TakeOwnerShip | BitLockerSAK -TakeTPMOwnership |
Encryption operations equivalences
The following table lists the most common encryption WMI methods (based on Win32_EncryptableVolume) and their BitLockerSAK equivalents.
| WMIMethod | BitLockerSAK |
Get protection status | .protectionStatus + code to convert return code. | BitLockerSAK -GetProtectionStatus |
Get encryption state | .GetConversionStatus() + encryptionpercentage | BitLockerSAK -GetEncryptionState |
Get key protector type | .GetKeyProtectorType(“ID”) | BitLockerSAK - GetKeyProtectorTypeAndID |
Get key protector ID | .GetKeyProtectors(). volumekeyprotector | BitLockerSAK - GetKeyProtectorTypeAndID |
Delete key protector | .DeleteKeyProtectors() | BitLockerSAK –DeleteKeyProtector –protectorID “ID” |
Encrypt drive | Specify the protector type +.Encrypt() | BitLockerSAK –encrypt –pin “123456” |
Pause encryption | .PauseConversion() | BitLockerSAK -PauseEncryption |
Windows Powershell cmdlets in Windows 8.1
Windows 8.1 brought a lot of new features, but one thing that was missing for some time were official Windows PowerShell cmdlets for TPM and encryption management. Luckily, Windows 8.1 came with Windows PowerShell 4.0 and a new set of cmdlets for managing BitLocker operations.
BitLocker cmdlets
The following cmdlets are provided in Windows 8.1 for BitLocker operations:
TPM cmdlets
There are 11 cmdlets for the TPM operations, and they are available in a module called TrustedPlatformModule.
I have updated the equivalence tables with these new cmdlets to help finding the information easier.
BitLocker equivalences
| WMIMethod | BitLockerSAK | Windows 8.1 cmdlets |
Get protection status | .protectionStatus + code to convert return code. | BitLockerSAK | Get-BitLockerVolume |
Get encryption state | .GetConversionStatus() + encryptionpercentage | BitLockerSAK | (Get-BitLockerVolume).EncryptionPercentage |
Get key protector type | .GetKeyProtectorType(“ID”) | BitLockerSAK
| (Get-BitLockerVolume).keyprotector |
Get key protector ID | .GetKeyProtectors(). volumekeyprotector | BitLockerSAK
| (Get-BitLockerVolume).keyprotector[0].KeyProtectorID |
Delete key protector | .DeleteKeyProtectors()
| BitLockerSAK –DeleteKeyProtector –protectorID “ID” | Remove-BitLockerKeyprotector |
Encrypt drive | Specify the protector type + .Encrypt() | BitLockerSAK –encrypt –pin “123456” | Enable-BitLocker |
Pause encryption | .PauseConversion() | BitLockerSAK -PauseEncryption | Suspend-BitLocker |
TPM sheet
| WMIMethod | BitLockerSAK | Windows 8.1 Cmdlets |
TPM Enabled | .IsEnabled().isenabled | BitLockerSAK | Get-TPM |
TPM Activated | .IsActivated().isactivated | BitLockerSAK | Get-TPM |
TPM Owned | .IsOwned().Isowned | BitLockerSAK | Get-TPM |
Take TPM OwnerShip | .ClearTpm + .TakeOwnerShip | BitLockerSAK -TakeTPMOwnership | Initialize-Tpm -AllowClear |
Here is my contact information:
Website: PowerShell District
Twitter: @Stephanevg
Linked-In: Stéphane van Gulick
~Stephane
Thank you again, Stephane, for sharing your time and knowledge. This has been an awesome series, and one that is timely and important.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy