Windows 10 Feature Updates – Using Setupconfig.ini to Manage Feature Updates in the Enterprise

This is the next installment in my Windows 10 Feature Updates series.


Setupconfig.ini is a file that can be used to pass command line arguments to the Windows Setup engine during a Windows installation. When Setupconfig.ini is specified in the command line (/ConfigFile <path to Setupconfig.ini>) or exists in the default location (%systemdrive%\Users\Default\AppData\Local\Microsoft\Windows\WSUS\Setupconfig.ini), any arguments in it will take precedence over arguments specified in the commandline. If you use this file, ensure that you aren’t counteracting commandline args that you may be using with other deployment methods such as Task Sequences (Task Sequences simply generate a command line based on which options you select). Here’s an example of the Setupconfig.ini that I have been testing in production (most people will not use all of these).

If you were to change this to a Windows setup command line, it would look like this:

By managing a Setupconfig.ini file on all of your Windows 10 devices, you can have full control of how Windows 10 Feature Updates are installed, from Task Sequence, Intune, WSUS or even if users install them directly from Windows Update. By using the available command line options, you can run post-upgrade scripts, apply updated drivers and even handle third party disk encryption needs (see ReflectDrivers). Before deploying this file, be sure to check the Windows Setup Command-Line Options doc for details on each option. After testing several versions of it, the one I deployed into production is different than above. Some of the options deserve a bit more explanation:


If you have dealt with Windows 10 upgrades (7-10 or 10-10 Feature Updates) you have likely run into blocks due to drivers or applications. Many of these blocks can be ignored but only if you include /Compat IgnoreWarning in command line either using media or a Task Sequence. If you’ve ever tried to apply a Feature Update from Windows Update or through SCCM’s Windows 10 Servicing, you have likely run into blocks that prevented you from upgrading certain devices that would have otherwise worked fine from a Task Sequence using the /Compat IgnoreWarning argument. You can just add the line Compat=IgnoreWarning to your Setupconfig.ini now and breeze right past those annoying blocks.

When we first started testing Feature Updates, we were seeing a high number of failed upgrades compared to our InPlace Upgrade Task Sequence and nearly all of them ended up being dismissable compat issues. Once we added Setupconfig.ini, our failures were almost non-existent. In the donut chart below you can see that over 27% of devices reported dismissable errors and were successfully upgraded since we are using the Compat=IgnoreWarning option.


Another one that really deserves it’s own blog post some day is the change in Dynamic Updates in Windows 10 1809 and up. Several of us in the community have been writing and speaking how important Dynamic Updates are for successful upgrades but in Windows 10 1809 the mechanism for applying Dynamic Updates changed. As you may have read recently on Michael Niehus’s blog on the topic, Dynamic Updates can ONLY be pulled from the internet currently and are ENABLED BY DEFAULT!! Delivery Optimization and BracheCache don’t help either. Your clients may download large amounts of data (Latest Cumulative Update, Dynamic Updates, Etc) directly from the internet if you leave Dynamic Updates enabled.

Interestingly, while this change has made it much more difficult to offline service your Windows 10 Upgrade media, it is what has helped make Feature Updates using native servicing work, but ONLY if you leave Dynamic Updates enabled.

Regardless of where they come from, Dynamic Updates still are an integral part of the Feature Update process. You can disable Dynamic Updates using DynamicUpdate=Disable, but then you MAY run into issues that Dynamic Updates would have addressed for you (we have seen it in our testing). The choice is up to you. If you have concerns about melting your networks, please proceed with caution and test before rolling this out work with your networking team if you before you enable it.

If you’re thinking that since you are offline servicing your Windows 10 Upgrade media for your SCCM Task Sequence, you’ll just keep using that — the “feature” that causes Windows Setup to go out to the internet for Dynamic Updates is also what has removed them from WSUS (though we just had 1 show up in WSUS or 1809 last month) and has made it difficult (not impossible) to continue to include them in our offline serviced media. Additionally, you should know that if you are deploying 1809+ via Task Sequence, you may ALREADY be downloading updates from the internet without knowing it since this is a default behavior now (more info in the next section).

Deploying/Managing Setupconfig.ini

Depending on your needs, you can deploy Setupconfig.ini any number of ways including:

  • Static file copied down via:
    • GPO
    • SCCM Package or Application
    • Logon Script
    • etc
  • SCCM Client Settings – Limited Use (see screenshot below)
    • Priority (1902)
    • Dynamic Updates (1906)
  • SCCM Task Sequence
    • Detect model and cache drivers
    • Build Setupconfig.ini based on device requirements
  • SCCM Application or Package
  • SCCM Configuration Item (CI) and Configuration Baselines
    • Detect and enforce settings by line
    • Add/Remove/Update lines without impacting lines managed by other means (like SCCM Client Settings)
  • Intune PowerShell Scripts or Win32 Application

SCCM Client Settings

New Client Settings policies have been added to SCCM 1902 and 1906 that modify Setupconfig.ini to change priority for Feature Updates from Low to Normal (speeds up the downlevel portion of the upgrade) and disable Dynamic Updates. If you want to customize Setupconfig.ini, consider whether you will also be using these client settings. If so, you need to ensure that you aren’t overwriting the file with whatever process you use to maintain the file.

New settings for SCCM 1902 and 1906. Screenshot from 1906.

SCCM Task Sequence

At the August 2019 Charlotte Systems Management User Group hosted by Microsoft PFE Julie Andreacola, she detailed how to deploy a Task Sequence that can customize the file based on device type and driver requirements. If you have driver requirements, this could certainly be a viable option. If you don’t it may be a bit complex when a simple file copy will work for you.

SCCM Configuration Item (CI) and Configuration Baselines

The approach I’m taking is using an SCCM Configuration Item and Configuration Baseline to manage the Setupconfig.ini file, as well as several other scripts/files needed by Windows Setup. The CI uses a PowerShell script that will read in the contents of the existing ini file and only edit the lines specified by the remediation script. This allows the script to play nicely with other processes such as SCCM Client settings that may also be managing the file. There will be a link to my whole Feature Updates GitHub repository at the end of the post.

No matter which approach you take to managing Setupconfig.ini, be sure that it can handle the complexities of your environment. Start simple and work up from there. My scripts have ended up a bit more complex than I expected, but I took the time to make them re-usable for other things in the future. You can certainly simplify them if you need to.

Errors caused by Setupconfig.ini

During testing of Setupconfig.ini, some of my tests were failing with random errors right away. Here’s what I found.


This error occurs whenever you have line items in your Setupconfig.ini file that aren’t valid command line options. This is typically a typo or incorrect entry in the Setupconfig.ini file.


Some entries in Setupconfig.ini are paths to files such as PostOOBE=c:\~FeatureUpdateTemp\Scripts\SetupComplete.cmd. Any files you specify must exist at the time that you launch the Feature Update, or you will receive this message.

You can run ProcMon from the SysInternals suite to track down issues like this. I was able to quickly determine that SetupHost was looking at Setupconfig.ini then looking for SetupComplete.cmd and returned File Not Found.

Script for Managing Setupconfig.ini

As I mentined above, I’m managing Setupconfig.ini in production using an SCCM Configuration Item and Baseline. In my Windows10FeatureUpdates GitHub Repo, I’ve included the script that I wrote to manage the file. Thanks to Colin Wilkins for helping me get started with the script and CI (see the original here). To use this file in production, you will set up a Configuration Item and Baseline in SCCM. The script will perform the Detection and Remediation all in one.

Before you start, download the script from my Github Repo. It is located under Admin\ComplianceScripts\FeatureUpdateCIScript.ps1

Edit the parameters of the script to match settings required for your environment. Here’s a snippet of the params. Read the help in the script for more info. You will use this script in Steps 4 & 5 below.

In the SCCM Console under \Assets and Compliance\Overview\Compliance Settings\Configuration Items, Click Create Configuration Item and enter a new name for the CI then click Next.

For Supported Platforms click Windows 10.

On Settings click New then Enter the following:

  1. Name: Setupconfig.ini
  2. Setting Type: Script / Data Type: String
  3. Discovery Script – Click Add Script and paste the contents of the script from the repo. Ensure that the $Remediate variable in the is set to $Remediate=$False
  4. Remediation Script – Click Add Script and paste the contents of the script from the repo. Ensure that the $Remediate variable in the is set to $Remediate=$True
  5. Click Compliance Rules.

Click New. In the Create Rule Pane enter the following:

  1. Name: Setupconfig.ini Valid
  2. The setting must comply with he following rule: Compliant
  3. Check the box
  4. Check the other box
  5. Noncompliance set Critical
  6. Click OK.

Click OK again. You should be back at the main wizard.

Click Summary, Next, Close.

Next we you need to create a new Configuration Baseline and select the new CI that you just created (Just follow the wizard). Then deploy to a collection of your Windows 10 devices that you expect to manage with Feature Update servicing.

Clients will get the baseline whenever they check in for new Machine Policies. You can manually launch the baseline from the Configuration Manager applet. My baseline is named Feature Update Files.

When you run this on a client, you should see a new Setupconfig.ini file appear on the client under C:\Users\Default\AppData\Local\Microsoft\Windows\WSUS\Setupconfig.ini.


I know there’s a whole lot of information in this post which may make Setupconfig.ini seem complicated, but I assure you that it is very simple to use – I just chose to complicate it because I wanted a very dynamic management option for it. As I mentioned, you can simply create a new Setupconfig.ini file with the minimal set of options and copy it to C:\Users\Default\AppData\Local\Microsoft\Windows\WSUS\Setupconfig.ini on your clients. The thing that I like most about this file is that it works no matter where the Feature Update is deployed from (Task Sequence, WSUS, Intune, Windows Update). In my future posts, I’ll show you how I’m managing the SetupComplete.cmd and Failure.cmd files to help gather logs and integrate with SetupDiag. Stay tuned.


  • Reply
    Austin B
    October 25, 2019 at 9:01 am

    Wonderful write up, this has helped with my current deployment setup, but how did you get around it being an unsigned script?

    • Reply
      Adam Gross
      October 25, 2019 at 9:16 am

      You can either sign the scripts or you can change your client settings to bypass.

      • Reply
        Austin B
        November 21, 2019 at 8:05 am

        Are you signing your scripts? If so, how?

  • Reply
    November 12, 2019 at 1:36 am

    Hi, great wrap up on the use of setupconfig, but…: I can’t seem to get the InstallDrivers to work. That is, I copy the driverpackage to a folder, specify Installdrivers in the ini file, place the file in the WSUS folder but it just doesn’t seem to work (use the drivers). Are there any rule of how deep the structure can be or something like that? I see the switch in the setup commandline (setupact.log) but it’s just not working. I’ve also seen this with the use of compat=

  • Reply
    Arjan Versloot
    December 5, 2019 at 2:48 pm


    We are on 1709 and want to upgrade to 1903 ir maybe 1909, and we use mdt. I’m looking for a method to upgrade our 1709.
    Which methods are there and which are recommended. Thanks in advance

    • Reply
      Adam Gross
      December 5, 2019 at 3:33 pm

      Use Feature Updates as described in these blogs or use Task Sequences. Either one is well supported and documented.

  • Reply
    December 6, 2019 at 8:36 am

    Interesting if it is possible to use SetupConfig.ini to remove Build-in APPX applications like Calculator or Maps prior to any user logging in? I was trying to call CMD file from SetupConfig.ini. In CMD file I called PS script with command like “Get-AppxPackage -name Microsoft.WindowsCalculator -allusers | Remove-AppxPackage”. No results. All standard commands like “reg add” or “make dir” works except PowerShell.

    • Reply
      Austin B
      December 12, 2019 at 8:54 am

      Do you have remote scripting enabled? That’s our issue is we have it disabled at the root of our domain. I’d suggest looking at the OOBE switch in the setupconfig.ini to run once after the OS update is completed. You can also try copying the scripts locally so it doesn’t try to run it remotely.

  • Reply
    December 2, 2020 at 9:29 am

    Excellent write up and would love to move our estate forward using Windows Servicing rather than task sequences.

    One of our concerns would be if we enabled dynamic updates the additional load it would have on the network via Windows Update Services (internet traffic).

    Do you know if we were to use the switches /DynamicUpdate ‘Enable’, /InstallLangPacks and /InstallDrivers if it would ‘consolidate’ all contents together and only download from Windows Update Services, if its required and not available locally.

    The thought process being if we PreCached as much of the LangPacks and Drivers contents to the device prior to the Feature Update, would Dynamic Updates ‘only’ top up from Windows Update Services if it was required and missing from /InstallLangPacks and /InstallDrivers

    This would give a good balance of network usage and Dynamic contents for a smooth upgrade experience

    • Reply
      January 16, 2021 at 12:57 pm

      I would like to know the same thing as Steve – what happens if I use both /DynamicUpdate (in my case with NoLCU but whatever) and also /InstallLangPacks with a UNC share?
      The reasoning is that I have devices that are out of the office (lockdown) which I want to use DU to get their LP, but I also have devices in the office that can’t do that because of firewall, corporate proxy, etc. So I want those to use a UNC path. In theory this solves the problem because the on-site devices can get to the UNC but not Microsoft Update, whereas the external devices are the other way around. I want them to download direct from MU rather than over our VPN. The tricky part is that the devices are occasionally moving to and from the office, they aren’t static sets.
      The question is – will this actually work, or will I have install failures (since in recent versions, a LP install failure is fatal)? I’m still in the planning phase for 20H2 ugrade at the moment, so I don’t know if I will even use servicing / dynamic update.
      Related question – can I enable dynamic update and provide a GPO “repair source” to allow FOD content to be installed from a UNC for on-site devices, DU for off-site?

      • Reply
        Adam Gross
        January 16, 2021 at 1:32 pm

        I haven’t tested any of these scenarios and don’t have to manage Language packs. Your best option would be to get security to open up DU for everyone. This should be easy enough for you to test though.


    This site uses Akismet to reduce spam. Learn how your comment data is processed.


    Fatal error: Uncaught GuzzleHttp\Exception\ClientException: Client error: `POST` resulted in a `400 Invalid instrumentation key` response: {"itemsReceived":1,"itemsAccepted":0,"errors":[{"index":0,"statusCode":400,"message":"Invalid instrumentation key"}]} in /opt/bitnami/apps/wordpress/htdocs/wp-content/plugins/application-insights/vendor/guzzlehttp/guzzle/src/Exception/RequestException.php:113 Stack trace: #0 /opt/bitnami/apps/wordpress/htdocs/wp-content/plugins/application-insights/vendor/guzzlehttp/guzzle/src/Middleware.php(66): GuzzleHttp\Exception\RequestException::create(Object(GuzzleHttp\Psr7\Request), Object(GuzzleHttp\Psr7\Response)) #1 /opt/bitnami/apps/wordpress/htdocs/wp-content/plugins/application-insights/vendor/guzzlehttp/promises/src/Promise.php(203): GuzzleHttp\Middleware::GuzzleHttp\{closure}(Object(GuzzleHttp\Psr7\Response)) #2 /opt/bitnami/apps/wordpress/htdocs/wp-content/plugins/application-insights/vendor/guzzlehttp/promises/src/Promise.php(156): Guzzle in /opt/bitnami/apps/wordpress/htdocs/wp-content/plugins/application-insights/vendor/guzzlehttp/guzzle/src/Exception/RequestException.php on line 113