Detect Windows Update Failures with PowerShell History Query · CloudEngineerHub
Detection & Remediation9 min read4 views
Detecting Windows Update Failures with PowerShell
Build a read-only Intune detection script that reads update history over COM, flags failed installs in the last 14 days, and names repeat offenders.
C
CloudEngineerHub Team
Published: September 16, 2026· Last Updated: September 28, 2026·9 min read
Every fleet has devices where updates fail quietly. The ring reports deployed, the dashboard is green for the population, and meanwhile one laptop has failed the same cumulative update six times. Nobody looks at update failure history until an incident forces them to, and by then the useful signal, which update kept failing and with what result, is buried under weeks of newer entries. The device itself keeps that history; the trick is reading it without an admin sitting at the console.
Visual Walkthrough
The engine behind every “Detect with PowerShell” runbook on this site.
Exit 1 flips the device into the remediation queue for this package.
remediate
3
Remediation executes
Fix script runs silently as SYSTEM with a 5-minute budget.
verify
4
Re-run detection
The same detection script must now exit 0 — proof, not hope.
telemetry
5
Results report
Per-device outcome lands in Reports → Remediations for trend analysis.
6
Repeat on schedule
The loop continues — drift gets caught the day it appears.
Continuous cycle
Proactive Remediations — The Detect → Fix → Verify Loop. 1. Detection script runs: Scheduled (daily/hourly): exit 0 healthy, exit 1 unhealthy — nothing more. — 2. Verdict: unhealthy: Exit 1 flips the device into the remediation queue for this package. — 3. Remediation executes: Fix script runs silently as SYSTEM with a 5-minute budget. — 4. Re-run detection: The same detection script must now exit 0 — proof, not hope. — 5. Results report: Per-device outcome lands in Reports → Remediations for trend analysis. — 6. Repeat on schedule: The loop continues — drift gets caught the day it appears.
C
CloudEngineerHub Team
Author
The CloudEngineerHub editorial team — Microsoft endpoint management and cloud engineers writing hands-on guides on Intune, Azure, Entra ID, Autopilot, Defender and PowerShell. Every article is reviewed against current Microsoft documentation.
The Windows Update agent records every install attempt locally, and the same COM session used to search for pending updates can also enumerate that history. This article turns that into an Intune detection script that walks the most recent history entries, counts the ones that failed within your look-back window, and names the repeat offenders. It pairs naturally with the pending-update detection in this series: one script tells you what is missing, this one tells you why it keeps coming back.
Symptoms
A cumulative update or .NET update fails on specific devices every month, often with the same result each time.
Windows Update history on the device shows a long column of failed entries for the same title.
The device re-offers the same update after every scan, and users report endless "we couldn't complete the updates, undoing changes" reboot cycles.
An audit or incident review asks for per-device update failure evidence and the only source is the device itself.
Possible Causes
Servicing stack or component store damage
Failed installs that repeat identically across attempts usually mean the servicing stack or the component store cannot process the package. The failure result repeats because the underlying condition does.
Corrupt download or datastore state
A damaged SoftwareDistribution store produces file-not-found style failures on otherwise healthy systems. The component reset post in this series is the usual fix once this script has identified the pattern.
Insufficient disk space
Updates fail when the free space on the system volume drops below what the install needs. The failure result often differs per attempt as available space shifts.
Third-party interference
Endpoint security products, disk cleanup tools running during installs, or drivers that hold files open can fail an install at commit time. These failures cluster around specific titles.
Botched previous install
An update that half-applied and rolled back leaves state that makes the next attempt fail early. History shows a tight series of failed attempts, sometimes interleaved with rollback events.
Detection Script
The script below is read-only and runs in 64-bit SYSTEM context. It reads the 50 most recent update history entries through the COM history API, filters to entries whose result code is 4 (the agent's Failed value) within the last 14 days, logs each failure with its date, title, and the result recorded by the device, and flags titles that failed repeatedly. It completes in seconds because it reads local data only.
PowerShell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#Requires -RunAsAdministrator
[CmdletBinding()]
param()
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
<#
.SYNOPSIS
Detection script: reports failed Windows Update installs by reading the
local update history through the Microsoft.Update.Session COM API.
.NOTES
Intended for Intune Scripts and Remediations, 64-bit SYSTEM context.
Read-only: the script never installs, uninstalls or repairs anything.
Exit 0 = no failed installs in the look-back window, 1 = one or more
failed installs found, 3 = unexpected error. ResultCode 4 is the
Windows Update agent's "Failed" value; HResult values reported in the
log are read from the device's own history store.
#>$logDir = 'C:\ProgramData\Company\Logs'$logFile = Join-Path $logDir'WindowsUpdateFailureDetection.log'$lookBackDays = 14$historyEntriesToRead = 50function Write-DetectionLog {
param([Parameter(Mandatory)][string] $Level, [Parameter(Mandatory)][string] $Message)
if (-not (Test-Path $logDir)) { New-Item -Path$logDir -ItemType Directory -Force | Out-Null }
"$([DateTime]::UtcNow.ToString('o')) [$Level] $Message" | Out-File -FilePath$logFile -Append -Encoding utf8
}
try {
Write-DetectionLog 'INFO''Windows Update failure detection run started.'$session = New-Object -ComObject Microsoft.Update.Session
$searcher = $session.CreateUpdateSearcher()
$cutoff = (Get-Date).ToUniversalTime().AddDays(-$lookBackDays)
Write-DetectionLog 'INFO'"Reading the $historyEntriesToRead most recent history entries (look-back window: $lookBackDays days)."$history = $searcher.QueryHistory(0, $historyEntriesToRead)
$failures = @()
$partialSuccesses = 0foreach ($entryin$history) {
if ($entry.Date -lt$cutoff) { continue }
if ($entry.ResultCode -eq4) {
$hresultText = 'not-recorded'if ($entry.HResult -ne0) { $hresultText = '0x{0:X8}' -f ($entry.HResult -band0xFFFFFFFF) }
$failures += [pscustomobject]@{
Date = $entry.Date
Title = $entry.Title
HResult = $hresultText
}
}
elseif ($entry.ResultCode -eq3) { $partialSuccesses++ }
}
if ($partialSuccesses -gt0) {
Write-DetectionLog 'WARN'"$partialSuccesses update(s) in the window completed with errors (ResultCode 3)."
}
if ($failures.Count -eq0) {
Write-DetectionLog 'INFO'"No failed update installs in the last $lookBackDays days."
exit 0
}
Write-DetectionLog 'ERROR'"$($failures.Count) failed update install(s) in the last $lookBackDays days:"foreach ($failurein ($failures | Sort-Object -Property Date -Descending)) {
Write-DetectionLog 'ERROR'" - $($failure.Date.ToString('yyyy-MM-dd')) | $($failure.Title) | Result: $($failure.HResult)"
}
$repeatOffenders = @($failures | Group-Object -Property Title | Where-Object { $_.Count -gt1 } | Sort-Object -Property Count -Descending)
foreach ($groupin$repeatOffenders) {
Write-DetectionLog 'WARN'"Repeated failure: '$($group.Name)' failed $($group.Count) time(s) in the window."
}
exit 1
}
catch {
Write-DetectionLog 'ERROR'"Unexpected failure: $($_.Exception.Message)"
exit 3
}
Detection Logic
History query.QueryHistory(0, 50) on the object returned by CreateUpdateSearcher() reads the 50 most recent history entries. Fifty is enough to cover a two-week window on a normally scanning device; the point is bounded runtime, not a complete archive.
Result codes. Each entry carries a ResultCode. The agent's enumeration is documented: 2 means succeeded, 3 means succeeded with errors, 4 means failed, 5 means aborted. This script treats 4 as a failure and counts 3s separately as warnings, because a partial success usually precedes a hard failure.
Window filter. Only entries dated within the last 14 days count, so a device that had problems last quarter does not flag forever. Adjust $lookBackDays to your patch cadence.
Result capture. Failed entries carry an HResult from the device's own history store. The script formats non-zero values as hexadecimal and logs them verbatim. These are real codes recorded by the agent, such as the familiar file-not-found or access-denied families, not guesses, and they are the fastest bridge between this script's output and a support article.
Repeat detection. The script groups failures by title and logs any title that failed more than once in the window. A single failure is noise; the same title failing three times is a device that will keep failing until someone intervenes.
For deeper forensics, the agent's own ETL traces under C:\Windows\Logs\WindowsUpdate decode into a readable log with the built-in Get-WindowsUpdateLog cmdlet. That decode is slow and CPU-heavy, which is why this script stays on the history API and leaves ETL decoding to interactive troubleshooting.
Remediation Script
There is no remediation script here, and adding one would be a mistake. The right fix depends entirely on the failure: a component reset, freeing disk space, repairing the servicing stack with DISM, or excluding an interfering product. Each of those is its own decision with its own blast radius, and automating the wrong one makes devices worse. This package's job is to make the failure visible, name the failing updates, and hand you the device's own result codes so the human choosing the fix has real evidence. Use the exit code to route the device into whatever targeted remediation your investigation supports.
Deploying Through Intune
Deploy as a detection-only package:
In the Microsoft Intune admin center, open Devices → Scripts and remediations and choose + Add on the Windows 10/11 tab.
On Basics, name it, for example Detect - Windows Update failures, and describe it as a read-only history review.
On Detection script, upload detect.ps1. Set Run script in 64-bit PowerShell: Yes and Run this script using signed-on credentials: No. Leave Enforce script signature check: No unless your organization signs internal scripts.
On the Remediation script step, select No remediation script.
On Schedule, set Weekly so failures are caught within one patch cycle. Daily runs add log volume without changing outcomes, since a failed update rarely heals on its own.
On Assignments, target a pilot ring first, then your broad Windows device group. Consider excluding devices you know are paused or in a lab scope.
Review and create.
Post-specific notes:
Non-zero detection results count as "remediation needed" in the portal even without a remediation script. Plan your review workflow around that column.
Tune $lookBackDays to at least one full patch cycle so a mid-cycle failure is always caught at least once.
The history API reports what the agent recorded. If a device's history was cleared, the window starts from the clear, which the low entry count in the log will make obvious.
Visual Walkthrough
Why the order of servicing stack and cumulative updates matters.
Offline servicing completes; Windows reports the new revision.
Windows Servicing Stack — SSU + LCU Anatomy. 1. SSU first: Servicing Stack Update upgrades the updater itself. — 2. LCU delivered: Latest Cumulative Update rides on top of a healthy SSU. — 3. CBS transaction: Component-Based Servicing stages payloads transactionally. — 4. Pending reboot markers: Registry markers hold the state until restart. — 5. Finalize on reboot: Offline servicing completes; Windows reports the new revision.
Exit Codes
Code
Meaning
0
No failed update installs recorded in the look-back window
1
One or more failed installs found in the window; dates, titles, and results are in the log
3
Unexpected script error — inspect the log
Logging
The script writes to C:\ProgramData\Company\Logs\WindowsUpdateFailureDetection.log. Each line has a UTC timestamp, a level (INFO, WARN, ERROR), and a message. Failed installs are logged one per line with date, title, and the result recorded by the device, followed by WARN lines naming repeat offenders with their failure counts. The summary line at the top of the failure block gives the total count, so a single grep gives you fleet numbers if you centralize these logs. Growth is modest, a dozen lines per run on problem devices; rotate monthly.
Troubleshooting the Script
No log file at all. The script never executed. Verify the assignment, the device's last check-in, and that the run was not pinned to signed-on credentials on a device nobody signs into.
Exit 3 with a COM error. The history API can fail when the update datastore is badly damaged. That failure is itself a signal: treat the device as needing agent-level repair, starting with the component reset post in this series.
Everything looks failed after servicing. Major servicing events and repair installs can write unusual history entries. Read the titles in the log before alarming anyone; the entry text usually explains itself.
Failures older than the window still show. They should not; the date filter uses the entry's recorded date. If you see this, confirm the device clock is sane, because wildly wrong clocks corrupt date filtering of any kind.
Need the full story for one device. Run Get-WindowsUpdateLog interactively on that device to decode the ETL traces, then match the timestamps from this script's log against the decoded log.
How to Validate the Fix
On a lab device, run the script manually in an elevated 64-bit PowerShell console. On a healthy device expect exit code 0 and the "No failed update installs" line.
Simulate the detection path: find a device with a known failed update, or force one in a lab by interrupting an install. Re-run and confirm exit code 1, the failure lines, and the result code captured from the device.
Confirm the repeat-offender logic by letting a failing title fail twice; the second run should add the WARN line naming the title.
In the admin center, check the package's Device status after the first scheduled run. Devices split between the code 0 and code 1 populations.
After applying whatever fix your investigation chose, re-run detection. The window resets as new successful installs push the failure out of the look-back range.
Production Considerations
Execution context. SYSTEM in 64-bit Windows PowerShell. The history API is machine-scoped; no user token or user data is involved.
Idempotency. The script is a pure read of local history. Re-running it any number of times changes nothing on the device.
Side effects. None. Unlike the pending-update search, this reads local data only and makes no network calls, so it is safe on offline and metered devices.
Interpretation. Exit code 1 means "failed installs in the window", which includes failures already fixed by a later successful attempt. Read the dates before triaging.
Security review and test ring. The script contains no credentials and changes nothing, but your PowerShell automation SME and security team should still review it and validate it in a test ring before broad production rollout, alongside your other remediation packages.
Why only the 50 most recent history entries?
The history API returns entries from the top of the stack, and 50 comfortably covers a two-week window on a device that scans normally. Raising the count makes the script slower and mostly reads noise from months ago. If you need a deeper dig on a specific device, do it interactively rather than in a fleet script.
What does ResultCode 4 mean exactly?
It is the Windows Update agent's value for a failed operation, per the documented result-code enumeration used by the update history API. Codes 2, 3, and 5 mean succeeded, succeeded with errors, and aborted respectively. This script flags only 4 as failure and warns on 3.
The log shows a hex result for a failed update. What now?
That value came from the device's own history and identifies the failure family, for example file-not-found or access-denied. Search your knowledge base for the code together with the update title, and cross-reference the decoded Windows Update log from Get-WindowsUpdateLog if you need the full sequence. Recurring identical results across devices usually indicate a deployment-side problem rather than a device problem.
Comments
0No comments yet — be the first to share your thoughts.