feat: install Windows Update via WUA COM API (replaces PSWindowsUpdate)#611
Conversation
PSWindowsUpdate's Install-WindowsUpdate filters out optional driver
updates client-side even when the underlying COM API can install them.
On a freshly-updated Windows 11 system, the only updates ever offered
are optional drivers/firmware — and PSWindowsUpdate would scan, find 20,
post-search filter to 0, and report "Installed 0" while SysManager's UI
falsely claimed success.
Switch the install path to direct Microsoft.Update.Session COM calls.
Live, real per-update progress streams to the console:
Connecting to Windows Update…
[1/N] HP Inc. SoftwareComponent Driver Update (4.8.130.0)
Downloading…
Installing…
✓ Installed
The new WindowsUpdateService also handles scan (IsInstalled=0), EULA
acceptance, reboot detection, and per-update categorization (Security,
Cumulative, Defender, Driver, Servicing, .NET, Feature upgrade, Hidden).
Categories come from WUA's own Categories collection where available,
title heuristics as fallback.
PSWindowsUpdate is kept only for the History view; install no longer
depends on it.
UI cleanup:
- Status column widened to 200px with tooltip for long messages like
"Installed (reboot required)"
- Live output panel: fixed 240px, no auto-resize that stole grid space
- Removed redundant Card+Expander wrapper around ConsoleView (left the
panel with two stacked headers)
- Removed live output from Ping and Traceroute (graphical chart and hops
grid are sufficient; the console was redundant and noisy)
- KB column header tooltip explains the abbreviation since drivers,
firmware, Defender Definitions etc. legitimately have no KB
Tests: 11 new unit tests for ClassifyCategory + FormatSize. Removed the
obsolete ParseInstallResults / StripAndSplitKb tests (no longer needed
with COM API).
Closes the silent-success bug surfaced during 1.17.4 testing where 16
selected drivers reported "Installed" but were never actually applied.
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThis PR migrates the Windows Update feature from PowerShell-based operations to Windows Update Agent COM API integration, refactors the ViewModel and UI accordingly, removes redundant console outputs from Ping/Traceroute views, and releases version 1.17.5. ChangesWindows Update COM API Migration
Sequence DiagramsequenceDiagram
participant User
participant VM as WindowsUpdateViewModel
participant Svc as WindowsUpdateService
participant WUA as WUA COM API
User->>VM: List Updates
VM->>Svc: ScanAsync
Svc->>WUA: Create session, search
WUA-->>Svc: Update objects
Svc->>Svc: Map to UpdateEntry
Svc-->>VM: Update list
VM->>VM: Sort by title
User->>VM: Install selected
VM->>Svc: InstallAsync
Svc->>WUA: Re-scan live state
Svc->>WUA: Download updates
Svc->>WUA: Install via COM
Svc->>Svc: Track per-entry status
Svc-->>VM: InstallReport
VM->>VM: Update UI and toast
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsGit: Failed to clone repository. Please run the Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
SysManager/SysManager/Services/WindowsUpdateService.cs (1)
126-144: ⚖️ Poor tradeoffNo timeout on download/install operations.
The download (line 131) and install (line 158) operations call synchronous COM methods without any timeout. If the WUA service hangs or a download stalls, the operation will block indefinitely, and cancellation token checks only occur between operations, not during them. Consider documenting this limitation or implementing a timeout wrapper.
Also applies to: 152-176
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@SysManager/SysManager/Services/WindowsUpdateService.cs` around lines 126 - 144, The download and install calls (dl.Download() and installer.Install()) in WindowsUpdateService are synchronous COM operations that can block indefinitely; wrap these calls in a cancellable/timeout-aware Task (e.g., Task.Run with a timeout and the provided CancellationToken) so the method can abort a hung operation, ensure Marshal.FinalReleaseComObject is called on dl/dlResult/installer even on timeout/exception, and set the entry.Status/failed counter and emit a clear timeout message when the operation times out; locate dl.Download() and installer.Install() in the update loop and replace them with this timeout-wrapper pattern or use WUA async APIs if available.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@SysManager/SysManager/Services/WindowsUpdateService.cs`:
- Around line 44-61: The COM objects (updates, result, searcher, session)
created in the scan path can leak if an exception is thrown inside the for-loop;
wrap the whole COM interaction in a try/finally (or try/catch/finally) around
the code that accesses updates and builds the list (the block that uses
result.Updates, updates.Item(i), MapToEntry, etc.) and move the
Marshal.FinalReleaseComObject calls into the finally block so each COM object is
released even on exceptions (check for null before releasing and swallow/log any
exceptions during release to avoid hiding the original error).
---
Nitpick comments:
In `@SysManager/SysManager/Services/WindowsUpdateService.cs`:
- Around line 126-144: The download and install calls (dl.Download() and
installer.Install()) in WindowsUpdateService are synchronous COM operations that
can block indefinitely; wrap these calls in a cancellable/timeout-aware Task
(e.g., Task.Run with a timeout and the provided CancellationToken) so the method
can abort a hung operation, ensure Marshal.FinalReleaseComObject is called on
dl/dlResult/installer even on timeout/exception, and set the entry.Status/failed
counter and emit a clear timeout message when the operation times out; locate
dl.Download() and installer.Install() in the update loop and replace them with
this timeout-wrapper pattern or use WUA async APIs if available.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 3ab9350b-d861-46cf-844c-846c6c86eae8
📒 Files selected for processing (14)
CHANGELOG.mdREADME.mdSysManager/SysManager.Tests/WindowsUpdateViewModelTests.csSysManager/SysManager/Models/UpdateEntry.csSysManager/SysManager/ServiceRegistration.csSysManager/SysManager/Services/WindowsUpdateService.csSysManager/SysManager/SysManager.csprojSysManager/SysManager/ViewModels/MainWindowViewModel.csSysManager/SysManager/ViewModels/PingViewModel.csSysManager/SysManager/ViewModels/TracerouteViewModel.csSysManager/SysManager/ViewModels/WindowsUpdateViewModel.csSysManager/SysManager/Views/PingView.xamlSysManager/SysManager/Views/TracerouteView.xamlSysManager/SysManager/Views/WindowsUpdateView.xaml
💤 Files with no reviewable changes (4)
- SysManager/SysManager/Views/PingView.xaml
- SysManager/SysManager/ViewModels/PingViewModel.cs
- SysManager/SysManager/Views/TracerouteView.xaml
- SysManager/SysManager/ViewModels/TracerouteViewModel.cs
📜 Review details
🔇 Additional comments (10)
SysManager/SysManager/SysManager.csproj (1)
13-15: LGTM!CHANGELOG.md (1)
9-26: LGTM!README.md (1)
122-143: LGTM!SysManager/SysManager/Models/UpdateEntry.cs (1)
23-23: LGTM!SysManager/SysManager/ServiceRegistration.cs (1)
62-62: LGTM!SysManager/SysManager/Services/WindowsUpdateService.cs (1)
70-201: LGTM!Also applies to: 203-213, 215-237, 239-255, 257-300, 302-313
SysManager/SysManager.Tests/WindowsUpdateViewModelTests.cs (1)
18-18: LGTM!Also applies to: 208-238
SysManager/SysManager/ViewModels/WindowsUpdateViewModel.cs (1)
19-19: LGTM!Also applies to: 33-48, 63-63, 136-171, 262-313
SysManager/SysManager/ViewModels/MainWindowViewModel.cs (1)
169-169: LGTM!SysManager/SysManager/Views/WindowsUpdateView.xaml (1)
34-35: LGTM!Also applies to: 154-167, 180-190, 203-285, 290-295
| var updates = result.Updates; | ||
| var count = (int)updates.Count; | ||
| Emit($"Found {count} update(s)."); | ||
|
|
||
| var list = new List<UpdateEntry>(count); | ||
| for (int i = 0; i < count; i++) | ||
| { | ||
| ct.ThrowIfCancellationRequested(); | ||
| var u = updates.Item(i); | ||
| list.Add(MapToEntry(u)); | ||
| Marshal.FinalReleaseComObject(u); | ||
| } | ||
|
|
||
| Marshal.FinalReleaseComObject(updates); | ||
| Marshal.FinalReleaseComObject(result); | ||
| Marshal.FinalReleaseComObject(searcher); | ||
| Marshal.FinalReleaseComObject(session); | ||
| return list; |
There was a problem hiding this comment.
COM object leak risk on exception path.
If an exception occurs during the loop (lines 49-55), the updates collection, result, searcher, and session COM objects won't be released. Consider wrapping the entire COM interaction in a try/finally block similar to InstallAsync.
🛠️ Proposed fix
public Task<IReadOnlyList<UpdateEntry>> ScanAsync(CancellationToken ct = default) =>
Task.Run<IReadOnlyList<UpdateEntry>>(() =>
{
ct.ThrowIfCancellationRequested();
Emit("Connecting to Windows Update…");
var session = CreateSession();
var searcher = session.CreateUpdateSearcher();
searcher.IncludePotentiallySupersededUpdates = false;
+ dynamic? result = null;
+ dynamic? updates = null;
- // "IsInstalled=0" returns everything not yet installed,
- // including optional drivers and feature upgrades.
- var result = searcher.Search("IsInstalled=0");
- ct.ThrowIfCancellationRequested();
-
- var updates = result.Updates;
- var count = (int)updates.Count;
- Emit($"Found {count} update(s).");
-
- var list = new List<UpdateEntry>(count);
- for (int i = 0; i < count; i++)
+ try
{
- ct.ThrowIfCancellationRequested();
- var u = updates.Item(i);
- list.Add(MapToEntry(u));
- Marshal.FinalReleaseComObject(u);
+ result = searcher.Search("IsInstalled=0");
+ ct.ThrowIfCancellationRequested();
+
+ updates = result.Updates;
+ var count = (int)updates.Count;
+ Emit($"Found {count} update(s).");
+
+ var list = new List<UpdateEntry>(count);
+ for (int i = 0; i < count; i++)
+ {
+ ct.ThrowIfCancellationRequested();
+ var u = updates.Item(i);
+ list.Add(MapToEntry(u));
+ Marshal.FinalReleaseComObject(u);
+ }
+ return list;
+ }
+ finally
+ {
+ if (updates is not null) Marshal.FinalReleaseComObject(updates);
+ if (result is not null) Marshal.FinalReleaseComObject(result);
+ Marshal.FinalReleaseComObject(searcher);
+ Marshal.FinalReleaseComObject(session);
}
-
- Marshal.FinalReleaseComObject(updates);
- Marshal.FinalReleaseComObject(result);
- Marshal.FinalReleaseComObject(searcher);
- Marshal.FinalReleaseComObject(session);
- return list;
}, ct);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@SysManager/SysManager/Services/WindowsUpdateService.cs` around lines 44 - 61,
The COM objects (updates, result, searcher, session) created in the scan path
can leak if an exception is thrown inside the for-loop; wrap the whole COM
interaction in a try/finally (or try/catch/finally) around the code that
accesses updates and builds the list (the block that uses result.Updates,
updates.Item(i), MapToEntry, etc.) and move the Marshal.FinalReleaseComObject
calls into the finally block so each COM object is released even on exceptions
(check for null before releasing and swallow/log any exceptions during release
to avoid hiding the original error).
|
|
||
| try | ||
| { | ||
| if (!(bool)u.IsDownloaded) |
| } | ||
| } | ||
|
|
||
| if (!(bool)u.EulaAccepted) |
| inst.Updates = coll; | ||
| var iResult = inst.Install(); | ||
| var iCode = (int)iResult.ResultCode; | ||
| bool needsReboot = (bool)iResult.RebootRequired; |
| Title = title, | ||
| KB = kb, | ||
| Size = FormatSize(size), | ||
| UpdateId = (string)u.Identity.UpdateID, |
| var kb = kbList.Count > 0 ? "KB" + string.Join(",", kbList) : string.Empty; | ||
| long size = 0; | ||
| try { size = (long)(decimal)u.MaxDownloadSize; } catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException) { } | ||
| catch (COMException) { } |
| if (!string.IsNullOrWhiteSpace(id)) list.Add(id); | ||
| } | ||
| } | ||
| catch (COMException) { } |
| } | ||
| } | ||
| catch (COMException) { } | ||
| catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException) { } |
| } | ||
| Marshal.FinalReleaseComObject(cats); | ||
| } | ||
| catch (COMException) { } |
| Marshal.FinalReleaseComObject(cats); | ||
| } | ||
| catch (COMException) { } | ||
| catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException) { } |
| var kbList = ExtractKbIds(u); | ||
| var kb = kbList.Count > 0 ? "KB" + string.Join(",", kbList) : string.Empty; | ||
| long size = 0; | ||
| try { size = (long)(decimal)u.MaxDownloadSize; } |
| var kb = kbList.Count > 0 ? "KB" + string.Join(",", kbList) : string.Empty; | ||
| long size = 0; | ||
| try { size = (long)(decimal)u.MaxDownloadSize; } | ||
| catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException) { } |
…ation test - WindowsUpdateViewModel no longer kicks off CheckModuleAsync at construction. PSWindowsUpdate is needed only for the History view, so probe it lazily there. This keeps the constructor side-effect-free and prevents IsBusy from racing with construction-time test assertions on faster CI runners. - ClassifyCategory test: replace the "Intel Corporation - Display - ..." case with "HP Firmware Driver Update (...)". The Intel string contains neither "Driver" nor "Firmware" — the title-only path correctly returns "Update". On a real system the COM Categories collection classifies it as Driver; the unit test (which doesn't pass a COM object) cannot.
Summary
Reproduced live in 1.17.4: select 16 driver updates, click Install — UI shows "Installed 16", no real install happens, re-scan returns the same 16. Root cause investigated end-to-end: PSWindowsUpdate's
Install-WindowsUpdatefilters out optional driver updates client-side, even when the underlying Windows Update Agent COM API can install them perfectly. Verified with a manualMicrosoft.Update.Sessioninstall of a single Dell Monitor driver — succeeded withResultCode=2while every PSWindowsUpdate variant (KB, Title, UpdateID, pipe-based) returnedInstalled [0].This PR migrates the scan + install paths to direct WUA COM API calls. PSWindowsUpdate is kept only for the History view.
What changes
WindowsUpdateService— new service wrappingMicrosoft.Update.Session. Handles scan (IsInstalled=0), download, EULA acceptance, install, reboot detection. Exposes aLogevent for live console streaming.WindowsUpdateViewModel—ListUpdatesAsyncandInstallUpdatesAsyncnow call the new service. Per-row Status updated as install progresses (Pending…→Downloading…→Installing…→Installed/Installed (reboot required)/Failed/Not applied). Aggregate report shows real counts.Categoriescollection is the primary signal (authoritative), title heuristics as fallback. Pills colored per category.Files
SysManager/Services/WindowsUpdateService.cs(new)SysManager/ViewModels/WindowsUpdateViewModel.cs(rewritten install/scan, kept history)SysManager/ViewModels/PingViewModel.cs(removed Console)SysManager/ViewModels/TracerouteViewModel.cs(removed Console)SysManager/Models/UpdateEntry.cs(addedUpdateIdproperty)SysManager/ServiceRegistration.cs,MainWindowViewModel.cs(DI)SysManager/Views/WindowsUpdateView.xaml,PingView.xaml,TracerouteView.xamlSysManager.Tests/WindowsUpdateViewModelTests.cs(11 new tests forClassifyCategory+FormatSize; removed obsolete tests)CHANGELOG.md,README.md, csproj version → 1.17.5Test plan
dotnet build -c ReleaseSysManager.csproj — 0 errorsdotnet build -c ReleaseSysManager.Tests.csproj — 0 errorsResultCode=2 (Succeeded))Summary by CodeRabbit
Release Notes v1.17.5
New Features
Improvements
Bug Fixes