How to Get Windows Update History Using PowerShell

To get the Windows update history in PowerShell, ask the Windows Update Agent through its Microsoft.Update.Session COM object. QueryHistory returns the same records you see on the Update history page in Settings:

$s = (New-Object -ComObject Microsoft.Update.Session).CreateUpdateSearcher()
$s.QueryHistory(0, $s.GetTotalHistoryCount()) | Select-Object Date, Title

Get-HotFix is quicker to type, but it shows only part of the picture. Below I compare both, decode the result codes, find failed updates, export to CSV and read the event log, in PowerShell 7 and Windows PowerShell 5.1.

List installed updates with Get-HotFix

Get-HotFix reads the Win32_QuickFixEngineering class and returns KB numbers with an install date. I sort newest first and format the date so it doesn’t depend on regional settings:

Get-HotFix |
    Sort-Object InstalledOn, HotFixID -Descending |
    Select-Object HotFixID, Description,
        @{ n = 'InstalledOn'; e = { $_.InstalledOn.ToString('yyyy-MM-dd') } }

Output:

HotFixID  Description     InstalledOn
--------  -----------     -----------
KB5129195 Security Update 2026-10-09
KB5126052 Update          2026-10-09
KB5124007 Security Update 2026-10-09
KB5054156 Update          2025-12-20

To check one update, pass its KB number to -Id. A missing KB writes an error, so silence it and test the result:

foreach ($kb in 'KB5129195', 'KB5000000') {
    if (Get-HotFix -Id $kb -ErrorAction SilentlyContinue) { "$kb is installed" }
    else { "$kb is not installed" }
}

Output:

KB5129195 is installed
KB5000000 is not installed

-Id doesn’t accept wildcards, but -Description does, for example -Description 'Security*'. To see what’s waiting to install instead, check for pending Windows updates.

Why doesn’t Get-HotFix show all updates?

Microsoft documents that Win32_QuickFixEngineering returns only updates supplied by Component Based Servicing. Updates from Windows Installer or the Windows Update site aren’t in it. On my test PC the gap was huge:

$searcher = (New-Object -ComObject Microsoft.Update.Session).CreateUpdateSearcher()
$history  = $searcher.QueryHistory(0, $searcher.GetTotalHistoryCount())
$installed = $history | Where-Object { $_.Operation -eq 1 -and $_.ResultCode -eq 2 }

"Get-HotFix entries:     $(@(Get-HotFix).Count)"
"Update history entries: $($history.Count)"
"Successful installs:    $(@($installed).Count)"

Output:

Get-HotFix entries:     4
Update history entries: 874
Successful installs:    844
Get-HotFix count compared with the Windows Update history count in PowerShell
Get-HotFix sees a handful of updates, the update history sees hundreds (PowerShell 7)

Defender intelligence updates, driver updates, the Malicious Software Removal Tool and Store app updates all land in the history but not in Get-HotFix. To match a cumulative update to your build, get the Windows version too.

Get the full Windows Update history with QueryHistory

QueryHistory(start, count) returns entries newest first, and GetTotalHistoryCount tells you how many exist. ResultCode is a number, so an array turns it into a word. I skip the daily Defender intelligence updates to see the Windows patches:

$searcher = (New-Object -ComObject Microsoft.Update.Session).CreateUpdateSearcher()
$history  = $searcher.QueryHistory(0, $searcher.GetTotalHistoryCount())
$results  = 'Not started', 'In progress', 'Succeeded', 'Succeeded with errors', 'Failed', 'Aborted'

$history |
    Where-Object { $_.Title -match 'KB\d+' -and $_.Title -notmatch 'Security Intelligence' } |
    Select-Object -First 5 @{ n = 'Date (UTC)'; e = { $_.Date.ToString('yyyy-MM-dd HH:mm') } },
        @{ n = 'KB'; e = { [regex]::Match($_.Title, 'KB\d+').Value } },
        @{ n = 'Result'; e = { $results[$_.ResultCode] } },
        @{ n = 'Title'; e = { $_.Title.Substring(0, [math]::Min(46, $_.Title.Length)) } } |
    Format-Table -AutoSize

Output:

Date (UTC)       KB        Result    Title
----------       --        ------    -----
2026-10-15 02:05 KB4052623 Succeeded Update for Microsoft Defender Antivirus antima
2026-10-09 06:07 KB890830  Succeeded Windows Malicious Software Removal Tool x64 -
2026-10-09 02:09 KB5129195 Succeeded 2026-09 Security Update (KB5129195) (26200.945
2026-10-08 09:20 KB5126052 Succeeded 2026-09 .NET Framework Security Update (KB5126
2026-09-18 17:48 KB4052623 Succeeded Update for Microsoft Defender Antivirus antima
PowerShell Windows Update history with QueryHistory showing KB numbers and results
The latest KB updates from the Windows Update history (PowerShell 7)

In my tests the Date values matched the event log time in UTC, so I label them that way. The KB column comes from a PowerShell regex match on the title, since the history has no KB property.

Operation is 1 for an installation and 2 for an uninstallation. ResultCode runs from 0 (not started) to 5 (aborted), with 2 for success and 4 for failure.

Find failed Windows updates

Group the history by ResultCode for a quick health check, then list the failures with their HResult in hex, the form error lists use:

$searcher = (New-Object -ComObject Microsoft.Update.Session).CreateUpdateSearcher()
$history  = $searcher.QueryHistory(0, $searcher.GetTotalHistoryCount())
$results  = 'Not started', 'In progress', 'Succeeded', 'Succeeded with errors', 'Failed', 'Aborted'

$history | Group-Object ResultCode | ForEach-Object { '{0,-10} {1}' -f $results[[int]$_.Name], $_.Count }

$history | Where-Object { $_.ResultCode -eq 4 -and $_.Title -match 'KB\d+' } |
    Select-Object -First 3 @{ n = 'Date (UTC)'; e = { $_.Date.ToString('yyyy-MM-dd') } },
        @{ n = 'KB'; e = { [regex]::Match($_.Title, 'KB\d+').Value } },
        @{ n = 'HResult'; e = { '0x{0:X8}' -f $_.HResult } } |
    Format-Table -AutoSize

Output:

Succeeded  844
Failed     26
Aborted    4

Date (UTC) KB        HResult
---------- --        -------
2026-09-03 KB2267602 0x80240016
2026-08-19 KB2267602 0x80240016
2026-06-10 KB2267602 0x80240016
PowerShell list failed Windows updates with HResult codes in Windows PowerShell 5.1
Result counts and failed KB updates with hex error codes (Windows PowerShell 5.1)

0x80240016 means another installation was running or a restart was pending, so the update simply retried later. Once you know why it failed, you can