Windows Autopilot

Prompt for time zone (and maybe other stuff) during Autopilot

In one of my sessions this week at the Workplace Ninja Summit, I included a video that showed how you could modify the OOBE flow to prompt for the time zone that should be used in the device, and (very quickly) showed how it worked, promising to post more information. This is that information.

First, I need to provide some additional background. In a perfect world, there would be extensibility to the Windows Autopilot OOBE flow for you to do anything you want. And maybe at some point that capability will be realized. In the meantime, the discussion is simpler: Is there a way to hack up the process to achieve the desired goal? The answer to that is (somewhat) yes.

There is one page in the OOBE flow that you can customize: the MDM terms of use page. Normally this page points to your MDM server’s terms of use URL (so that it can present some boilerplate legal text that tells people that they agree to behave if they continue), but you can point it to whatever URL you want, as long as it follows the prescribed rules. The challenging part about that is to figure out how to take any information that is gathered by that page and then use it during the Windows Autopilot process. The expected behavior is to use the Azure AD user token presented to the web page to connect back to the MDM service and save stuff there, but that whole flow is controlled by the MDM service (mostly — you could use the token to do whatever the MDM app defined in Azure AD allows, but that’s likely limited to certain Graph calls). And trying to customize that would be a security headache, especially when all we want to do is pass the information forward to a script or app being installed/executed during the Windows Autopilot process.

So what’s the trick? Understanding how OOBE is structured is a good first step. It’s a UWP app called CloudExperienceHost, written in JavaScript and HTML (with some native code hooked in). You can find the JavaScript and HTML source files in C:\Windows\SystemApps\Microsoft.Windows.CloudExperienceHost_cw5n1h2txyewy. While you could probably hack that up, that wouldn’t be supported. But during the execution of that app, it needs to display the terms of use web page (and other web pages, e.g. the Azure AD sign-in flow, OOBEAADV1). How does it do that? By embedding an HTML IFRAME into a page being hosted in the UWP app’s web view. That web view is based on Internet Explorer (which will never fully die).

So why does that matter? Well, Internet Explorer (like all web browsers) support local storage JavaScript APIs to store information locally on the device. We can leverage those to store the user’s choice, e.g. the time zone that they select:

But then how do we retrieve that value later? Understanding how the web view works is key to that. The Internet Explorer engine running inside the Cloud Experience Host app does it’s normal storage “stuff,” but instead of writing to the user’s IE storage location, it writes to an app-specific location. And it keeps each domain’s data separate, so we just need to poke around a little to find that:

(Yes, figuring that out took more than a few minutes the first time.) So we can see the value that we entered, stored in a nice machine-readable XML file. So, we can read that via PowerShell and use it to set the time zone:

In my case, since I’m using my own MDM service that only knows how to deploy MSI apps (no Win32, no PowerShell), I packaged that script inside an MSI targeted to the computer, so that it runs during device ESP, but you can choose to run it however you want.

So that gives us the pieces:

  • A PowerShell script that can process the results from that web site. You can find that here, in the same GitHub repo. (The MSI file and files to create it are there as well, if you want to use it that way.)

So, with an Azure AD tenant configured to point to the terms of use page:

And an MSI being delivered to devices that enroll, you end up with something like this:

If you watch through to the end, you’ll see yes, we read the time zone value specified and set it as expected.

A few items to note:

  • This only works for the Autopilot user-driven Azure AD Join scenario. If you’ve ever tried to use the terms of use page with Hybrid Azure AD Join scenario, you’ll notice that it’s not displayed. (I think that’s a bug.) And it’s not shown in Self-deploying or Pre-provisioning scenarios either, as it would get in the way. (At least that part is by design.)
  • This only works if the devices have an Autopilot profile assigned. If you try to go through OOBE without having an Autopilot profile (either for a registered device, explicitly assigned, or an existing device using a JSON file), you’ll notice that the time zone still gets set to Pacific Standard Time. It appears that Autopilot alters the timing of this (you can see it logged in C:\Windows\Panther\UnattendGC\setupact.log) so that the value set by the PowerShell script gets overwritten by OOBE itself.
  • The web page is hosted on an Azure app service free tier. Normally I would expect you to clone the page for your own use, but you can use it as-is if you want to. (Note that if you use a different page, you’ll have to update the PowerShell script because the XML file name is based on the URL being used.)
  • Maybe at some point the Cloud Experience Host app will switch from using a WebView to instead using a WebView2 control. If that happens, then it will use the Chromium way of storing local data, which will break the PowerShell scritp. I’m guessing we won’t need to worry about that any time soon.