MSC improvements: per-row stages, removal UX, failure surfacing (WIP)#57
Merged
Conversation
…ation start InstallInfoService (FileSystemWatcher), TriggerService (flag-file poll loop), ProgressServer/ProgressPipeClient (socket read loops) all raised events on threadpool threads. Viewmodel handlers set XAML-bound ObservableProperties, so PropertyChanged hit active bindings cross-thread: COMException 0x8001010E (RPC_E_WRONG_THREAD) inside async void handlers, killing the process. Seen in the field as MSC crashing the moment a self-serve install finished and InstallInfo.yaml changed. New UiDispatcher captures the App DispatcherQueue at startup; sources raise through Post(), which runs inline when no queue is set (unit tests). ShellViewModel now navigates to Updates whenever an operation starts, so a self-serve click surfaces the pending item immediately. RequestNavigate resets NavigateToPage to null first; the generated setter suppresses PropertyChanged for equal values, which silently dropped repeat navigations. Program.cs logs AppDomain.UnhandledException to msc_crash.log — background crashes previously bypassed App.UnhandledException and left no trace.
… at InstallInfo write time Self-serve promotion flipped a manifest optional item's action to install, so WriteInstallInfo never emitted its optional_installs record and the item vanished from the software list (and from Featured) the moment a user clicked Install. PromotedFromOptional now marks promoted/flipped items and WriteInstallInfo always writes their optional record, with status tracking the pending action (will-be-installed / will-be-removed) and reverting to installed / not-installed when the action completes. The toInstall/toUpdate/toUninstall lists are computed before the install phase, so the post-session InstallInfo write described items installed minutes earlier as still pending (installed: true + status: will-be-installed for VLC). All branches now re-run CheckStatus at write time and drop pending records whose action already completed.
IconImage is a plain property without change notification; assigning it after the ObservableCollections were bound left already-rendered rows iconless. Load icons into the source lists first, then assign.
Log viewer extracted to a shared LogViewerControl (tail, filter, autoscroll). View Log now opens a flyout anchored to the button by default; its pop-out button (and Ctrl+L) opens the standalone LogWindow. LogWindow centered itself with hardcoded 2250x1500 math against a 900x600 window, so on shorter displays Y went negative and it opened above the visible area. Both windows now center using actual window size offset by the work area origin and clamped to non-negative coordinates; MainWindow also caps its initial size to the work area.
…tive Each MSC click >10s apart spawned its own managedsoftwareupdate process; the second lost the instance lock and exited code 1, so queued self-serve installs silently never ran. CimianWatcher now claims a run slot before consuming a flag file: while a triggered process is alive, new/updated flag files stay on disk (MSC keeps merging additional --item requests into them) and are consumed on the first poll after the run exits. MSC TriggerService no longer counts wait time against its 30s timeout while a managedsoftwareupdate process is running — the unconsumed flag is the queued request, not a dead service (it used to delete the queue and throw). engine: pending records for promoted self-serve items survive targeted (--item) writes — a queued item outside the run's filter is still pending and keeps its managed_installs record, so the Updates list no longer goes blank after a partial run. msc: standing self-serve subscriptions no longer render as eternal 'Install pending' on installed items (Software, item detail, My Items); removal requests only show pending while the item is installed. My Items also loads icons before binding.
It was registered transient: MainWindow held one instance, but every page navigation constructed a fresh one via App.GetService — created AFTER the operation-started event fired, so a newly navigated Updates page saw IsInstalling=false and showed stale content while an install ran silently. Each throwaway instance also left its service event subscriptions behind. Shell state (progress overlay, badges, navigation requests) is app-wide; register it once.
…cel, categories grid Engine streams itemStatus messages over the status connection (new 'item' field): pending -> downloading -> downloaded -> installing -> installed, plus removing/removed/failed. The status TCP stream is now duplex — the reporter runs a command read loop and the GUI's stop command cancels the run gracefully before the next item (previously SendCommandAsync was a silent no-op). Updates page: the full-page takeover overlay is replaced by a compact progress banner; the item list stays visible underneath with a live stage indicator per row (spinner while downloading/installing/removing, glyph + color for terminal states). View Log stays accessible during a run and the banner has a Cancel button that clears a queued flag file and stops a running session. Items the engine reports that aren't in the list yet get synthetic rows so a fresh self-serve click shows progress immediately. UpdatesViewModel is a singleton so navigation keeps mid-run stage state. InstallableItem implements INotifyPropertyChanged for LiveStage only. Categories page: rows become fixed-width wrapping grid cards.
…ly per-item pending stage; MSI uninstall idempotency Streams a failure reason (parsed exit code, common MSI codes glossed) on the itemStatus pipe and writes problem_items into InstallInfo.yaml. Emits an early pending stage per targeted item so GUI rows show progress from t=0. Treats msiexec 1605/1614 (already-absent product) as idempotent removal success.
…ebug filter Updates tab shows self-serve install-requested items and renders each problem's error inline on its row (orphans only in the standalone section). My Items shows Remove for installed items, Cancel for pending. Progress banner verb-aware (Removing vs Installing). Self-serve manifest raises RequestsChanged so a cancel clears Updates immediately. Log viewer hides DEBUG by default with a Show debug toggle, plus a Copy button. Adds tests/gui-harness for headless drive of the install/remove path.
When an item reaches installed/removed, its row lingers on the Updates page with the green check instead of vanishing the instant InstallInfo is rewritten. Excluded from the pending count and Install Now targets; cleared on explicit re-check or when navigating away from the page.
Engine:
- configurable --status-port (default 19847); MSC uses 19848 so the
login-window CimianStatus listener can no longer collide with MSC
- emit terminal itemStatus for an already-satisfied --item so the GUI
confirms instead of sitting empty
MSC:
- single "--item A B C" arg form; repeated --item flags crashed the
engine's sequence option ("defined multiple times", exit 1) and
stranded multi-item self-serve batches
- surface self-serve removals live (symmetric to installs) so Remove
shows under Pending Removals immediately with real name/version/icon
- enrich synthetic live-progress rows from InstallInfo; reclassify a
removal mis-filed under installs via the generic pending stage
- per-row progress for --item runs vs global banner for broad runs
- TriggerService waits for the engine process to exit and clears the
running state, so a run that never signals completion can't leave
the GUI stuck on Stop
- drop the modal install/removal error dialog; failures show inline on
the row and under Problem Items
There was a problem hiding this comment.
Pull request overview
This draft PR iterates on the Managed Software Center (WinUI 3) + managedsoftwareupdate engine contract to provide per-item live lifecycle stages, improved removal UX, and more durable/visible failure reporting (including parsed exit-code summaries), plus a new headless GUI harness for faster end-to-end flow testing.
Changes:
- Engine: adds per-item streamed stages (
itemStatus), stop-command support over the status connection, idempotent removal handling, and durable failure surfacing viaproblem_items. - GUI: renders per-row live stages, refines install/remove/cancel UX (incl. “Remove” in My Items), improves log viewing via a reusable
LogViewerControl, and hardens cross-thread event delivery viaUiDispatcher. - Tests/harness: updates argument-building expectations (single
--itemflag), adds uninstallability + silent-switch inference tests, and introducestests/gui-harnessfor headless contract-driven scenarios.
Reviewed changes
Copilot reviewed 40 out of 40 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Shared/BootstrapArgsBuilderTests.cs | Updates tests for single --item flag with multiple values + quoting behavior. |
| tests/Managedsoftwareupdate/UpdateModelsTests.cs | Adds uninstallability tests for exe installers and opt-out behavior. |
| tests/Managedsoftwareupdate/InstallerServiceTests.cs | Adds tests for uninstall silent-switch inference from registry uninstaller exe name. |
| tests/gui-harness/README.md | Documents the new headless GUI/engine contract test harness. |
| tests/gui-harness/MscHarness.ps1 | Implements the headless harness for install/remove/cancel/scenario flows. |
| shared/core/Services/SelfServiceManifestService.cs | Adds RequestsChanged event to support immediate GUI refresh on manifest mutations. |
| shared/core/Services/BootstrapArgsBuilder.cs | Switches to single --item flag, improves extraction for new/legacy forms. |
| gui/ManagedSoftwareCenter/Views/UpdatesPage.xaml.cs | Refactors run-state UI logic; adds log flyout lifecycle wiring; dismisses completed rows on unload. |
| gui/ManagedSoftwareCenter/Views/UpdatesPage.xaml | Adds per-row stage UI, new header controls, and embedded progress banner + log flyout viewer. |
| gui/ManagedSoftwareCenter/Views/MyItemsPage.xaml.cs | Adds Remove click handler to trigger uninstall from My Items. |
| gui/ManagedSoftwareCenter/Views/MyItemsPage.xaml | Adds Remove vs Cancel button logic and uses user-facing status text. |
| gui/ManagedSoftwareCenter/Views/LogWindow.xaml.cs | Simplifies window into a thin host around LogViewerControl; fixes centering math. |
| gui/ManagedSoftwareCenter/Views/LogWindow.xaml | Hosts reusable LogViewerControl instead of inlined log viewer UI. |
| gui/ManagedSoftwareCenter/Views/CategoriesPage.xaml | Switches categories display to wrapping GridView cards. |
| gui/ManagedSoftwareCenter/ViewModels/UpdatesViewModel.cs | Adds per-item stage routing, request-change refresh, self-serve request surfacing, and “linger completed rows” logic. |
| gui/ManagedSoftwareCenter/ViewModels/SoftwareViewModel.cs | Adjusts status derivation for standing requests; fixes targeted removal trigger call. |
| gui/ManagedSoftwareCenter/ViewModels/ShellViewModel.cs | Introduces ShowGlobalBanner, improves cancellation path, and ensures navigation requests fire even when unchanged. |
| gui/ManagedSoftwareCenter/ViewModels/MyItemsViewModel.cs | Adds Remove-from-MyItems command, better status labels, icon-load ordering, and installed-state handling. |
| gui/ManagedSoftwareCenter/ViewModels/ItemDetailViewModel.cs | Refines “pending request” evaluation for standing installs vs actual pending state; fixes removal trigger call. |
| gui/ManagedSoftwareCenter/Services/UiDispatcher.cs | Adds shared UI-thread dispatcher helper for safe cross-thread event delivery. |
| gui/ManagedSoftwareCenter/Services/TriggerService.cs | Adds --status-port injection, targeted multi-item triggering, improved batching, and a process-exit safety net. |
| gui/ManagedSoftwareCenter/Services/ProgressServer.cs | Moves MSC to port 19848, adds itemStatus parsing and duplex stop command support, marshals events to UI thread. |
| gui/ManagedSoftwareCenter/Services/ProgressPipeClient.cs | Marshals pipe events to UI thread via UiDispatcher. |
| gui/ManagedSoftwareCenter/Services/ITriggerService.cs | Extends trigger interface for item-scoped operations and multi-item targeted runs. |
| gui/ManagedSoftwareCenter/Services/InstallInfoService.cs | Marshals file watcher callbacks to UI thread. |
| gui/ManagedSoftwareCenter/Program.cs | Logs background unhandled exceptions for better post-mortem signal. |
| gui/ManagedSoftwareCenter/Models/ProgressMessage.cs | Adds ItemStatus message type for per-row live stages. |
| gui/ManagedSoftwareCenter/Models/InstallInfo.cs | Adds LiveStage and LiveStageDetail with change notification. |
| gui/ManagedSoftwareCenter/MainWindow.xaml.cs | Caps window size to work area and fixes centering on smaller displays. |
| gui/ManagedSoftwareCenter/Converters/Converters.cs | Adds converters for stage rendering and “no stage” visibility. |
| gui/ManagedSoftwareCenter/Controls/LogViewerControl.xaml.cs | Adds reusable log viewer with filtering, debug toggle, copy, pop-out, and tailing support. |
| gui/ManagedSoftwareCenter/Controls/LogViewerControl.xaml | Defines reusable log viewer UI (toolbar + log + status). |
| gui/ManagedSoftwareCenter/App.xaml.cs | Initializes UiDispatcher; makes Shell/Updates viewmodels singletons to preserve live state. |
| cli/managedsoftwareupdate/Services/UpdateEngine.cs | Adds status-port support, itemStatus emission, stop handling, durable failure surfacing, and targeted-run optimizations. |
| cli/managedsoftwareupdate/Services/StatusReporter.cs | Adds configurable port, duplex command reader, and itemStatus messages. |
| cli/managedsoftwareupdate/Services/ManifestService.cs | Marks optional items promoted by self-serve intent. |
| cli/managedsoftwareupdate/Services/InstallerService.cs | Adds uninstall-via-registry fallback, silent-switch inference, and idempotent MSI uninstall handling. |
| cli/managedsoftwareupdate/Program.cs | Adds --status-port option and wires it into engine run. |
| cli/managedsoftwareupdate/Models/UpdateModels.cs | Extends uninstallability rules for exe installers; adds promoted-from-optional metadata. |
| cli/cimiwatcher/Services/FileWatcherService.cs | Serializes flag consumption while an update is running to avoid losing queued requests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…shal # Conflicts: # cli/managedsoftwareupdate/Models/UpdateModels.cs # cli/managedsoftwareupdate/Services/ManifestService.cs
- ITriggerService: correct --item doc to single-flag form (--item A B) - InstallerService: dispose registry base keys (handle leak on uninstall) - UpdatesViewModel: guard async-void progress handler with try/catch - Converters: cache ItemStageConverter brushes (avoid per-call alloc) - MscHarness: emit single --item flag so multi-item runs don't exit 1
PowerShell renders Write-Error to stderr with a prefix (and Windows PowerShell echoes the command, doubling the marker on the line) plus ANSI color codes under pwsh. The start-anchored regex never matched a marker emitted via stderr. Strip ANSI and walk to the last marker occurrence on the line so stderr-emitted warnings are captured like stdout ones.
rodchristiansen
marked this pull request as ready for review
June 20, 2026 00:16
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Managed Software Center improvements (WIP — kept open for iteration)
Ongoing work on the MSC WinUI 3 GUI and the
managedsoftwareupdateengine. This PR is a draft and will keep getting commits as we iterate; do not merge yet.Engine
problem_itemsin InstallInfo.yaml (durable, user-reportable).pendingstage per targeted--itemso GUI rows show progress from t=0 instead of flashing at the end.1605/1614(product already absent) as idempotent removal success.Managed Software Center
RequestsChangedevent); Updates surfaces self-serve install-requested items.tests/gui-harnessto drive the install/remove path headlessly.Status
Built signed and deployed locally (MSU+MSC 2026.06.14). Iterating — next up includes install-side silent-flag inference for stub installers and a repo-wide uninstall-coverage audit (tracked separately in Azure Boards).