A feature update fails. Somebody opens CBS.log, finds nothing that looks like an upgrade, and moves on to Get-WindowsUpdateLog. That produces a few hundred thousand lines of ETW trace with no mention of the failure either. Two hours gone, no root cause.
Both files were the wrong file. Windows Setup keeps its own logs, in its own folders, in its own format, and it does not write to either of those places.
And when admins do find the right folder, the next mistake is worse: they open the biggest file in it.
A feature update failure is Windows Setup, not CBS and not the Windows Update agent, so the logs live in a Panther folder — and which Panther folder depends on which phase failed. Microsoft documents setuperr.log as the error-only companion to setupact.log, and the size difference is the whole argument: on the device I wrote this on, setupact.log is 573,433,781 bytes across 2,688,076 lines and setuperr.log is 9,099 bytes across 63 lines. Same upgrade. A 63,022:1 reduction. Read the small one first, anchor on the last occurrence of the result code — Microsoft's own words are that "the last failure in the log file is typically the fatal error, not the first one" — and only then go into the big file, at the timestamp the small file gave you.
The problem: the failure log is 547 MB and you have twenty minutes
Here is the actual state of C:\Windows\Panther on the Windows 11 Enterprise 25H2 device (build 26200.9168) I used to write this. Fifty-two files, 934.2 MB in total. I sorted by size and took the top of the list.
Two files account for 93 percent of it. setupact.log at 547 MB and MigLog.xml at 321 MB. Notepad will open the first one eventually. Ctrl+F in it will not be a pleasant experience.
And 9,099 bytes down the list is the file that actually names the failure.
setupact.log. That is not a rounding error — it is the entire point of the file.By line count the gap is slightly less extreme and slightly more useful, because lines are what you actually read. I counted both with a StreamReader rather than Get-Content, because loading 547 MB into a PowerShell array is its own outage.
| Measure | setupact.log | setuperr.log | Reduction |
|---|---|---|---|
| Bytes | 573,433,781 | 9,099 | 63,022:1 |
| Lines | 2,688,076 | 63 | 42,668:1 |
Timestamped entries at Error level | 72 | 50 | 1.4:1 |
| Time to read end to end | Not a real option | About 30 seconds | — |
Look at the third row for a moment, because it is the one nobody expects. setupact.log contains 2,688,076 lines. Exactly 72 of them are logged at Error level. Everything else is Info (2,544,217) or Warning (6,802). You are hunting 72 needles in a 2.7-million-line haystack, and there is a 63-line file sitting next to it that already did the hunting.
SoftwareDistribution does nothing for a Setup failure because Setup does not read it. Running sfc /scannow writes its [SR] entries into CBS.log, which is a different log about a different subsystem. Worst of all, retrying the upgrade overwrites $Windows.~BT\Sources\Panther — the folder holding the down-level logs for the attempt that just failed. Copy the whole folder tree out before you retry anything. Once it is gone, you are diagnosing the retry, not the failure.So before any command, three questions need answers.
- Which phase failed, because that decides which folder holds the relevant log?
- What is the result code and the extend code, and where can I still recover them?
- Which of the errors in the file is the one that actually killed the upgrade, as opposed to noise that a successful upgrade would also have produced?
Question three is not rhetorical. I will show you a setuperr.log from a successful upgrade that contains 0x8007042B — the exact result code Microsoft uses in its own failure walkthrough.
Why it happens: Setup has its own logging stack, and it moves
Windows Setup is a different program from Windows Update
A cumulative update is applied by the component-based servicing engine. It logs to CBS.log. A feature update is applied by Windows Setup, which orchestrates CBS as one of several subordinate engines and logs to its own files in its own format. The Windows Update agent's job ends when the payload is on disk and setup.exe is invoked.
That is why CBS.log looks unhelpful during a feature-update failure. It is not that CBS was silent; it is that CBS is one chapter and you are looking for the plot. In my 547 MB setupact.log, 1,302,873 lines carry the CBS tag — nearly half the file. Setup swallowed CBS's logging whole and inlined it. The interesting lines are the other half.
Microsoft documents four phases plus an uninstall phase, with a reboot between each, and each phase has its own failure signature.
| Phase | What it does (documented) | Documented failure character |
|---|---|---|
| Down-level | Runs inside the old OS. Windows files copied, installation components gathered. | "Because this phase runs on the source OS, upgrade errors aren't typically seen." |
| SafeOS | Booted into Windows PE. Recovery partition configured, files expanded, updates installed, rollback prepared. | "Errors most commonly occur during this phase due to hardware issues, firmware issues, or non-microsoft disk encryption software." Example codes 0x2000C, 0x20017. |
| First boot | Initial settings applied under the new OS. | "relatively rare, and almost exclusively caused by device drivers." Example codes 0x30018, 0x3000D. |
| Second boot (OOBE) | Final settings applied. Reaches 100 percent here. | "most commonly due to anti-virus software or filter drivers." Example codes 0x4000D, 0x40017. |
| Uninstall | Only runs if the upgrade was unsuccessful. | Example codes 0x50000, 0x50015. |
HKLM\System\Setup\mosetup\volatile\SetupProgress, holding a binary value from 0 to 100. It exists only during the upgrade. That makes it useless for post-mortem work but genuinely useful if you are watching a fleet mid-flight and want to know whether a device is stuck or just slow.The log locations, and why they move
This is the part that sends people to the wrong file. There is no single setupact.log. There are five documented locations, and the one you want depends on which phase failed. Here is Microsoft's table, condensed.
| Log file | Phase and documented location | When to use it |
|---|---|---|
setupact.log | Pre-initialization (before down-level): Windows | "If setup fails to launch." |
setupact.log | Down-level: $Windows.~BT\Sources\Panther | "All downlevel failures and starting point for rollback investigations." |
setupact.log | Rollback: $Windows.~BT\Sources\Rollback | "Investigating generic rollbacks - 0xC1900101." |
setupact.log | OOBE: $Windows.~BT\Sources\Panther\UnattendGC | OOBE-phase rollbacks and operations 0x4001C to 0x4001F. |
setupact.log | Post-upgrade (after OOBE): Windows\Panther | "Investigate post-upgrade related issues." |
setuperr.log | Same as setupact.log | "Review all errors encountered during the installation phase." |
miglog.xml | Post-upgrade: Windows\Panther | "Identify post upgrade data migration issues." |
BlueBox.log | Down-level: Windows\Logs\Mosetup | Communication between setup.exe and Windows Update. WSUS and WU down-level failures, 0xC1900107. |
setupmem.dmp, setupapi.dev.log, *.evtx | $Windows.~BT\Sources\Rollback | Bug check during upgrade; device install issues (0x30018); generic rollbacks. |
Read the mechanism behind that table and it stops being something to memorise. $Windows.~BT is Setup's working directory on the source OS. While the down-level phase runs, that is where logging goes. If the upgrade completes, the whole thing is migrated into C:\Windows\Panther on the new OS. If it rolls back, a Rollback sibling folder appears.
So the existence and freshness of these folders is itself diagnostic, before you read a single line.
miglog.xml becomes relevant.C:\Windows\Panther\UnattendGC\setupact.log was last written today — and its tail is [svchost.exe] Enter WinReIsWimBootEnabled, a WinRE state check, not Windows Setup. Meanwhile the setuperr.log beside it is 0 bytes and dated to the original upgrade. Check the content and the date inside the last entry, not the file's LastWriteTime.The log entry format, including the field Microsoft does not document
Microsoft documents four elements in a setupact.log or setuperr.log entry: the date and time, the log level (Info, Warning, Error, Fatal Error), the logging component, and the message.
In practice there is a fifth, optional field. A bracketed hex message identifier can appear between the level and the component. Measured on my 50-entry setuperr.log: 16 entries carried one, in the form [0x0808fe]. Any regex you write to parse these files must treat that field as optional, or it will silently mis-assign the component tag on a third of your lines.
Those last two are worth noticing. Apply failed. Last error: 0x00000000 is untagged, and its "last error" is zero. A naive parser reads that as the fatal line and reports a success code as the cause. It is a summary line, not a diagnosis. This is precisely why you anchor on the result code from the phase, not on whatever line happens to say "failed".
The component tags, checked against reality
Microsoft lists nine logging components: CONX, MOUPG, PANTHR, SP, IBSLIB, MIG, DISM, CSI, CBS. It then singles out three: "The logging components SP (setup platform), MIG (migration engine), and CONX (compatibility information) are useful for troubleshooting Windows Setup errors."
I histogrammed all 2,688,076 lines of my setupact.log against that list. All nine are real and all nine are present.
| Tag | Meaning | Lines in my 547 MB setupact.log |
|---|---|---|
CBS | Component-based servicing — the same engine that writes CBS.log | 1,302,873 |
MIG | Migration engine — user and system state | 626,201 |
SP | Setup platform — the operation queue that drives the upgrade | 217,094 |
CONX | Compatibility information (Appraiser, compat blocks) | 53,718 |
CSI | Component servicing infrastructure — CBS's lower layer | 48,994 |
DISM | Deployment image servicing, including its providers | 14,671 |
MOUPG | Modern setup / setuphost — the outermost orchestration layer | 7,221 |
PANTHR | Panther logging infrastructure itself | 114 |
IBSLIB | Image-based setup library | 40 |
Microsoft's list is accurate. It is also incomplete. Four more tags appear in real volume and are not in the documented nine.
| Undocumented tag | Lines observed | What it is doing there |
|---|---|---|
SXS | 237,678 | Side-by-side assembly resolution. Third largest tag in the entire file, and absent from the documented list. |
DPX | 2,745 | Delta/package expansion. Also named in the SetupDiag rule UpdateAgentExpanderFailure, which matches "DPX expander failures in the downlevel phase". |
SYSPRP | 1,590 | Sysprep operations. SetupDiag has a matching rule, SysPrepLaunchModuleFailure. |
TOOL | 476 | External tool invocation from within Setup. |
WebServices, Deployment, Metadata, Shared, Reporter, SLS and Driver alongside the published ones. In both stacks, treat Microsoft's tag list as a guide to the important tags rather than an exhaustive schema. Never build a parser that discards lines whose tag it does not recognise.The binaries that write these lines
Tags are not abstractions. Each one is a component with a file on disk, and knowing which file lets you check versions when a phase misbehaves. Everything below I verified on the device rather than assumed.
| Binary | Verified location and version | Role |
|---|---|---|
SetupPlatform.exe | C:\Windows\System32\oobe\SetupPlatform\ — 10.0.26100.3476, description "SetupPlatform module", 357,816 bytes | Owns the SP tag. Drives the operation queue. When you see CSetupPlatformPrivate::Execute: Execution of operations queue failed, abandoning, this is the process saying it. |
UNBCL.DLL | Same folder — 10.0.26100.3476, description "Unmanaged BCL", 1,090,976 bytes | Setup's base class library. Its fingerprints show up inside error text: one of my MIG errors names class UnBCL::XmlNode in an XML parse failure. |
AppxProvider.dll | C:\Windows\System32\Dism\ — not System32 proper — 10.0.26100.8457, "DISM App Package (.appx) Provider" | A DISM provider. Source of all 22 DISM-tagged error lines in my file. Remember this one; it is the punchline later. |
migisol.dll | C:\Windows\System32\ — 10.0.26100.8972, "Migration System Isolation Layer" | Isolation layer for the migration engine, so a badly behaved plug-in cannot take the whole engine down. |
IasMigPlugin.dll | C:\Windows\System32\ — 10.0.26100.5074, "NPS Migration DLL" | An individual migration plug-in. Named by full path in my setuperr.log, which is what makes MIG errors actionable — the log tells you which DLL failed. |
wimmount.sys | C:\Windows\System32\drivers\ — 10.0.26100.8972, "Wim file system Driver" | Mounts the install image. Two SetupDiag rules exist purely for its failure modes: WimMountDriverIssue and PreReleaseWimMountDriverFound. |
SetupDiag.exe | Extracted to %SystemDrive%\$Windows.~bt\Sources during upgrade; moved under Windows.old afterwards | Parses these logs for you. Not present on my device — Windows.old has been cleaned up, and Microsoft documents that removing it removes SetupDiag too. |
/Output:%windir%\logs\SetupDiag\SetupDiagResults.xml and /RegPath:HKEY_LOCAL_MACHINE\SYSTEM\Setup\SetupDiag\Results. Check for that output before you download anything — the answer may already be on the box. It was on mine.Result codes and extend codes
An unsuccessful Setup returns two codes, and you need both. A result code, corresponding to a Win32 or NTSTATUS error, and an extend code carrying "information about both the phase in which an error occurred, and the operation that was being performed". They are reported as a pair, for example 0xC1900101 - 0x4000D.
The extend code decodes positionally: first digit is the phase, last two digits are the operation.
| First digit | Phase constant | Which folder to open |
|---|---|---|
0 | SP_EXECUTION_UNKNOWN | Start with C:\Windows\setupact.log (pre-initialization). |
1 | SP_EXECUTION_DOWNLEVEL | $Windows.~BT\Sources\Panther |
2 | SP_EXECUTION_SAFE_OS | $Windows.~BT\Sources\Panther, then the Rollback folder. |
3 | SP_EXECUTION_FIRST_BOOT | Windows\Panther and Windows\Panther\NewOS. |
4 | SP_EXECUTION_OOBE_BOOT | $Windows.~BT\Sources\Panther\UnattendGC, then Windows\Panther. |
5 | SP_EXECUTION_UNINSTALL | $Windows.~BT\Sources\Rollback |
The operation is the low byte, from a documented list of 33 values. 0D is SP_EXECUTION_OP_MIGRATE_DATA, 0C is SP_EXECUTION_OP_APPLY_IMAGE, 07 is SP_EXECUTION_OP_INSTALL_DRIVERS, 02 is SP_EXECUTION_OP_DOWNLOAD_UPDATES. Microsoft's worked example: "An extend code of 0x4000D, represents a problem during phase 4 (0x4) with data migration (000D)."
How to verify: a triage order that starts at 9 KB
Here is the order. Each step is cheap, each step narrows the next, and nothing in it modifies the machine.
Step 1 — Establish the phase before you open any log
Test for the folders. This takes a second and tells you which of five setupact.log files matters.
That output is a diagnosis on its own. Down-level logs present, no rollback folder, and a $Windows.~BT tree stripped of everything except the log folders. Setup failed early, and Windows deliberately preserved the evidence while reclaiming the space.
Step 2 — Recover the result and extend code, from whichever source still has it
There are three documented places the code pair lives, and they decay at different rates. Try them in this order.
2a. The Windows Error Reporting event. Microsoft documents that when Setup fails, the result code and extend code "are recorded as an informational event in the Application log by Windows Error Reporting as event 1001. The event name is WinSetupDiag02." Ten parameters are listed; P5 is the result error code and P6 the extend error code.
| Event ID | Message / event name | What it tells you |
|---|---|---|
| 1001 | Event name WinSetupDiag02, ten parameters P1–P10 | The authoritative code pair. P4 install result (0=Success, 1=Failure, 2=Cancel, 3=Blocked), P5 result code, P6 extend code, P2 setup mode (1=Downlevel, 5=Rollback), P7/P9 source and new OS build. |
| 1001 | Event name WinSetupDiag01 | Same role on legacy operating systems. Search for both names, not just the newer one. |
| 1001 | Any other WER report bucket | Why filtering on the ID alone fails. 1001 is a shared, high-traffic WER event. I checked the 400 most recent 1001 events on my device: zero were WinSetupDiag. Filter on the event name. |
Microsoft's own caveat on this route is easy to miss: "The following Event logs are only available if Windows was updated from a previous version of Windows to a new version of Windows." And there is a second, harsher limit that nobody documents — the Application log rolls.
2b. The SetupDiag registry key. This is the route that survived. A 20 MB circular event log wraps in weeks; a registry value does not.
| Value / subkey | Meaning | What to look for |
|---|---|---|
SetupDiag\Results | Where Setup writes SetupDiag output when it runs it automatically after a failure. | ProfileName (the matched rule), FailureDetails (the code pair), UpgradeStartTime/UpgradeEndTime. Persists indefinitely. Check here first. |
MoSetup\Volatile\SetupDiag | The default /RegPath when you run SetupDiag manually without specifying one. | Present only after a manual run. Absent on my device — proof that the results I found came from Setup's own automatic invocation, not from someone poking at it. |
MoSetup\Volatile\SetupProgress | Binary progress value, 0–100, documented as existing only during an upgrade. | Useful live, useless post-mortem. Its absence confirms no upgrade is currently in flight. |
2c. The log itself. If both of the above are gone, setupact.log records the code pair in plain text as Setup hands it to WER and to Windows Update. On my device the down-level log contains all three of these, and they agree.
Reporting error event -> is worth putting in your runbook. It is logged at Info level, which means it is invisible in setuperr.log and survives in setupact.log even when the WER event has aged out and nobody ran SetupDiag. Two anchors, one command: search setupact.log for Reporting error event and for Setup360Result.Step 3 — Read setuperr.log end to end. All of it.
This is the step that does not exist in most people's process, because they have never noticed the file. It takes thirty seconds and it usually ends the investigation.
Microsoft's documented procedure is ten steps. Compressed, with the reasoning that makes each one make sense:
- Get the Setup error code (step 2 above).
- Use the extend code's phase digit to pick the folder.
- Open the log in a text editor.
- Search for the result code and find the last occurrence. Microsoft spells out the mechanics: go to the bottom of the file, then Find with Direction set to Up.
- From that last occurrence, "scroll up a few lines from this location in the file and review the processes that failed before generating the result code".
- Search for two specific strings:
Shell application requested abortandAbandoning apply due to error for object. - Decode the Win32 errors in that region.
- Write down the timestamps.
- Search the other logs at those timestamps.
Step 4 is the one that matters most, and it is the opposite of instinct. You do not want the first error. Microsoft states it plainly in the SetupDiag documentation: "When SetupDiag indicates that there were multiple failures, the last failure in the log file is typically the fatal error, not the first one."
The reason is architectural. Setup unwinds. A failure deep in a migration plug-in propagates upward through the operation queue, and each layer logs the same code on its way out. The outermost frame is the last line written and the one that describes what Setup actually gave up on.
Step 4 — Only now open setupact.log, at the timestamp you already have
By this point you have a phase, a code pair, a component tag and a timestamp accurate to the second. That turns 2,688,076 lines into a twenty-line window. Open the big file, jump to that second, read the Info lines either side of it. The Info lines are where the context lives — which object, which plug-in, which registry hive.
Shell application requested abort and Abandoning apply due to error for object. I counted both across all 573,433,781 bytes of setupact.log: each appears exactly once. Both of those single occurrences are also in the 9,099-byte setuperr.log. Both of the documented text-string searches, on this device, could have been satisfied by reading the small file.The fix: anchor on the last result code, then widen
The fix in this post is a reading order, not a repair. That is deliberate. Every repair for a feature-update failure — remove the driver, delete the profile, uninstall the agent, free the disk — depends entirely on which one it is, and the logs are how you find out. Guessing the repair is how a two-hour job becomes a two-week one.
What setuperr.log leaves out, exactly
Before you trust the small file, you should know its blind spots. So I diffed them: every Error-level entry in setupact.log, checked for presence in setuperr.log.
| Component tag | Error entries in setupact.log | Present in setuperr.log | Dropped |
|---|---|---|---|
MIG | 22 | 22 | 0 |
DISM | 22 | 0 | 22 |
SP | 15 | 15 | 0 |
CONX | 9 | 9 | 0 |
MOUPG | 1 | 1 | 0 |
DU | 1 | 1 | 0 |
| untagged | 2 | 2 | 0 |
| Total | 72 | 50 | 22 |
The filter is not "level equals Error". Twenty-two entries were dropped, and every single one of them was DISM-tagged. Here is what they were.
So setuperr.log is a curated view, not a mechanical grep for the word "Error". On this device the curation was correct: it kept every SP, MIG, CONX, MOUPG and DU entry — including all three of the tags Microsoft names as the useful ones — and discarded a repetitive DISM provider channel that had nothing to do with the outcome.
setuperr.log does not mean there is nothing wrong. If the failure lived in a channel the filter drops — a DISM provider, a CBS sub-operation — the small file will be quiet while the upgrade fails. In that case go to setupact.log and search for the result code directly, and check CBS.log for the same timestamp window. Use setuperr.log to accelerate triage, never to conclude it.Which log answers which question
This is the table I wish I had had the first time. It is the routing layer between a symptom and a file.
| Question | Read this | Why not the obvious one |
|---|---|---|
| What error did Setup give up on? | setuperr.log in the phase folder | setupact.log has the same answer buried in 2.7 million lines. |
| What was Setup doing when it failed? | setupact.log at the timestamp from setuperr.log | The context is at Info level, so setuperr.log cannot show it. |
| What is the result + extend code pair? | WER event 1001 WinSetupDiag02, else HKLM\SYSTEM\Setup\SetupDiag\Results, else grep setupact.log for Reporting error event | The WER event ages out with the Application log. The registry key does not. |
| Which user or system data failed to migrate? | miglog.xml in Windows\Panther | 321 MB on my device. Only open it once setuperr.log has told you it is a MIG problem. |
| Did the client ever get the right payload from WSUS or WU? | BlueBox.log in Windows\Logs\Mosetup | Documented as the setup.exe-to-Windows-Update conversation. The one to open for 0xC1900107. |
| Did a driver fail to install during the upgrade? | setupapi.dev.log in the Rollback folder | Documented for device install issues, 0x30018. |
| Did the machine bug check mid-upgrade? | setupmem.dmp in $Windows.~BT\Sources\Rollback or %WinDir%\Panther\NewOS\Rollback | Setup extracts a minidump specifically so you can debug it. SetupDiag can debug it for you with /LogsPath. |
| Did a cumulative update fail, not a feature update? | CBS.log | Different engine entirely. And an error count in CBS.log is not a verdict — I have measured a successful cumulative update that logged 715 error-level CSI and SXS entries. |
| Did the WU agent fail before Setup ever launched? | The ETW traces under C:\Windows\Logs\WindowsUpdate\, via Get-WindowsUpdateLog | C:\Windows\WindowsUpdate.log is a ~276-byte stub pointing at that cmdlet. And -ForceFlush stops the Update Orchestrator and Windows Update services, so never make it your first command. |
Decoding the codes you find
Microsoft's rule: first hex digit 8 means a Win32 error code, C means an NTSTATUS value; take the last four digits and look them up. In practice certutil -error does that lookup for you — for some families. I measured which.
That last point deserves emphasis. 0xC1900101 is the single most-searched Windows upgrade code and certutil cannot say a word about it. Microsoft documents it directly instead: "A result code of 0xC1900101 is generic and indicates that a rollback occurred. In most cases, the cause is a driver compatibility issue." The instruction is to analyse the extend code to find the phase — which brings you straight back to picking the right folder.
Proof it worked: two setuperr.log files from one device
Everything so far is documentation plus measurement. Now the two cases that make the argument, both from the same machine.
Case A — the failure, in 21 lines
C:\$Windows.~BT\Sources\Panther on my device holds logs from 2025-05-14. That is fifteen months after the successful upgrade whose logs sit in C:\Windows\Panther. Two different events, two different folders, and the small folder is the failure.
| File | Bytes | Lines | Dated | Outcome |
|---|---|---|---|---|
C:\Windows\Panther\setupact.log | 573,433,781 | 2,688,076 | 2025-03-15 | Succeeded |
C:\Windows\Panther\setuperr.log | 9,099 | 63 | 2025-03-15 | Succeeded |
C:\$Windows.~BT\Sources\Panther\setupact.log | 88,314 | 778 | 2025-05-14 | Failed |
C:\$Windows.~BT\Sources\Panther\setuperr.log | 2,666 | 21 | 2025-05-14 | Failed |
An admin investigating the May failure who opens C:\Windows\Panther\setupact.log — the obvious file, the biggest file, the one every article names — is reading 547 MB about a successful upgrade from two months earlier. Meanwhile the failure is fully described in a 2,666-byte file in a hidden folder.
Here is that file, essentially complete.
This is what "the last failure is typically the fatal error" looks like in the wild. Twenty-one lines, one component tag, and a single result code propagating up a call stack from OnCancel to CSetupHost::Execute. The last line is the outermost frame and confirms what Setup surfaced. The useful lines are the two named ones in the middle: CDlpActionDU::ExecuteDUSetup tells you it died in Dynamic Update, and CSetupManager::ExecuteDownlevelMode tells you the phase in plain English.
Note also that not every line carries the same code. Two lines report 0x8007139F (ERROR_INVALID_STATE) and two report the undecodable 0xC1800104 / 0xC180010A. If you had anchored on the first error, or on whichever error looked most exotic, you would have chased the wrong one. Anchoring on the last occurrence of the code the phase reported keeps you honest.
SetupDiag had already answered it, on disk, unprompted
And then the part that reframes the whole exercise. Setup ran SetupDiag itself when this failed, fifteen months ago, and left the answer in the registry and in a 1,081-byte XML file.
Three independent artefacts, in agreement: the registry key, the XML file, and the log line SetupHost: Reporting error event -> [0x800704C7, 0x40004]. All three say 0x800704C7 - 0x40004. And C:\Windows\Logs\Mosetup\BlueBox.log is timestamped 2025-05-14 11:24:44 — the same 27-second window, the documented log for the setup.exe-to-Windows-Update conversation, sitting there ready for the next question.
0x40004: first digit 4 is SP_EXECUTION_OOBE_BOOT, last two digits 04 is SP_EXECUTION_OP_INSTALL_RECOVERY_ENVIRONMENT. But SetupDiag matched FindAbruptDownlevelFailure, the log line reads CSetupManager::ExecuteDownlevelMode, the telemetry line reads [Downlevel], and the whole thing lasted 27 seconds — nowhere near OOBE. The table disagrees with four other artefacts. Remember Microsoft's own caveat that those extend codes "apply to Windows 10 version 1607"; this is build 26100. Use the extend code to pick a folder, then let the log text overrule it. Do not open a ticket that says "OOBE phase failure" because a 1607-era table said so.Case B — the success that contains a textbook failure code
Now the other file, and the reason you must never treat "errors present" as "upgrade failed".
The 63-line setuperr.log in C:\Windows\Panther is from an upgrade that completed successfully. It contains 0x8007042B — the exact result code Microsoft uses in its own worked failure example on the log-files page.
How do I know it succeeded? Because of what happened after that timestamp.
| Evidence | Timestamp | What it proves |
|---|---|---|
The 0x8007042B line in setuperr.log | 16:48:50 | A migration apply phase failed on one object. |
Last line in setuperr.log | 17:58:12 | Setup kept logging errors for another 69 minutes after the "fatal" one. |
Last line in setupact.log: CBS Ending TrustedInstaller finalization. | 18:04:20 | Setup ran 75 minutes past the 0x8007042B and finished cleanly with a CBS finalization, not a rollback. |
No $Windows.~BT\Sources\Rollback folder from that date | — | No rollback was ever prepared or executed for this upgrade. |
Logs present in C:\Windows\Panther | — | The documented post-upgrade location. Setup only migrates its logs here on completion. |
| Device now running 25H2, build 26200.9168 | today | The upgrade this log describes is the one that put the OS where it is. |
Setup hit an access-denied writing a security agent's HKCU key during offline migration, logged it with both of Microsoft's canonical abort strings and a genuinely fatal-looking result code, then carried on and completed. The migration engine is designed to tolerate that. A single object failing to apply is not a failed upgrade.
Which is exactly why the procedure says to find the last occurrence of the result code Setup actually reported — not the last scary line in the file. Without a code from step 2, 0x8007042B at 16:48:50 looks like your answer. With one, you know there is no failure to explain.
setuperr.log is a result, not an empty file. On my device C:\Windows\Panther\UnattendGC\setuperr.log is 0 bytes — the OOBE phase logged no errors at all. That is one of the fastest facts you can establish in the whole investigation, and it eliminates an entire phase in the time it takes to read a file size. Check the size of every setuperr.log in every phase folder before you open any of them.The full component histogram of a 63-line file
One last measurement, because it closes the loop on the tag documentation. Here is the complete component breakdown of the successful upgrade's setuperr.log — all 50 timestamped entries.
| Tag | Entries | Share | Documented as useful? |
|---|---|---|---|
MIG | 22 | 44% | Yes — "migration engine" |
SP | 15 | 30% | Yes — "setup platform" |
CONX | 9 | 18% | Yes — "compatibility information" |
MOUPG | 1 | 2% | Listed in the nine |
DU | 1 | 2% | Not in the nine. Documented elsewhere as "DU = Driver/device updates". |
| untagged | 2 | 4% | Summary and [SetupPlatform.exe] lines. |
Microsoft says SP, MIG and CONX are the three that matter for troubleshooting Windows Setup errors. On a real device, those three are 46 of 50 entries — 92 percent of the error log. The documentation is not being vague. It is telling you precisely where to look.
The whole thing, as a runbook
- Do not retry. Copy
C:\$Windows.~BT,C:\Windows\PantherandC:\Windows\Logs\Mosetupsomewhere safe first. - Check which phase folders exist. No Rollback folder means it never got that far.
- Read
HKLM\SYSTEM\Setup\SetupDiag\Results. Setup may have diagnosed it for you already. - If that is empty, look for Application event 1001 named
WinSetupDiag02and readP5andP6. If the log has rolled, grepsetupact.logforReporting error event. - Use the extend code's first digit to pick the folder — then let the log's own phase text overrule the table.
Get-Itemeverysetuperr.login every phase folder. Zero bytes eliminates a phase for free.- Read the non-empty
setuperr.logend to end. It is measured in tens of lines. - Find the last occurrence of the result code from step 4. Look for
Shell application requested abortandAbandoning apply due to error for object. - Decode the Win32 codes with
certutil -error. Expect it to fail on0xC18xxxxxand0xC19xxxxx; use the documented tables for those. - Take the timestamp into
setupact.logand read theInfolines either side. That is where the object, plug-in or hive is named. - Only now widen:
miglog.xmlfor MIG failures,BlueBox.logfor payload and WSUS problems,setupapi.dev.logfor drivers,CBS.logat the same timestamp for servicing. - Only now decide on a fix.
Twelve steps, and the first ten are read-only. The 547 MB file does not get opened until step 10, and by then you know exactly which second of it you need.
References
- Log files and resolving upgrade errors — the per-phase log location table,
setupact.logversussetuperr.log, the log entry structure and component list, and the ten-step "Analyze log files" procedure including the last-occurrence search and the two abort strings. - SetupDiag — "the last failure in the log file is typically the fatal error, not the first one", the automatic-invocation parameters and registry path, the offline log folders to copy, and the full rule catalogue including
FindAbruptDownlevelFailure. - Upgrade error codes — result codes versus extend codes, the phase and operation tables, the 1607 versioning caveat, and the
0xC1900101guidance. - Troubleshooting upgrade errors — the four phases plus uninstall, the failure character of each phase, the
SetupProgressvolatile registry key, and "Use the SetupDiag tool before you begin manually troubleshooting". - Windows Error Reporting — Application log event 1001, event name
WinSetupDiag02, and the P1–P10 parameter table whereP5is the result code andP6the extend code. - Resolve Windows upgrade errors — the index for this whole section, with the 100 to 400 level ordering that makes a sensible escalation path.
- Win32 error codes (MS-ERREF) — the lookup table behind
ERROR_CANCELLED,ERROR_PROCESS_ABORTED,ERROR_FILE_CORRUPTand the rest.
All measurements in this article were taken read-only from a live Windows 11 Enterprise 25H2 device, build 26200.9168, on 23 August 2026. No log file was modified, and no repair action was performed. File sizes, line counts, component histograms and registry values were re-measured for this article rather than quoted from memory.