Somebody reports that updates have stopped. You remote in, open C:\Windows\WindowsUpdate.log, and the entire file is four lines long.
Not truncated. Not empty. Four lines, 276 bytes, telling you to run a PowerShell command.
At this point a lot of admins conclude logging is broken, or that something wiped the log. Neither is true. That file has been a signpost rather than a log since Windows 8.1, and the real evidence sits in ninety-nine binary trace files in a subdirectory nobody opens.
This post is about getting at that evidence, and reading it, before you touch a service.
Microsoft documents that starting in Windows 8.1, the Windows Update client uses Event Tracing for Windows (ETW) instead of writing a text log. The real evidence is a rolling set of .etl files in C:\Windows\Logs\WindowsUpdate, written by a live ETW session called WindowsUpdate_trace_log. Get-WindowsUpdateLog merges and decodes those files into one readable WindowsUpdate.log, by default on your Desktop. It used to need a Microsoft symbol server; from Windows 10 1709 onward it does not, and the SymbolServer parameter is deprecated. Decode first. The reflex fix of stopping wuauserv and deleting SoftwareDistribution destroys the datastore that the decoded log's identifiers point at, and -ForceFlush stops services too, so order matters.
The problem: WindowsUpdate.log is 276 bytes of pointer text
The file still exists. That is what makes the trap effective.
If C:\Windows\WindowsUpdate.log had been deleted outright, everyone would have learned the new method in a week. Instead Microsoft left a stub, so the muscle memory still works, the file still opens, and the result looks like a broken log rather than a redirect.
Here is that file on the Windows 11 device I used. Read-only, nothing changed.
Note the LastWriteTime: 10:27 that same morning. Something rewrote the stub hours earlier. That timestamp is the best clue on the device, and I will come back to what it means.
The damage is not the wasted five minutes. It is what people do next. Finding no log, the reflex is the standard reset: stop wuauserv and bits, delete C:\Windows\SoftwareDistribution, restart, retry.
Sometimes that clears the symptom. It also guarantees you never learn the cause, which matters on a fleet, because that cause is about to hit two hundred more devices.
%WINDIR%\SoftwareDistribution\Datastore\Datastore.edb, and that the "Local IDs" you see in Windows Update logs are serial numbers issued by that datastore. Delete SoftwareDistribution and every local identifier in the log you have not decoded yet becomes unresolvable. You have not fixed the device, you have shredded the only copy of the evidence and reset the counters that would have told you whether the problem recurred.Why it happens: the agent writes ETW, and the agent is not where you think
What ETW actually is
Event Tracing for Windows is a kernel-level tracing facility. A component registers as a provider with a GUID and emits events into a buffer; a session subscribes to providers and drains those buffers to disk as .etl files.
The part that matters for troubleshooting is what the provider writes. To keep tracing cheap enough to leave on permanently, providers do not write your readable sentence. They write a numeric event ID plus raw arguments, and the human-readable format string lives outside the trace, in the binary's metadata or symbols.
That is the whole reason a decode step exists. The .etl file is not encrypted or corrupt, it is shorthand. Opening one in Notepad gives you mojibake because the sentence you want was never in there.
Three sessions, not one
Windows Update does not have a single log. Three separate ETW sessions run on an idle, healthy Windows 11 device, covering different stages of the same operation. Decode only the first and you miss entire classes of failure.
| Session | Writes to | Covers |
|---|---|---|
WindowsUpdate_trace_log | C:\Windows\Logs\WindowsUpdate | The Windows Update Agent: scan, applicability, metadata, download, installer handoff. |
UpdateSessionOrchestration | C:\ProgramData\USOShared\Logs | The Update Session Orchestrator: the decisions about when to scan, download, install and reboot. |
NotificationUxBroker | C:\ProgramData\USOShared\Logs | The toast and banner the user did or did not see. |
Microsoft's guidance on which to read is unusually direct: go to the orchestrator logs when "updates are available but download isn't getting triggered", when "updates are downloaded but installation isn't triggered", and when "updates are installed but reboot isn't triggered".
A clean split. Agent log for "the update never arrived". Orchestrator log for "it arrived and then nothing happened".
The binaries behind the agent
Every component tag in the decoded log maps to a real file. Knowing which file, and where it lives, is what lets you check a version or spot a tampered install. These versions come from the device I used, read with Get-Item.
| File | Location and role | Version on this device |
|---|---|---|
wuaueng.dll | System32. Described as "Windows Update Agent". The agent core, and the source of the Agent, ProtocolTalker and DownloadManager traces. | 1509.2607.1012.0 |
wuapi.dll | System32. Described as "Windows Update Client API": the COM surface documented in wuapi.h, exposing IUpdateSession and IUpdateSearcher. ComApi traces come from callers crossing this boundary. | 1509.2607.1012.0 |
MoUsoCoreWorker.exe | Not in System32 - it is at C:\Windows\UUS\amd64. Described as "MoUSO Core Worker Process": the orchestrator worker that calls the agent. | 1509.2607.1012.0 |
usosvc.dll | System32. "Update Session Orchestrator Service" - the service DLL behind UsoSvc. | 10.0.26100.8737 |
usoapi.dll | System32. "Update Session Orchestrator API". | 10.0.26100.8521 |
UsoClient.exe | System32. Command-line entry point into the orchestrator. | 10.0.26100.8328 |
Look at the version column, because it holds the most useful thing in this table. The orchestrator files carry 10.0.26100.x, a normal Windows build number. The agent files carry 1509.2607.1012.0, which is not a Windows build number at all. And MoUsoCoreWorker.exe sits under C:\Windows\UUS beside a uusp.json and per-architecture folders, not in System32 with everything else.
The practical consequence: the update agent is serviced on its own track with its own version lineage, independent of the OS build. This device reports OS build 10.0.26200.9168 and an agent at 1509.2607.1012.0. Comparing an agent version against an OS build tells you nothing.
C:\Windows\System32\usocore.dll. On the Windows 11 device I checked, that file does not exist. What is in System32 is usosvc.dll, usoapi.dll, usodocked.dll and usocoreps.dll (the last one being the "USO Core Worker Proxy Stub"), while the actual worker executable is off in C:\Windows\UUS\amd64. A detection rule built on Test-Path against the wrong name will report a healthy device as broken, forever. Verify the filename on a known-good machine before you ship the rule.How to verify: prove the trace is running before you decode it
Do not start by decoding. Start by establishing that there is anything worth decoding, because "the log is empty" and "the trace was never running" need different fixes. Follow these in order; each step either clears the device or hands you the root cause.
Step 1. Confirm the ETW sessions are alive. One command settles the "logging is broken" question outright.
Step 2. Read the session configuration. Almost nobody does this, and it answers the two questions that decide whether your evidence still exists: where is it, and how long does it survive?
Three readings matter here.
Buffers Lost is your data-integrity check. Zero means the trace kept up. Non-zero means events were dropped under load, so a gap in the decoded log at the interesting moment may be an artefact rather than a clue.
The Output Location filename solves the timestamp puzzle from earlier. It reads WindowsUpdate.20260823.102701.286.1.etl, and the stub file's LastWriteTime was 10:27:01 that same day. Same moment: the session rolled to a new segment and rewrote the stub, exactly what you expect after a reboot or service restart. A stub timestamp far in the past is a real red flag; one matching the live segment is a healthy device.
Segment Max Size is the one with a deadline attached. Traces roll. If a user reports a failure from three weeks ago, the trace covering it may be gone, and no fix recovers it.
.etl files first, before you try anything. A plain Copy-Item C:\Windows\Logs\WindowsUpdate\*.etl to a share takes seconds, needs no decoding, and freezes the evidence while you still have it. Decode later at your desk, on any machine running 1709 or newer. On my device the whole directory was 99 files and about 42 MB, which is nothing next to being unable to answer the question at all.Step 3. Inventory the ETLs and check freshness.
That last distinction is the payoff for doing steps 1 to 3 at all. An old newest-ETL is not a logging problem, it is the symptom: the agent is not being invoked, so look at the orchestrator next.
Where the agent keeps its state
The decoded log constantly refers to service IDs, SLS decisions and reporting state. Those live under one parent key.
| Value | Meaning | What to look for |
|---|---|---|
Services | Update services registered with the agent (Windows Update, Microsoft Update, WSUS, DCat Flighting). | Cross-reference the ServiceId GUIDs on ProtocolTalker lines. An unexpected service ID here explains an unexpected scan source. |
SLS | Service Location Service cache: the endpoints the agent was told to talk to. | Pair with SLS-tagged log lines. A stale cache alongside SLS errors points at network or proxy, not the agent. |
Orchestrator | Scan, download and install scheduling state owned by USO. | Populated but no recent activity in the decoded log means the orchestrator is not calling the agent. |
Reporting | State for reporting results back to the service. | Correlate with Reporter lines when a device installs updates but never reports compliance. |
Auto Update | Agent-side automatic update state, including the RebootRequired marker. | RebootRequired here is a genuine pending-reboot signal. Absent on the clean device I checked, as expected. |
ClientCache | Cached client-side metadata. | Relevant when Metadata lines show repeated refetches of the same data. |
Trace | Does not exist. | Older blog posts tell you to create trace flags here. There is no Trace subkey and creating one does nothing. Tracing is configured through the ETW session. |
The fix: Get-WindowsUpdateLog, run properly
Now decode. Microsoft's description is worth reading literally: the cmdlet "merges and converts Windows Update .etl files into a single readable WindowsUpdate.log file".
Two verbs. It merges the directory of segments into one chronological stream, and it converts shorthand events into sentences. The output is a new file, not a view onto the old ones.
| Parameter | What it does | When to use it |
|---|---|---|
-ETLPath | Source. A directory, a single .etl, or a comma-separated list. Defaults to the local trace directory. | Point it at ETLs copied off another device. This is what makes offline triage possible. |
-LogPath | Destination file. Defaults to WindowsUpdate.log on the current user's Desktop. | Always set it. A case folder beats littering Desktops. |
-IncludeAllLogs | Decodes Windows Update, the orchestrator and the update UX into WindowsUpdate.log, USO.log and UX.log. Mutually exclusive with every other parameter. | Any "downloaded but never installed" or "never prompted the user" case. |
-ForceFlush | Forces the agent to flush pending traces to .etl. Microsoft documents that this stops the Update Orchestrator and Windows Update services. Needs elevation. | Only when you need the last seconds before a failure. Read the warning below first. |
-ProcessingType | XML (default) or CSV for intermediate temp files, which land in $env:TEMP\WindowsUpdateLog. | Rarely. Switch to CSV to sidestep XML schema warnings. |
-ForceFlush is not a harmless "get me the freshest data" switch. Microsoft states plainly that it stops the Update Orchestrator and Windows Update services to do its job. If you are troubleshooting a live in-progress operation, running it will interrupt that operation and change the very state you are measuring. Run the cmdlet without -ForceFlush first. You almost always have what you need, because the session flushes on its own.The symbol server history, and why it stopped mattering
If you inherited a runbook saying this decode needs internet access to Microsoft's symbol server, that runbook is at least eight years old, but it was not wrong when written.
Microsoft documents the boundary precisely. For Windows 10 versions before 1709 (build 16299), the cmdlet required access to a Microsoft symbol server and decoding had to be done from a pre-1709 build. From 1709 onward no symbol server is needed, and logs must be decoded on 1709 or higher. The SymbolServer parameter survives in the older documentation, explicitly marked deprecated for 1709 and above.
That is why the old runbook produced garbage logs full of unresolved placeholders on locked-down networks: the decode was silently failing to fetch format strings. On anything modern that data ships with the OS and the failure mode is gone.
Reading the result: the format and the tags
Microsoft documents the decoded log as four identities: timestamp, process and thread ID, component name, and update identifiers. In practice you read the component column and ignore most of the file. Here is the documented list, condensed to the tags that carry weight in a real investigation.
| Tag | What it is | Read it when |
|---|---|---|
Agent | The Windows Update agent itself: scan start and end, applicability results. | Always. "Found N updates" lands here. |
ProtocolTalker | Client-to-server sync. | Scan fails, times out, or hits the wrong endpoint. |
DownloadManager | Creates and monitors payload downloads. | Download stalls at 0% or errors. |
ComApi | The Windows Update API surface, i.e. wuapi.dll callers. | Identifying who triggered a scan; the caller is on the line. |
Handler, Setup | Installer handlers, CBS among them. | Download succeeds, install fails. Your handoff to CBS.log. |
EEHandler | Evaluating update applicability rules. | An update you expect is never offered. |
DataStore | Caching update data locally, i.e. Datastore.edb. | Corruption suspicions, before you delete anything. |
IdleTimer | Tracking active calls and stopping the service. | Operations that abandon partway. |
WebServices, Deployment, Metadata, Shared, Reporter, Driver and SLS. Do not assume an unfamiliar tag means a corrupt decode. Also note that Microsoft's structure documentation describes process and thread IDs as four hex digits each, which reflects the older format; the modern decoded output prints them as plain decimal columns.Proof it worked: one scan, three independent records
The test of a decode is not that a file appeared. It is whether you can take one event and find it in three places written by three different mechanisms. If they agree, your evidence is sound.
I ran the cmdlet against the real trace directory without -ForceFlush, so no service was touched.
The schema warning appeared on every batch, on a device with no update fault, and the cmdlet reported success each time. Treat it as noise unless the output file is missing.
Now the correlation. Here are the last lines of the decoded log, landing at 10:43:24 on 2026-08-23.
Earlier in the same file, an Agent-tagged line shows the same kind of operation from the agent's side rather than the API's, carrying the applicability arithmetic:
evaluated appl. rules of 702 out of 894 deployed entities
Twelve updates found, 702 applicability rules evaluated out of 894 deployed entities. When an update you expect is never offered, that ratio and the nearby EEHandler lines are where the answer is.
Now the third record. The same scans are reported independently to the event log, by the event channel below.
These are the real counts from that channel on the same device, over the retained window.
| Event ID | Message | What it tells you |
|---|---|---|
| 26 | Windows Update successfully found 0 updates. | A scan completed. The number is the answer to "did the device see the patch?". Seen 1,815 times here. Correlate the timestamp against the Agent or ComApi "Updates found" line to identify which caller ran it. |
| 41 | An update was downloaded. | Payload transfer completed. Seen 335 times. Pairs with DownloadManager lines in the decoded log, which carry the DO job ID and update GUID that this event omits. |
| 25 | Windows Update failed to check for updates with error 0x80240438. | Scan failure with the error code attached. Seen 24 times. Take the timestamp to the decoded log and read the ProtocolTalker and SLS lines around it. |
| 31 | Windows Update failed to download an update. | Download failure. Seen twice. The event does not say which update or why; the DownloadManager lines at that timestamp do. |
That table is the argument for this whole exercise. The event log tells you that something happened, with a timestamp. The decoded log tells you which update, caller, endpoint and error. Neither suffices alone, and the event log is the one that survives a SoftwareDistribution reset.
So the three records for the 10:43 scan agree: the live ETL segment WindowsUpdate.20260823.102701.286.1.etl was receiving events, the decoded log's final entry reads Updates found = 0 at 10:43:24, and Event 26 reads "successfully found 0 updates". Three mechanisms, one story. That is a trustworthy decode.
One last real detail, because it settles the version question from earlier. The agent stamps its own version into the device attributes it sends on every scan:
&WuClientVer=1508.2606.17012.0&OSVersion=10.0.26200.8875
&OSArchitecture=AMD64&InstallationType=Client&TenantId=REDACTED
On 10 August this device reported WuClientVer=1508.2606.17012.0. By 23 August the on-disk agent was 1509.2607.1012.0 - a full generation, while the OS build barely moved. If a scan fails on some devices and not others, WuClientVer beats the OS build as a comparison across the fleet, and it is only visible here.
References
- Get-WindowsUpdateLog - the cmdlet reference: full syntax, every parameter, the
-ForceFlushservice-stop warning, and the 1709 symbol-server boundary. - Get-WindowsUpdateLog (Windows Server 2016 view) - the older parameter set, still documenting
-SymbolServerand its deprecation. Useful when auditing an inherited runbook. - Windows Update log files - the log location table, the ETW-since-8.1 statement, the component tag list, the four-part log structure, the static-snapshot note, and the identifier schemes including
Datastore.edb. - Windows Update issues troubleshooting - worked decoded-log excerpts with
Agent,Misc,ProtocolTalkerandDownloadManagertags, plus the HTTP RANGE proxy requirement behind manyDownloadManagererrors. - Wuapi.h header - the Windows Update Agent API that
wuapi.dllimplements, includingIUpdateSession,IUpdateSearcherandISearchResult. This is the boundary theComApitag marks.
Everything measured here came from one Windows 11 Enterprise device, build 26200.9168, read-only except the decode itself.