Custom Action Scripts


Windows Upgrade Custom Action Scripts

The Custom Action Scripts allow you to run additional commands during the Windows Setup Process. Microsoft essentially gave you the ability to inject your own commands into the setup process. The Windows 10 Setup engine has several hooks at different points that reach out, check specific locations and call scripts if they exist. Microsoft has documented them pretty well below, but the intent of this post will be to go a bit deeper, showing them in action, showing how you can create them, and then showing the logs of when they get called.

MS Docs

If you’re using native windows upgrade features, "Feature Updates", and not leveraging a task sequence to do your customizations, you’ll need other methods to trigger actions, like disabling services or security products during your upgrades. This means you’ll need to pre-stage the files ahead of time, leveraging a Task Sequence or package, so that when the upgrade goes, those files will be there for the setup engine to leverage.

Typical Reasons for using Custom Action Scripts

  • Disabling Services or AV before starting Downlevel Stage
  • Trigging a 3rd Party Encryption pre-boot bypass
  • Fix some applications after upgrade
    • For us, we had several apps that required files being re-registered or running the repair method on the MSI installer.
    • When this happens, I also suggest working with those vendors to get them to update their apps to survive an upgrade, or look for a different software.
  • Apply some customizations
    • Lock Screen, Wallpapers, User Profile Pictures, etc
  • Run Setup Diag
  • Copy Logs
  • Restore the CM Client after a rollback or OSUninstall (when using a Task Sequence to upgrade)

At this point, I’m going to assume you read the MS Docs, and know the basics:

  • Names of the Script Files (and associated error codes on failures)

    • preinstall.cmd | 0XC19001e2
    • precommit.cmd | 0XC19001e3
    • postuninstall.cmd |
    • failure.cmd | 0XC19001e4
    • success.cmd |
  • Locations of where to put the files

    • %windir%System32updaterun<GUID>
      • These Scripts will persist from upgrade to upgrade (permanent)
    • %windir%System32updaterunonce<GUID>
      • These Scripts will be removed after the upgrade (temporary)

In this Post, we’re going to create each one of those files, and walk through Windows Setup leveraging them.


While the Scripts Windows Setup calls are simple batch files, you can use those batch files to call more complex PowerShell Scripts


In this example, I have an "Application" in CM that runs a powershell script that creates the custom action scripts, or copies pre-created scripts into place to be used in the Feature Update process.

Top level script is called: "CreateCustomActionScripts.ps1", it will generate one of each of the scripts in both a run and runonce folder, which tags the registry with a time stamp of when the script runs. This will assist in helping to understand when the scripts run. The script will also check the contents of the folder it’s in, and if it finds a script, will copy it into the correct location.

Application Contents

CustomActionScripts 02

Results of the Custom Action Scripts Install

The Application tags the registry with a version of the script, creates the custom action scripts, creates the customization files in ProgramDataWaaS and logs it all
CustomActionScripts 03
CustomActionScripts 03a

Scripts for Application Content are hosted on GitHub


Basic Demo showing Logs

SetupAct.log = C:windowsPanthersetupact.log (Post Upgrade Location)
The Preinstall script ran early in the process, before the Setup Engine was doing much.
CustomActionScripts 04
The Machine finishes it work in the downlevel phase, took about 20 minutes on my VM, then it pauses and waits for the end user to approve the restart.
CustomActionScripts 07
CustomActionScripts 01
Once the Restart is approved, the precommit script will run before the machine actually restarts. From these logs you can see that from the moment I clicked Restart, there was 39 seconds, in which time there is a lot of log.
CustomActionScripts 05
CustomActionScripts 06

At this point, we don’t see much for the custom actions scripts until the end, either success or failure.

CustomActionScripts 08

So that’s a simple sample showing when they run, and how they show up in the setupact.log

For the next demo, lets kick it up a notice and have the scripts do something more than write to the registry.

PreCommit Checks – Create a Failure

In this demo, I’m going to create a pre-flight style check that will error out the upgrade if it matches a rule we create in the preinstall script.
Shown here is Software Center displaying the results of the Feature Update, and the error code. 0xC19001E2 relates to an issue with preinstall.cmd

CustomActionScripts 09

The log shows it ran the preinstall script and was given an exit code of 253, which is the exit code my custom script creates when it detects an application that doesn’t meet the requirements I’ve set. Since the preinstall script returns a non-zero code, it tells the setup engine there was an issue, and the setup engine starts to fail out of the upgrade before it really even started.
CustomActionScripts 10
Because the upgrade is failing, it calls my custom failure.cmd script, which currently I don’t have it doing anything other than writing a registry entry to confirm it ran when it said it did, which match up perfectly with the setupact.log.
CustomActionScripts 11
CustomActionScripts 13
Also, another great feature of Windows 10 20H2, it automatically triggers SetupDiag when a failure happens. It captures to XML and to the registry. Here is a capture of the registry.
CustomActionScripts 12
For additional details on SetupDiag and how it runs and what it captures, check out the Docs

Success Script information, and leveraging for Customizations

This demo will be leveraging the success.cmd file, nothing too fancy, but a good example of what can be done.
Like with the other custom action scripts, I’ll have success.cmd trigger a powershell script that will do the heavy lifting. I also have it doing a couple other things.


The Success.cmd file does not run from the place you put it (C:WindowsSystem32Update), the setup engine makes a copy in C:WindowsSetupScriptsupdate, which is why you’ll see it executing there.

CustomActionScripts 18
CustomActionScripts 17

Here is the custom actions scripts, and the contents of the success.cmd file. I’m using an "Application" to trigger a script to add the files into the correct locations.
CustomActionScripts 14

The Success.cmd sets some permissions to setup replacing the Lock Screen and Wallpaper images, then calls two different powershell scripts.

In the scripts we’re setting a few registry values, replacing the lock screen image and wallpaper images, and running setupdiag.exe for fun to get some metrics.

I have my scripts write to a custom log (c:ProgramDataWaaSCustomActions.log) so I know exactly when my scripts ran and what the did. Each script uses a different "Component" name, to be able to keep track of which script is writing to the log file.
CustomActionScripts 15

The Success.cmd file I have, triggers two different powershell files, Success.ps1 and SuccessSetupDiag.ps1, which both write to that log with different "Component" names.

  • SuccessSetupDiag.ps1 (Triggered by Success.cmd, but is not monitored, Success.cmd continues)

    • Monitors the creation of windows.old, once created, triggers SetupDiag.exe to collect results and adds them to the registry: HKLMSOFTWAREWAAS%BUILDNUMBER%
  • Success.ps1 (Triggered by Success.cmd and Success.cmd waits for return code)

    • Modifies Registry values to customize User Experience
    • Copies Lock Screen Image from staged area (ProgramDataWaaS) to Windows location
    • Copies Backgound Image from staged area to Windows location
    • Copies the User Profile Images (Default Corporate Logo) from staged area to Windows Location
    • Any additional customizations I’d want to do, I’d add here

The log above, shows the output from those two scripts running.
The results in the Registry after a Successful Upgrade. Every Script that ran I have tag the registry to ensure it ran, and at the time it ran.
CustomActionScripts 19
The SetupDiag results:
CustomActionScripts 20

After the upgrade, as expected, the "Run" Folder has been migrated to the new OS, to be used for the next feature update, and the "RunOnce" Folder is gone
CustomActionScripts 16


CustomActionScripts 21

  • PreInstall ran after downloading the DUs, which took 10 to 15 minutes on my VMs, if you disable dynamic updates, then PreInstall will run very quickly after starting the upgrade.
  • PreCommit ran shortly after I clicked "Restart". Basically the Feature Upgrade ran through the initial downlevel phase, got to a point where it notifies (or requests) the user of a reboot, then once approved, or continues automatically, the final downlevel phase processes run, including the PreCommit.
  • Success ran nearly at the end of the entire process. There were just a couple minutes between the time it ran and the time I was presented with the desktop logon screen.


This runs when a user leverages the "go back" feature. There are going to be several overlapping items between a "Uninstall" and a "rollback", logs will be in the same location, and it’s actually the same log file for both. But the system is smart enough to know the difference and run either the failure.cmd or the PostUninstall.cmd

The logs are located here: $WINDOWS.~BTSourcesRollback

At the start of the log, it shows Initiating rollback/uninstall, to confirm you’re at the right place.
CustomActionScripts 22

Towards the end, it shows that it ran the PostUninstall.cmd file.
CustomActionScripts 23

Stay tuned

  • This post is not yet complete, still have plans to add information about failure.cmd and postuninstall.cmd with demos.


Community Blog Links

See how Right Click Tools are changing the way systems are managed.

Immediately boost productivity with our limited, free to use, Community Edition.

Get started with Right Click Tools today:

Share this: