Enforce BitLocker startup PIN on Windows with Intune

The purpose of this blog post is to inform you how to enforce a BitLocker startup Pin for standard users. I was inspired by the solution of Oliver Kieselbach, but his solution was user-driven and not enforced so I decided to change some settings, make a proactive remediation script, and create a custom Compliance check to enforce the BitLocker startup pin.

Requirements:

  • Microsoft Intune license
  • Windows 10/11 Enterprise E3/E5 Or Microsoft 365 F3/E3/E5 license
  • Intune MDM managed device
  • Intune Admin or Global Admin Role
  • Endpoint Analytics

Original solution

By default, a standard user is not able to set a BitLocker startup pin. Only a local admin can set the pin code, so there must be another method. I did some research, and I found the solution of Oliver Kieselbach. You can find his blog and the original content here.

Enforced solution

The solution did not force the user to set the startup pin, but I wanted to enforce it, so I decided to modify his solution. I change some settings in his code, so the user cannot close the pin code diagram anymore. Created the Win32 app and set it as a required install so everything was on the device. The last piece of the puzzle was to enforce it with a proactive remediation script and a custom compliance policy.

What did I change?

File: popup.ps1
Line: 1
Original:

# Author: Oliver Kieselbach (oliverkieselbach.com)

Changed to:

# Author: Oliver Kieselbach (oliverkieselbach.com) & Rene Laas (endpointcave.com)

File: popup.ps1
Line: 12
Add:

# - 02/23/2022 Added simple PIN i.e. 000000 check # added by Rene Laas - Endpointcave.com

File: popup.ps1
Line: 66
Add:

# Test of all numbers in array are the same  
# Function Added by Rene Laas Endpointcave.com
function Test-Number
{
    param (
        [String]
        $NumberString
    )
    
    $array = $NumberString.ToCharArray()
    
    If ( @($array | Select -Unique).Count -eq 1 ) 
    
        { 
            return $true
        }
    
    Else 
        { 
            return $false
        }
    
}

if(!(Test-Path $PSScriptRoot\BitLockerRunning.txt)) 

    {

File: popup.ps1
Line: 113
Original:

$language = Get-Content language.json -Encoding UTF8 | ConvertFrom-Json

Changed to:

$language = Get-Content "C:\progra~2\BitLockerPINDialog\language.json" -Encoding UTF8 | ConvertFrom-Json # Modified by Rene Laas Endpointcave - Change json location

File: popup.ps1
Line: 176
Original:

$pathPINFile = $(Join-Path -Path "$env:SystemRoot\tracing" -ChildPath "168ba6df825678e4da1a.tmp")

Changed to:

#$pathPINFile = $(Join-Path -Path "$env:SystemRoot\tracing" -ChildPath "168ba6df825678e4da1a.tmp")
$pathPINFile = "C:\PinTemp\PIN-prompted.txt" # Modified by Rene Laas Endpointcave.com

File: popup.ps1
Line: 183
Add:

Remove-Item -Path $PSScriptRoot\BitLockerRunning.txt -Force # Added by Rene Laas Endpointcave.com

File: popup.ps1
Line: 241
Original:

$formBitLockerStartupPIN.ClientSize = '445, 271'

Changed to:

$formBitLockerStartupPIN.ClientSize = '445, 305' # Modified by Rene Laas Endpointcave - change 271 to 305

File: popup.ps1
Line: 1783
Add:

$formBitLockerStartupPIN.ControlBox = $False  # Added by Rene Laas Endpointcave

File: popup.ps1
Line: 1794
Original:

$labelPINIsNotEqual.Location = '275, 181'

Changed to:

$labelPINIsNotEqual.Location = '275, 210' # Modified by Rene Laas Endpointcave - from 181 to 210

File: popup.ps1
Line: 1805
Original:

$labelRetypePIN.Location = '26, 146'

Changed to:

$labelRetypePIN.Location = '26, 186' # Modified by Rene Laas Endpointcave - from  146 to 186

File: popup.ps1
Line: 1815
Original:

$labelNewPIN.Location = '26, 105'

Changed to:

$labelNewPIN.Location = '26, 145' # Modified by Rene Laas Endpointcave - from 105 to 145

File: popup.ps1
Line: 1834
Original:

$panelBottom.Controls.Add($buttonCancel)

Changed to:

#$panelBottom.Controls.Add($buttonCancel) # Disabled by Rene Laas Endpointcave

File: popup.ps1
Line: 1837
Original:

$panelBottom.Location = '-1, 209'

Changed to:

$panelBottom.Location = '-1, 243' # Modified by Rene Laas Endpointcave - from 209 to 243

File: popup.ps1
Line: 1855
Original:

$buttonSetPIN.Location = '225, 17'

Changed to:

$buttonSetPIN.Location = '320, 17' # Modified by Rene Laas Endpointcave - from 225 to 320

File: popup.ps1
Line: 1877
Original:

$textboxRetypedPin.Location = '140, 143'

Changed to:

$textboxRetypedPin.Location = '180, 183' # Modified by Rene Laas Endpointcave - from 140 to 180 and 143 to 183

File: popup.ps1
Line: 1886
Original:

$textboxNewPin.Location = '140, 102'

Changed to:

$textboxNewPin.Location = '180, 142' # Modified by Rene Laas Endpointcave - from 140 to 180 and 102 to 142

File: popup.ps1
Line: 1901
Add:

}
Else
    {
        # Check if file is older/newer
        $lastWrite = (get-item $PSScriptRoot\BitLockerRunning.txt).LastWriteTime
        $timespan = new-timespan -days 1

        if (((get-date) - $lastWrite) -ge $timespan) 
            {
                Write-Output "BitLockerRunning.txt is created more than 1 day ago, deleting file and waiting for next cycle."
                Remove-Item -Path $PSScriptRoot\BitLockerRunning.txt -ForceRemove-Item -Path $PSScriptRoot\BitLockerRunning.txt -Force
                    
            }
        else
            {
                Write-Output "BitLockerRunning.txt is created less than 1 day ago."
            }    
    }

File: Setbitlockerpin.ps1
Line: 13
Original:

.\ServiceUI.exe -process:Explorer.exe "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -WindowStyle Hidden -Ex bypass -file "$PSScriptRoot\Popup.ps1"

Changed to:

C:\progra~2\BitLockerPINDialog\ServiceUI.exe -process:Explorer.exe "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -WindowStyle Hidden -Ex bypass -file "C:\progra~2\BitLockerPINDialog\Popup.ps1" # Modified by Rene Laas Endpointcave - Path modified

File: Setbitlockerpin.ps1
Line: 19
Original:

$pathPINFile = $(Join-Path -Path "$env:SystemRoot\tracing" -ChildPath "168ba6df825678e4da1a.tmp")

Changed to:

#$pathPINFile = $(Join-Path -Path "$env:SystemRoot\tracing" -ChildPath "168ba6df825678e4da1a.tmp")
$pathPINFile = "C:\PINtemp\PIN-prompted.txt" # Modified by Rene Laas Endpointcave

Add install/uninstall script to the source?

For the correct working of the script, some files must be present on the device. I have added therefore a PowerShell installation and an uninstallation script that will make sure the needed files are in the required subdirectory of the program files directory or can be deleted.

Install.ps1:

New-Item -ItemType Directory -Force -Path C:\progra~2\BitLockerPINDialog\
Move-Item -Path .\*.* -Destination C:\progra~2\BitLockerPINDialog\

Uninstall.ps1:

Remove-Item -Path C:\progra~2\BitLockerPINDialog -Recurse

How to configure the BitLocker settings

  • Open Microsoft Endpoint manager
  • In the menu select Endpoint security
  • Under Manage, select Disk encryption
    Or use the following link Disk encryption – Microsoft Endpoint Manager admin center
  • Click on + Create Policy
  • Select Windows 10 and later as the platform and BitLocker as the profile
  • Click on create
  • Set a name and optionally the description and click on Next
  • Configure the following settings

BitLocker – Base settings

SettingValue
Enable full disk encryption for OS and fixed data drives Yes
Require storage cards to be encrypted (mobile only)Not configured
Hide prompt about third-party encryptionYes
Allow standard users to enable encryption during Autopilot Yes
Configure client-driven recovery password rotationEnable rotation on Azure AD and Hybrid-joined devices

Bitlocker OS Drive Settings

SettingValue
BitLocker system drive policyConfigure
Startup authentication requiredYes
Compatible TPM startupAllowed
Compatible TPM startup PINAllowed
Compatible TPM startup keyBlocked
Compatible TPM startup key and PINBlocked
Disable BitLocker on devices where TPM is incompatibleNot configure
Enable preboot recovery message and URL Not configure
System drive recoveryConfigure
Recovery key file creationAllowed
Configure BitLocker recovery package Password and key
Require device to back up recovery information to Azure ADYes
Recovery password creationAllowed
Hide recovery options during BitLocker setupYes
Enable BitLocker after recovery information to storeYes
Block the use of certificate-based data recovery agent (DRA)Not configure
Minimum PIN length6
Configure encryption method for Operating System drivesAES 256bit XTS

Note. I recommended to configure BitLocker – Fixed Drive Settings and BitLocker – Removable Drive Settings as well, but both are not required for the BitLocker Startup Pin

How to create the BitLocker Startup Pin package

  • Download the SetBitLockerPin files of Oliver his Github page.
    Link: Intune/Win32/SetBitLockerPin at master · okieselbach/Intune · GitHub
  • Copy the SetBitLockerPin files to an empty folder
  • Modify the files that are mentioned in the paragraph What did I change?
  • Create the .intunewin package with the Intune content preparation tool with the following command:
{directory of WinappUtil}\IntuneWinAppUtil.exe -c { directory of SetBitLockerPin files } -s install.ps1 -o .\ -q

Or

{directory of WinappUtil}\IntuneWinAppUtil.exe -c {directory of SetBitLockerPin files} -s install.ps1 -o .\ -q

Or

  • Download the .intunewin package here

How to install BitLocker solution

  • Open Microsoft Endpoint manager
  • In the menu select Apps
  • Under Apps, select Windows
    Or use the following link Windows Apps – Microsoft Endpoint Manager admin center
  • Click on + Add
  • Select the App Type Windows App (Win32) and click on Select
  • Click on the Select app package file and click on the blue directory icon
  • The file explorer will open and browse to the .Intunewin file that you have created.
  • Click on OK
  • Set a name, description, and publisher of the app
  • Click on Next
  • Set the following Install and uninstall commands

Install:

powershell.exe -executionpolicy bypass -file ".\install.ps1"

Uninstall:

powershell.exe -executionpolicy bypass -file ".\uninstall.ps1"
  • Select 64-bit as the operating system architecture
  • Select Windows 10 20H2 as the minimum Operating system
  • Select Manually configure detection rules as the rules format
  • Click on + Add and configure the following detection rule
SettingValue
Rule TypeFile
PathC:\progra~2\BitLockerPINDialog\
File or folderServiceUI.exe
Detection methodFile or folder exists
Associated with a 32-bit app on 64-bit clientsNo
  • Click on Next and on the Dependencies and Supersedence section click also next
  • Set the assignment to require for all devices
  • Click on next. Check the app configuration and click on Create.
  • The app will be created and uploaded

Proactive Remediation Scripts that need to be uploaded to Intune

Detection Script:

$pin = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector  | Where { $_.KeyProtectorType -eq 'TpmPin' }

if (((Get-BitLockerVolume -MountPoint $env:SystemDrive).VolumeStatus) -ne "FullyDecrypted")
    {
    Write-Output "Encryption enabled"
    if ($pin -ne $null)
        {
            Write-Output "TPM Pin set"
            Exit 0
        }
    else
        {
            Write-Output "TPM Pin is not set"
            Exit 1
        }

    }
else
    {
        Write-Output "Encryption not yet started"
        Exit 0
    }

Remediation Script:

#Author: Oliver Kieselbach (oliverkieselbach.com) & Rene Laas (endpointcave.com)
# Date: 08/01/2019
# Description: Starts the Windows Forms Dialog for BitLocker PIN entry and receives the PIN via exit code to set the additional key protector
# - 10/21/2019 changed PIN handover
# - 02/10/2020 added content length check
# - 09/30/2021 changed PIN handover to AES encryption/decryption via DPAPI and shared key
#              added simple PIN check for incrementing and decrementing numbers e.g. 123456 and 654321
#              language support (see language.json), default is always en-US
#              changed temp storage location and temp file name
 
# The script is provided "AS IS" with no warranties.

# Added by Rene Laas endpoincave

if (!(Test-Path "C:\PINtemp")) 
{ 
New-Item -Path 'C:\PINtemp' -ItemType Directory 
Write-output "Temp directory created"
} 
else 
{
Write-output "Temp directory already exist"
}
# Added by Rene Laas Endpointcave
# Pin temp file 
$PathPINtemp = "C:\PINtemp\"
$pathPINFile = $PathPINtemp + "PIN-prompted.txt"

#######################

C:\progra~2\BitLockerPINDialog\ServiceUI.exe -process:Explorer.exe "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -WindowStyle Hidden -Ex bypass -file "C:\progra~2\BitLockerPINDialog\SetBitLockerPin.ps1" # Modified by Rene Laas Endpointcave
$exitCode = $LASTEXITCODE

# ASR rules can block the write access to public documents, so we use a writeable path for users and system
 # Check with sysinternals tool: accesschk.exe users -wus c:\windows\*
# "c:\windows\tracing" should be fine as temp storage
#$pathPINFile = $(Join-Path -Path "$env:SystemRoot\tracing" -ChildPath "168ba6df825678e4da1a.tmp")

# Alternativly use public documents, but keep in mind the ASR rules!
#$pathPINFile = $(Join-Path -Path $([Environment]::GetFolderPath("CommonDocuments")) -ChildPath "168ba6df825678e4da1a.tmp")

If ($exitCode -eq 0 -And (Test-Path -Path $pathPINFile)) { 
    $encodedText = Get-Content -Path $pathPINFile
    if ($encodedText.Length -gt 0) {
       # Using DPAPI with a random generated shared 256-bit key to decrypt the PIN
        $key = (43,155,164,59,21,127,28,43,81,18,198,145,127,51,72,55,39,23,228,166,146,237,41,131,176,14,4,67,230,81,212,214)
        $secure = ConvertTo-SecureString $encodedText -Key $key

        # Code for PS7+
        #$PIN = ConvertFrom-SecureString -SecureString $secure -AsPlainText

        # Code for PS5
        $PIN = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure))

        Add-BitLockerKeyProtector -MountPoint $env:SystemDrive -Pin $(ConvertTo-SecureString $PIN -AsPlainText -Force) -TpmAndPinProtector
    }
}

# Cleanup ###
# Modified by Rene Laas Endpointcave

Remove-Item -Path $pathPINFile -Force -Recurse
if(!(Test-Path $pathPINFile)) { Write-output "Encrypted Pin file deleted" }

Remove-Item -Path $PathPINtemp -Force -Recurse
if(!(Test-Path $PathPINtemp)) { Write-Output "Deleted temp folder" }

How to enforce BitLocker Startup pin via Intune with Proactive Remediation Scripts

  • Open Microsoft endpoint manager
  • In the menu select Reports
  • Click on Endpoint analytics in the menu under Analytics
  • Click on Proactive remediations
    Or use the following link Endpoint analytics – Microsoft Endpoint Manager admin center
  • Click on Create script package
  • Provide a Name script name
  • Set a description, so that everyone with access to the portal knows the purpose of the script
  • Click on Next
  • Upload the Detection scripts and Remediation scripts.
  • Configure the following PowerShell script settings
SettingValue
Run this script using the logged on credentialsNo
Enforce script signature checkNo
Run script in 64-bit PowerShell HostYes
  • Click on Nextand on the Scope tags section click on Next
  • Assign remediation script to All User
  • Change the schedule via the 3 bullets and Click on Edit
  • Click on the dropdown list and change the frequency from Daily to Hourly and click on Next
  • Check on the review + create page if all settings are configured correctly and click on Create

 

Result:

Create a Custom Compliance check

SettingValue
NameBitLockerPin
DescriptionCustom compliance check for BitLocker Startup Ping
PublisherEndpointcave.com
  • Click on next
  • Configure the following Custom script settings

Detection script Script

$BLinfo = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector | Where { $_.KeyProtectorType -eq 'TpmPin' }
$hash = @{KeyProtectorType = $BLinfo.KeyProtectorType}
return $hash | ConvertTo-Json -Compress
SettingValue
Run this script using the logged on credentialsNo
Enforce script signature checkNo
Run script in 64-bit PowerShell HostYes
  • Click on Next, review the configuration, and click on Create
  • The next step is to open an existing compliance policy
  • In the menu click on policies
  • Open an existing compliance policy to enforce BitLocker startup Pin
  • Click on Properties under Manage
  • Edit the compliance settings via the edit button
  • Open the Custom Compliance section
  • Configure the following Custom Compliance settings
SettingValue
Custom complianceRequired
Select your discovery scriptBitlockerPin

JSON

{
"Rules":[ 
    { 
       "SettingName":"KeyProtectorType",
       "Operator":"IsEquals",
       "DataType":"Int64",
       "Operand":"4",
       "MoreInfoUrl":"https://endpointcave.com",
       "RemediationStrings":[ 
          { 
             "Language":"en_US",
             "Title":"Bitlocker Startup Pin must be set",
             "Description": " Bitlocker Startup Pin must be set "
          }
       ]
    }   
]
}

Result:

Enforce BitLocker startup Pin End-user experience