Documenting with PowerShell: Chapter 2 – Documenting Bitlocker keys

Our RMM system currently does not have support to securely store the bitlocker key inside of the RMM system itself. I’ve subscribed to the school of bitlocking everything that passes through my company, So also computers that sometimes never get connected to Azure AD, Active Directory to store the key in. We also get users that lost the USB drive or piece of paper that the key was stored on.

As we use a documentation system (IT-Glue) to store all our passwords, I figured why not try to also store our Bitlocker keys there, while tagging the device too so we can always find which device belongs to which key easily.

First for the none IT-Glue users I’ll generate a HTML file. With some small adaptation you can upload this to Confluence, ITBoost, or any other system you use. After that example, we’ll get onto IT-Glue again. So let’s get started!

Base script

The base script is the part of the script that captures the data that we want. In our case This will be the Bitlocker key, and output it an HTML file in C:\Temp\Temp.html You can use this script however you’d like.

$BitlockVolumes = Get-BitLockerVolume
#Some HTML to make the page pretty.
$head = @"
<script>
function myFunction() {
    const filter = document.querySelector('#myInput').value.toUpperCase();
    const trs = document.querySelectorAll('table tr:not(.header)');
    trs.forEach(tr => tr.style.display = [...tr.children].find(td => td.innerHTML.toUpperCase().includes(filter)) ? '' : 'none');
  }</script>
<title>Audit Log Report</title>
<style>
body { background-color:#E5E4E2;
      font-family:Monospace;
      font-size:10pt; }
td, th { border:0px solid black; 
        border-collapse:collapse;
        white-space:pre; }
th { color:white;
    background-color:black; }
table, tr, td, th {
     padding: 2px; 
     margin: 0px;
     white-space:pre; }
tr:nth-child(odd) {background-color: lightgray}
table { width:95%;margin-left:5px; margin-bottom:20px; }
h2 {
font-family:Tahoma;
color:#6D7B8D;
}
.footer 
{ color:green; 
 margin-left:10px; 
 font-family:Tahoma;
 font-size:8pt;
 font-style:italic;
}
#myInput {
  background-image: url('https://www.w3schools.com/css/searchicon.png'); /* Add a search icon to input */
  background-position: 10px 12px; /* Position the search icon */
  background-repeat: no-repeat; /* Do not repeat the icon image */
  width: 50%; /* Full-width */
  font-size: 16px; /* Increase font-size */
  padding: 12px 20px 12px 40px; /* Add some padding */
  border: 1px solid #ddd; /* Add a grey border */
  margin-bottom: 12px; /* Add some space below the input */
}
</style>
"@

foreach($BitlockVolume in $BitlockVolumes) {
$HTMLTop = @"
    <h1>Bitlocker Information</h1>
    <b>Computername: </b>$($BitlockVolume.ComputerName)<br>
    <b>Encryption Method:</b>$($BitlockVolume.EncryptionMethod)<br>
    <b>Volume Type:</b>$($BitlockVolume.VolumeType)<br>
    <b>Volume Status:</b>$($BitlockVolume.VolumeStatus)<br>
"@
$HTML += $BitlockVolume.KeyProtector | convertto-html -Head $head -PreContent "$HTMLTop <br> <h1>Keys for $($ENV:COMPUTERNAME) - $($BitlockVolume.Mountpoint)</h1>"
}
$html | Out-File C:\Temp\temp.html

Now, that’s cool. This gives us a good ol’ HTML file. We now have a choice, use the previous script found here and adapt it to upload it to IT-Glue as a Flexible Asset or make the choice to upload it as an embedded password and tag the correct device. That sounds cooler to me!

This script looks for a configuration in your IT-Glue database based on the computer’s serial number. If it finds a match it uploads the bitlocker key as an embedded password, with the name “COMPUTERNAME – DRIVE:” as an example for my computer “DESKTOP-U3984 – C:” – We do this because the hostname might change over time and you’d want the keys to be uploaded separately.

IT-Glue script

#####################################################################
$APIKEy =  "APIKEYHERE"
$APIEndpoint = "https://api.eu.itglue.com"
$orgID = "ORGIDHERE"
#####################################################################
#Grabbing ITGlue Module and installing,etc
If(Get-Module -ListAvailable -Name "ITGlueAPI") {Import-module ITGlueAPI} Else { install-module ITGlueAPI -Force; import-module ITGlueAPI}
#Settings IT-Glue logon information
Add-ITGlueBaseURI -base_uri $APIEndpoint
Add-ITGlueAPIKey $APIKEy
#This is the data we'll be sending to IT-Glue. 
$BitlockVolumes = Get-BitLockerVolume
#The script uses the following line to find the correct asset by serialnumber, match it, and connect it if found. Don't want it to tag at all? Comment it out by adding #
$TaggedResource = (Get-ITGlueConfigurations -organization_id $orgID -filter_serial_number (get-ciminstance win32_bios).serialnumber).data
foreach($BitlockVolume in $BitlockVolumes) {
$PasswordObjectName = "$($Env:COMPUTERNAME) - $($BitlockVolume.MountPoint)"
$PasswordObject = @{
    type = 'passwords'
    attributes = @{
            name = $PasswordObjectName
            password = $BitlockVolume.KeyProtector.recoverypassword[1]
            notes = "Bitlocker key for $($Env:COMPUTERNAME)"

    }
}
if($TaggedResource){ 
    $Passwordobject.attributes.Add("resource_id",$TaggedResource.Id)
    $Passwordobject.attributes.Add("resource_type","Configuration")
}

#Now we'll check if it already exists, if not. We'll create a new one.
$ExistingPasswordAsset = (Get-ITGluePasswords -filter_organization_id $orgID -filter_name $PasswordObjectName).data
#If the Asset does not exist, we edit the body to be in the form of a new asset, if not, we just upload.
if(!$ExistingPasswordAsset){
Write-Host "Creating new Bitlocker Password" -ForegroundColor yellow
$ITGNewPassword = New-ITGluePasswords -organization_id $orgID -data $PasswordObject
} else {
Write-Host "Updating Bitlocker Password" -ForegroundColor Yellow
$ITGNewPassword = Set-ITGluePasswords -id $ExistingPasswordAsset.id -data $PasswordObject
}
}

This script can also be found as an AMP file here, that’s it! as always, happy PowerShelling!

24 Comments

  1. Ross January 16, 2020 at 5:42 pm

    Nice post! The script works flawlessly pushing keys into ITG.
    I wonder if it would be possible to push the embedded passwords into a dedicated ‘BitLocker Keys’ folder in the password manager for easier organisation.

    1. Kelvin Tegelaar January 17, 2020 at 6:38 pm

      Yes you can! you can add catagories to each password type.

      1. Brian G. September 2, 2020 at 12:30 am

        Hi Kelvin,
        I’m also intrigued by this – how would one modify the IT Glue script to push the keys into a “Bitlocker Keys” folder?
        Thanks,
        Brian

        1. Kelvin Tegelaar September 9, 2020 at 5:59 pm

          the API currently does not allow setting of the folder, sorry!

  2. Dan February 12, 2020 at 9:10 pm

    Hey – can you tell me if I need to place a Customer ID for each client I run this on? Or can I run it for all devices at once?

    Also – the API key, did you just create a custom API key within IT Glue (in the place near Warranty Master’s input)?

    Thank you for any help you can provide!

  3. Pingback: Documenting with PowerShell: Passportal API Examples - CyberDrain

  4. Kay March 19, 2020 at 10:27 am

    I’ve been getting the following errors when trying to import it the data to ITGlue. Did a bit of digging and changing the script but nothing seems to work and is driving me nuts. Any ideas?
    ——————————–
    At C:\ProgramData\CentraStage\Packages\76a97ef0-e000-4d86-8f15-ddad70dcb92c#\command.ps1:34 char:139
    + … otectorType -eq “RecoveryPassword” }).KeyProtectorId -replace ?[{}]?, …
    + ~
    You must provide a value expression following the ‘-replace’ operator.
    At C:\ProgramData\CentraStage\Packages\76a97ef0-e000-4d86-8f15-ddad70dcb92c#\command.ps1:34 char:140
    + … ctorType -eq “RecoveryPassword” }).KeyProtectorId -replace ?[{}]?, ”
    + ~~
    Unexpected token ‘?[‘ in expression or statement.
    At C:\ProgramData\CentraStage\Packages\76a97ef0-e000-4d86-8f15-ddad70dcb92c#\command.ps1:34 char:139
    + … otectorType -eq “RecoveryPassword” }).KeyProtectorId -replace ?[{}]?, …
    + ~
    The hash literal was incomplete.
    At C:\ProgramData\CentraStage\Packages\76a97ef0-e000-4d86-8f15-ddad70dcb92c#\command.ps1:39 char:5
    + }
    + ~
    Unexpected token ‘}’ in expression or statement.
    At C:\ProgramData\CentraStage\Packages\76a97ef0-e000-4d86-8f15-ddad70dcb92c#\command.ps1:56 char:1
    + }
    + ~
    Unexpected token ‘}’ in expression or statement.
    + CategoryInfo : ParserError: (:) [], ParseException
    + FullyQualifiedErrorId : ExpectedValueExpression
    ————————————–

    1. Kelvin Tegelaar March 19, 2020 at 10:52 am

      There is a new version in the DattoRMM store for this, the version you’re currently using seems to have some weird UTF-8 issues 🙂

      1. Kay March 20, 2020 at 2:28 am

        Hi Kelvin,

        Didn’t notice there was an update. This seems to work perfectly now. UTF-8 issues was not even something I had in mind when I was troubleshooting this 😀

  5. Mark Hill May 13, 2020 at 2:56 pm

    Hi, struggling running these scripts in Datto RMM.

    If I run direct in PS works perfectly, but in DattoRMM they just time out.

    Any ideas?

    thanks
    Mark

    1. Kelvin Tegelaar May 13, 2020 at 2:59 pm

      Hi Mark,

      Did you get the latest version out of the comstore? You’ll also need to run the component “Install Nuget Provider” before you run this one.

  6. Matthew Lewis June 1, 2020 at 1:30 am

    two questions –

    How do I add into the script to create a BitLocker folder?

    When I run the script it creates two entries;

  7. Chris Pegrum June 24, 2020 at 5:38 pm

    Hi,
    This is amazing, but i keep getting the following errors – any idea on how to resolve?

    Creating new Bitlocker Password
    New-ITGluePasswords : {“errors”:[{“source”:{“pointer”:”/data/attributes/password”},”detail”:”can’t be blank”,”title”:”Unprocessable Entity”,”status”:422}]}
    At line:36 char:19
    + … wPassword = New-ITGluePasswords -organization_id $orgID -data $Passwo …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,New-ITGluePasswords

    Cannot index into a null array.
    At line:17 char:1
    + $PasswordObject = @{
    + ~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : NullArray

    Exception calling “Add” with “2” argument(s): “Item has already been added. Key in dictionary: ‘resource_id’ Key being added: ‘resource_id'”
    At line:27 char:5
    + $Passwordobject.attributes.Add(“resource_id”,$TaggedResource.Id)
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : ArgumentException

    Exception calling “Add” with “2” argument(s): “Item has already been added. Key in dictionary: ‘resource_type’ Key being added: ‘resource_type'”
    At line:28 char:5
    + $Passwordobject.attributes.Add(“resource_type”,”Configuration”)
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : ArgumentException

    Creating new Bitlocker Password
    New-ITGluePasswords : {“errors”:[{“source”:{“pointer”:”/data/attributes/password”},”detail”:”can’t be blank”,”title”:”Unprocessable Entity”,”status”:422}]}
    At line:36 char:19
    + … wPassword = New-ITGluePasswords -organization_id $orgID -data $Passwo …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,New-ITGluePasswords

    1. thebowatts November 8, 2020 at 3:55 am

      I’m running into this same problem that I can’t get past

  8. Cliff October 2, 2020 at 10:16 pm

    I know Hudu is a newcomer to the block, any chance you could whip up a Hudu equivalent?

    1. Kelvin Tegelaar October 4, 2020 at 10:25 pm

      I don’t believe Hudu has a public API currently, atleast last time I checked. 🙂

  9. Hendrik Huibers January 11, 2021 at 4:18 pm

    can you tell me if I need to place a Customer ID for each client I run this on? Or can I run it for all devices at once?
    In other words do we need to configure this per site?
    So yes, there are any possibilities to document this automatically for all our sites, or not?

    Thanks for answering me,

    Kind regards,
    H. Huibers

    1. Koos August 16, 2021 at 9:51 am

      Good morning!

      I have the same question as Hendrik. And I see this is also a question Dan asked a couple of comments before.
      Is this possible, and how?

      Thanks in advance!

  10. Shane O'Connor July 21, 2021 at 12:07 pm

    Hi,

    I’m afraid this is a beginner question, but we are considering using the “Enable Bitlocker and Document to UDF [WIN]” component in Datto RMM that links to this article and I just wanted to know if this would require the end users to input the key the next time they boot the device?

    Regards,

    S O’Connor

    1. Kelvin Tegelaar July 21, 2021 at 12:08 pm

      Hi Shane,

      No, this saves the key in the TPM chip. the script fails if no TPM is found.

      Regards,

  11. Mark July 23, 2021 at 4:09 pm

    Hi,

    I’m using the Enable Bitlocker and Document to UDF [WIN] from the Datto RMM comstore as well. However, the script does not save to a UDF field right now. Is there a known issue that we are able to work around?

    The output makes me think that it is working, but nothing shows up in the UDF.

    1. Niels August 5, 2021 at 11:47 am

      There’s an error in the Datto RMM version, the wrong variable is being used.

      New-ItemProperty “HKLM:\SOFTWARE\CentraStage” -Name Custom$env:usrUDF -PropertyType string -value $BitlockerKey -Force

  12. Tim August 13, 2021 at 2:58 pm

    I just tried this on DRMM and it said it was successful, but nothing showed up in ITG. I am guessing the variables were wrong. How do we locate the org ID and API Endpoint?

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.