Skip to content

Fix UI helper errors during headless -Preset and -Config runs - #4941

Merged
ChrisTitusTech merged 3 commits into
ChrisTitusTech:mainfrom
hiphucto:fix/headless-run-ui-errors
Aug 9, 2026
Merged

Fix UI helper errors during headless -Preset and -Config runs#4941
ChrisTitusTech merged 3 commits into
ChrisTitusTech:mainfrom
hiphucto:fix/headless-run-ui-errors

Conversation

@hiphucto

@hiphucto hiphucto commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Type of Change

  • New feature
  • Bug fix
  • Documentation update
  • Refactor
  • UI/UX improvement

Description

Running WinUtil headlessly with -Preset or -Config writes a wall of errors into the session log on every run.

scripts/main.ps1 handles -Preset (lines 37-51) and -Config (lines 53-65) in early-return blocks that call Invoke-WinUtilAutoRun and then exit. Both return before line 67 loads presentationframework and before line 74 creates $sync["Form"], so the tweak workflow calls UI-only helpers against a window that does not exist:

  • Invoke-WPFUIThread runs $sync.form.Dispatcher.Invoke(...) on $null and throws InvokeMethodOnNull
  • Set-WinUtilTweaksProgressIndicator cannot resolve [Windows.Visibility] and throws TypeNotFound

The errors are non-terminating, so tweaks still apply correctly, but a -Preset Advanced run logs six error records before the first tweak even starts. Sample from a real run:

[INFO] [Tweaks] Tweaks requested: 17 selected tweak(s), DNS provider: Default
You cannot call a method on a null-valued expression.
+     $sync.form.Dispatcher.Invoke([action]$ScriptBlock)
Unable to find type [Windows.Visibility].
+     $indicatorVisible = if ($Visible) { [Windows.Visibility]::Visible ...

Why the guard goes in the helpers

Loading the WPF assembly earlier does not fix this. System.Windows.Visibility lives in PresentationCore, which only loads once a WPF object is actually instantiated, so LoadWithPartialName('presentationframework') leaves the type error unchanged. And giving $sync.Form a real window only trades the dispatcher errors for PropertyNotFound on WPFTweaksProgressBar, WPFTweaksProgressLabel and WPFTweaksProgressValue, which are only populated at main.ps1:135.

The repo already has a $hasUI = $null -ne $sync.Form -and $null -ne $sync.Form.Dispatcher convention in Invoke-WPFInstall, Invoke-WPFUnInstall, Invoke-WPFAppxInstall, Invoke-WPFAppxRemoval and Invoke-WPFOOSU — but it was never applied to Invoke-WPFtweaksbutton, Invoke-WPFundoall or Invoke-WPFFeatureInstall, which is exactly why these errors appear. Guarding the two helpers covers all three missed workflows in two lines instead of ~15, and any future call site along with them. The existing $hasUI blocks become redundant rather than wrong and are left untouched.

Stray console output

A second commit removes two pieces of output that a GUI click handler silently discards but that reach the console on automation runs:

  • Invoke-WPFRunspace returns an IAsyncResult that no call site uses, printing a CompletedSynchronously / IsCompleted block. The return value itself is kept because pester/runspace.Tests.ps1 asserts it, so the suppression is applied at the four call sites reachable from Invoke-WinUtilAutoRun.
  • Set-WinUtilRegistry was the only New-PSDrive call site not piping to Out-Null, printing a PSDrive table mid-run. Invoke-WinUtilCurrentSystem and Get-WinUtilToggleStatus already suppress it.

Testing

  • New pester/headless-ui.Tests.ps1 (5 tests) covers the no-window cases for both helpers and asserts the GUI path still marshals onto the dispatcher and still updates the indicator controls. The three no-window tests were confirmed to fail against the pre-fix functions, and the two GUI-path tests pass both before and after, so the guard provably does not disable the GUI behaviour.
  • Full Pester suite: 480 passed, 0 failed.
  • .\Compile.ps1 succeeds.
  • PSScriptAnalyzer with lint/PSScriptAnalyser.ps1 on the changed files: no new findings.
  • Verified against a live -Preset Advanced run that all 17 tweaks apply both before and after this change, including all 12 registry keys for WPFTweaksTelemetry. This PR only removes the error noise; it does not change which tweaks run.

No behaviour change in the GUI: when a window exists, both helpers work exactly as before.

Issue related to PR

The -Preset and -Config paths run Invoke-WinUtilAutoRun before the XAML
form is created and before PresentationCore is loaded, so every call into
Invoke-WPFUIThread failed with InvokeMethodOnNull and every call into
Set-WinUtilTweaksProgressIndicator failed to resolve [Windows.Visibility].
The errors were non-terminating, so tweaks still applied, but each run
filled the log with noise.

Loading presentationframework earlier does not help: Visibility lives in
PresentationCore, which only loads once a WPF object is instantiated, and
the XAML-named progress controls do not exist in these paths either.
Guarding the two helpers covers Invoke-WPFtweaksbutton, Invoke-WPFundoall
and Invoke-WPFFeatureInstall, which the existing per-call-site $hasUI
convention never reached.
Invoke-WPFRunspace returns an IAsyncResult that no caller uses, and
Set-WinUtilRegistry was the only New-PSDrive call site that did not
suppress its output. A GUI click handler discards both, but the -Preset
and -Config paths call the workflows directly, so an async handle dump
and a PSDrive table landed in the user's log.

The handle itself is kept because pester/runspace.Tests.ps1 asserts that
Invoke-WPFRunspace returns a single IAsyncResult, so the suppression goes
at the four call sites reachable from Invoke-WinUtilAutoRun.
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@github-actions github-actions Bot added the bug Something isn't working label Aug 7, 2026
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8606bf55-6168-445e-8faa-1ecf13980d91

📥 Commits

Reviewing files that changed from the base of the PR and between 8d3adb5 and 7f0925a.

📒 Files selected for processing (9)
  • AGENTS.md
  • functions/private/Set-WinUtilRegistry.ps1
  • functions/private/Set-WinUtilTweaksProgressIndicator.ps1
  • functions/public/Invoke-WPFAppxRemoval.ps1
  • functions/public/Invoke-WPFFeatureInstall.ps1
  • functions/public/Invoke-WPFInstall.ps1
  • functions/public/Invoke-WPFUIThread.ps1
  • functions/public/Invoke-WPFtweaksbutton.ps1
  • pester/headless-ui.Tests.ps1

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Improved stability when configuration or preset workflows run before the application window is available.
    • Prevented unnecessary background-operation output from appearing during installation, feature setup, app removal, and tweak workflows.
    • Ensured progress updates and UI actions safely skip execution when the interface is not ready.
  • Tests

    • Added coverage for headless operation and UI behavior before and after window initialization.

Walkthrough

WPF helpers now no-op when the form or dispatcher is unavailable. Headless Pester tests cover these paths and initialized controls. Registry and runspace commands now suppress incidental pipeline output. Project guidance documents the UI-safety requirement.

Changes

UI safety and output control

Layer / File(s) Summary
Headless WPF guards and validation
functions/public/Invoke-WPFUIThread.ps1, functions/private/Set-WinUtilTweaksProgressIndicator.ps1, pester/headless-ui.Tests.ps1, AGENTS.md
UI helpers return safely when the form or dispatcher is unavailable. Tests cover no-op behavior and normal dispatcher-driven updates. Project guidance records this requirement.
Runspace and registry output suppression
functions/private/Set-WinUtilRegistry.ps1, functions/public/Invoke-WPFAppxRemoval.ps1, functions/public/Invoke-WPFFeatureInstall.ps1, functions/public/Invoke-WPFInstall.ps1, functions/public/Invoke-WPFtweaksbutton.ps1
Registry-drive creation and WPF runspace invocations pipe output to Out-Null without changing their surrounding workflows.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: christitustech

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly explains the headless UI errors, output suppression changes, affected helpers, tests, and validation results.
Title check ✅ Passed The title clearly identifies the primary change: fixing UI helper errors during headless preset and configuration runs.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ChrisTitusTech

Copy link
Copy Markdown
Owner

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. What shall we delve into next?

Reviewed commit: 7f0925a594

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ChrisTitusTech
ChrisTitusTech merged commit 6de45a3 into ChrisTitusTech:main Aug 9, 2026
4 checks passed
@hiphucto
hiphucto deleted the fix/headless-run-ui-errors branch August 10, 2026 06:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants