PowerShell and BitLocker: Part 2


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:

Image of command output

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:

Image of command output

Get-WMIObject returns a bunch more methods—there are so many that we cannot see them all on this screenshot:

Image of command output

The CIM option returns only 18 results when piped to Get-Member:

Image of command output

But good old Get-WMIObject returns 84 results:

Image of command output

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:

Image of command output

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:

Image of command output

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.

Image of command output

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:

Image of command output

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.

Image of flow chart

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.

Image of flow chart

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.

Image of command output

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.

Image of flow chart

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:

Image of 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:

Image of command output

TPM cmdlets

There are 11 cmdlets for the TPM operations, and they are available in a module called TrustedPlatformModule.

Image of command output

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

Comments (3)

  1. Yet another good one, thanks

  2. DC says:

    It seems like the bitlocker cmdlets are broken with Posh V5. Anybody found a workaround?

  3. mBaird says:

    The pin parameter data type of [int] will only take a value that is 9 digits or less. I had to change it to [decimal] to get a full 20 digits.

Skip to main content