A patch-compliance dashboard flags forty devices as missing the August 2026 cumulative update. You pick one, connect to it, and run the same check the dashboard ran. It agrees: no KB5121003. Then you look at the build number. The device is on 26200.9168.
Microsoft's own release-information page says 26200.9168 is KB5121003. The update is installed. The dashboard is wrong, the device is compliant, and the reason is a documented limitation in the one cmdlet almost every patch report in the world is built on.
Get-HotFix wraps the Win32_QuickFixEngineering WMI class, and Microsoft documents that this class "returns only the updates supplied by Component Based Servicing (CBS)" and that updates from Windows Installer or the Windows Update site "are not returned". On the real device measured for this post, Get-HotFix returned four rows, while the package inventory on the same box reported 121 Installed, 199 Superseded and 95 Staged packages. The class also has no concept of supersedence, so a row is a historical event, not current state. The authoritative answer for a cumulative update is CurrentBuild plus UBR, which is the pair Microsoft publishes against every KB. Diagnose with four sources, understand why they legitimately disagree, and stop asking "is KB X present".
The problem: a four-row table is being read as a patch inventory
Here is the measurement that started this. A live Windows 11 25H2 Enterprise device, corporate managed, fully patched as of August 2026. The whole of Get-HotFix on that machine is four rows.
Three of those four are identifiable from Microsoft's published KB articles, and the identification is instructive.
KB5121003 is the August 2026 cumulative update, published as "OS Builds 26200.9168 and 26100.9168". KB5123304 is the servicing stack update that Microsoft's KB article says is included in it: "Includes KB5123304 (Build 26100.9156)". KB5120708 is not a Windows update at all in the sense a compliance report cares about, it is the "Cumulative Update for .NET Framework 3.5 and 4.8.1".
So of four rows, one is the operating system patch, one is a component of that same patch, and one is a .NET Framework payload that happens to ship through CBS. The report is three-quarters noise and one-quarter signal, and nothing in the output tells you which is which.
Now look at what the same device holds when you ask the servicing stack directly rather than through WMI.
Four hundred and fifteen packages against four rows. That gap is not a bug and it is not corruption on this device. It is the documented behaviour of the class, and it produces three distinct failure shapes in a compliance report.
The false negative. A KB was delivered by a mechanism Win32_QuickFixEngineering does not enumerate, so the query returns nothing and the device is reported as unpatched. This is the one that generates emergency remediation against machines that are already fine.
The false positive. A row exists for a KB whose package has since moved to Superseded, or whose files were rolled back. The row is a record that an install event happened once. It is not an assertion about the current state of the machine.
The null answer. The device is patched, the KB number you are testing was folded into a combined package, and no row carries that number in any form. The query is not wrong so much as unanswerable.
if (-not (Get-HotFix -Id KB5121003)). Microsoft's own Get-HotFix documentation demonstrates exactly this pattern in its "Verify if an update is installed" example, and the pattern is sound only for updates you already know were delivered by CBS. Wire it to a monthly cumulative update across a mixed fleet and you will be pushing an LCU at devices that already have it, which at best wastes bandwidth and at worst starts a reboot storm.Why it happens: QuickFixEngineering is a CBS-only keyhole
This is not folklore. Microsoft states the limitation in the class reference and repeats it in the cmdlet reference, in almost identical words.
From the Win32_QuickFixEngineering class page: the class "returns only the updates supplied by Component Based Servicing (CBS). These updates are not listed in the registry. Updates supplied by Microsoft Windows Installer (MSI) or the Windows update site (https://update.microsoft.com) are not returned by Win32_QuickFixEngineering."
The Get-HotFix Notes section carries the same sentences, because the cmdlet is a thin wrapper. There is no additional intelligence in the PowerShell layer. If the class cannot see it, the cmdlet cannot see it.
Reason one: the class has no state, only history
Compare the two data models side by side and the gap becomes obvious. DISM exposes a documented enumeration for package state. Every package on the device sits in exactly one of these.
| State | Value | Microsoft's description |
|---|---|---|
| DismStateNotPresent | 0 | The package or feature is not present. |
| DismStateUninstallPending | 1 | An uninstall process for the package or feature is pending. Additional processes are pending and must be completed before the package or feature is successfully uninstalled. |
| DismStateStaged | 2 | The package or feature is staged. |
| DismStateRemoved | 3 | Metadata about the package or feature has been added to the system, but the package or feature is not present. |
| DismStateInstalled | 4 | The package or feature is installed. |
| DismStateInstallPending | 5 | The install process for the package or feature is pending. Additional processes are pending and must be completed before the package or feature is successfully installed. |
| DismStateSuperseded | 6 | The package or feature has been superseded by a more recent package or feature. |
| DismStatePartiallyInstalled | 7 | The package or feature is partially installed. Some parts of the package or feature have not been installed. |
Now look at what Win32_QuickFixEngineering gives you. Its properties are Caption, Description, InstallDate, Name, Status, CSName, FixComments, HotFixID, InstalledBy, InstalledOn and ServicePackInEffect.
There is a Status property, but it is inherited from CIM_ManagedSystemElement and its documented values are the generic health vocabulary: OK, Error, Degraded, Unknown, Pred Fail, Starting, Stopping, Service. Nothing in that list means "superseded" and nothing means "staged, pending reboot".
That is the structural problem in one line. The class was designed to answer "did this QFE ever get applied", not "what is this device's current servicing state". Ninety-five staged packages on the lab device are invisible to it, because staged is a state and the class does not model states.
Reason two: combined SSU and LCU packages
Microsoft has shipped the servicing stack update inside the cumulative update since February 2021. The servicing stack updates page puts it plainly: "the cumulative update includes the latest servicing stack updates, providing a single combined cumulative update payload for Windows Update, Windows Server Update Services (WSUS), and the Microsoft Update Catalog."
One thing goes in. What comes out the other side, as far as an installed-updates enumeration is concerned, is not guaranteed to be one row with the number you deployed. The lab device is a clean demonstration: it shows both KB5121003 and KB5123304, the LCU and the SSU that Microsoft's own KB article says is included in it. A report that counts rows would call that two updates. It was one deployment.
The inverse is the situation that actually hurts. If your report only ever sees the SSU row and the LCU number never appears, every device looks like it is missing the security update it definitely installed.
Reason three: the date column cannot be trusted for ordering
Every row in the lab output has an InstalledOn of 12:00:00 AM. That is not a coincidence and it is not this device being odd. Microsoft warns about the property directly.
InstalledOn "may use different formats, depending on when the QuickFix was installed. Most systems use a standard date format ... However, some systems may return a 64-bit hexidecimal value in the Win32 FILETIME format." A sort that assumes a parseable date will silently mis-order or throw on those rows. Second, the wmic qfe command that older RMM checks still shell out to is on the way out: Microsoft lists the WMIC utility as deprecated and says it "will be removed in an upcoming Windows release", that it is "superseded by Windows PowerShell for WMI", and that the WMIC Feature on Demand "will be disabled by default". WMI itself is not affected, only the command-line tool. If your compliance collector calls wmic, it has an expiry date independent of everything else in this post.Reason four: duplicates, by design
The class reference includes a Remarks line that is easy to miss and awkward in practice: "Because updates are stored in two places, an enumeration of this class can result in duplicates." So a naive count of returned objects is not a count of installed updates, and a Group-Object HotFixID is not paranoia.
There is also a documented contradiction worth knowing about, because it will confuse you the first time you go looking. The class page says these updates "are not listed in the registry", yet the MappingStrings qualifier on HotFixID, InstalledOn, InstalledBy, FixComments and ServicePackInEffect all point at one registry key.
Treat that key as a legacy artefact rather than a source of truth. The practical takeaway is that reading it directly will not rescue you, because it is the same impoverished view the class already gives you.
The binaries actually involved
Four separate code paths are in play once you start cross-checking sources, and it helps to know which file you are talking to.
| Binary | Role | Where it lives |
|---|---|---|
CIMWin32.dll | The CIM provider that implements Win32_QuickFixEngineering. This is the code Get-HotFix ultimately calls. | Registered as a WMI provider in the Root\CIMV2 namespace, class defined in CIMWin32.mof. Resolve the path on your own device rather than assuming it. |
Wuapi.dll | Implements the Windows Update Agent COM surface, including IUpdateSearcher and IUpdateHistoryEntry. The Microsoft.Update.Session program identifier creates into this. | Named as the implementing DLL in Microsoft's WUA interface reference. |
DismAPI.dll | Exports the DISM API that defines DismPackageFeatureState, the state values Get-WindowsPackage surfaces. | Named as the API location in Microsoft's DISM API reference. |
wuaueng.dll | The Windows Update Agent engine. Its file version is the practical fingerprint for which WUA build a device is running. | Measured on the lab device at file version 1509.2607.1012.0. |
cbscore.dll | The CBS core the servicing stack loads. Everything in the DISM package view comes from here. | Not System32. It sits in the versioned WinSxS servicing-stack component directory, which a real CBS.log "Loaded Servicing Stack" line confirms. |
TrustedInstaller.exe / TiWorker.exe | FileDescription "Windows Modules Installer" and "Windows Modules Installer Worker". The processes that actually move packages between states. | TrustedInstaller.exe is in C:\Windows\servicing, not System32. |
How to verify: four sources and what each one can actually see
Work the steps in order. Each one narrows the question, and the point of doing them in order is that you learn why the sources disagree rather than just picking the one you like.
1Build and UBR
The installed cumulative update, expressed as the number Microsoft publishes. Ground truth for an LCU.
2DISM package state
Every package the servicing stack tracks, with a real state value including Superseded and Staged.
3WUA session history
What the update agent attempted, in what order, and whether each attempt succeeded.
4Event log and Settings
The human-readable timeline, useful for correlating a failure to a moment.
Step 1: read the build and the UBR, because that is the answer
A cumulative update changes the operating system build revision. That is what makes CurrentBuild plus UBR the authoritative pair: Microsoft publishes every release against exactly that pair, so you can resolve a device to a KB without asking the device what KBs it thinks it has.
| Value | Meaning | What to look for |
|---|---|---|
CurrentBuild | The OS build. Fixed for the life of a release: 26100 is 24H2, 26200 is 25H2. | Use this, not ProductName, to identify the release family. Any value of 22000 or above is Windows 11. |
UBR | Update Build Revision. The number a cumulative update increments. | Combined with CurrentBuild it gives the full build. On the lab device: 9168. |
DisplayVersion | The marketing release label. | 25H2 on the lab device. This is the value to report, not ReleaseId. |
ProductName | A legacy product string. | Do not key detection on this. On a genuine Windows 11 25H2 Enterprise device it still reads Windows 10 Enterprise. Scripts that match on it fail silently. |
EditionID | SKU identifier. | Enterprise, Professional, and so on. Matters because support lifecycles differ by edition. |
BuildLabEx | Full build lab string including the revision and branch. | Useful as a corroborating read when you suspect a partially applied update. |
There is one trap in this method and you should know it before you build a report on it. The UBR alone is ambiguous across releases. Microsoft's release-information page lists the same August 2026 update, KB5121003, as build 26200.9168 for 25H2 and 26100.9168 for 24H2. Identical revision, different build, same KB. So a compliance rule that tests UBR -ge 9168 without also pinning CurrentBuild will pass a 24H2 device against a 25H2 baseline.
Step 2: enumerate packages, and read the states
Get-WindowsPackage with -Online targets "the running operating system on your local computer" and returns the servicing stack's own view. This is where the 199 Superseded and 95 Staged packages on the lab device live.
Two limitations to hold in mind. Microsoft states you cannot use the cmdlet "to obtain detailed package information for .msu files, only .cab files", which matters if you are trying to inspect a downloaded update rather than an installed one. And a Staged count is not a problem by itself, it is a normal resting state, so do not go hunting corruption because 95 packages are staged.
The valuable move here is matching the KB number in the package name against the state. A package sitting at InstallPending tells you the device needs a reboot to finish, which is a completely different remediation from "the update never arrived", and Get-HotFix cannot distinguish those two situations at all.
Step 3: ask the update agent what it actually did
The Windows Update Agent keeps its own session history, reachable through the documented COM surface in Wuapi.dll. IUpdateSearcher exposes GetTotalHistoryCount, which "returns the number of update events on the computer", and QueryHistory, which "synchronously queries the computer for the history of the update events". You create the searcher from the Microsoft.Update.Session program identifier.
Each entry is an IUpdateHistoryEntry carrying Date, Title, Operation, ResultCode, HResult, UnmappedResultCode, ClientApplicationID and ServerSelection. Two of those come back as integers you have to decode.
| Property | Value | What it means |
|---|---|---|
Operation | 1 / 2 | uoInstallation / uoUninstallation. A 2 against a KB you expect to be present is the smoking gun for a rollback. |
ResultCode | 0 | orcNotStarted - the operation is not started. |
ResultCode | 1 | orcInProgress - the operation is in progress. |
ResultCode | 2 | orcSucceeded - completed successfully. This is the only clean result. |
ResultCode | 3 | orcSucceededWithErrors - complete, but errors occurred and "the results might be incomplete". Frequently mis-reported as success. |
ResultCode | 4 | orcFailed - failed to complete. Read HResult and UnmappedResultCode next. |
ResultCode | 5 | orcAborted - the operation is canceled. |
Step 4: correlate against the event log
The event log is where you pin a state change to a moment. Two channels matter, and the Event IDs below were observed on the lab device rather than copied from a list.
| Event ID | Message | What it tells you |
|---|---|---|
| 26 | Windows Update successfully found N updates | Scan completed and reached a source. If N is 0 on a device you believe is behind, your problem is scoping or policy, not installation. |
| 41 | An update was downloaded | Payload arrived. If you see 41 but never a corresponding state change, the failure is in install, not in transfer. |
| Event ID | Message | What it tells you |
|---|---|---|
| 2 | Package <KB> was successfully changed to the Staged state | CBS accepted the package. Staged is not installed. This event is the single clearest explanation of why a device can be genuinely mid-update and still absent from every KB-presence report. |
The Settings update history page under Windows Update > Update history is the fourth lens, and it is worth understanding what it is rather than dismissing it. It is a rendering of the same agent history you read programmatically in step 3, grouped into friendly categories, which means it inherits every limitation of that source: it is a log of attempts, not a statement of current state, and it will happily show an entry for an update whose package has since gone Superseded. Its one genuine advantage is that it resolves KB numbers to human titles, so it is often the fastest way to confirm that the LCU number you cannot find anywhere else really did arrive as part of a combined package. Treat it as a corroborating read for a single device, never as a fleet data source.
If you need more depth than the event log gives you, the log files are documented. Microsoft's Windows Update log files page lists C:\Windows\Logs\WindowsUpdate for the agent trace, C:\ProgramData\USOShared\Logs for UpdateSessionOrchestration.etl from the Update Orchestrator Service, and %systemroot%\Logs\CBS for CBS.log, which "provides insight on the update installation part in the servicing stack". Convert the ETL traces with Get-WindowsUpdateLog.
Step 5: know why the four sources legitimately disagree
This is the part that turns a confusing pile of contradictory output into a diagnosis. They are not four attempts to answer the same question. They are four different questions.
| Source | The question it answers | What it cannot see | On the lab device |
|---|---|---|---|
CurrentBuild + UBR | Which cumulative update is actually applied to this OS right now? | Anything that is not an OS cumulative update. Says nothing about .NET, drivers or optional components. | 26200.9168, which resolves to KB5121003 |
Get-WindowsPackage -Online | What does the servicing stack currently hold, and in what state? | MSI-delivered software. Cannot read .msu detail. Package names are not KB numbers. | 121 Installed, 199 Superseded, 95 Staged |
WUA history via Wuapi.dll | What did the update agent attempt, and did it succeed? | Anything installed by another mechanism, and anything whose history was cleared. Not a state view. | Full attempt timeline with result codes |
Get-HotFix | Which CBS-supplied QFE rows were ever recorded? | MSI updates, Windows Update site updates, all package states, supersedence, and reboot-pending. | 4 rows |
The fix: pivot from KB presence to a build floor
The fix is not a different cmdlet. It is a different question. Stop asking "is KB X installed on this device" and start asking "is this device at or above the build floor for its release".
Before any of that, though, protect the evidence. The two reflex actions in Windows Update troubleshooting both destroy the exact data this diagnosis depends on.
wuauserv and rename or delete SoftwareDistribution. That directory holds Datastore.edb, the agent's local update cache, and clearing it takes the WUA session history you would read in step 3 with it. You will have destroyed the only record of what the agent attempted and why it failed, and the KB-presence check that misled you in the first place will still return the same wrong answer. Equally, do not run DISM /Online /Cleanup-Image /StartComponentCleanup /ResetBase to "tidy up" mid-investigation. It removes superseded components, which erases the 199-row Superseded view that tells you the servicing history, and it removes your ability to uninstall the update you are investigating.With the evidence intact, the report rewrite is short. Read build, UBR and DisplayVersion. Compare against a per-release floor. Report the delta in revisions, not a boolean.
Handle the things a build floor cannot cover as separate, explicitly scoped checks rather than folding them into the same boolean. .NET Framework cumulative updates such as KB5120708 genuinely do arrive through CBS, so Get-HotFix -Id is a legitimate tool for them. Driver and firmware updates need their own inventory. Application patching belongs nowhere near this report.
That separation is the real deliverable. Get-HotFix is not a broken cmdlet. It is a correctly functioning, narrowly scoped one that has been quietly promoted to a job it was never documented as able to do.
wusa.exe /uninstall /kb:####### /quiet as deprecated, and that "the uninstall command with /quiet switch fails with event ID 8 in the Setup event log". Any automation still calling it is failing silently. And because SSU and LCU now ship combined, the servicing stack portion of a monthly update generally cannot be removed, so "just uninstall last month's patch" is not the reliable rollback it used to be.Proof it worked: one device through all four lenses
Here is the whole diagnosis on one machine, in one pass, with every number measured rather than assumed.
Read the reconciliation and the story is unambiguous. The device is fully patched. The servicing stack version it reports, 10.0.26100.9156, is precisely the build Microsoft's KB article names for the servicing stack update carried inside KB5121003. The build revision, 9168, is precisely the revision Microsoft publishes for that KB on 25H2. Two independent sources agree, and they agree on a specific number rather than a yes.
Meanwhile Get-HotFix saw four of 415 tracked packages. It happened to include the LCU on this device, which is exactly what makes it dangerous: it will look correct on plenty of machines and then quietly be wrong on the ones where it matters, and there is nothing in the output to tell you which kind of machine you are looking at.
One last honest note about the 25H2 build itself. The lab device carries the policy value TargetReleaseVersionInfo set to 24H2 under HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate, while the installed DisplayVersion is 25H2. The device is running a later release than the one it is pinned to, which means the pin has been overtaken rather than enforced. That is a separate diagnosis and a separate post, but it is a good illustration of the same lesson: the configured intent and the measured state are different things, and only one of them is on the disk.
References
- Win32_QuickFixEngineering class - the CBS-only limitation, the full property list, the duplicates remark, the
InstalledOnFILETIME warning, and theMappingStringsregistry key. - Get-HotFix - the cmdlet reference, its Notes section repeating the limitation, and the "verify if an update is installed" example.
- Get-WindowsPackage - the
-Onlineparameter and the .msu limitation. - DismPackageFeatureState enumeration - the eight documented package states.
- IUpdateSearcher and IUpdateHistoryEntry - the WUA history surface in
Wuapi.dll. - OperationResultCode and UpdateOperation - the enumerations behind the integer history fields.
- Windows 11 release information - the authoritative build-to-KB table, including 26200.9168 and 26100.9168 for KB5121003.
- Servicing stack updates - the combined SSU and LCU payload.
- Windows Update log files - log locations and
Get-WindowsUpdateLog. - Deprecated features in the Windows client - the WMIC utility and
wusa.exe /uninstall /quietentries. - KB5121003 (OS Builds 26200.9168 and 26100.9168) - the release note naming the included SSU, KB5123304 (Build 26100.9156).