Windows Autopilot Hybrid Join Complete Toast Notification


I’ve worked on a few Windows Autopilot hybrid join implementations and the end user experience, when provisioning the device, isn’t as smooth as the Azure AD joined.

To get around the amount of time it can take for the backend process to complete, with a registered Azure AD joined and a hybrid joined object to exist in Azure AD we usually implement Skip User ESP. We can do this by creating a custom profile and the OMA-URI ./Device/Vendor/MSFT/DMClient/Provider/MS DM Server/FirstSyncStatus/SkipUserStatusPage with a Data Type of Boolean and a Value of True.

The benefit of using Skip User ESP is that it gets the device to the desktop, however the negative side is that the device really isn’t ready to use. For example, if you fire up Office or Teams, you’ll be prompted to ‘Allow my organisation to manage my device’ and this will create an Azure AD registered object in Azure AD and we don’t want this. If you attempt to load up OneDrive, you’ll be met with the onboarding wizard, where the user has to enter creds authenticate. So if you’ve implemented your policy to automatically do this, you’ll be bitter disappointed that at this stage it’s not ready to use.

So we can tell the users that the device is almost ready to use, but ideally they should sit back and wait for the registration process to complete on the back end and then they are good to go.

The problem is, how does the user know this process is complete? Is it going to take 5 minutes? 30? An hour? Or longer?

So I decided to take a look at creating something which would assist with this. Ideally I wanted something visual to happen on the end user’s device, something which would let them know the device is ready to use. The Windows Autopilot Hybrid Join Complete Toast Notification is the result of playing around to try and achieve this.

Before I get going on how this was created and operates, I have uploaded all the code and files up to my GitHub and it’s all available here.

The code itself is very much beta, it does the job but it needs refining and smoothing out, but I’m happy to release this. It may or may not get updated over time.

So what does the Hybrid Join Complete Toast Notification do? Here’s a breakdown of the process.

  • Two Win32 apps are deployed from Intune during Autopilot provisioning.
  • The first app sets some registry keys which define a reboot protocol, which will be used by the toast notification script.
  • The second app contains the toast notification script, along with a scheduled task XML, images and a PowerShell script to set everything up.
  • When the second app is executed, a scheduled task is created the triggering being a Windows event ID which relates to the device registration task being completed.
  • This executes the toast notification to alert the end user that their device is now ‘Enterprise ready’ and they can reboot the device immediately or later.

Reboot Protocol Script

The reboot protocol script sets a registry key in the location HKCR:\rebootnow\shell\open\command. The key will execute a restart bat file which is delivered as part of the Toast Notification application. The bat file – Reboot.bat – will be located in the c:\temp folder on the endpoint. This location will be created when the Toast Notification application is executed. Note that you could amend the solution to use a folder location you desire but you will need to amend all the references in the scripts provided.

The reboot protocol script needs to be execute as SYSTEM or an administrator and hence, when we create the Win32 app in Intune, we will use the SYSTEM context to run the application .

The code is fairly straight forward.

# Creating registry entries if they don't exists
New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT | Out-Null 
$RegPath = "HKCR:\rebootnow\"
New-Item -Path "$RegPath" -Force
New-ItemProperty -Path "$RegPath" -Name "(Default)" -Value "URL:Reboot Protocol" -PropertyType "String"
New-ItemProperty -Path "$RegPath" -Name "URL Protocol" -Value "" -PropertyType "String"
New-Item -Path "$RegPath\shell\open\command" -Force
New-ItemProperty -Path "$RegPath\shell\open\command" -Name "(Default)" -Value 'c:\temp\Reboot.bat' -PropertyType "String"

New-Item -Path "$env:ALLUSERSPROFILE\Microsoft\IntuneManagementExtension\Logs" -Name "RebootProtocol.txt" -ItemType "file" -Value "Hybrid Join Reboot Protocol set"

After adding in the registry key, I am creating a txt file called RebootProtocol.txt in the ProgramData\Microsoft\IntuneManagementExtension\Logs folder. I will use this as my detection method for successful install of the Win32 app.

So let’s create the app in Intune.

Create the Reboot Protocol Win32app

If you don’t have the Microsoft Win32 Content Prep Tool, you will need to download this from https://github.com/microsoft/Microsoft-Win32-Content-Prep-Tool.

This utility is used to create .intunewin files which we need to import into Intune to create our application.

When running the tool, I tend to have a Source and an Output folder, the source containing my binaries and the output to contain my generated .intunewin file.

Take a look at my blog post on creating a Win32 app to get an understanding of the process involved.

As you can see, I have put the InstallRebootProtocol.ps1 file into it’s own Source folder. I can now run the Win32 Content Prep tool to create the .intunewin file.

Run the IntuneWinAppUtil.exe file and enter in the source location, the setup file, which will be the InstallRebootProtocol.ps1 file, and the output folder for the .intunewin file. When prompted to create a catalog file you can say No.

Once conversion has completed, the .intunewin file will reside in the output folder.

We will now create the app in the Endpoint Manager console, so head to https://endpoint.microsoft.com. Navigate to Apps\Windows and click Add.

From the App type drop down, choose Windows app (Win 32) and then choose Select when prompted.

Click the Select app package file link.

Click the folder icon to browse.

Navigate to the location of your .intunewin file and select it. Click OK.

At the App information screen, enter a Name and Publisher as a minimum. You can enter other details and upload a logo if you wish. Click Next.

At the Program screen, enter the Install command as below:

C:\Windows\Sysnative\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -file "InstallRebootProtocol.ps1"

Ensure that the Install behavior is set to System.

I’ve added in the same command to Uninstall as this is mandatory but I’ve not actually created an uninstall script. You could create a script to remove the keys which will be set, and remove the txt file from the ProgramData\Microsoft\IntuneManagementExtension\Logs folder. Click Next.

At the Requirements screen, enter architecture and minimum operating system release. The scripts have been tested on 20H2. Click Next.

In the Detection rules screen, choose Manually configure detection rules from the Rules format drop down and click the Add link. Enter the following detection rule.

  • Rule type: File
  • Path: C:\ProgramData\Microsoft\IntuneManagementExtension\Logs
  • File or folder: RebootProtocol.txt
  • Detection Method: File or folder exists

Click OK and then Next.

Click through the wizar d to completion. Don’t worry about assigning this application as it’s going to be a dependency of the Toast Notification application.

The Toast Notification magic

OK so onto the toast notification itself. This is a combination of scripts, images and an XML file. When run they will produce the following toast notification.

Image

We have the following:

  • Autopilot.jpg – a hero image which will appear a the top of the toast notification. A hero image must be 364×180 pixels at 100%. The image used in my solution is from here as a free download. I have cropped the image to size.
  • AutopilotAppLogo.jpg – this an appLogoOverride image and is the the airplane icon in my notification This must be 48×48 pixels at 100% scaling. This image is available for non-commercial use here.
  • Check for hybrid join completion.xml – this is a exported scheduled task and will act as the method to trigger the toast notification.
  • InstallADJoinedPopUp.bat – this batch file will copy the content into a location on the endpoint (c:\temp), import the scheduled task and create a file for the Win32app detection method.
  • Reboot.bat – this batch file is called when the Reboot button is pressed on the toast notification. You may remember that this was the value of our registry key entry in the Reboot Protocol Script.
  • SchedTask.vbs – I’ve added this as an option to use with the toast notification script if you want to hide a flashing blue window with the toast_notify script is executed by the scheduled task.
  • Toast_Notify.ps1 – the toast notification script itself which produces the pop up for the end user.

OK let’s take a look at what’s happening.

Check for hybrid join completion.xml

When hybrid join Autopilot hits the desktop, after a Skip User ESP, we have this no-man’s land period where we are waiting for the AD object to sync up to Azure AD via AAD Connect and become registered.

On the endpoint itself, we can check the Windows event logs to determine when the registration of the device has succeeded.

The event is event ID 306 under Application and Services Logs\Microsoft\Windows\User Device Registration\Admin. Once this event occurs we can start to use Office, Teams etc without being prompted to add the device to the organisation, thereby creating an unwanted Azure AD registered device.

This event ID, therefore, acts as a trigger for the toast notification.

When creating a scheduled task, we could use PowerShell to generate the task, however there aren’t any built in commands to create a task based on an event ID trigger. So I decided that I would create the scheduled task, export as XML and then use a command to import the task into the device as it provisions.

The Check for hybrid join completion.xml read as below:

<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <RegistrationInfo>
    <Date>2021-12-14T09:33:56.5497995</Date>
    <Author>INTERNAL\paul.winstanley</Author>
    <URI>\Check for hybrid join completion</URI>
  </RegistrationInfo>
  <Triggers>
    <EventTrigger>
      <Enabled>true</Enabled>
      <Subscription>&lt;QueryList&gt;&lt;Query Id="0" Path="Microsoft-Windows-User Device Registration/Admin"&gt;&lt;Select Path="Microsoft-Windows-User Device Registration/Admin"&gt;*[System[EventID=306]]&lt;/Select&gt;&lt;/Query&gt;&lt;/QueryList&gt;</Subscription>
    </EventTrigger>
  </Triggers>
  <Principals>
    <Principal id="Author">
      <GroupId>S-1-5-32-545</GroupId>
      <RunLevel>LeastPrivilege</RunLevel>
    </Principal>
  </Principals>
  <Settings>
    <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
    <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
    <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
    <AllowHardTerminate>true</AllowHardTerminate>
    <StartWhenAvailable>false</StartWhenAvailable>
    <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
    <IdleSettings>
      <StopOnIdleEnd>true</StopOnIdleEnd>
      <RestartOnIdle>false</RestartOnIdle>
    </IdleSettings>
    <AllowStartOnDemand>true</AllowStartOnDemand>
    <Enabled>true</Enabled>
    <Hidden>false</Hidden>
    <RunOnlyIfIdle>false</RunOnlyIfIdle>
    <WakeToRun>false</WakeToRun>
    <ExecutionTimeLimit>PT72H</ExecutionTimeLimit>
    <Priority>7</Priority>
  </Settings>
  <Actions Context="Author">
    <Exec>
      <Command>powershell.exe</Command>
      <Arguments>-NoProfile -WindowStyle Hidden -executionpolicy bypass C:\temp\Toast_Notify.ps1</Arguments>
    </Exec>
  </Actions>
</Task>

When imported, it will appear as follows on the device:

As you can see, the trigger is the event ID. There’s nothing to stop you changing this value in the properties and selecting other events, exporting out and using this solution for other events in the event log….just a thought!

When triggered the scheduled task is going to run the toast_notify PowerShell script.

SchedTask.vbs

As mentioned, you may wish to suppress a flashing blue window which appears when executing the PS1 file from the scheduled task. To do this, include the SchedTask.vbs in your toast notification app and edit the scheduled task itself as such:

The export the scheduled task and and overwrite the Check for hybrid join completion.xml file before creating the Win32app.

The content of the SchedTask.vbs is as follows:

command = "powershell.exe -NoProfile -WindowStyle Hidden -executionpolicy bypass C:\temp\Toast_Notify.ps1"
set shell = CreateObject("WScript.Shell")
shell.Run command,0 

Toast_Notify.ps1

There’s quite a few toast notification solutions out there. Take a look at fellow MVP’s Martin Bengtsson Windows 10 Toast Notification Script and Ben Whitmore’s Deploy Service Announcement Toast Notifications in Windows 10 with MEMCM blog post for further inspiration. Also read the official MS docs on Toast content to dig further.

I want to keep the design as simple as possible, with some pizzaz and a reboot option. Here’s the full script.

#Specify Launcher App ID
$LauncherID = "{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\WindowsPowerShell\v1.0\powershell.exe"

#Load Assemblies
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null
 
#Build XML Template
[xml]$ToastTemplate = @"
<toast duration="long" scenario="reminder">
    <visual>
        <binding template="ToastGeneric">
            <text>Autopilot Build Process</text>
            <text>This device is now set up for use in the environment. Please restart to complete the process</text>
            <image placement="appLogoOverride" src="C:\temp\AutopilotAppLogo.jpg" />
            <image placement="hero" src="C:\temp\Autopilot.jpg" />
        </binding>
    </visual>
</toast>
"@

#Build XML ActionTemplate 
[xml]$ActionTemplate = @"
<toast>
    <actions>
       
        <action activationType="protocol" arguments="rebootnow:" content="Reboot" />       
        <action arguments="dismiss" content="Later" activationType="system"/>

    </actions>
</toast>
"@
		
#Define default actions to be added $ToastTemplate
$Action_Node = $ActionTemplate.toast.actions
		
#Append actions to $ToastTemplate
[void]$ToastTemplate.toast.AppendChild($ToastTemplate.ImportNode($Action_Node, $true))
 
#Prepare XML
$ToastXml = [Windows.Data.Xml.Dom.XmlDocument]::New()
$ToastXml.LoadXml($ToastTemplate.OuterXml)

#Prepare and Create Toast
$ToastMessage = [Windows.UI.Notifications.ToastNotification]::New($ToastXML)
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($LauncherID).Show($ToastMessage)

Let’s strip it down a little.

The #Build XML Template is where we can define the text that will appear in the toast, along with pointing the code to where our images are and what type they are. I have also set that toast duration to long, so users will be aware that the notification is there and it won’t disappear in seconds..unlike the users who’ve probably disappeared from their desk whilst waiting for the device to finish up! I’ve also set this to a reminder style toast, so I can add in the hero image and I have a set a binding template of ‘generic’. You can play with the design here, reference to the official docs to assist with building this up.

The #Build XML ActionTemplate will define the actions on the toast, in my case I wanted a reboot and later button. I’ve set the later button to just be a dismiss action. It’s not going to remind the user to reboot but will just force the toast to go away. You could get fancy here and start to look into other actions to add.

The reboot action uses activationType=”protocol” arguments=”rebootnow:”, this will use the registry key we set with our reboot protocol script and in turn run the reboot.bat will to restart the computer. Nice!

The final lines will compile, prepare and create the toast notification for use based on our defined setings.

InstallADJoinedPopUp.bat

To get everything in place on the endpoint we have the final script, the InstallADJoinedPopUp.bat file (yeah it’s a bat file!).

This serves three purposes:

  1. Copy all the Win32app content to the c:\temp folder.
  2. Create the scheduled task called Check for hybrid join completion in a folder called Hybrid Join in the Scheduled Task app.
  3. Create a file called CheckHybridJoin.txt in the ProgramData\Microsoft\IntuneManagementExtension\Logs folder which will be used for the detection method in the Win32app.
xcopy *.* "C:\temp\" /Y /I
SCHTASKS /Create /TN "Hybrid Join\Check for hybrid join completion" /xml "Check for hybrid join completion.xml"
Echo Scheduled Task Created >> "%ALLUSERSPROFILE%\Microsoft\IntuneManagementExtension\Logs\CheckHybridJoin.txt"

Create the Hybrid Join Complete Toast Notification Win32app

Again, we will need the Win32 Content Prep tool to create the Win32app for the toast notification.

Make sure you have downloaded the file and have the following in your source folder.

Then run the intunewinapputil.exe. In this example, I am using the switches available

  • -c = Source folder
  • -s = Setup file
  • -o = Output location for the .intunewin file.

Note that I am using the InstallADJoinedPopUp.bat as my setup file for the Toast Notification Win32 app.

Now we will repeat the process in Endpoint Manager to create the Win32app. Obviously upload your new .intunewin file.

Give the app a Name etc.

The Install command will be the InstallADJoinedPopUp.bat and I haven’t created an uninstall script so I’ve filled in with the same .bat file since this field is mandatory. Again, ensure Install behavior is set to System.

For the detection method we will follow the same logic as the reboot protocol script but this time the File or folder to check for is called CheckHybridJoin.txt.

On the Dependencies screen, click Add.

Choose the Install Reboot Protocol app created earlier and click Select.

Ensure that Automatically install is set to Yes. Click Next.

We want to ensure that we add an Assignment to this app. I have targeted my dynamic group for hybrid joined devices which is just the group I am using to target my hybrid join Autopilot deployment profile. Click through the wizard to completion.

Finally, we want to make sure that all the scripts have processed as the device is provisioned, so we can add it into our list of apps to ….

In the Endpoint Manager portal, go to Devices\Windows\Windows enrollment\Enrollment Status Page and select the ESP policy you are using.

Ensure that Block device use until these required apps are installed if they are assigned to the user/device is set to Selected, then click the Select apps button.

Choose the Toast Notification app. You do not need to select the Install Reboot Protocol app, this will automatically deploy as a dependency. Click Select and Review + save the changes to the ESP.

When the device is provisioned, the magic will happen. The binaries will be copied down to the c:\temp folder and the reboot protocol and scheduled task created. When the User Device Registration event ID 306 kicks in, the user will be notified all is good to go.

In the next part of the blog post, I will show how you can add in some AppLocker file hash exemptions, if you want to use the Toast Notification script and you are using AppLocker and therefore your PowerShell is running in constrained language mode.

Feel free to feedback on this blog post. As mentioned, this solution is really beta and I’ve cobbled it together to serve a purpose, there are plenty of improvements that could be made.

Hope you find this post useful. Look out for the next one.

8 comments

  1. Will be testing it this week or next week with PreProvisioning, will let you know how it pans out, but expect it to work as normal.

  2. im getting the below error when im running Toast_Notify.ps1 as system ! 🙂

    Exception calling “Show” with “1” argument(s): “Access denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))”
    At line:46 char:1
    + [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotif …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : UnauthorizedAccessException

    1. Have you followed the guide to set this up as a win32app? Although there’s nothing to stop you running the PS1 file, even as a standard user, if you have the rest of the content in the c:\temp folder it should execute and display the toast of you wanted to test.

  3. im getting the same error running manually notify_toast.ps1 as a user and a system !

    Exception calling “Show” with “1” argument(s): “access denied. (Undtagelse fra HRESULT: 0x80070005 (E_ACCESSDENIED))”

    im troubleshooting that 🙂 but thanks for the solution

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s