Removable Storage Automatic BitLocker Recovery Key Escrow to Azure AD | BitLocker-to-go Guide for Intune - SMBtotheCloud
BitLocker on removable drives is known as “BitLocker to go”, but I will just refer to it as BitLocker in this writing. Requiring BitLocker on removable drives is fairly easy with the built-in Intune Endpoint Security profile templates. Some of you may be thinking removable storage should be completely blocked for security reasons. I agree in most cases, but there are still use cases where removable storage may be necessary. For example, I worked with a law enforcement agency in the past where evidence needed to be stored on USB storage if it was sent off-site. To further enhance security on removable devices, we can do some additional things, such as disabling write access to removable storage drives if they were not BitLocker encrypted from a corporate Intune enrolled device.
After I started doing some testing, I wanted the BitLocker recovery keys to be uploaded to Azure AD, but there was no native way to enforce this with the provided BitLocker templates. The selection to “Require device to back up recovery information to Azure AD” is not available for removable drives. Also, there is no way to “auto encrypt” a removable storage device. We can enforce requiring BitLocker for removable storage, but this still requires user interaction to complete the encryption process. There is no way around requiring some user interaction for the encryption process, but I wanted to figure out a way to automatically escrow removable storage BitLocker recovery keys to AAD. If we are requiring BitLocker for removable drives, we need a way, as Admins, to unlock removable drives. I created a script that does this, but I needed a trigger to run the script when a removable drive is encrypted. I eventually found a pretty simple solution using a scheduled task to trigger the script on EventID 768, when a drive starts encrypting with BitLocker. This post is longer than usual, but walks through the following items:
  • How to enforce BitLocker on removable storage
  • How to enforce read-only permissions on removable storage devices not encrypted on a company-owned device
  • The PowerShell script used as the action in our scheduled task to upload the recovery key to AAD
  • Deploying the scheduled task to workstations as a Win32 app
  • Locating a Drive’s Key ID & Recovery Key so you can unlock a drive. (This is useful if a device was lost and returned or found and we do not know which PC encrypted it)
An animated GIF of the end result is below, followed by all the details and steps:
notion image

How to Enforce BitLocker for removable storage devices:

  1. Within the Intune admin dashboard, select Endpoint security > Disk Encryption. Make a new Policy or edit an existing policy. A quick note about making a new policy 0
        • notion image
  1. A quick note if you’re making a new Policy – the encryption type for each drive type is a required field. If you are making a new policy, you still must “configure” the other drive types and select an encryption method, or you will receive an error. If this is a new policy and you’re only interested in enforcing removable storage, you’ll need it set at least like the screenshot below, or you’ll get an error when trying to create the policy. If you already have a BitLocker Policy you are editing, you can disregard this.
        • notion image
          At a minimum, set like this to avoid the above error:
        • notion image
  1. Under BitLocker – Removable Drive Settings, configure like the below screenshot. Depending on your environment, you may want to use CBC encryption instead of XTS. If we want to enforce that all removable storage is encrypted, we want to block write access to removable drives until they are BitLocker encrypted. We will get to the fourth option to block write access configured in another organization in the next section.
        • notion image
          Save your configuration profile and apply to your target groups.
  1. Once the device receives the policy, this is what will happen if a user inserts a removable storage device:
      • They will be required to encrypt the device if they want to write files to it. Since its very likely corporate data will be written to the drive, this is a good practice.
      • When they select to Encrypt the drive, they’ll be prompted to set a password or use a smart card. In the vast majority of situations, they’ll be setting a password:
      • They’ll need to save the recovery key somewhere. Assuming you have BitLocker enforced on your system and data drives (which you should), they’ll need to use the print option since you can’t save a BitLocker recovery key to a BitLocker-protected volume. They can print to a PDF file and save that on the local computer. Later on, we will show you how this can be auto-escrowed to AAD.
      • In most situations, they can select Encrypt used space only. However, depending on the sensitivity of your data, and if you’re enforcing this in an environment where sensitive data has already been stored and deleted from removable drives, it may be better to encrypt the entire drive:
      • Click Start encrypting:
      • Wait for the drive to finish the encryption process:
      • When completed, you can see the drive showing it’s protected by BitLocker:
      • If the drive is removed and reinserted or inserted into another PC, the user will be required to unlock it to access the contents:
      • They have the option to remember the device if it will be used frequently on their device:

Disable write access to removable storage devices unless encrypted from a corporate device:

Enabling this setting will prevent write access to a removable storage device unless it is BitLocker encrypted from a corporate-owned device.
  • In your BitLocker policy, enable “Block write access to devices configured in another organization”
  • Once Enabled, if a user inserts a drive that was BitLocker encrypted on a device outside of your organization, they’ll still be promoted to unlock the device, and can read data on the device, but if they try writing to the device they will be blocked with an error stating the drive is write protected:
  • If needed, the drive can be decrypted and re-encrypted on the user’s device if they need to write to the storage device

Automatically Escrow removable storage device key to AAD – The Script

The script that escrows the recovery key for a removable storage device to AAD is only four lines, and it’s simpler than many might think. The biggest hurdle with this script was only identifying removable storage drives. The rest of the script uses the same commands we use to upload regular fixed or OS drive BitLocker recovery keys to AAD. The script is below with some details. Remember, this script will be triggered from our scheduled task based on the EventID generated when a drive begins encrypting.
The top line with the $USBDrives variable identifies any removable storage drive plugged into a device. It accomplishes this by only selecting disks with a drive type of “2”, which is a removable storage device. It then only selects the DeviceID field, which is the letter the drive mounts as. We need this to identify the mount point later in the script:
The rest is pretty simple. We have a Foreach statement to process the commands to upload recovery keys to AAD for each drive letter identified as a removable storage drive. This is necessary because if Drive D is inserted and was previously encrypted, but a second USB drive is inserted and encrypted (E:), we need to process the commands for each drive letter to ensure they’re escrowed to AAD. Duplicates are not created in AAD if the recovery key for a drive already exists. A successful upload of recovery keys to Azure AD will result in EventID 845 in the Microsoft-Windows-BitLocker-API/Management log.

Automatically Escrow Removable Storage Recovery key to AAD – The Scheduled Task

The reason I chose to use a scheduled task, is we want the recovery key escrowed to Azure AD as soon as possible. We also want the drive connected while this happens. Proactive remediations are preferable to scheduled tasks in most cases, but currently, proactive remediations can only be run at certain intervals. For this task, I want it to trigger based on an event ID, and a scheduled task can do that. The details of the scheduled task are:
  • It runs as system
  • The trigger is the event ID when a drive starts BitLocker encryption – EventID 768 from the Microsoft-Windows-BitLocker-API/Management log
  • The action is the script from the previous section. I have the script being copied to the %windir%\system32\tasks directory. This is where the scheduled tasks are stored, and access to that directory requires local admin rights. So, users can’t go into that directory and delete or edit the files.
I won’t get into the weeds on how to deploy a scheduled task with Intune. For that, see my other blog post here – https://smbtothecloud.com/deploy-scheduled-tasks-with-a-win32app/. To begin, we need our scheduled task XML file, our script to escrow recovery keys to AAD, a PowerShell script to create the scheduled task, and an uninstall script if we want to remove the scheduled task. All of this, including the .intunewin file if you want to deploy this exact solution, is available on my Github here. A quick review of the necessary files is here:
  • BL2GOEscrowtoAAD.xml – This is our Scheduled task XML we will be importing
  • BL2GoToAAD.ps1 – this is our action script that escrows the recovery keys to AAD
  • BL2Go-ScheduledTask.ps1 – this is our install script for the Win32 app to create the scheduled task and also copy our action script, BL2GoToAAD.ps1, to the %windir%\system32\tasks directory
  • Uninstall.ps1 – this will remove the scheduled task if we want to uninstall the Win32 app from Intune
As I previously mentioned, I won’t go into details on the Scheduled Task XML file in this post – see my other blog post linked above for more details on that. But here are the other scripts we use for this solution. The BL2GoToAAD.ps1 script was shown in the previous section, so no review is needed here. Below are the Install and uninstall scripts for the Win32 App:
Our Install script, which creates the scheduled task and copies the action script to %windir%\system32\tasks:
And our uninstall script, which removes the scheduled task if we ever want to delete it:
Now, some of you can skip this part, but for those of you who want the steps to create the Win32 App, here they are. I’m assuming you know how to wrap files as a .intunewin file already. Move all the above files into the same directory and wrap them into a .intunewin file like below:
Create a new Win32 app in Intune and upload your .intunewin file when prompted. Provide a name and description and click Next:
Under Program, we want our install and uninstall commands to be exactly like they are below. If you do not use %sysnative%, the task will be created properly, but our action script will be uploaded to C:\Windows\SysWOW64\tasks. If you aren’t familiar with using %sysnative%, see this blog post for more information – https://smbtothecloud.com/make-registry-changes-with-intune-win32-apps/. We also want to make sure the install behavior is System.
  • Install Command: %windir%\sysnative\WindowsPowerShell\v1.0\powershell.exe -Executionpolicy Bypass .\BL2Go-ScheduledTask.ps1
  • Uninstall Command: %windir%\sysnative\WindowsPowerShell\v1.0\powershell.exe -Executionpolicy Bypass .\uninstall.ps1
Add the requirements you want for your deployment and move on to detection rules. We will add two detection rules. The first is to make sure the scheduled task exists. The second is to make sure our action script exists. We will use file detection for both. Here are the scheduled task detection rules. Note that although this file is technically an XML file after the XML is imported and the scheduled task is created, the .XML extension is removed. This is why there is no file extension on the BL2GOEscrowtoAAD file.
And the detection for the action script:
Complete the remaining steps and deploy the app to your devices. You should start getting successful installs after devices check-in. If you don’t want to wait, you can restart the Microsoft Intune Management Extension service on devices to force them to check in:
Here is what the task looks like on the devices after it’s deployed. You may need to open task scheduler as an admin to see the task:
And the trigger:
And the Action:

Locating the Recovery Key for a removable storage device if you don’t know which device encrypted it:

This is important. After all, what’s the point of enforcing BitLocker on removable storage if it’s going to be a huge pain to locate the recovery key? If a device needs to be unlocked but we don’t know which PC encrypted it, we can use the Graph API to locate the device object in AAD where the recovery key is stored. We do this based on the device Key ID.
  1. Insert the protected volume into a PC, when prompted to unlock it, select enter recovery key:
  1. The first 8 digits of the Key ID will be shown on your screen, record this:
  1. Next, go to the graph Explorer and authenticate to your tenant – https://developer.microsoft.com/en-us/graph/graph-explorer
  1. Select the Information Protection resource, and drill down to Bitlocker > RecoveryKeys. Or, enter this string into the query input: https://graph.microsoft.com/v1.0/informationProtection/bitlocker/recoveryKeys. Remove {bitlockerRecoveryKey-id} from the query search if its there so the query returns all keys in your tenant. Also, make sure you consent to the necessary permissions before running the query.
  1. After consenting, run the Query. All the Key ID’s with the device ID where they were encrypted should be returned, like the screenshot below:
  1. Press CTRL+F to search, and enter the value we recorded in step 2. In this example, we are searching for a key that starts with 82EACEE7. Our results show the full Key ID, along with the DeviceID for the device that encrypted the device:
  1. Go to Azure AD, and search for the device ID that encrypted the drive. The device ID in our example is cc30adb8-7d0a-4b87-81aa-652d0f1dd584. This will return the device Object in the Azure AD GUI:
  1. Select the device, and then click on BitLocker keys. Find the Key ID, and the corresponding recovery Key to unlock the device:
You’re probably familiar with toast notifications in Windows. They are the notifications that pop up in the bottom right-hand corner of your screen. They can be purely informational or have action buttons to click, such as opening a website. I wanted to figure out how to create custom notifications and use the action buttons to perform certain tasks for the signed-in user, such as running a PowerShell script. A similar example is a recent post by Damien Van Robaeys (https://www.systanddeploy.com/2022/02/a-toast-notification-to-display-warning.html) to show a toast notification warning a user if their device has not been rebooted in a certain amount of days. It took me several weeks of on-and-off research and testing to get a solid understanding of how toast notifications work. The biggest hurdle for me was having the action buttons run PowerShell scripts. I broke this post into two separate parts since there is so much to cover. If you aren’t interested in what’s happening on the backend, and you purely want to start making custom toast notifications, I suggest you install the BurntToast PowerShell module and start using that. It was something I stumbled across when I began researching custom toast notifications. It’s a great module and uses simple commands to build and deliver a Toast notification. However, I wanted to figure out how toast notifications work and how to build them from scratch, so that’s what this post will mostly focus on.
This post is a compilation of much research and testing, and there were a few resources in particular that were a huge help. If you ever want to use custom toast notifications, I hope this covers all basic steps and save you time from doing a lot of R&D. This is a deep topic, and toast notifications can be much more complex than what is covered here. My goal was to make custom notifications and have the action buttons do more than simply launch a website (which is what they do in most cases). I wanted to run custom PowerShell scripts with the action buttons. We will review the nuts and bolts of toast notifications and then walk through setting up a toast notification to check if any 30+ day-old 500+MB files exist in the user’s downloads folder. The user will have three action buttons – 1. Open the user’s downloads folder, 2. Open a PowerShell Gridview of the affected files, or 3. Auto deletes those files. All files used in these posts are available on my github. Also, I understand Storage Sense is a better solution for cleaning up the downloads folder, but this is an easy example that can be modified to do other things. Here is everything this post will cover:

Part 1:

  • Sources I used for my learning on this topic. There were a few community contributors who provided great learning content on toast notifications
  • Toast Notification XML files and how to customize them
  • Triggering a toast notification from PowerShell
  • Running custom scripts from the action buttons and how to create a custom protocol handler

Part 2:

  • Files needed for deploying toast notifications with Intune
  • Deploying the needed files to our target machines
  • Deploy custom toast notifications as a proactive remediation
  • Deploy custom toast notifications as a scheduled task
  • A Quick Review of the Burnt Toast PowerShell module
Let’s get into Part 1 of this post. I added a table of contents below since this is a longer-than-usual post:
Table of contents
  1. Sources and Credits
  1. Toast notification XML files and how to customize
  1. Initiating a Toast Notification from PowerShell
  1. Running Scripts from our Action Buttons & Creating a custom Protocol Handler

Sources and Credits

Before I begin, I need to give some other people credit for the content they have out there. These contributors have great content which helped me learn about toast notifications.
  • Adam Driscoll – his module review of the BurntToast module also has details on creating a custom protocol handler. This allows us to run PowerShell scripts from the action buttons on the toast notifications.

Toast notification XML files and how to customize

When I first dove into this, I had no backend knowledge of Toast notifications. I simply knew what they were. So, I had to start almost from scratch. The first step was determining how they are built. A Toast notification is simply an XML file that Windows reads to display the nice-looking notifications we receive. Remember, this is only the payload for the notification. We still need something to trigger or initiate the notification, which we get to in the next section. Additional info on the structure of the notifications and other features can be found here – https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/adaptive-interactive-toasts?tabs=xmlAt. The XML for a very basic Toast notification to show some text is displayed below:
This XML displays the toast notification below:
notion image
We can add many customizations to a Toast notification, but as previously mentioned, we won’t go too deep into this. What I wanted were some action buttons for scripts. I also wanted to make it look nice with some additional images. Something pertaining to your company or your IT department will make the notification appear more professional and real to the end users. The resulting XML I used to accomplish my goal is below along with a screenshot pointing to the various fields and how they’re displayed in the notification. A quick note on the hero image – the recommended size of the image is 364×180.
You can do more with your notifications if you’d like, but for me, this checked all the boxes for what I wanted and it’s easy to repurpose in the future.

Initiating a Toast Notification from PowerShell

Now that we have our XML file, we need a way to trigger a toast notification. This is where Ben Whitmore’s blog post was a big help to me. We accomplish this with PowerShell by importing the ToastNotificationManager and XmlDocument Windows Runtime classes. This allows PowerShell to read the XML file and trigger a toast notification. This allows us to manually test our XML notification files – just make sure to change the $XMLString variable to the location of your XML file. The resulting PowerShell script to trigger a notification from our XML file is below.
Note that we have an AppID variable. This is a required field and displays on the Toast Notification as the app where the notification came from. We can get a list of these by using the PowerShell command Get-StartApps. You can use the AppID value for the App you want to use as displaying the notification. I use Windows PowerShell as the app, but this quick example shows how we can change it to something else.
In our real-world example, we don’t want a notification triggering unless there is at least one file older than 30 days and larger than 500MB in the user’s downloads folder. Since my downloads folders in my lab have barely any items, I modified the script to be 1 day instead of 30 and 1 MB instead of 500 so I could properly test. You can adjust these values as needed. I also use PowerShell as the AppID in my script.

Running Scripts from our Action Buttons & Creating a custom Protocol Handler

This part was a headscratcher at first, and for a little while, I didn’t think I’d be able to figure this out (or if it was even possible). I first attempted to simply point my XML action to open a program based on the file path. I then tried pointing it at a script to launch a program or run an action, but it continued to fail. As I continued to research, I discovered that toast notification actions rely on URL schemes, which rely on Protocol Handlers. Anything that does not have a protocol handler built into Windows needs a custom protocol registered. For example, Windows already knows what to do for URL schemes such as HTTP:// HTTPS:// FTP:// or FILE://. This basically tells the OS how to handle a URL with a defined protocol handler. So, we need to create one for powershell:// that can launch PowerShell scripts. This was not as easy as it sounds. After a while of testing and searching for help online, I found a YouTube video from Adam Driscoll that clarified everything for me. It is linked above, but the portion of the video from 4:30 – 7:45 clarifies using a PowerShell protocol handler. Another good explanation for using a protocol handler for PowerShell is here – Custom Protocol Handler and PowerShell (Part 2) – IT Constructors.
To create a custom protocol handler, we need to add some registry Keys. They will be placed in the HKEY_CLASSES_ROOT hive. The Reg File is below along with screenshots of the registry keys and values. The value under the command key is the script called (or program launched) when using the parent key in URL format. For example, PowerShell: or PowerShell://. We will use a CMD script to launch PowerShell. More clarification on all this is below. I did my best to simplify it since this was the most difficult part of this project (for me). Here are the reg keys needed when creating a custom protocol handler. I also have this available as a PowerShell script to add the keys and values on my github. Keep in mind you’ll need the command key default value pointing at your location of the ToastScript.cmd file.
Before we move on, let’s take a step back and look at another example. A much simpler example is if we wanted to create a protocol handler that launched an application. For example, perhaps you want to send a weekly toast notification with a reminder to complete a timesheet or submit regular data to an application, and you wanted that application to open with a toast notification button. If we use Microsoft word as an example, we can make a protocol handler for word:// to launch Microsoft word:
After creating the above registry entries, if we use the word URL protocol (word: or word://) it will launch Microsoft Word. We can do this from a browser, PowerShell, or a Run prompt:
OK, so now that we have a basic understanding of protocol handlers (and there is much more to them, but this was as far as I needed to go for what I needed to accomplish), let’s get back to how PowerShell it’s being launched for our custom protocol handler. Since we want to run actual scripts, we need a way to pass those scripts to the custom protocol handler. Here is our ToastScript.cmd file, which is what runs when the PowerShell:// URL protocol is called.
The script launches PowerShell with some additional parameters to keep it hidden. The key to how this works is the -Command “& ‘%1’.Replace(‘powershell://’, ”).Trim(‘/’)” portion of the script. The %1 variable in the registry value (see below) passes our PowerShell:// URL and additional test after the // into the script where the ‘%1’ variable is.
We can see how the script interprets this if we remove the .Replace(‘powershell://’, ”).Trim(‘/’)” part of the script, add a pause at the end, and then try passing some characters. An example is below. I’ve shortened the toastscript.cmd so it no longer contains “.Replace” and everything after, and added a pause so the window stays open and we can see what happens. You can see below the URL “powershell://testingcommand” was successfully passed into our toastscript.cmd in place of the %1 variable.
Now that we have tested the script and passed the variable so we can see it’s working correctly, We need to strip some of the characters out so it represents real PowerShell syntax. If we consider the example above, we need to get rid of these highlighted characters – ‘powershell://TestingCommand/’ which will then leave us with only what we add after the URL handler – powershell://TestingCommand. To accomplish this, we add the .Replace(‘powershell://’, ”).Trim(‘/’)” back to our script, which will remove our unneeded characters. To demonstrate, we will use the get-service cmdlet. Our complete toastscript.cmd is shown below along with the example. I am leaving the pause in the script so we can see the output, but remember to remove the pause once you’ve tested successfully.
We can see that our test cmdlet was successful. The above picture really ends up from the Windows command line like this in the background:
This is what will be happening in the background for our toast notifications if the action buttons are clicked. The only difference is instead of our action buttons calling specific cmdlets, we will be pointing to a .ps1 file to run a script. Hopefully, now you have a basic understanding of this. It took me a lot of testing to fully understand how this all works together. Now that we understand how the scripts work with the protocol handler, let’s call the scripts we want to run for our toast notification action buttons. I have three that I am going to use:
  1. Open the user’s downloads folder
  1. Open a PowerShell gridview of the affected files
  1. Automatically delete the affected files
I should note that although the file protocol handler (file:) can open file paths in file explorer, it was failing to open when I was using variables like %username% or %userprofile% in the XML file. For that reason, I am using a PowerShell script to open the user’s downloads folder. Here are our scripts:
Open the logged-in user’s downloads folder:
Open a PowerShell Gridview of the affected files – The size and age of files here should match our script from above. I have it set to 1 day and 1 MB for testing purposes
Auto-delete affected files – Same as above. Make sure your age/size here is the same as our script to detect if a notification should be triggered:
The script files will need to be placed on the local machine somewhere (or on a network share if the device will always have a line of sight). In this example, I have them in the c:\programdata\toast directory. Next, we edit our XML file. The Content field contains the names of the action buttons, and arguments contain the action to take if the button is clicked. Notice how we are using the URL protocol handler we created earlier. Clicking the buttons injects our .ps1 action script paths into our ToastScript.cmd file, which executes the action script. Test your notification out by triggering it with PowerShell to make sure the buttons are displaying properly and the scripts trigger when clicking the buttons.
Here it is an action:
That was a long section, but now we have working buttons that run specific scripts on our Notifications. Now we need to deliver this to our target machines via Intune. That will all be covered in Part 2. Thanks for reading.