The setup.exe field manual
I think I know why you’re here. Either you went looking for Microsoft’s Windows Setup command-line reference and figured it’s been deleted, or a silent upgrade came back with a hex code and every search result told you to run sfc /scannow.
Neither issue is what it looks like. The reference is alive and current - it just doesn’t live in the same place anymore. And the hex code has a decoder, plus a shortcut that skips the decoder entirely and is the thing I actually use.
So here’s all of it: where the docs went, every switch, the exit codes and how their structure works, third-party disk encryption and the /ReflectDrivers switch people half-remember, the logs, and four things I measured on a real machine that might come back and bite you.
Part 1: the docs aren’t gone, they moved
The page is Windows Setup Command-Line Options. It sits under windows-hardware/manufacture/desktop/ - the OEM and ADK tree, next to DISM and WinPE. Not under Windows client deployment, and not anywhere near the Windows 11 upgrade guidance that tells you to use it.
It’s current. Last revision was 12 November 2025, and /ConfigFile, /CopyLogs, /CompactOS, /DiagnosticPrompt, /InstallDrivers, /PostOOBE and /ReflectDrivers all picked up WinPE support in Windows 11 24H2. So nobody abandoned it. They filed it somewhere you’d never look.
Bookmark it. Then read the rest of this, because the doc tells you what each switch does and nothing about which ones matter.
Part 2: the switches
Everything in these tables is from that page. The notes are mine.
The ones that decide whether your upgrade works
| Switch | Values | What it does |
|---|---|---|
/Auto | Clean | DataOnly | Upgrade | Upgrade keeps apps and data. DataOnly keeps data and drops apps. Clean wipes. Get this wrong and you’ve reimaged a machine you meant to upgrade. /Auto also disables unattend files - you can’t use both. |
/Quiet | - | Suppresses all setup UI, including the rollback UI. Without it your “silent” deploy puts a window on someone’s screen. |
/EULA | accept | New in Windows 11, and you have to pass it. See below. |
/Compat | IgnoreWarning | ScanOnly | ScanOnly runs the eligibility checks and exits without touching the machine. IgnoreWarning pushes past dismissible messages. You can pass both. |
/NoReboot | - | Commits the update and suppresses only the first reboot. Not the ones after it. |
/SkipFinalize | - | Prepares the update without committing it. Microsoft’s preferred way to pend an upgrade. Part 3. |
/Finalize | - | Commits what /SkipFinalize staged, then reboots immediately. Never pass it with /NoReboot. |
/DynamicUpdate | Enable | Disable | NoDrivers | NoLCU | NoDriversNoLCU | Whether setup pulls fixes mid-flight. The three granular values arrived in Windows 10 version 2004 (the May 2020 release, not the year - Microsoft’s version numbering is a menace here) and NoDrivers is the useful one: take the servicing fixes, refuse the surprise driver swaps. |
/ReflectDrivers | <path> | Third-party encryption drivers. Part 4. |
/Priority | Normal | Low | Media installs run Normal by default. Drop to Low if somebody’s using the box. |
/BitLocker | AlwaysSuspend | TryKeepActive | ForceKeepActive | Defaults to TryKeepActive: try without suspending, suspend and retry if that fails. ForceKeepActive fails the upgrade instead of suspending. |
/MigrateDrivers | All | None | By default setup decides per driver. |
/InstallDrivers | <path> | Adds drivers from a folder and recurses into subfolders. |
/CopyLogs | <path> | Copies compressed logs out on failure. Runs as SYSTEM, so pick somewhere SYSTEM can write. |
Scripting, timing, and the rest
| Switch | Values | What it does |
|---|---|---|
/ConfigFile | <path> | Points at a Setupconfig.ini. Its values beat the command line. |
/PostOOBE | <path>[\setupcomplete.cmd] | Runs a script after setup finishes. |
/PostRollback | <path> [/PostRollbackContext {system|user}] | Runs a script if the upgrade fails and rolls back, or if a user goes back to the old version later. |
/ShowOOBE | Full | None | Whether somebody has to click through OOBE. None for anything unattended. |
/Telemetry | Enable | Disable | Setup’s own install reporting. |
/Uninstall | Enable | Disable | Whether the go-back option survives. |
/MigNEO | Disable | Turns off the offline-phase optimizations. NEO is the work that moved tasks out of the offline window, and it’s why a modern upgrade is one reboot instead of a parked machine. Only disable it to prove it’s the problem. |
/TempDrive | <letter> | Puts temp files elsewhere. It does not move the install. The OS still upgrades on the partition you ran setup from. People misread this constantly. |
/CompactOS | Enable | Disable | Compressed system files. Setup decides on its own by default. |
/ResizeRecoveryPartition | Enable | Disable | Whether setup can resize or create the WinRE partition. |
/ImageIndex | <n> | Which image inside install.wim. Ambiguous plus /Quiet gets you 0xC1900215. |
/InstallFrom | <path> | A different install.wim. |
/InstallLangPacks | <path> | Installs every lp.cab under the path, subfolders included. |
/PKey | <key> | Product key. |
/Unattend | <file> | Answer file. Can’t be used with /Auto. |
/DiagnosticPrompt | Enable | Disable | Shift+F10 during setup. Off by default, and worth turning on when you’re chasing a box that dies in the offline phase. |
/m | <folder> | Alternate files through the $OEM$ structure. |
/AddBootMgrLast | - | Boot Manager last in the UEFI boot order. WinPE only. |
/Debug, /1394Debug, /NetDebug, /BusParams, /EMSPort, /USBDebug, /WDSDiscover, /WDSServer | various | Kernel debugging, EMS and WDS. WinPE-side, and not what most of us are doing. |
The EULA trap
This one catches people moving from Windows 10 media to Windows 11 media.
On Windows 11, /Auto no longer accepts or suppresses the license agreement, and setup won’t start until the EULA is accepted. Pass /Quiet without /EULA accept and you get 0xC190010E, MOSETUP_E_EULA_ACCEPT_REQUIRED, and nothing happens. You get the same thing anywhere a user can’t interact with it.
So a command line that worked for years against Windows 10 media just stops. If you inherited a task sequence, check that before you go looking at drivers.
One more from the same paragraph of the docs: since Windows 11 22H2, /Auto Upgrade needs media whose System Default UI Language matches the machine. The en-US to en-GB upgrades that used to slip through are gone.
Part 3: the command lines
Scan first:
setup.exe /auto upgrade /quiet /eula accept /compat scanonly /copylogs C:\ProgramData\Logs
That took 9 minutes on my test machine, and it gives you an exit code you can check.
Then the upgrade:
setup.exe /auto upgrade /quiet /eula accept /noreboot /dynamicupdate disable /copylogs C:\ProgramData\Logs
The /noreboot matters more than it looks. Here’s what I measured going from Windows 10 22H2 (19045) to Windows 11 25H2 (26200), keeping apps and data:
| Phase | Time | What the user sees |
|---|---|---|
Compatibility scan (/compat scanonly) | 9 min | nothing, machine fully usable |
| Offline phase, to “100%“ | 17 min | nothing, machine fully usable |
| Reboot through the online phases | ~4 min | the entire outage |
| Total wall clock | 23 min |
Twenty-three minutes total, but only four of them where the machine is unavailable. Without /noreboot, setup reboots on its own as soon as the offline phase finishes, and the user loses the machine for that whole stretch instead.
That was a 2 vCPU, 4 GB VM on a virtual disk, so a slower physical disk will take longer. The ratio should hold though. Most of the time goes into the offline phase, and people can keep working through it.
/SkipFinalize is a better way to pend the reboot
I didn’t know about this one until I read the page for this post.
/NoReboot commits the changes and skips the first reboot. /SkipFinalize prepares the update and commits nothing, and you commit later with /Finalize, which reboots right away. Microsoft’s words: /SkipFinalize “is the preferred approach to pending the update as the /NoReboot option will commit the changes but suppress the first reboot.”
That matters if you’re staging the upgrade days before the reboot:
REM stage it now, commit nothing, stay out of the user's way
setup.exe /auto upgrade /quiet /eula accept /priority low /skipfinalize
REM in the maintenance window
setup.exe /auto upgrade /finalize
/SkipFinalize doesn’t imply quiet and doesn’t imply low priority. Pass both or you’ll put a progress window in front of somebody mid-call. It never reboots on its own either, so /NoReboot is redundant next to it. Call /Finalize on a machine that wasn’t staged and you get 0xC1900132.
When you don’t control the command line
Feature updates coming down through Windows Update give you nowhere to put switches. Setup looks for a config file at a fixed path instead:
%systemdrive%\Users\Default\AppData\Local\Microsoft\Windows\WSUS\SetupConfig.ini
[SetupConfig] header, one parameter per line, = with no spaces for anything taking a value, bare for flags:
[SetupConfig]
NoReboot
ShowOobe=None
Telemetry=Enable
ReflectDrivers=C:\Program Files (x86)\CheckPoint\Endpoint Security\Full Disk Encryption\Driver
That file does the same thing as the equivalent command line. One thing to watch: if you run from media with /ConfigFile, the ini takes precedence over the command line. I’d have assumed the opposite.
Part 4: third-party disk encryption and /ReflectDrivers
If you half-remember a switch you had to pass on encrypted machines, this is it. It’s still there and nothing replaced it.
setup /auto upgrade /quiet /reflectdrivers C:\Fabrikam\drivers
Point it at a folder of .inf files and those drivers get installed into the environment setup boots into. Skip it and the machine reaches WinRE for the offline phase, WinRE has no idea how to read a disk encrypted by a third-party filter driver, and the upgrade rolls back. The alternative is decrypting, upgrading, then re-encrypting, which takes hours per machine and leaves the disk unencrypted while you do it.
It arrived in Windows 10 version 1607, it’s still on the current page for Windows 11, and since 24H2 it works from WinPE too.
The paths are vendor-specific:
| Product | Driver path |
|---|---|
| Check Point FDE | C:\Program Files (x86)\CheckPoint\Endpoint Security\Full Disk Encryption\Driver (sk120667) |
| McAfee / Trellix Drive Encryption | %programfiles%\McAfee\Endpoint Encryption\OSUpgrade |
| Trend Micro Full Disk Encryption | C:\Program Files\Trend Micro\Full Disk Encryption\drivers |
| VeraCrypt | C:\Program Files\VeraCrypt |
| Symantec Endpoint Encryption | see Broadcom’s article |
Check the vendor’s current article rather than a path from a forum post. Check Point’s guidance moves with client version, and on E86.40 and above you can let Windows Update run normally when FDE is installed but switched off.
Two things in the doc that are easy to skip:
Point it at a minimal set of drivers. Microsoft says more drivers than necessary “can negatively impact upgrade scenarios.” Don’t just point it at the whole vendor install directory.
The failure has a signature. 0xC1900101 - 0x20017 is documented as a SafeOS boot failure “typically caused by drivers or non-Microsoft disk encryption software.” If you see that on encrypted machines, check /ReflectDrivers before anything else. Its cousin 0xC1900101 - 0x40017 has a worked example of the same thing: Citrix VDA’s CtxMcsWbc filter driver blocking the upgrade from writing to disk, rolling machines back after the second reboot.
It’s really about filter drivers in general. Encryption products are just where most people run into it.
Part 5: reading the codes
Setup returns two codes and most tooling shows you one:
- A result code - a specific Win32 or NTSTATUS error.
- An extend code - which phase it was in and what it was doing.
Written together as 0xC1900101 - 0x4000D. If you only see the first half, your tool didn’t capture the extend code. That’s the one that tells you which phase it failed in.
Decoding a result code
The first hex digit tells you which table to use. 8 is Win32, C is NTSTATUS. Take the last four digits and look them up.
0x80070070starts with 8, so Win32, and0070is ERROR_DISK_FULL.0xC1900107starts with C, so NTSTATUS, and0107is STATUS_SOME_NOT_MAPPED. In practice that means a cleanup from a previous attempt is pending and the machine needs a reboot.
Decoding an extend code
First digit is the phase, last two are the operation.
| Phase | Meaning |
|---|---|
| 0 | SP_EXECUTION_UNKNOWN |
| 1 | SP_EXECUTION_DOWNLEVEL |
| 2 | SP_EXECUTION_SAFE_OS |
| 3 | SP_EXECUTION_FIRST_BOOT |
| 4 | SP_EXECUTION_OOBE_BOOT |
| 5 | SP_EXECUTION_UNINSTALL |
The operations you’ll actually see: 0D MIGRATE_DATA, 07 INSTALL_DRIVERS, 0C APPLY_IMAGE, 04 INSTALL_RECOVERY_ENVIRONMENT, 17 BOOT, 18 SYSPREP, 1E PRE_OOBE, 1F POST_OOBE. Full list is on the error codes page.
So 0x4000D is phase 4, operation 0D. It died in the OOBE boot phase migrating data. That’s a useful sentence and it was sitting in five hex digits.
The compatibility codes
/Compat ScanOnly exists to hand you these, and they’re the ones you branch on:
| Code | Name | Meaning |
|---|---|---|
0xC1900210 | MOSETUP_E_COMPAT_SCANONLY | No issues found. This is your success. |
0xC1900208 | MOSETUP_E_COMPAT_INSTALLREQ_BLOCK | A real compat issue, usually an app |
0xC1900204 | MOSETUP_E_COMPAT_MIGCHOICE_BLOCK | The migration choice isn’t available, like Enterprise to Home |
0xC1900200 | MOSETUP_E_COMPAT_SYSREQ_BLOCK | Machine isn’t eligible. The TPM and CPU wall. |
0xC190020E | MOSETUP_E_INSTALLDISKSPACE_BLOCK | Not enough free space |
Notice that success is a 0xC19... value. A script that treats “starts with 0xC1900” as failure marks every clean scan as broken.
The mosetup codes you’ll actually see
Full MOSETUP_E_* list is on the resolution procedures page. These are the ones that actually turn up:
| Code | Name | Meaning |
|---|---|---|
0xC1900101 | MOSETUP_E_SETUP_PLATFORM | Generic rollback. Almost always a driver. Go read the extend code. |
0xC1900107 | MOSETUP_E_CLEANUP_PENDING | Previous attempt still cleaning up. Reboot. |
0xC190010A | MOSETUP_E_UNKNOWN_CMD_LINE | Unknown argument. Usually a typo or a switch that doesn’t exist here. |
0xC190010D | MOSETUP_E_INVALID_CMD_LINE | Invalid argument |
0xC190010E | MOSETUP_E_EULA_ACCEPT_REQUIRED | The Windows 11 trap from Part 2 |
0xC1900132 | MOSETUP_E_FINALIZE_RESUME_FAILURE | /Finalize on a machine /SkipFinalize never staged |
0xC1900215 | MOSETUP_E_NO_MATCHING_INSTALL_IMAGE | Ambiguous image plus /Quiet. Pass /ImageIndex. |
0xC1900216 | MOSETUP_E_ROLLBACK_PENDING | A rollback is still pending |
0xC19001DF | MOSETUP_E_DISK_FULL | Out of disk space |
0xC1900103 / 0xC1900104 | ..._COMPAT_TIMEOUT / ..._PROCESS_TIMEOUT | Ran out of time, not out of luck |
0xC19001E2 / E3 / E4 | preinstall / precommit / failure script failed | Your /PostOOBE or /PostRollback script failed, not setup |
0xC1900209 | MOSETUP_E_COMPAT_INSTALLREQ_CANCEL | Setup can migrate the data but not the installed apps |
0xC1900400-0xC1900405 | MOSETUP_E_UA_* | Update Agent side: no packages, corrupt payload, the install process crashed |
Two more that aren’t setup-specific: 0x80070070 is out of disk space. 0x8007042B - 0x4000D is a file, app or driver that couldn’t be migrated during the second boot.
Part 6: when the code tells you nothing, run it in the GUI
Everything above is worth knowing. It’s still not what I do first.
If a silent upgrade comes back with a code you can’t make sense of, go run the same setup.exe interactively on that machine and read what it says.
The GUI runs the same compatibility evaluation your silent command line did. Then it tells you what’s wrong in a sentence, which the silent path never does. Not enough disk space. This PC must support TPM 2.0. This app has to be uninstalled first. Thirty seconds, plain English, on the actual machine.
The hex route to the same answer goes: find the code, work out that 0xC1900101 is generic, go hunting for the extend code your tool didn’t capture, dig setuperr.log out of a hidden folder, search upward for the last occurrence, read the twenty lines above it. Part 5 covers all of that for when you need it. But that’s 45 minutes to get to an answer the dialog box hands you right away.
I’ve watched people burn an afternoon on a code the installer would have explained on the first try. I’ve done it myself. Microsoft’s error-code docs look like they’ll give you an answer, and usually they don’t. 0xC1900101 means “something rolled back,” and the official advice is update your drivers and try a clean boot.
What actually works:
- Run it in the GUI on one failing machine. Get the sentence. Most of the time you’re done there, because the answer is disk space, an app, or a hardware requirement.
- If the GUI works where silent failed, your command line or your execution context is the problem, not the machine.
/EULA acceptand running as SYSTEM are the first two things to check. - Then go to the codes and logs, for the genuinely weird ones and for anything you need to detect at fleet scale, where you obviously can’t click through 200 dialogs.
Step 1 is for diagnosing one machine. Part 5 is for building automation once you know what you’re looking for.
Part 7: the logs
Which folder depends on the phase, which you get from the extend code. setupact.log is the one that matters and setuperr.log sits next to it everywhere.
| Phase | Path |
|---|---|
| Down-level | $WINDOWS.~BT\Sources\Panther |
| OOBE | $WINDOWS.~BT\Sources\Panther\UnattendGC |
| Rollback | $WINDOWS.~BT\Sources\Rollback |
| Before down-level, if setup never launched | Windows |
| Post-upgrade | Windows\Panther |
Also miglog.xml in Windows\Panther for what migrated, BlueBox.log in Windows\Logs\Mosetup for setup-to-Windows-Update traffic (that’s your 0xC1900107 log), and in the Rollback folder: setupmem.dmp if the machine bugchecked, setupapi.dev.log for device install problems, and the .evtx event logs.
Every line is timestamp, level, component, message. The components you care about are SP (setup platform), MIG (migration engine) and CONX (compatibility). To read one of these: find the last occurrence of your result code, read upward from there, and search for Shell application requested abort and Abandoning apply due to error for object. That’s where it names the actual file or driver.
SetupDiag does a first pass automatically and it’s free. It won’t beat the GUI for speed on one machine, but it helps when you’re going through logs from a lot of them.
Part 8: four things that’ll bite you
I hit all of these myself on the test machine.
1. Exit code 0 doesn’t mean the machine upgraded
With /NoReboot, setup returns 0 having done everything it can, and the machine is still on the old build until it reboots. That’s correct behavior, but it’s easy to trip over, because “the command succeeded” is what most scripts check.
Verify the build after the reboot. Never report success off the exit code.
2. ProductName still says “Windows 10” on Windows 11
After a fully successful upgrade:
CurrentBuild : 26200.8037
DisplayVersion : 25H2
ProductName : Windows 10 Pro <-- Microsoft's value, not a bug in your script
Caption (CIM) : Microsoft Windows 11 Pro
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProductName doesn’t get updated by the upgrade. Plenty of inventory scripts read that key. If yours does, every successful upgrade reports as a failure and you go hunting a problem that isn’t there.
Check CurrentBuild >= 22000, or Win32_OperatingSystem.Caption.
3. The published disk requirement isn’t the requirement
Microsoft says 64 GB of storage. My first attempt ran about three hours on a 64 GB volume and rolled back with 0x80070070, ERROR_DISK_FULL. Setup wanted 20.4 GB free and had 19.1.
The 64 GB is about the size of the disk. What actually decides whether you finish is free space, and it needs to be a lot more than 64 GB implies. Gate on free space, and gate before you start rather than three hours in.
A rolled-back attempt also leaves C:\$WINDOWS.~BT, C:\$WINDOWS.~WS and C:\$GetCurrent behind, owned by SYSTEM with restrictive ACLs. A plain delete leaves most of the tree behind and barely moves your free space. Take ownership first.
4. The Installation Assistant does nothing as SYSTEM and exits 0
If you’re staging an ISO you’ll never hit this, and in an enterprise that’s what you’re doing. It’s here because Windows11InstallationAssistant.exe is the tool Microsoft puts in front of consumers, so it’s what people tend to reach for when they’re scripting this from an RMM instead of staging media.
Run it as SYSTEM and it returns instantly with exit code 0, having done nothing at all. No process, no log, no error. I checked it against a control: the same command line launched into an interactive session started a real upgrade.
Retrying makes it worse. After a failed attempt it re-armed itself, recreated its staging folders on its own, drove free space down by 20 GB and forced a reboot. Then it refused to run at all, still exiting 0.
If it returns 0 whether it worked, failed, or refused to run, there’s nothing for a script to check. Stage the ISO and use setup.exe.
And a fifth, if you’re building a progress bar
Setup writes progress into C:\$WINDOWS.~BT\Sources\Panther\setupact.log:
2026-09-04 02:23:39, Info MOUPG Overall progress: [100%]
Overall progress is the line to read. But it stops being written for long stretches - I watched about ten minutes of silence while setup worked normally at 90% CPU during the migration gather. A watchdog that kills the job after N minutes without a progress line will kill upgrades that are working fine.
Judge whether it’s alive on the log file’s modified time plus the presence of SetupHost and SetupPrep. Use the percentage for display only.
What survives
This is the question everyone asks, so: installed services came through. Both agent services on the test box were running afterwards, explorer was up, 107 services running, and C:\Windows.old was there, so the ten-day rollback window applies if you need it.
In place really does mean in place. You just have to plan around the reboot.
The rule under all of it
Setup is better documented than its reputation suggests. The docs are just filed somewhere unhelpful and written as reference instead of guidance. Most of what costs me a day is one of three things: a switch I didn’t know existed (/EULA accept, /SkipFinalize, /ReflectDrivers), a code I read as opaque when it actually has a structure, or a success signal that never meant anything.
And when it really is opaque, go run it in the GUI and let it tell you.