Encrypting devices during Windows Autopilot provisioning (WhiteGlove) – Part 3

Introduction

Note: This method is not officially supported by Microsoft. That said, this speeds up compliance and more importantly increases security as the device is already encrypted (part 1) before the user logs on (part 2). BitLocker recovery key changes after the user has completed enrolment are handled automatically (part 3).

Windows Autopilot preprovisioning (WhiteGlove) is the ability to pre-stage content and policies to devices while it’s been installed in the factory. We had a challenge to speed up the overall compliance of Windows Autopilot devices and the obvious solution was to stage as much content as we could during pre-provisioning (WhiteGlove) but to also enable BitLocker encryption during that process, the only problem is that Microsoft don’t officially support BitLocker encryption during the WhiteGlove scenario as the recovery key information is only uploaded after a user logs in. In our initial testing, Bitlocker disk encryption wouldn’t even start until the user logged in.

That is not so much of a problem for a small amount of content on the hard disc but what if you have hundreds of Gigabytes of data to encrypt which could potentially take hours to encrypt after the user has logged on. As BitLocker encryption is a common Compliance policy setting, this needed to be addressed.

The challenge was to do the heavy lifting (pre-provisioning and encryption) during the WhiteGlove process and to only upload the key to Intune once the user actually enrolled the device. That need brought about this solution which is in 3 parts. The first part covers device encryption during provisioning at the factory. The second part uploads the recovery key to Intune after the user has signed in and completed WHFB setup and the final part moves those successfully encrypted devices to a WhiteGlove_Completed azure ad group targeted with BitLocker policy to take care of rotating recovery key info etc.

All parts are listed below:

 

Step 1. Create an Azure AD group

In Microsoft Intune, create an assigned device group called WhiteGlove Completed. This group will dynamically fill with computers that have completed the upload of the BitLocker recovery info in Part 2 based on registry settings via a scheduled task.

whiteglove completed object id.png

 

Step 2. Add a function app

In previous blog posts I showed you how to use http triggers via function apps to dynamically add devices to Azure AD groups, if you’ve already completed that then move to the next step otherwise follow the advice here. The code used for this trigger is pasted below.

Make sure to modify the following three variables otherwise it won’t do anything.

  • $ApplicationID             = “” # this is the id of the app you created in app registrations
  • $TenantDomainName = “” # your tenant name, eg: windowsnoob.com
  • $AccessSecret            = “” # this is the secret of the app you create in app registrations

 

# Niall Brady 2023/03/05 (used by the WhiteGlove Completed, Check Compliance, Software Updates to devices and others...)
# Dynamically ADDS a device to an azure ad group 
# 

using namespace System.Net
# Input bindings are passed in via param block.
param($Request, $TriggerMetadata)

# Write to the Azure Functions log stream.
Write-Host "PowerShell HTTP trigger function processed a request."
# Interact with query parameters or the body of the request.
$deviceId = $Request.Query.deviceId
$GroupID = $Request.Query.GroupId

if (-not $deviceId) { 
$deviceId = $Request.Body.deviceId
}
if (-not $GroupId) { 
$GroupId = $Request.Body.GroupId
} 

# define the following variables
$ApplicationID =    "" # this is the id of the app you created in app registrations
$TenantDomainName = "" # your tenant name, eg: windowsnoob.com
$AccessSecret =     "" # this is the secret of the app you create in app registrations

# create the body
$Body = @{
Grant_Type = "client_credentials"
Scope = "https://graph.microsoft.com/.default"
client_Id = $ApplicationID
Client_Secret = $AccessSecret
}

# make initial connection to Graph
$ConnectGraph = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$TenantDomainName/oauth2/v2.0/token" -Method POST -Body $Body
# get the token
$token = $ConnectGraph.access_token
$token

# to improve logging...
$triggerName = "add_device_to_aad_group"
$a = Get-Date
$body = " `n"
$body = $body + "$a Starting the '$triggerName' function...`n"
$body = $body + "$a Connected to tenant: $TenantDomainName.`n"

#START $FindDevice
if ($deviceId -and $GroupId) {
	$Group = Invoke-RestMethod -Method Get -uri "https://graph.microsoft.com/v1.0/groups?`$filter=Id eq '$GroupId'" -Headers @{Authorization = "Bearer $token"} | Select-Object -ExpandProperty Value
	$GroupName = $Group.displayName
	$body = $body + "$a You supplied deviceId: '$deviceId'" + ".`n" 
	$body = $body + "$a You supplied groupId: '$GroupId'" + ".`n"    
	$body = $body + "$a Group.displayName: '$GroupName'" + ".`n"
    
	#$GroupMembers =  Invoke-RestMethod -Method Get -uri "https://graph.microsoft.com/v1.0/groups/$GroupID/members?$filter " -Headers @{Authorization = "Bearer $token"} | Select-Object -ExpandProperty Value
	# | Select-Object -ExpandProperty Value
    
    # below fixes the 100 members per returned result in AAD problem
    $GroupMembers2 = Invoke-RestMethod -Method GET -uri "https://graph.microsoft.com/v1.0/groups/$GroupID/members?`$count=true&`$filter=startswith(deviceid,'$deviceId')" -Headers @{Authorization = "Bearer $token";"ConsistencyLevel" = "eventual"}
	
	# if found do this
	if ($GroupMembers2.value.deviceId){
		#$body = $body + "--------------------------------------------------------------------`n"
        #$body = $body + "This device was found in the AAD group so no need to add it again...`n"
		#$body = $body + "deviceId: " + $GroupMembers2.value.deviceId + "`n"
        #$body = $body + "displayName: " + $GroupMembers2.value.displayName + "`n"
		#$body = $body + "--------------------------------------------------------------------`n"
		Write-Host -ForegroundColor Yellow "$GroupMembers2.value.displayName is in the group" 
		$body = $body + "$a Device: " + $GroupMembers2.value.displayName + " is already in the " + $GroupName + " group, nothing to do.`n"
		$body = $body + "$a The computer is already in the group, nothing to do.`n"
		$Status = "Already present in group"
	}
	else	{
        $AddDevice = Invoke-RestMethod -Method Get -uri "https://graph.microsoft.com/v1.0/devices?`$filter=deviceId eq '$deviceId'" -Headers @{Authorization = "Bearer $token"} | Select-Object -ExpandProperty Value | %{ 
        Write-Host -ForegroundColor Green "Adding $($_.DisplayName) ($($_.ID)) to the group"
        $body = $body +  "$a Adding $($_.DisplayName) ($($_.ID)) to the group with ObjectID $GroupID.`n"
        $ComputerName = $($_.DisplayName) 
        $Status = "ADDED"
        $BodyContent = @{
            "@odata.id"="https://graph.microsoft.com/v1.0/devices/$($_.id)"
        } | ConvertTo-Json
            # code to add it here...
            # the $ref variable is explained here... kinda # https://docs.microsoft.com/en-us/graph/api/group-post-members?view=graph-rest-1.0&tabs=http
            try {Invoke-RestMethod -Method POST -uri "https://graph.microsoft.com/v1.0/groups/$GroupID/members/`$ref" -Headers @{Authorization = "Bearer $token"; 'Content-Type' = 'application/json'} -Body $BodyContent
            # pause some seconds to allow time for the object to be populated if recently added...
            sleep 30
            }
            catch { $body = $body + "$a ERROR ADDING THE DEVICE`n"
                    $body = $body + "Here is the error message: '$_.ErrorMessage'"
                    $Status = "ERROR ADDING THE DEVICE"
                    }
	}
    }

}
#END $FindDevice

$a = Get-Date
$body = $body + "$a Exiting Azure function."
# Associate values to output bindings by calling 'Push-OutputBinding'.
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
StatusCode = [HttpStatusCode]::OK
Body = $body
})

Once done and tested, copy the functions URL in the function app

get function url.png

Step 3. Download the scripts

Next, download the attached 7 ZIP file, use 7-Zip to decompress.

Note: Only logged on members of windows-noob.com can download this file.

WhiteGlove – Add device to aad group.7z 4.84 kB · 0 downloads

 

Step 4. Modify the scripts

Modify the AddWhiteGloveDeviceToAzureAdGroup.ps1 script and configure the Object ID (of the WhiteGlove Completed group) and the URL of the http trigger for adding devices to an azure ad group.

 

groupid and graph function url.png

Once done with your changes, you’ll need to encode the files to a base64 text file. To do that, modify the path of where you are running the encode script, then run it in PowerShell ISE.

 

encode path.png

This will leave you with 2 new encoded scripts, shown below

encoded scripts.png

Open each of the respective encoded text files in notepad and mark all text within (CTRL+A) then COPY the text (CTRL+C),

copy the text.png

The paste that copied text into the respective variable in the Win.AP.CreateScheduledTask_AddWhiteGloveDeviceToAzureAdGroup.ps1 script shown here.

paste in the encoded scripts.png

save your changes, to the Win.AP.CreateScheduledTask_AddWhiteGloveDeviceToAzureAdGroup.ps1 script

Step 5. Add the Win32 app

Next, using the latest version of the IntuneWinappUtil.exe app, create a Win32 app. Configure the app settings as follows:

Name: Win.AP.CreateScheduledTask_AddWhiteGloveDeviceToAzureAdGroup
Program Install command:  install.Win.AP.CreateScheduledTask_AddWhiteGloveDeviceToAzureAdGroup.cmd
Program uninstall command: install.Win.AP.CreateScheduledTask_AddWhiteGloveDeviceToAzureAdGroup.cmd
Install behavior: System
Device restart behavior: No specific action
Return codes:
0 Success
1707 Success
3010 Soft reboot
1641 Hard reboot
1618 Retry

Requirements

Operating system architecture: x64
Minimum operating system Windows 10 1903

Registry:HKEY_LOCAL_MACHINE\SOFTWARE\windows-noob\WhiteGlove

Value name: KeyUploadedAfterWhiteGlove

as per the screenshot below:

registry requirements.png

 

Detection rules

Rules format: Manually configure detection rules

Detection rules File: C:\Windows

File or folder: Installed_WhiteGlove_Add_device_to_aad_group.txt

Detection method: File or folder exists

 

detection rules.png

finally, assign the Win32 app as Required to our WhiteGlove Computers Azure ad group created in part 1.

configure required assignment to whitelove computers.png

 

Step 6. Duplicate your Endpoint Protection BitLocker Policy

Locate your existing Endpoint Protection Bitlocker Policy (which you excluded from the WhiteGlove Computers azure ad group in part 1). Copy the settings to a new BitLocker Policy and assign this new policy to the WhiteGlove Completed azure ad group.

duplicated endpoint protection bitlocker policy.png

Step 7. Enroll a new device and verify Bitlocker recovery

Now you’ve completed all the parts necessary to verify the overall solution in action.

To do that preprovision a WhiteGlove computer, get a user to enroll it, and after it’s all complete the device will get magically added to the WhiteGlove Completed Azure AD group via the Win32 app in this part.

device added.png

 

It will then get the BitLocker policy that we targeted to that group.

To verify everything is working, take note of the BitLocker recovery info on the device…

before rotation.png

Verify it’s the same in Intune

recovery key before rotation.png

and next, trigger a BitLocker key rotation action in Intune

bitlocker key rotation.png

You can then confirm on the device that it’s rotated

after rotation.png

and the new recovery key information is stored in Azure.

key rotated.png

Job done !

Troubleshooting

This Win32App creates some files which are extracted to C:\Windows\Temp. Review the log files for the generation of the Scheduled Task. Below is a reference log file, use it to compare to your attempts.

 

03/05/2023 05:03:39 Starting the 'AddWhiteGloveDeviceToAzureAdGroup' version: '0.1' script...
03/05/2023 05:03:39 Checking if registry path registrypath: 'HKLM:\SOFTWARE\windows-noob\WhiteGlove\' exists...
03/05/2023 05:03:39 registrypath exists...
03/05/2023 05:03:39 found 'KeyUploadedAfterWhiteGlove', will now add the device to the azure ad group...
03/05/2023 05:03:39 Starting the AddDeviceToAzureAdGroup Function...
03/05/2023 05:03:40 Using the following deviceId: 4a6f1a9a-46d0-4fd1-8b4b-f158e01fda2c
03/05/2023 05:03:40 Using the following URL: https://graph-functions-function-app.azurewebsites.net/api/add_device_to_aad_group?code=<SNIPPED>==&deviceId=4a6f1a9a-46d0-4fd1-8b4b-f158e01fda2c&GroupId=43e106bc-b8ce-4f1c-97bb-f9c21fcbc8cf
03/05/2023 05:03:40 Making the query...
03/05/2023 05:04:13 The query returned:  
03/05/2023 13:03:42 Starting the 'add_device_to_aad_group' function...
03/05/2023 13:03:42 Connected to tenant: windowsnoob.com.
03/05/2023 13:03:42 You supplied deviceId: '4a6f1a9a-46d0-4fd1-8b4b-f158e01fda2c'.
03/05/2023 13:03:42 You supplied groupId: '43e106bc-b8ce-4f1c-97bb-f9c21fcbc8cf'.
03/05/2023 13:03:42 Group.displayName: 'WhiteGlove Completed'.
03/05/2023 13:03:42 Adding AP-5CG03729P0 (aa1278ab-03d5-49f6-868e-5c8e7d2f415d) to the group with ObjectID 43e106bc-b8ce-4f1c-97bb-f9c21fcbc8cf.
03/05/2023 13:04:13 Exiting Azure function.
03/05/2023 05:04:13 Group name identified as: 'WhiteGlove Completed'
03/05/2023 05:04:13 The computer is not confirmed in the group yet, will retry...
03/05/2023 05:04:29 The query returned:  
03/05/2023 13:04:28 Starting the 'add_device_to_aad_group' function...
03/05/2023 13:04:28 Connected to tenant: windowsnoob.com.
03/05/2023 13:04:28 You supplied deviceId: '4a6f1a9a-46d0-4fd1-8b4b-f158e01fda2c'.
03/05/2023 13:04:28 You supplied groupId: '43e106bc-b8ce-4f1c-97bb-f9c21fcbc8cf'.
03/05/2023 13:04:28 Group.displayName: 'WhiteGlove Completed'.
03/05/2023 13:04:28 Device: AP-5CG03729P0 is already in the WhiteGlove Completed group, nothing to do.
03/05/2023 13:04:28 The computer is already in the group, nothing to do.
03/05/2023 13:04:29 Exiting Azure function.
03/05/2023 05:04:29 Group name identified as: 'WhiteGlove Completed'
03/05/2023 05:04:29 The computer was confirmed as added to the group.
03/05/2023 05:04:29 Removing the scheduled task...
03/05/2023 05:04:29 About to delete scheduled task: AddWhiteGloveDeviceToAzureAdGroup
03/05/2023 05:04:31 Succeeded to remove scheduled task: AddWhiteGloveDeviceToAzureAdGroup
03/05/2023 05:04:31 Exiting script.

and the scheduled task creation log file

03/05/2023 05:02:34 Starting the 'Win.AP.CreateScheduledTask_AddWhiteGloveDeviceToAzureAdGroup' version '0.01' script...
03/05/2023 05:02:34 Checking if computer was enrolled within the allowed timeframe...
03/05/2023 05:02:34 Current date/time = 03/05/2023 05:02:34, computer install date/time = 03/05/2023 12:45:48
03/05/2023 05:02:34 Enroll date [03/05/2023 12:45:48] created within the allowed timeframe...
03/05/2023 05:02:34 Hours since enrollment: -7.72057391491667
03/05/2023 05:02:34 EnrollmentDateCheck detected as: True
03/05/2023 05:02:34  Logged on user is: AzureAD\NiallBrady
03/05/2023 05:02:34 extracting scripts to 'C:\Windows\Temp'...
03/05/2023 05:02:34 decoding BASE64 encoded file...AddWhiteGloveDeviceToAzureAdGroup.ps1
03/05/2023 05:02:34 decoding BASE64 encoded file...AddWhiteGloveDeviceToAzureAdGroup.vbs
03/05/2023 05:02:34 Creating windows-noob foldername...
03/05/2023 05:02:34 Creating scheduled task...
03/05/2023 05:02:36 Info: The scheduled task doesn't exist, will create it.
03/05/2023 05:02:36 DEBUG: Using the following values for the scheduled task...
03/05/2023 05:02:36 DEBUG: Time: 03/04/2023 05:03:36 Script: C:\Windows\Temp\AddWhiteGloveDeviceToAzureAdGroup.vbs Action: MSFT_TaskExecAction Trigger: MSFT_TaskTimeTrigger Settings: MSFT_TaskSettings3 Principal: MSFT_TaskPrincipal2 Foldername: windows-noob.
03/05/2023 05:02:37 DEBUG: task=MSFT_ScheduledTask (TaskName = "AddWhiteGloveDeviceToAzureAdGroup", TaskPath = "\windows-noob\") taskName=AddWhiteGloveDeviceToAzureAdGroup run=03/05/2023 05:03:36
03/05/2023 05:02:37 DEBUG: settings the scheduled task settings=MSFT_ScheduledTask (TaskName = "AddWhiteGloveDeviceToAzureAdGroup", TaskPath = "\windows-noob\")
03/05/2023 05:02:37 Exiting script.

That’s it for this mini series, see you in the next one !

cheers

niall

This entry was posted in AzureAD, BitLocker, functionapp, httptrigger, Intune, win32 app, Windows AutoPilot. Bookmark the permalink.

Leave a Reply

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.