A feature update fails on eleven machines overnight. Every one of them reports the same thing: 0xC1900101. So you do what everybody does. You tell the user to unplug their dock, you get approval to disable the endpoint agent, and you queue the update again. Four of them work. Seven fail again.
You have learned nothing, because you never read the second half of the error.
0xC1900101 is not the error. Microsoft's own words are blunt about it: "A result code of 0xC1900101 is generic and indicates that a rollback occurred." Generic. It tells you Windows Setup gave up and put the old OS back. It does not tell you where, when, or why. That information lives in a completely separate number that Setup returns alongside it, and most reporting surfaces throw it away.
Windows Setup returns two codes on failure: a result code and an extend code, written as 0xC1900101 - 0x30018. The result code is the generic rollback signal. The extend code is the actionable one: its first digit is the setup phase and its last two digits are the operation, and Microsoft publishes the decoding tables for both. Decode the pair and you go from "a driver, somewhere" to "the sysprep operation in the first boot phase", which tells you exactly which log to open and which class of driver to suspect. Only then do you go hunting in setuperr.log and setupapi.dev.log for the specific file. Clean-booting first is the one move that guarantees you never find out which driver it was.
The problem: one code, nine documented root causes
Microsoft documents nine distinct 0xC1900101 pairs, each with its own cause and its own mitigation. They are not variations on a theme. One is a recovery-environment build failure. One is a WIM apply failure. One is a display driver hanging during data migration. One is a filter driver from a virtualisation agent blocking disk writes. The remediation for those four has almost nothing in common - yet if your reporting captured only the result code, all four look identical on your dashboard.
Why the extend code goes missing
Setup returns both codes, but not every consumer of that data keeps both. Microsoft calls this out directly on the error codes page: "If only a result code is returned, this can be because a tool is being used that was not able to capture the extend code."
So the first diagnostic act is not troubleshooting at all. It is recovery of a number you were supposed to have been given. The documented place to find it is the Application event log, where Windows Error Reporting writes an informational event 1001 named WinSetupDiag02.
That absence is itself a diagnostic result, and a useful one. Microsoft documents that "Devices updating from Windows 11, version 24H2 use an enablement package" - a small switch that activates dormant features rather than swapping the OS. An enablement package is a servicing operation. It has no downlevel phase, no WinPE pass and no second boot, so it cannot produce a setup phase code.
0xC1900101 at all - and knowing which path a fleet took tells you immediately whether driver compatibility is even in scope.The reflex that destroys the evidence
The standard advice for 0xC1900101 is to uninstall antivirus, disconnect every peripheral, and clean boot. Microsoft does publish those steps and they often work. But look at what a clean boot does: it changes the loaded driver set wholesale. If the update then succeeds, you know only that something in the difference was responsible - not what. Next month the same driver ships to the same fleet and you start over, because you never wrote down a filename.
$WINDOWS.~BT folder, do not run Disk Cleanup, and do not let the user re-attempt the update before you have harvested the rollback logs. Microsoft documents that the minidump, the rollback event logs and the device install log all live under $Windows.~bt\Sources\Rollback - and that if the upgrade later proceeds normally, the whole Sources directory is moved under Windows.old and is removed when Windows.old is deleted. Every retry and every cleanup overwrites or destroys the only copy of the evidence.Why it happens: the extend code is a phase plus an operation
Windows Setup does not perform an upgrade in one pass. It runs four phases, with a reboot between each, and Microsoft states that "Progress is tracked in the registry during the upgrade process" using a volatile key that exists only while the upgrade is running.
Each phase runs in a different environment, with a different set of drivers loaded. That is the whole reason the phase digit is diagnostic: it tells you which driver environment was live when the wheels came off.
What 0xC1900101 actually is
The result code has a name. In Microsoft's modern setup error table, 0xC1900101 is listed as MOSETUP_E_SETUP_PLATFORM, described as "The Setup Platform has encountered an unspecified error." That is a component reporting its own failure to continue, not a description of a fault.
0xC19xxxxx codes. Those are mosetup-defined values with their own published symbolic names, so 0xC1900101 resolves to MOSETUP_E_SETUP_PLATFORM in the modern setup errors table, not to whatever 0x0101 happens to mean in the NTSTATUS list. Reading it the generic way produces a confident, wrong answer.Splitting the extend code
Microsoft's decoding rule has exactly three steps: use the first digit for the phase, use the last two digits for the operation, and match both against the published tables.
That is the entire trick. And it is checkable: Microsoft's rollback guidance says separately that "The device install log is helpful if rollback occurs during the sysprep operation (extend code 0x30018)". The arithmetic and the prose agree.
The phase table
These are the six documented phase values. The fourth column is Microsoft's own list of example extend codes seen in each phase, which is a useful sanity check on your decode.
| Hex | Phase constant | What runs in it | Documented example codes |
|---|---|---|---|
| 0 | SP_EXECUTION_UNKNOWN | Phase not recorded. Treat the decode as untrustworthy. | - |
| 1 | SP_EXECUTION_DOWNLEVEL | Runs inside the old OS. Files copied, components gathered. | - |
| 2 | SP_EXECUTION_SAFE_OS | Windows PE. Recovery partition configured, files expanded, updates installed, rollback prepared. | 0x2000C, 0x20017 |
| 3 | SP_EXECUTION_FIRST_BOOT | Booted into the new OS. Initial settings applied. | 0x30018, 0x3000D |
| 4 | SP_EXECUTION_OOBE_BOOT | Final settings applied. Microsoft also calls this the second boot phase. | 0x4000D, 0x40017 |
| 5 | SP_EXECUTION_UNINSTALL | The rollback path itself. | 0x50000, 0x50015 |
Note the naming collision, because it will confuse you when you cross-reference Microsoft's own pages. Phase 4 is SP_EXECUTION_OOBE_BOOT in the constants table, but the resolution procedures article calls it "the second boot phase". The troubleshooting article reconciles them explicitly: "Second boot phase: Final settings are applied. This is also called the OOBE boot phase." Same phase, two names.
There is also a hard rule buried in the docs that saves time. Microsoft states 0xC1900101 "can be thrown at any stage of the upgrade process, with the exception of the downlevel phase." So a reported pair of 0xC1900101 - 0x1xxxx is self-contradictory. If you see one, distrust the capture rather than the machine.
The operation table
The last two digits identify what Setup was doing. This is the full published list.
| Hex | Operation constant | What it was doing |
|---|---|---|
| 0 | SP_EXECUTION_OP_UNKNOWN | Operation not recorded |
| 1 | SP_EXECUTION_OP_COPY_PAYLOAD | Copying the installation payload |
| 2 | SP_EXECUTION_OP_DOWNLOAD_UPDATES | Downloading dynamic updates |
| 3 | SP_EXECUTION_OP_INSTALL_UPDATES | Installing dynamic updates |
| 4 | SP_EXECUTION_OP_INSTALL_RECOVERY_ENVIRONMENT | Building WinRE |
| 5 | SP_EXECUTION_OP_INSTALL_RECOVERY_IMAGE | Installing the recovery image |
| 6 | SP_EXECUTION_OP_REPLICATE_OC | Replicating optional components |
| 7 | SP_EXECUTION_OP_INSTALL_DRIVERS | Installing drivers |
| 8 | SP_EXECUTION_OP_PREPARE_SAFE_OS | Preparing the WinPE environment |
| 9 | SP_EXECUTION_OP_PREPARE_ROLLBACK | Staging the rollback |
| A | SP_EXECUTION_OP_PREPARE_FIRST_BOOT | Preparing first boot |
| B | SP_EXECUTION_OP_PREPARE_OOBE_BOOT | Preparing OOBE boot |
| C | SP_EXECUTION_OP_APPLY_IMAGE | Applying the WIM |
| D | SP_EXECUTION_OP_MIGRATE_DATA | Migrating data |
| E | SP_EXECUTION_OP_SET_PRODUCT_KEY | Setting the product key |
| F | SP_EXECUTION_OP_ADD_UNATTEND | Applying unattend |
| 10 | SP_EXECUTION_OP_ADD_DRIVER | Adding a driver |
| 11 | SP_EXECUTION_OP_ENABLE_FEATURE | Enabling a feature |
| 12 | SP_EXECUTION_OP_DISABLE_FEATURE | Disabling a feature |
| 13 | SP_EXECUTION_OP_REGISTER_ASYNC_PROCESS | Registering an async process |
| 14 | SP_EXECUTION_OP_REGISTER_SYNC_PROCESS | Registering a sync process |
| 15 | SP_EXECUTION_OP_CREATE_FILE | Creating a file |
| 16 | SP_EXECUTION_OP_CREATE_REGISTRY | Creating registry data |
| 17 | SP_EXECUTION_OP_BOOT | Booting |
| 18 | SP_EXECUTION_OP_SYSPREP | Running sysprep |
| 19 | SP_EXECUTION_OP_OOBE | Running OOBE |
| 1A | SP_EXECUTION_OP_BEGIN_FIRST_BOOT | Entering first boot |
| 1B | SP_EXECUTION_OP_END_FIRST_BOOT | Leaving first boot |
| 1C | SP_EXECUTION_OP_BEGIN_OOBE_BOOT | Entering OOBE boot |
| 1D | SP_EXECUTION_OP_END_OOBE_BOOT | Leaving OOBE boot |
| 1E | SP_EXECUTION_OP_PRE_OOBE | Pre-OOBE work |
| 1F | SP_EXECUTION_OP_POST_OOBE | Post-OOBE work |
| 20 | SP_EXECUTION_OP_ADD_PROVISIONING_PACKAGE | Applying a provisioning package |
How to verify: recover the pair, then name the driver
Here is the order. Each step either produces the answer or narrows the next step. Nothing here changes the machine.
Step 1 to 3: recover the code pair
1. Read the SetupDiag result from the registry. Windows Setup runs SetupDiag automatically when an upgrade fails, and Microsoft documents the exact parameters it uses, including /RegPath:HKEY_LOCAL_MACHINE\SYSTEM\Setup\SetupDiag\Results. That key is the fastest read on the box and it is remotely queryable, which makes it the right first move across a fleet.
| Value | Meaning | What to look for |
|---|---|---|
FailureDetails | The result and extend code pair, named as ErrorCode and ExCode. | This is the whole prize. Read ExCode and decode it. |
ProfileName | Which SetupDiag rule matched. | Corroborates your phase decode. A rule naming a phase that disagrees with your arithmetic means stop and re-check. |
ProfileGuid | The rule's unique identifier. | Use it to look the rule up in Microsoft's published rules table. |
DateTime | When SetupDiag ran. | Check this before anything else. The key is not cleared, so a stale entry will happily mislead you. |
FailureData | SetupDiag's narrative plus the matching log line. | Contains the raw setuperr.log line and its component tag, which points at the right log. |
HostOSVersion / TargetOSVersion | Build you came from and build you were going to. | Confirms which upgrade attempt this record describes. |
UpgradeElapsedTime | How long the attempt ran. | Sanity check on the failure class. A driver rollback takes many minutes; seconds means something aborted early. |
RollbackStartTime / RollbackEndTime | When the rollback ran. | All zeroes means no rollback happened - so it was not a 0xC1900101 class failure at all. |
SetupDiagVersion | Version of the tool that produced the record. | Older versions carry fewer rules. Microsoft recommends running the latest build manually. |
Here is that key read on my own device. It is a real record, and it is a worked example of why you check DateTime first.
Four independent signals in one key all say the same thing, and none of them is "driver". Had I skipped straight to the extend code and decoded phase 4, I would have gone looking for a filter driver in the second boot phase and found nothing, because there was nothing to find.
2. Cross-check the Application log. The event is the authoritative record of the pair as Setup reported it.
| Event ID | Message / event name | What it tells you |
|---|---|---|
| 1001 | WinSetupDiag02 - informational, ten parameters | The authoritative result and extend code pair for a Setup failure. Also carries links to the log files for that attempt. |
| 1001 | WinSetupDiag01 - legacy event name | Same role on legacy operating systems. Search for both names before concluding no event exists. |
The parameter positions matter, because the raw event data is an unlabelled list of ten strings.
| Parameter | Holds | Documented example |
|---|---|---|
| P1 | The setup scenario | 1=Media, 5=WindowsUpdate, 7=Media Creation Tool |
| P2 | Setup mode | x=default, 1=Downlevel, 5=Rollback |
| P3 | New OS architecture | x=default, 0=X86, 9=AMD64 |
| P4 | Install result | x=default, 0=Success, 1=Failure, 2=Cancel, 3=Blocked |
| P5 | Result error code | 0xc1900101 |
| P6 | Extend error code | 0x20017 |
| P7 | Source OS build | 9600 |
| P8 | Source OS branch | Not typically available |
| P9 | New OS build | 16299 |
| P10 | New OS branch | rs3_release |
P4 is worth a second look. If it reads 2 (Cancel), you are not chasing a driver, whatever P5 says. That single field would have short-circuited the stale record above.
3. If both are empty, run SetupDiag yourself. Microsoft ships it inside Windows Setup, extracted to %SystemDrive%\$Windows.~bt\Sources during the upgrade, and recommends downloading the current build for manual runs. Note that when you run it by hand without /RegPath, results land at HKLM\SYSTEM\Setup\MoSetup\Volatile\SetupDiag - a different key from the automatic one. Two keys, two provenances; do not read one and think you are seeing the other.
Step 4 to 6: turn the phase into a log, and the log into a filename
4. Let the phase choose the log. This is the payoff of decoding. Microsoft's log table is indexed by phase, and says so plainly: "The log files are located in a different folder depending on the Windows Setup phase. Recall that the phase can be determined from the extend code."
| Log file | Phase: location | When to use it |
|---|---|---|
setupact.log | Down-Level: $Windows.~BT\Sources\Panther | All downlevel failures, and the starting point for rollback investigations. |
setupact.log | Rollback: $Windows.~BT\Sources\Rollback | Investigating generic rollbacks - that is, 0xC1900101. |
setupact.log | OOBE: $Windows.~BT\Sources\Panther\UnattendGC | Rollbacks in the OOBE phase - operations 0x4001C through 0x4001F. |
setupact.log | Post-upgrade: Windows\Panther | Post-upgrade issues, and the logs left behind by a run that completed. |
setuperr.log | Same as setupact.log | Every error encountered during installation. Always open this one first. |
setupmem.dmp | $Windows.~BT\Sources\Rollback | Bug check during upgrade. Setup extracts a minidump. |
setupapi.dev.log | $Windows.~BT\Sources\Rollback | Device install failures - specifically extend code 0x30018. |
BlueBox.log | Down-Level: Windows\Logs\Mosetup | Communication between setup.exe and Windows Update. |
5. Read setuperr.log, not setupact.log. Both files exist in the same folder. One of them is readable.
SP is a setup platform operation failure and your extend code decode is trustworthy. A last line tagged MIG points at data migration - a profile, a file, a registry key - not a driver. CONX is the compatibility appraiser and usually noise. MOUPG means the failure happened above the setup platform, and your phase digit may be meaningless. Microsoft documents the tag position as the third element of every log entry, after the timestamp and the log level.6. Find the last occurrence, not the first. Microsoft's documented procedure is specific about direction: search for the result code, then "find the last occurrence of the code", searching upward from the bottom of the file. Then look for two exact strings: Shell application requested abort and Abandoning apply due to error for object. SetupDiag's own documentation carries the same warning: "When SetupDiag indicates that there were multiple failures, the last failure in the log file is typically the fatal error, not the first one."
Here is why that discipline is not optional. This is real content from that 9 KB setuperr.log, from a device that upgraded successfully.
Step 7 to 8: name the driver
7. For a device install failure, open setupapi.dev.log. This is where a driver stops being a category and becomes a file. Microsoft's published example ends with the exact marker you are looking for.
8. If it bug-checked, get the stop code. For a 0x4000D failure, Microsoft's instruction is to look for setupmem.dmp and "Review the rollback log and determine the stop code", and it publishes a sample crash block showing Crash 0x0000007E detected with four bugcheck parameters. SetupDiag can debug that dump for you if the Windows Debugging Tools are installed and you pass /LogsPath, since memory dumps cannot be debugged in online mode.
The binaries and files in this path
Knowing where these actually live saves a detection script that silently never fires.
| File | Role | Where it lives |
|---|---|---|
SetupDiag.exe | Parses setup logs and matches them against a rules XML to name a root cause. | Not in System32. Extracted to %SystemDrive%\$Windows.~bt\Sources during upgrade, then moved under Windows.old. Verified absent from System32 and the Windows root on my 25H2 device. |
SetupPlatform.exe | Owns the phase and operation queue. It is the SP component in the logs and the thing MOSETUP_E_SETUP_PLATFORM refers to. | Not in System32 itself - verified at C:\Windows\System32\oobe\SetupPlatform\SetupPlatform.exe on my 25H2 device. Appears in real log lines as SETUPPLATFORMEXE. |
setuphost.exe | Surfaces failures early in the upgrade, before the setup platform is running. | Ships with the setup media, not the installed OS - a depth-limited search of the Windows tree on my device did not find it. Named in SetupDiag's FindSetupHostReportedFailure rule. |
appraiser.sdb | Compatibility appraiser database. The source of the CONX Appraiser lines that fill setuperr.log with noise. | %WINDIR%\Panther - verified present, 2,811,457 bytes, alongside Appraiser_Data.ini and the CompatData_*.xml scan results. |
wimmount.sys | WIM file system driver. Setup mounts the install image through it, so a broken registration fails the SafeOS WIM apply. | C:\Windows\System32\drivers\wimmount.sys - verified present, 79,344 bytes, FileDescription "Wim file system Driver". Two SetupDiag rules exist purely for it. |
setup.etl | ETW trace of Setup performance events, openable in Event Viewer. | %WINDIR%\Panther - verified present, 1,552,384 bytes. |
setupapi.dev.log | Plug and Play device installation log. Where a driver gets named. | %WINDIR%\Inf\ for the live OS - verified 3,935,513 bytes; the rollback copy is under $Windows.~BT\Sources\Rollback\setupapi\. |
miglog.xml | Record of what the migration engine actually moved. | %WINDIR%\Panther - verified present at 336,444,054 bytes, about 321 MB. |
The fix: what each phase actually tells you to do
Now the extend code earns its keep. Each documented pair carries its own cause and its own first move, and they are not interchangeable.
| Extend code | Decodes to | Microsoft's documented cause | Documented first move |
|---|---|---|---|
0x20004 | SAFE_OS / INSTALL_RECOVERY_ENVIRONMENT | "Windows Setup encountered an error during the SAFE_OS with the INSTALL_RECOVERY_ENVIRONMENT operation. This error is caused by out-of-date drivers." | Uninstall antivirus; remove unused SATA devices and unused devices and drivers; update drivers and BIOS. |
0x2000c | SAFE_OS / APPLY_IMAGE | Unspecified error during WIM apply in the WinPE phase. Out-of-date drivers, or disk corruption. | Run checkdisk to repair the file system; update drivers; disconnect everything except mouse, keyboard and display. |
0x20017 | SAFE_OS / BOOT | "A driver has caused an illegal operation... This is a SafeOS boot failure, typically caused by drivers or non-Microsoft disk encryption software. This can also be caused by a hardware failure." | Open setuperr.log and setupact.log in %windir%\Panther and locate the problem drivers, then update or uninstall them. |
0x30018 | FIRST_BOOT / SYSPREP | "A device driver has stopped responding to setup.exe during the upgrade process." | Disconnect all peripherals except mouse, keyboard and display; get updated drivers from the hardware vendor. Read setupapi.dev.log. |
0x3000D | FIRST_BOOT / MIGRATE_DATA | "Installation failed during the FIRST_BOOT phase while attempting the MIGRATE_DATA operation. This can occur due to a problem with a display driver." | Disconnect peripherals; update or uninstall the display driver. |
0x4000D | OOBE_BOOT / MIGRATE_DATA | "A rollback occurred due to a driver configuration issue... This can occur because of incompatible drivers." | Look for setupmem.dmp in $Windows.~BT\Sources\Rollback, get the stop code, and act on the driver the bug check names. |
0x40017 | OOBE_BOOT / BOOT | "Upgrade failed after the second reboot. This is caused by a faulty driver. For example: antivirus filter drivers or encryption drivers." | Clean boot, then upgrade. Microsoft documents a named case here: Citrix VDA's CtxMcsWbc filter driver preventing setup from writing to disk. |
0x4001E | OOBE_BOOT / PRE_OOBE | "Installation failed in the SECOND_BOOT phase with an error during PRE_OOBE operation." | Treated as generic; fall back to the 0xC1900101 procedures. |
Read down the "decodes to" column and the pattern is the point. Phase 2 failures are WinPE-environment problems - storage stacks, disk encryption filters, firmware. Phase 3 failures are the new OS meeting your hardware for the first time - device and display drivers. Phase 4 failures are filter drivers and security agents now loaded under the new kernel.
That is the inference the extend code buys you. 0x20017 and 0x40017 are the same operation - BOOT - in two different phases, pointing at two entirely different families of software. Disabling your antivirus is reasonable for 0x40017. It is close to irrelevant for 0x20017, where the antivirus filter driver is not even loaded yet.
The one thing to do before any of it
Harvest first. Copy the whole folder, then remediate. Microsoft's guidance for offline SetupDiag analysis is to copy one of \$Windows.~bt\sources\panther, \$Windows.~bt\Sources\Rollback, \Windows\Panther or \Windows\Panther\NewOS, and notes that if you copy the parent folder and all subfolders, SetupDiag searches every subdirectory itself. That is a one-command capture that survives every retry the user makes while you are still reading.
Proof it worked: real setup artefacts from a live device
Everything below was read from one live Windows 11 25H2 corporate device, build 26200.9168, read-only. It shows what the diagnostic surface looks like on a machine that is currently healthy - which is exactly what you need to recognise the abnormal.
The setupdiagresults.xml file on disk carried the same failure as the registry key, byte for byte in substance: <FailureDetails>ErrorCode = 0x800704C7, ExCode = 0x40004</FailureDetails>, matched by rule FindAbruptDownlevelFailure. Microsoft documents both destinations for the same run, and having the pair agree is how you confirm you are reading a genuine record rather than a leftover.
Three findings from this device are worth carrying into your own environment.
The stale record is real and it is dangerous. A May 2025 SetupDiag result was still sitting in the registry in August 2026 - fifteen months and one whole feature release later. Nothing cleared it. Fleet reporting that reads HKLM\SYSTEM\Setup\SetupDiag\Results without filtering on DateTime will raise tickets for upgrades that failed over a year ago and have long since been superseded.
A successful upgrade leaves 63 error lines behind. The setuperr.log from the March 2025 run sits in the post-upgrade location, meaning that run completed - and it still holds fifteen SP errors, including 0x8007042B, the very code Microsoft uses in its own documented failure walkthrough. Error lines are not evidence. The last occurrence of your specific result code is evidence.
Log sizes decide your method. setupact.log at 547 MB and miglog.xml at 321 MB are not files you open and read. setuperr.log at 9 KB is. A runbook that says "review setupact.log" without giving a search string is one nobody will follow at 2am.
FailureDetails, ProfileName and DateTime from HKLM\SYSTEM\Setup\SetupDiag\Results. Group the results by ExCode. If they cluster on one extend code you have one driver problem across the fleet and one phase to investigate. If they scatter across three or four extend codes, you do not have a single root cause and treating it as one will waste the week.References
- Windows upgrade error codes - the result code versus extend code split, the three-step decoding rule, and the complete phase and operation tables reproduced above.
- Windows upgrade resolution procedures - the 0xC1900101 section with every documented variant, cause and mitigation, plus the modern setup (mosetup) error table where
MOSETUP_E_SETUP_PLATFORMis defined. - Log files and resolving upgrade errors - the phase-indexed log location table, the log entry structure and component tags, the last-occurrence search procedure, and the worked
setupapi.dev.logexample. - Windows error reporting - Application log event 1001, the
WinSetupDiag02event name, and the P1 to P10 parameter map. - SetupDiag - the automatic run parameters, both registry destinations, the full published rules table, and the setup bug check analysis procedure.
- Windows upgrade issues troubleshooting - what each of the four phases does, the example extend codes per phase, and the volatile
SetupProgressregistry key. - Resolve Windows upgrade errors - the hub article and the 100 to 400 level map of this whole documentation set.
- Windows Setup Log Files and Event Logs - the
%WINDIR%\Pantherand%WINDIR%\Inf\Setupapi*.loglocations, and how to opensetup.etl. - System Error Codes (1000-1299) -
ERROR_CANCELLED, 1223 (0x4C7), "The operation was canceled by the user." - What's new in Windows 11, version 25H2 - the enablement package delivery path for devices already on 24H2, and why those devices produce no setup phase codes.