In my previous blog posts, I touched base on Azure AD join vs Hybrid Azure AD join and the pains of Hybrid AAD Join with Windows Autopilot in a managed domain environment.
If you have experience with Autopilot Hybrid AADJ in a managed domain environment, you already have the idea of the biggest challenge you would face – the device getting the Azure AD PRT when the user gets to the desktop straight after the device completes provisioning.
But why is this so? Though I have highlighted this in my previous blog posts linked above and also, this is very well explained by none other than Michael Niehaus in his OOFHours blog site, still for the readers, here is a brief explanation.
Table of Contents
Understanding the challenge with Autopilot Hybrid Azure AD Join process in a Managed Domain environment
For the Hybrid Azure AD join scenario, Windows Autopilot service and Microsoft Intune only take care of getting the device enrolled to Intune, by virtue of which it can receive the ODJ blob to get joined to Active Directory.
The actual “Hybrid Azure AD Join” in itself is a separate asynchronous process that happens in the backend, and for a managed domain environment, is dependent on the sync schedule of AAD Connect.
It is only after that the AAD Connect syncs the on-prem device object to Azure is when the Azure DRS process of automatic registration succeeds, thereby fetching the device its much needed Azure AD device certificate.
After this, when the user does a fresh sign-in to the device, it receives the Azure AD PRT and can start communicating with the Microsoft cloud services for proper functioning.
As such, for a managed domain environment, we cannot assure the devices to be in a production-ready state when the user gets to the Desktop screen, post the Windows login after completing the Device ESP phase during the Hybrid AAD Join Autopilot provisioning.
This is because the time taken by the device to complete the device ESP phase might not be enough to buffer the time delay of the Hybrid Azure AD Join backend process.
So how can we work around this “by design” flaw of Hybrid AAD Join with Autopilot and still ensure an acceptable user experience?
That’s what this post is all about.
Finding an answer to the challenge
We need something to prevent users from getting to the Desktop screen till the actual “Hybrid join” process gets completed.
This way, just like normal AADJ provisioning, when the user signs in to Windows and gets to the Desktop, the device will be in a business-ready state because that sign-in will fetch the device with the Azure AD PRT.
Enabling user ESP is not an option for HAADJ Autopilot in a Managed Domain environment.
Why it is recommended to skip the User ESP phase for HAADJ Autopilot in a Managed Domain environment?
This is because of the same issue of Azure AD PRT as described above. Instead of helping us get over the problem as described, it can lead to failed provisioning due to ESP timeout.
Explanation.
If the device ESP fails to hold the device for a time that is long enough to buffer the time required for the backend process to complete, the device will not receive the AzureAD PRT on the Windows login event post device ESP.
If the device does not receive the AzureAD PRT, then when the device starts the user ESP, it will not receive the user targeted policies as it cannot communicate with the service due to the lack of AzureAD device identity, thus leading to an inevitable ESP timeout.
Probable solution to the challenge
To plug in the time-gap of the process (the delay introduced due to the backend AAD Connect sync process), the only effective way that I can think of is an app [PS script packaged as win32] executed during the device ESP that will
- display a Windows 10 OOBE like Splash Screen to mask the Desktop screen post the Windows sign-in after device ESP, hiding the cursor and the taskbar and thereby disable user activity on the device till the setup completes.
- monitor the status of the Hybrid AAD join process to restart the device at the end.
This way, when the user performs Windows login post the restart, that sign-in event will fetch the Azure AD PRT and the device will be ready for production use.
π‘ If you ask why a PS wrapped as Win32 and not a direct PS script deployment, then it's because I have greater control over the win32 app deployment during ESP than a PS deployment.
Presenting the solution – Autopilot_HAADJ_Reworked
Here is the GitHub link to the solution that I named as Autopilot_HAADJ_Reworked.
Inside you will find 3 more directories as below
The directory named HAADJAutopilotCustomSplashScreen holds the main solution.
Note that this is inspired and adapted from two already existing solutions
- Create a custom Splash Screen for Windows 10 In-Place Upgrade by Trevor Jones, and
- WaitForUserDeviceRegistration.ps1 by Steve Prentice.
The bin folder contains the required DLL files and the company brand logo for the toast notification (the solution utilizes the MahApps.Metro framework) The xaml folder contains the WPF xml file which is basically the structure of the custom OOBE like splash screen The Set-AADHybridLockOOBE.ps1 script which gets executed by IME during device ESP. This script β copies the content of the script package from IMECache to C:PrgramData location for later use, and then β sets the Active Setup registry to run the Invoke-AADHybridLockOOBE.ps1 script on next login event β further, creates dummy files to block SCCM agent installation [SCCM agent is not deployed from Intune] The Invoke-AADHybridLockOOBE.ps1 script invoked on login as Active Setup command β creates PS Instances (runspaces) for each screen found on the device where the custom OOBE like splash screen will be displayed β invokes AADHybridLockOOBE.ps1 β checks every 90 seconds for a successful user device registration windows event (Event ID 306 which indicates the Hybrid Azure AD join process has completed succesfully) β on detecting the success event ID, initiates a force restart β before restart, sets up Run-Once key for InitialToastNotification.ps1 The AADHybridLockOOBE.ps1 invoked by Invoke-AADHybridLockOOBE.ps1 displays the custom OOBE like splash screen
π‘ Notice that additionally I am blocking the automatic SCCM client push on the devices. This is because the environment is a co-managed environment with automatic SCCM agent push as part of site discovery.
If your environment is not co-managed, you can choose to comment out the specific section in the script.
To clarify and compare-contrast my adaption with the original SMSAgent solution,
β’ AADHybridLockOOBE.ps1 is essentially the Create-FullScreenBackground.ps1 with modified messages for display. β’ Invoke-AADHybridLockOOBE.ps1 functionally equivalent to Create-Runspaces.ps1 with the added logic to monitor the Hybrid AAD join process. β’ Set-HybridLockOOBE.ps1 can be compared to Show-OSUpgradeBackground.ps1 adapted to run by IME during the device ESP, setting up Invoke-AADHybridLockOOBE.ps1 as an Active Setup script.
π‘ Note the use of the Active Setup method - this ensures that Invoke-AADHybridLockOOBE.ps1 will get executed very early during the Windows login process and way before the desktop appears - just what my use case requires.
π‘ Since Invoke-AADHybridLockOOBE.ps1 when executed invokes the execution of AADHybridLockOOBE.ps1, this ensures that the user will not get to the Desktop screen post the Windows login after device ESP.
Instead, the user gets presented with the custom Windows 10 OOBE like splash screen as below.
The Invoke-AADHybridLockOOBE.ps1 that invokes the AADHybridLockOOBE.ps1 to display the Splash Screen and itself remains alive in the backend to periodically check the User Device Registration events to check for successful completion of the backend Hybrid AAD Join process.
π‘ Event ID 306 of User Device Registration events indicates the device has successfully completed the Automatic registration.
Once it detects the mentioned event, it proceeds to restart the device.
The logic is quite simple as below
do{
Start-Sleep 90
$AutomaticRegistration = ""
$AutomaticRegistration = Get-WinEvent -LogName 'Microsoft-Windows-User Device Registration/Admin' | Where-Object {$_.Id -eq "306"}
}until($AutomaticRegistration.Id -eq "306")
shutdown.exe /r /t 0 /f
π‘ Here you can also use the logic as is in the WaitForUserDeviceRegistration.ps1 script by Steve Prentice. With that, you can have control of the maximum time limit for which the device will stay on the custom splash screen.
Post restart, when the end-user performs Windows login, that sign-in event will fetch the device the much-required AzureAD PRT. And that’s all.
Windows Autopilot Hybrid Azure AD Join – Reworked with Joy
Here is how the HAADJ Autopilot device provisioning looks like with the solution as explained above.
Further improvements to End-User experience
When the above solution is implemented, as the user gets to the desktop, the device can start communicating to the M365 services on basis of having the AzureAD PRT.
Thus when the user gets to the desktop,
- OneDrive sync starts as usual
- Bitlocker encryption kicks in (provided the silent encryption criterias are met)
- The user targeted policies (config/app) from Intune starts flowing in
However, it can still take some time for all the user-targeted enforced policies to settle in. Further, it requires a restart to collect the fresh boot logs for DHA to evaluate Bitlocker compliance. (If the Bitlocker compliance evaluation is set to use DHA.)
Thus it will be a nice value addition if we can possibly
- notify the end-user that the device is still being setup as soon as the user gets to the Desktop screen.
- and further, track the required user-targeted applications and on successful detection, notify the user again that the device setup is complete with an ask to restart the device. (device to be auto-restarted if user takes no action)
This is where the two Toast Notification scripts – InitialToastNotification.ps1 and FinalToastNotification.ps1 (again thanks to Trevor Jones) comes in.
π‘ Note that both the scripts are basically the same with the exception that the FinalToastNotification.ps1 has the added restart action. Further, these scripts are just the frontend to display the notification on the screen. They need to be triggered.
In this solution, I have set up the InitialToastNotification.ps1 as a Run-Once reg-key via the Invoke-AADHybridLockOOBE.ps1 script such that when it detects the successful registration event and restarts the device, the InitialToastNotification.ps1 will get triggered as a Run-Once item upon user login.
But what about the 2nd notification to the user stating the device setup is complete with an ask to restart the device?
The FinalToastNotification.ps1 script is for that, however as I stated earlier, this is only the frontend. I am sending the script to the endpoint as part of this solution, to be triggered later on via a seperate win32 app deployment from Intune named FinalToastNotification_Trigger.
FinalToastNotification_Trigger is actually a simple PS script to detect the presence of the required applications on the endpoint. Till all the apps are detected, the script will continue to run and when all the apps are successfully detected, it will exit triggering the FinalToastNotification.ps1 script that is already available on the endpoint locally.
The FinalToastNotification.ps1, when triggered, will notify the user that the device setup is complete (from Intune perspective) and will ask the user to restart the device. If the user clicks on the notification, the device will get restarted. If there is no user response, the script automatically restarts the device after a waiting period (30 mins).
You can easily modify this to suit your requirements.
π‘ As a side note, when you create and deploy a Win32 app out of this, you can also put all the required applications that the script is detecting as a dependency for this package. This way, the script will not be consuming execution resources for long time.
π‘ Why enforce a 2nd restart? It helps in the Bitlocker compliance evaluation when using the DHA settings.
Remember that we blocked the SCCM installation during device ESP as part of the Set-AADHybridLockOOBE.ps1 script execution by creating dummy files.
With the 2nd notification getting triggered means all the required apps from Intune are on the endpoint. As such, post this, there is no requirement to further block the SCCM agent installation on the device.
This is where I have the EnableSCCM win32 app package deployed from Intune.
This is again a very simple PS script whose sole function is to remove the dummy files that were created by the Set-AADHybridLockOOBE.ps1 to block the automatic push of the SCCM agent on the endpoint.
When the files are removed, the device becomes eligible again for the automatic push of the SCCM agent and it is observed that once the dummy files are removed, the SCCM client gets to the device within 40-45 minutes on average.
A visual thought process to outline the complete solution
The image below shows how the different scripts are stringed together to create the complete solution.
Build .Intunewin and deploy to test
To display only the custom screen post the initial Windows login after device ESP and further locking the device till the automatic device registration succeeds,
- build a .Intunewin file out of the contents of the directory HAADJAutopilotCustomSplashScreen with Set-AADHybridLockOOBE.ps1 specified as the installer file.
- create the Win32 app in Intune using the Install/Detection as already specified in the Intune_Install_Commands.txt file.
- deploy the win32 app as Required targeted to your autopilot device group.
- make sure to track this app in the ESP settings.
To display the 2nd notification when all the required apps from Intune have settled in,
- download the contents of the directory FinalToastNotification_Trigger
- modify the FinalToastNotification_Trigger.ps1 script to detect the apps that you want to be present on the device to considered as provisioning complete stage for the device.
- build a .Intunewin file with FinalToastNotification_Trigger.ps1 specified as the installer file.
- create the Win32 app in Intune using the Install/Detection as already specified in the Intune_Install_Commands.txt file.
- [Optional] Put all the apps that the script will be detecting as a dependency for this app to get executed for a shorter run time.
- deploy the win32 app as Required targeted to the required User group.
If your environment is co-managed with automatic SCCM agent push on the endpoints as part of site discovery or the SCCM agent is deployed via GPO and you have not commented out the SCCM agent install blocker section in the Set-AADHybridLockOOBE.ps1 (which you should not as if SCCM gets to the device while the device is locked up with the custom splash screen monitoring the backend HAADJ process, Intune will get jacked up till the device settles to the required co-managed state), then you would need to build and create another win32 app to enable SCCM agent installation on the endpoints.
- build a .Intunewin file out of the contents of the directory EnableSCCM with EnableSCCM.ps1 specified as the installer file.
- create the Win32 app in Intune using the Install/Detection as already specified in the Intune_Install_Commands.txt file.
- put the Win32 app as created above [FinalToastNotification_Trigger] as the dependency for this app to get executed.
- deploy the win32 app as Required targeted to the required User group.
That’s all.
The End
You might have a legit question – “what’s the purpose of deactivating the user ESP and then adding a splash screen which does nearly the same thing!?.“
Well, I have already answered above that keeping user ESP enabled for Autopilot HAADJ provisioning is not recommended.
This is due to the fact that if the backend hybrid join process does not complete before the device completes the device ESP phase, then with user ESP enabled, the ESP will timeout waiting for user-targeted policies which the device cannot receive due to the lacking Azure AD PRT.
As such, this solution of custom splash screen at the first glance may seem to block the user from getting to the desktop in the first place. But what’s the point of getting to the desktop if the device is not yet ready for business use?
It negatively impacts end-user experience and from a support perspective, burdens the IT support team with unnecessary support tickets and/or calls from users due to issues arising because of the missing Azure AD PRT.
For reasons whatsoever, when we decide to go with Hybrid Azure AD Join with Autopilot in a Managed Domain environment, we also consent to the fact of the time gap required to complete the actual HAADJ process.
It’s like those “* conditions apply” things.
In that perspective, this solution does seem to ensure an acceptable user experience that is quite in parity with that of the AADJ Autopilot process. At least as per me π
And before I draw the curtains on this blog post…
I thank my lucky stars for you!
This entire project would not have been possible without the support from my awesome colleagues at Atos, especially Wojciech Maciejewski who helped me troubleshoot and clarify my doubts regarding all the PS stuff that is involved here.
Working on Transition and Transformation projects can get boring with passing time, but I really feel lucky to be able to get along with such creative minds who always help and support each other to bring their A-game to the table every day.
Hi Joy,
Appreicate your post which really is life saver for many. Hope Microsoft implements something like this natively from your idea. I have one question though. Do we have to target this script for device group for users ?
Hey Saranraj,
Please check the “Build .Intunewin and deploy to test” section for detailed instructions.
Loved d idea n solution keep sharing thanks
Well done! Will try this out next week.
How about devices currently members in the dynamic Autopilot group in AzureAD and already deployed with active user logged on, what will the result be when this script targets such device? And at next reboot?
Well, this is for new device provisioning.
I have not tested the behavior on existing devices.
Just an idea. If you would like to exclude all the devices already deployed do the following. Go to the dynamic Autopilot group. Export members to csv. Create new group called “Autopilot devices until (enter your date)”. Import the members csv file. Then add this group to excluded on the win32 app package. Wouldn’t that work ?
Love this idea Tommy
What an excellent, comprehensive and clear article. Thank you for taking the time to write it.
Could I ask what, if any, impact pre-provisioning (formerly whiteglove) might have on this process?
Hey Donal,
This is a great question as I am yet to test this with pre-provisioning.
I don’t know how it will act, but essentially, since everything is set in motion depending on the Active Setup entry that is being created during device ESP, post white-glove when user phase starts and the user will do the windows login event, the Active Setup should essentially get triggered anyways. However, this is subject to test.
Hi Jay,
Great post and eager to test in our environment.
Question; should i bypass the SCCM part if we donβt use SCCM at all in our environment?
Yes, just find the SCCM section from the script and comment it out.
This is amazing, good job and keep it up :-), now that ive found your site ill be checking back in for tips to make my life so much easier π
Hey Aaron,
Thanks for the appreciation.
I hope that you can find something useful from this site.
Great post. How about apps that we require during ESP? We require Forticlient VPN to be installed before user gets logon screen. Can we also do that with this solution?
Until now we have used this Autopilot method: https://oofhours.com/2020/07/26/supercharge-the-hybrid-azure-ad-join-device-registration-process/#respond
This is only one of the apps during the device ESP. You can send other apps as you require.
Where this is implemented, we have 5-6 win32 apps tracked during device ESP of which one is Cisco Anyconnect for the VPN sign-in.
Also as a note, this is not an alternate for the supercharge method explained by Michael. Instead, this is more like a frontend only, showing a custom splash screen till it detects the 306 event for a successful registration.
Interesting thing. It seems to be working, even though I get the splash screen I don’t get the “Time Elapsed” The only one I am testing with at the moment is “HAADJAutopilotCustomSplashScreen package”
Kudos to you Joy, thanks for taking time and posting this amazing article to help organization who are stuck to get things right after ESP. This opens a door to resolve many known problems.
Amazing article, kept it simple but effective.
We did it slightly differently. I created a basic PS script that does a start-wait for 30 minutes, packed it as a win32 app, and deploy it as a forced app within the ESP. To the user, they just see it as “installing App 1 of 4” for a while, but it gives enough time for AADC to run the sync in the background.