Windows Autopilot

Two for one: Updated Autopilot Branding and Update OS scripts

A few days ago, I started working on a new script.  This blog is not about that script, but rather the unrelated issues I encountered while working on that script.  Let’s start with a little history lesson of the two existing scripts.

Introducing Autopilot Branding

Just over a year ago, I published two blogs (first and second) about a package of customizations that you could customize and package into an MSI that you could deploy to your devices via Intune as part of a Windows Autopilot deployment.  The types of things that can be configured include:

  • Apply a default Start menu layout
  • Apply a default desktop wallpaper
  • Set the time zone
  • Remove a list of in-box provisioned apps
  • Install the latest OneDrive client (per machine)
  • Disable the (old) Edge desktop icon
  • Install language packs
  • Configure language settings
  • Install features on demand

All of this can be found in GitHub at  You can download it (or clone the repository), customize it as required, and build it (after installing Wix).  It was a non-trivial process (necessitated at the time by the lack of support for tracking Win32 apps during ESP, something rectified in Windows 10 1903 and above), but quite a few people have used it.  Before you try it, though, read on – I’ve changed the process considerably to simplify the configuration, setting up the included PowerShell script as a Win32 app directly, without needing an MSI.

Introducing Update OS

About six months ago, I published another blog talking about another new script called UpdateOS.  This one performs a simple task:  It leverages the PSWindowsUpdate PowerShell module to identify any needed Windows updates that need to be installed, then downloads them and installs them.  So by the time Autopilot is done with the device setup, the device is fully patched.

Since this one was released after Windows 10 1903, I used a different mechanism to deploy this one:  Set it up as a Win32 app that runs the PowerShell script directly.  From an Intune perspective, it behaves like any other app, but really it’s just performing a one-time update process.

When 1 + 1 = 0

As I mentioned previously, I was working on a new script, adding it into the other configurations, including the two apps above, that I routinely deploy to each device.  But before it even got to my new script, I could see the UpdateOS script was failing.  As that script logs what it is doing to C:\ProgramData\Microsoft\UpdateOS, I could see that it was trying to install the May cumulative update, KB4556799, on top of my Windows 10 1909 installation.  But that cumulative update was routinely failing to install.  The Windows Update log showed a generic error (80070002), which wasn’t helpful.  Looking deeper into the CBS.LOG, I could see some interesting activity:  The .NET Framework was being installed around the same time as the cumulative update, which can’t be a good thing.

OK, so I need to do some sequencing:  Ideally the Autopilot Branding app would run first, and then the Update OS app would run.  That way, the additional features (e.g. the .NET Framework) being added by the Autopilot Branding app would get patched and there would be no need to reinstall the latest cumulative update.  The first step to setting up that dependency was to move the Autopilot Branding app from a Windows MSI line-of-business app to a Win32 app.  That’s not too difficult, packaging the .MSI file into a .INTUNEWIN file, uploading it to Intune, and configuring the appropriate command lines and detection rules.  At that point, I could set up a dependency that says Autopilot Branding is a dependency of Update OS, so Autopilot Branding must run first.

Sadly, that still didn’t work.  The Autopilot Branding app was failing, and as a result the Update OS app wouldn’t install (since the dependency is missing).  After adding an additional command line option to the Autopilot Branding app to get a verbose MSI log (“/L*v %TEMP%\AutopilotBranding.log”) I could see the failure was happening on the .NET Framework installation.  For this round of testing, my device was connected (via Always On VPN, not that it matters) to the corporate network, and therefore it was able to receive the AD GPOs, including a policy that pointed the Windows Update service to my ConfigMgr server.  But that won’t work for .NET Framework, as it needs to get those components from the internet.

So the next step was to fix that problem:  Since you can’t install features from WSUS, I added logic to the AutopilotBranding.ps1 script to turn off WSUS, restart the Windows Update service, install .NET (or any other specified optional components), turn WSUS back on, and then restart the Windows Update service again.  I built a new MSI and manually tried it out on an AD-joined device and it worked fine.  So I packaged that MSI into a new .INTUNEWIN file, uploaded it, and tried it again.

This time, it failed on something brand new:  The Autopilot Branding app appeared to install, but it wasn’t detected by Intune so then it never ran the Update OS app.  Since I rebuilt the MSI, it got a new MSI product ID, which messed up the existing detection rule.  OK, time for a new diversion, getting rid of the MSI app altogether and instead just running the PowerShell script directly as part of a Win32 app, just like the Update OS app does.

Finally, we’re getting somewhere:  The Autopilot Branding app installed successfully, and .NET Framework was added without issue as a new feature, pulled from Windows Update (even though the device was configured to talk to WU).  The Update OS app then also installed successfully, installing all detected updates successfully, including KB4556799.  The Update OS app then specified a return code of 3010 to indicate that the enrollment status page (ESP) should reboot when it finishes with the device ESP process.

After the reboot, everything was configured as expected:  .NET installed, customizations applied, latest updates installed.  Everything looks great.  (And that leaves out various additional attempts that I made – that’s the simplified version of the process I went through.)

Setting up Autopilot Branding as a Win32 app

All the changes to the Autopilot Branding files have been checked into the GitHub repo.  So download them or clone the repository, make your changes, and then run “makeapp.cmd” to build the AutopilotBranding.intunewin file that you need to set up the app.  Once you have that file, you can sign into the Intune portal and create a new Win32 app:


Next, browse to your AutopilotBranding.intunewin file.


Specify an appropriate name, description and publisher.


Specify the program details:


Install command:  powershell.exe -noprofile -executionpolicy bypass -file .\AutopilotBranding.ps1

Uninstall command:  cmd.exe /c del %ProgramData%\Microsoft\AutopilotBranding\AutopilotBranding.ps1.tag

Device restart behavior:  Determine behavior based on return codes

Specify both x86 and x64 for requirements (another benefit of not using an MSI, which is architecture-specific), and select Windows 10 1903 and above for OS version.


Specify a detection rule that looks for the file that the script creates.


No dependencies are needed.  Assign to a device group as desired (I used “All devices”).


Then create the app.


Note that I haven’t updated the documentation in GitHub – that came come later. 

Setting up Update OS as a Win32 app

The original blog has all the needed details – make sure you download or sync the latest version of the script to get the logic changes previously mentioned.  The GitHub repository also has an abbreviated set of instructions (basically, the same steps as above, just filling in different values).

Since the Update OS app will report a return code 3010, ESP will automatically reboot the computer at the end of the device setup phase.  So by the time the user signs in, you can check WINVER to see that the latest patch is indeed installed.  Note that this is going to add some time to the provisioning process, probably 15-30 minutes depending on the speed of the device.

If you really want to get updates from WSUS instead of from WU (and introduce a dependency on needing to connect to your corporate network), you can modify the UpdateOS.ps1 script yourself to remove the -WindowsUpdate switch on the Get-WindowsUpdate command.  But I wouldn’t recommend it.  If you do white glove deployments, or even VPN-driven deployments in the future, the Update OS app may not have the connectivity needed at the right point in the process.

Setting up the Dependency

You can add a dependency to the Update OS app, saying that the Autopilot Branding app is a dependency and should automatically be installed.  Intune will then take care of the process.


Putting together individual building blocks (scripts in this case) always seems simple enough, until you put them into more “real world” scenarios and combine them together into what should be repeatable processes.  Try it out yourself, and let me know if you find any issues (test accordingly, don’t blame me if you deploy it without testing and then find the problem).

Categories: Windows Autopilot

20 replies »

  1. Nice. The weird part is between this Branding Win32 version and the MSI version is that my Windows 10 Display Language doesn’t change from English United States to English Australia even though I am using the same Language.XML and have the same language packs. Every other language setting is set correctly.

    Liked by 1 person

  2. I love the autopilot branding script. Especialy the real branding part of it and the removal of the overhead of apps. However the update script won’t install feature updates as you stated. In my experience brand like HP and Dell seem to be using the n-1 method and always deploy their hardware with an ‘old’ build of Windows 10. In that case it takes up a lot of time in order to get the machine to the latest patch level so the end user doesn’t have to go through the long waiting time when he just got the device.

    The administrative effort on that is, in my opinion, way to high. A customer expects their workers to be productive from the get-go on the new device.

    I would love to see that, for example with whiteglove, the device will get itself to the latest, in intune defined. In order to get to that point now we use MDT, but it would be great if we could eliminate that step as MDT for every customer also involves a rather large amount of administrative effort.

    What is your take on that?

    Another thing I would love to see during autopilot is the ability to apply a PPKG package during the autopilot phase. I would love to be able to upload the PPKG package into the Autopilot profile in Intune so it gets processed during the autopilot phase. That also would make life so much more easy and customizing a device for a customer from very first moment.


    • The ability to install a feature update is on our backlog. It’s not a simple task, as we have to build a UI to monitor the downloading and installation of the feature update, make sure that update ring policies apply or that you can specify the right version, make sure that the right DO settings are used, etc. Lots of work there.

      OEMs will update their images to the latest Windows 10 release within 90 days of its release (with a few exceptions, e.g. when they find bugs). If you’re always ready for that, you’re doing better than most customers who are instead asking how to get an older version. OEMs do generally offer choices of N, N-1, or N-2 versions, but there are often charges for that.

      I don’t get the need for a PPKG file. First, the tools for creating one are poor. Next, you’re creating a package that consists of things (policies, scripts, apps) that could just as easily be done through Intune, where they are much easier to maintain as well.


  3. Hi Michael, thanks for the updated scripts. I’m using the autopilot branding tool for some while now and are very happy with it thanks!
    For the UpdateOS I have some questions is this quality and feature updates?(just read the blog maybe in the future I hope so, it would be a great extra option)
    Is it also possible to use the windows update for business rings from intune for this?


  4. Hi Michael, great update. Am I missing something, or should the second $currentWU during Features on Demand be $currentWU -eq 0?


    • No, it should be -eq 1. The value was only retrieved once and confirmed to be 1, so later that same value is checked to ensure that it needs to be put back to 1, since in between the value was changed to 0.


  5. Hi Michael, this is a very informative blog. I assume the device ESP does not support reboots yet, what if there are other required applications defined and the UpdateOS runs in between causing a reboot. Would the device ESP continue and install other required apps or will my device setup fail.


    • For an app that needs to reboot during device ESP, it should return an exit code of 3010 so that the reboot happens automatically at the end of device ESP.


  6. Hi Michael, I am using your UpdateOS and AutoPilot Branding scripts successfully with Autopilot but I have a question. I want to target these scripts to only run during Autopilot, is this possible?

    I am thinking if I create an Autopilot AAD dynamic group with these scripts it will work but if I decide to target future scripts for Autopilot only using the group then it will apply to devices that have already been autopiloted and new ones.


  7. Hi Michael, thanks for the great post and keep them coming!

    Curious if you found any benefit from using the PSWindowsUpdate module which needs to be installed vs. the commandlets built into Windows 10. Example:

    $updates = start-wuscan -searchcriteria “isinstalled=0 and ishidden=0 and isassigned=1”
    install-wuupdates -updates $updates
    if ($(get-wuispendingreboot) -eq $true) { restart-computer }


    • I did initially try the built-in cmdlets/module, but I had issues that required moving to PSWindowsUpdate. Of course now I don’t remember what those issues were…


  8. I’ve got UpdateOS as a tracked app in device ESP… and just found a user where it took 1h35m to run, which blew my 60 mins timeout out the water. What would folk pick here as an option… no longer make it tracked? Or increase the timeout? … Or other? Thanks. 🙂