* Fix WaaSMedicSvc restoration using direct registry write
Set-Service fails on WaaSMedicSvc with Access Denied since it's a
protected service (LaunchProtected=2). FirstLogon.ps1 silently
swallowed this failure via -ErrorAction SilentlyContinue, permanently
leaving WaaSMedicSvc disabled after setup.
Removes WaaSMedicSvc from the Set-Service restoration loop and
restores it via a direct registry write instead, matching the same
technique already used to disable it in WinUtil-PostInstall.ps1.
Added a test verifying the old Set-Service-based restoration is gone
and the new registry-write fix is present.
Addresses the WaaSMedicSvc portion of #5095; the broader AppX/Store
failure investigation is out of scope for this change.
* Assert exact registry-write contract for WaaSMedicSvc test
Per CodeRabbit review: the previous assertion only checked that
Set-ItemProperty and WaaSMedicSvc appeared near each other, which
would pass even with a wrong -Value or -Type. Now asserts the
complete command including -Value 3 and -Type DWord.
* Surface service restoration failures in FirstLogon.log
BITS, wuauserv, UsoSvc, and WaaSMedicSvc restoration all used
-ErrorAction SilentlyContinue, which suppresses errors before they
reach the output stream FirstLogon.ps1 redirects to its log file
(*>&1 ... >> FirstLogon.log). This meant any restoration failure —
not just the WaaSMedicSvc one already fixed — was invisible even in
the log.
Changed -ErrorAction SilentlyContinue to Continue on both the
Set-Service loop and the WaaSMedicSvc registry write, so failures
still don't halt the script but now actually land in
FirstLogon.log for troubleshooting.
Addresses the logging portion of the reporter's suggestion in #5095.
* Assert -ErrorAction Continue in WaaSMedicSvc test
Per CodeRabbit review: the existing test only checked the
Set-ItemProperty command and value, not the -ErrorAction Continue
change made for logging. Extended the same test to also assert
both the Set-ItemProperty and Set-Service lines use Continue instead
of SilentlyContinue.
* Extend WaaSMedicSvc test to cover full ErrorAction Continue
Per CodeRabbit review: the registry-write assertion stopped at
-Type DWord, so a regression back to -ErrorAction SilentlyContinue
would still pass. Extended the pattern to include -ErrorAction
Continue at the end of the command.
* Exercise FirstLogon service restoration behavior
---------
Co-authored-by: Chris Titus <contact@christitus.com>
* style(ui): clarify scope in the note
- clarify which command affects only the current user and which affects all users
- replace StackPanel with WrapPanel to prevent text clipping at 200% font scaling
* fix(ui): scope AppX installation note to local registration
---------
Co-authored-by: Chris Titus <contact@christitus.com>
* style(ui): replace 'Get' with 'Select'
To a programmer, 'Get' means read the current state. To a lay user, 'Get' means obtain or download (like the 'Get' button in the Microsoft Store).
'Select' indicates exactly what it does and maintains parallelism with 'Select All'.
* docs: align installed tweak references with selection labels
---------
Co-authored-by: Chris Titus <contact@christitus.com>
* fix(windows update): prevent sudden restart
Microsoft has documented that the `NoAutoRebootWithLoggedOnUsers` registry
key is unreliable and does not work as described in newer versions of Windows.
To effectively prevent unexpected restarts in modern configurations,
this PR updates the `Recommended` settings workflow on the `Updates` tab.
Changes
- Modify `AUOptions` to 3: automatic download and manual installation.
- Remove the obsolete registry key.
- Keep the removal of the registry key in functions that reset Windows Update to default.
- Update UI text and documentation.
- Update Pester tests.
Microsoft documentation:
https://learn.microsoft.com/en-us/windows/deployment/update/waas-restartResolves#5093
* Clarify update installation notification behavior
---------
Co-authored-by: Chris Titus <contact@christitus.com>
* Add friendly explanations for common DISM exit codes
Invoke-WinUtilISODism threw only a raw exit code on DISM failure
(e.g. "DISM add-driver failed with exit code 112"), leaving users to
look up what the number means themselves.
Adds a $knownExitCode lookup table mapping common Windows/DISM exit
codes (disk full, access denied, file/path not found, file in use,
timeout, etc.) to plain-English explanations. When a failure's exit
code is recognized, the thrown message now includes the explanation
in parentheses; unrecognized codes fall back to the original
plain-number message.
Verified the script still parses correctly after the change.
* Add missing period to fallback DISM error message
* Add tests for DISM known/unknown exit code error messages
Adds a test verifying a known exit code (112) produces the friendly
explanation in the thrown message, alongside the existing test
verifying an unrecognized code still falls back to the plain
numeric message.
Verified: all 35 tests in win11creator.Tests.ps1 pass.
* Document DISM friendly error messages in Win11 Creator troubleshooting
Adds a Troubleshooting table row explaining the new DISM error
message format (exit code + explanation in parentheses), with
guidance for the most common cases and a link to Microsoft's full
error code reference for anything not explained.
* Fix duplicate DISM calls in unmapped exit code test
The unmapped-code test called Invoke-WinUtilISOScript twice — once
via Should -Throw, once to capture the message for the no-parens
check — causing $script:dismCalls to double-count. Consolidated to
a single call, checking both the exit code and the absence of a
parenthesized explanation against one captured exception message.
Verified: all 36 tests in win11creator.Tests.ps1 pass.
* Fix broken DISM error code reference link in docs
Replaced the dead windows-hardware/manufacture link with Microsoft's
actual System Error Codes reference page, which DISM exit codes
correspond to.
* Strengthen DISM fallback regression coverage
---------
Co-authored-by: Chris Titus <contact@christitus.com>
- Update driver injection checkbox tooltip to describe individual package injection instead of a single DISM pass
- Make modification working label conditional so it displays 'Preparing setup media' when driver injection is disabled
- Update documentation screenshot to show the current 3-step wizard layout and vertical status log
* Warn instead of logging tweak completed after a step error
Invoke-WinUtilTweaks wrote "Apply tweak completed" unconditionally, even
when Invoke-WinUtilScript or another helper had just logged an ERROR for
that tweak. The job layer already counts those errors, so snapshot the
count after the header line and compare at the end: log a WARN line with
the error count when it grew, otherwise the existing completed line.
Two Pester cases run the real logger and script runner against a temp
log file to pin both outcomes.
* Count tweak step errors from the shared log list
The job error counter only increments inside a Start-WinUtilJob worker.
Toggle switches call Invoke-WinUtilTweaks directly on the UI thread, so
a failing toggle still logged "tweak completed" after its ERROR line.
Every ERROR line is added to $sync.LoggedErrors from any runspace, and
Invoke-WinUtilAutoRun already reads it as a before/after delta, so the
tweak runner now does the same. The completion-status tests run without
the worker flag and seed an earlier unrelated error, and a new case
covers a failing UndoScript.
* Count tweak errors on the logging runspace, not the shared list
Diffing $sync.LoggedErrors charged a toggle with errors a concurrent
job logged from its own runspace. Global scope is per runspace, so the
logger now bumps the runspace counter for every headline error and the
tweak status diffs that. Job workers still reset the counter at start
and end, so job results are unchanged.
Invoke-WinUtilISOScript.ps1's PostInstall script set DisableFileSyncNGSC
to 1, which FirstLogon.ps1 (from #4409) then immediately overrides back
to 0. The final value was correct, but only because one script undoes
the other's write on every install.
Removes the redundant PostInstall write so FirstLogon.ps1's existing
0 assignment is the only place setting this value.
Verified the script still parses correctly after the change.
* docs(win11creator): correct architecture and guide
Update documentation to match actual Win11 Creator implementation:
- Clarify customizations run at first logon via autounattend.xml rather
than offline WIM modification
- Correct removed bloat AppX count from 40+ to 19 packages
- Update USB partition layout to single FAT32 partition with WIM split
- Clarify OneDrive uninstall timing during first logon
- Document missing helpers including USB functions and oscdimg helpers
- Document edition pinning, $OEM$ fallback scripts, and WIM metadata validation
- Update logging location and remove offline registry tweak wording
* docs(win11creator): clarify timing and media limits
Toggles are identified by name prefix, not by the Type field, so the
WPFTweaks-prefixed key made Reset-WPFCheckBoxes treat this entry as an
ordinary checkbox and force it off on every tab build, deleting the
NoLockScreen value it had just set.
Closes#5060
* chore(dns): remove Mullvad DNS providers
Mullvad is shutting down its public encrypted DNS servers and sponsoring
Quad9 instead, so the six Mullvad profiles would resolve nothing once the
servers go dark.
Drops the Mullvad entries from config/dns.json, trims them out of the
WPFchangedns ComboItems, and removes the matching bullets and the
DoH-fallback note from the tweaks guide. Quad9 already ships as a provider,
so users have a documented destination.
The generated code-reference page for this tweak is left alone; the
pre-release workflow regenerates it from config/tweaks.json.
* test(dns): use generic fixtures for DoH-only providers
The DoH-only and SecondaryDohTemplate paths in Set-WinUtilDNS were covered
by fixtures named after Mullvad and carrying its real resolver addresses.
Those code paths still exist, so the coverage stays; only the naming was
tied to a provider WinUtil no longer ships.
Renames the fixtures to DohOnlyProvider and DohOnlyNoSecondary and swaps in
RFC 5737 / RFC 3849 documentation addresses and example.com templates, so
the tests no longer read as if they exercise a shipped provider.
* Rework the Win11 Creator tab into a three step wizard
- Turn the stacked sections into pages you can navigate between
- Move the edition picker and driver option to the modify step
- Show a working page while mounting and modifying run
- Show what the run changes and a finished panel with the output path
- Put the status log in its own card next to the steps
- Rename Clean & Reset to Start Over and move it below the actions
* Follow the existing colours on the Win11 Creator tab
- Take button text and icon colour from the button they sit in
- Stop the implicit TextBlock style painting a label background on buttons
- Colour step headers from the tab item state
- Use the label accent for headings and field labels like the other tabs
* Cut repeated markup on the Win11 Creator tab
- Render button icon and label from a ContentTemplate, glyph goes in Tag
- Render the step header from a HeaderTemplate the same way
- Move the page scroll wrapper into the tab control template
- Derive the text styles from one another instead of repeating setters
* fix: tidy the dropdown and stop the spinner stuttering
- combo box items painted their own background while the popup painted
another, so the list read as loose labels on a mismatched sheet; items are
transparent now and the popup carries the colour
- stretch the item presenter so a highlight spans the popup instead of just
the text, and bind the popup width to the closed box
- cache the working page's spinner: it animates a rotation on a TextBlock,
which re-rasterises the glyph every frame on the interface thread
- run that animation only while the page is visible; it was started on Loaded
and never stopped, so it kept spinning after the step was done
* Stop the Win11 Creator status log rendering justified
The shared TextBox style sets HorizontalContentAlignment to Stretch, which
WPF reads as TextAlignment.Justify. Every other box in WinUtil is single
line, so the status log is the only place it shows, and there it stretched
each wrapped line to the full width with gaps between the words.
Left-aligns it, and while the log is being read as a log rather than as
prose: monospace from a new Win11LogFontFamily theme key so the timestamps
form a column and a wrapped line is visibly not a new entry, and no effect,
since the shared style's drop shadow has neither border nor background to
sit under on this control and smeared the glyphs instead.
* Show the working page for export, USB write and Start Over
Only mount and modify moved to the working page. Saving an ISO, writing a
USB drive and Start Over all ran with the finished output page still on
screen, so the three longest operations in the tab were the ones that looked
like nothing was happening.
All three now open the working page for their duration and return to the
page they belong on: the output step for the two that produce media, the ISO
picker for Start Over, which has just deleted the working directory. Failures
leave the working page before their message box rather than putting a modal
over a spinner, and a finally guard covers cancellation, which skips catch.
Start Over spins the icon backwards. Its work undoes rather than produces,
and reading that off the animation is quicker than reading the label.
* cut comments
Leaves the four that answer a "why is it written this way" a reader cannot
get from the code: the shared TextBox style's justify and drop shadow, the
spinner tag being set before the page is shown, going back to Select rather
than Modify after a failed modification, and the finally guards existing for
cancellation rather than for failure.
* cut changes that only reword
Reverts three edits that changed wording without changing meaning, and drops
the comment on the failed-modification branch.
* cut comments that narrate the code
* address coderabbit review
Hyphenate three-step, and let the chevrons grow past their minimum instead of
pinning a size the glyph could outgrow.
* dismount a replaced ISO and name the USB disk when done
Picking a second ISO after verifying one left the first attached: the mount job
clears Win11ISOImagePath, which is the only handle cleanup has to it. Dismount
it in the job, off the interface thread, before that reset.
The done panel now names the disk it wrote, the way the ISO branch names its file.
* keep the wizard inside the card at the minimum window width
The step column and the chevron grid's middle column were both pinned at 620,
while the left card is about 485px at the 800px minimum. The pages do not
scroll sideways, so controls past the card edge were unreachable.
Both are now capped rather than fixed, and the removed WPFWin11ISOArchLabel was
never assigned by anything.
* stop a replacement mount when the old ISO will not dismount
The warn-and-continue path cleared Win11ISOImagePath anyway, which strands the
old mount for the rest of the session - the leak the dismount was added to
close. It now reports and throws instead, from inside the try so the finally
still re-enables the browse and mount buttons.
* cut comments that narrate the code
* drop the OneDrive sync claim from the modify step list
The post-install script sets DisableFileSyncNGSC=1, but it is prepended to
FirstLogon.ps1, whose own body then sets the same value back to 0. The policy
never survives, so the bullet was promising something the run does not do.
Replaced with search box suggestions, which it does do.
* Add a job layer for long running work
- Start-WinUtilJob owns busy state, progress, taskbar, logging and errors
- Write-WinUtilJobProgress reports from a job without UI checks in the body
- Post UI updates instead of waiting on the dispatcher for each one
- Move Invoke-WPFInstall onto it as the first workflow
* Move the remaining app and feature workflows onto the job layer
- Uninstall, AppX install, Features, OOSU and installed detection
- Drop the per workflow busy flag, progress, taskbar and error handling
- Rework their tests to check the job and its body instead of runspace internals
* Run the WinUtil interface on its own thread
main.ps1 now only manages the run: it creates a dedicated STA runspace for
the window, waits for it, and reports whatever the interface thread failed
with. The interface itself moved into Start-WinUtilUserInterface, so the
thread that owns the window does nothing but paint and dispatch.
- New-WinUtilSessionState builds one starting point for both the interface
runspace and the worker pool, carrying $sync, the compiled script globals
and every WinUtil function. The pool previously copied only functions
matching winutil|WPF, which is not enough for a runspace that has to build
a tab.
- Invoke-WPFUIThread hands work to the interface runspace as body text plus
parameters instead of marshalling a scriptblock. A scriptblock keeps the
session state it was written in; running one across runspaces loses the
caller's variables on an async post and costs roughly twenty times as much
per command, which turned a checkbox refresh into a multi-minute freeze.
- Both helpers stop at a shut-down dispatcher, so a job that outlives the
window finishes quietly.
* Move every long workflow onto the job layer
Tweaks, undo, AppX removal and the five Win11 Creator workflows now go
through Start-WinUtilJob like the install workflows already did. That
removes the five hand-built STA runspaces and the function-definition
injection the ISO code needed to reach its own helpers.
- One busy flag: $sync.ActiveJob replaces ProcessRunning and
Win11ISOProcessRunning, and only the job layer writes it.
- Write-WinUtilJobProgress -Hide absorbs the last use of
Set-WinUtilTweaksProgressIndicator, so the progress bar and taskbar item
have a single owner. The helper is gone.
- Show-WinUtilMessage marshals onto the interface thread and logs the
prompt, so a job body can ask a question without knowing which thread it
is on. The raw MessageBox calls in the ISO workflows are gone.
- Win11 Creator status-log lines also go to the session log, and the
per-workflow Log/SetProgress helpers are gone.
- Get-WinUtilOscdimgPath and Get-WinUtilFreeDriveLetter are now real
functions rather than nested ones, so the pool can resolve them.
* Log from every WinUtil thread into one session file
Start-Transcript only records the runspace it was started on, so every line
a worker or the interface logged was being dropped. Write-WinUtilLog now
appends to the session log directly, serialized with a named mutex, and the
console transcript gets its own file in the same logs directory.
* Document the threading model and the job layer
* Stop Invoke-WPFUIThread leaking the body's output to its caller
The helper returned whatever the body produced, including a bare $null. Callers written
against the old void signature then returned an array instead of their own value:
Get-WinUtilSelectedPackages handed back @($null, $split), both package lists read as empty,
and Install and Uninstall reported success without installing or removing anything.
Output is now suppressed unless -PassThru is asked for, which only Show-WinUtilMessage needs.
Covered by tests on both the helper and the package split.
* Put every button that changes the system on the job layer
Invoke-WPFButton now classifies the press instead of running it. Anything that changes the
system gets a job; tab switches, selection helpers, window chrome and the WPFPanel* applet
launchers stay on the interface thread. Updates, the Ultimate Performance plan, the Fixes
buttons, OpenSSH Server, the system repair scan and the AppX query previously ran inline,
which froze the window, produced no progress and interleaved their output with a running job.
The job layer also owns the console banner now. Write-WinUtilJobBanner draws it once, so the
eleven hand-drawn === boxes are gone and every operation announces its start, not only its end.
- The job is named after what the button says, read from the config or the control itself,
so there is no second list of labels to keep in step.
- Show-WinUtilMessage replaces the last raw MessageBox calls, which could not have worked
from a worker thread.
- Write-WinUtilJobProgress replaces the last direct Set-WinUtilTaskbaritem calls.
* Document button routing and the job banner
* Read function bodies from the FunctionInfo instead of the function provider
Building the session state is on the path to first paint, and going through
function:\ for every function cost about as much as the whole interface
runspace saved. Time to first window is back level with upstream.
* Render the taskbar overlays after first paint
The logo overlay render costs about 55ms and nothing can see it until the window
is up, so it no longer sits between the interface being built and being shown.
Both the logo and the status overlays are now rendered from the same deferred
call once the window has painted.
* Make a failed package fail the job
Both package helpers ran the manager and moved on regardless of its exit code, so
a run in which nothing installed still reported success with a green checkmark.
They now emit a result per package, classified from the exit code: succeeded,
skipped for WinGet telling us there was nothing to do, or failed. The workflow
collects them and Complete-WinUtilPackageRun prints the summary and throws when
anything failed, which is what puts the job into its failed state.
* Time every pipeline step and report the slowest ones
Measure-WinUtilStep wraps a step, passes its output through untouched, logs how
long it took and keeps the record. Every job and the interface build end with a
summary ranking the slowest steps and their share of the total, so "which tweak
is taking forever" and "what is holding up startup" are answerable from the log
instead of by guessing.
Wired into the interface build, each tweak, each undo, each feature, and each
package. Jobs also log their own wall-clock duration, and the interface logs the
moment it can first service input.
* Render the taskbar overlays on the main thread's runspace
The overlays need an STA thread, which the worker pool is not, so they get one of
their own. Starting it from the interface thread cost more than it saved: opening
the runspace took 154-221ms there against 88ms of rendering. Starting it from the
main thread instead is free, because that thread does nothing but wait for the
window, and the render then overlaps the interface build.
Measured over three runs each, time from start to the interface accepting input:
2071/2105ms before, 2105/2131/2193ms started from the interface thread,
2026/2044/2050ms started from the main thread.
Also caches the session state, which two runspaces now share, and moves the
runspace cleanup registration into its own function for the second caller.
* Cut startup to first interaction roughly in half
- wire button clicks by type name against a HashSet, not a pipeline per $sync key: 335ms to 81ms
- build no tab content before first paint; Invoke-WPFTab already builds the tab it activates
- group apps by category into Lists, not by appending to arrays
- interface built ~1460ms to ~465ms, ready for input ~2090ms to ~858ms
* Warm the unopened tabs while the interface is idle
- queue each remaining tab at ApplicationIdle priority after first paint
- one tab per queued operation so input is serviced in between
- first click on a tab no longer pays for its build
* Report failures with the context needed to act on them
- Write-WinUtilErrorRecord logs message, exception type, command, line and script stack
- used by the job layer, the button funnel, the interface dispatcher and the main thread
- route buttons to the job layer by whitelist, so chrome and popup toggles stop starting empty jobs
* Wire the Install tab controls where they are created
- ChocoRadioButton, WingetRadioButton and the install action buttons come from
appnavigation.json, so they do not exist until the Install tab is built
- the interface build wired them anyway, which is the three null-reference errors
reported on close since tab content moved behind first paint
- Initialize-WinUtilInstallTabControls now does it from the tab build, guarded
- offline mode disables the install buttons from there too, for the same reason
- new test fails if the interface build touches any config-generated control
* Stop losing warnings and non-terminating errors from job bodies
- a worker buffers its warning and error streams on an object nobody reads:
Write-Warning never reached the log, Write-Error reached nothing at all
- the job layer merges both into the log, so all 30+ Write-Warning and 4
Write-Error sites in the helpers are visible without touching each one
- the interface runspace warning stream is drained on exit too
- $sync.LoggedErrors counts error events, detail lines excluded
- a job that logged errors without throwing now finishes as "N error(s)"
with a warning overlay instead of a green checkmark
* Drive winget through Microsoft.WinGet.Client for real progress
The winget CLI hides its progress bar as soon as its output is redirected, so a
package could only ever be reported as started and finished. The module reports
progress and returns a structured result.
- Install-WinUtilWinGetClient installs and imports the module, cached per session
- Invoke-WinUtilWinGetCommand runs a cmdlet on a nested PowerShell and polls its
progress stream, which cannot be redirected like output or errors
- percentages map into the package's slice of the job bar: "7zip.7zip - 1.9 MB / 1.9 MB"
- outcome comes from Status and InstallerErrorCode, not an exit code
- a package already present is upgraded, not reinstalled: Install-WinGetPackage
re-downloads and re-runs the installer even without -Force
- detection uses Get-WinGetPackage and matches on name as well as id, so apps
installed outside winget are recognised (Brave, and every other ARP entry)
- falls back to the command line unchanged when the module cannot be installed
Verified against real winget in the eval VM, 12 checks; 545 unit tests pass.
* Show the install phase as running instead of finished
Measured what the module actually emits: 7 progress records whether the package
is 1.9 MB or 57.8 MB, only two of them download samples, and the install phase
reports 0 then 100 with nothing between. On VLC the install is 4.3s of the 9.7s.
- the download gets the first half of the package's slice, so reaching 100%
download no longer fills the bar
- the install phase pulses the bar and counts elapsed seconds in the label,
because neither winget nor the module exposes installer progress
- scan the whole progress collection, not just its last record: a byte sample
can be superseded within milliseconds
- RoundedProgressBarStyle gained an indeterminate trigger; it had none
Fixes uninstall reporting a package that is not installed as a failure, which is
what UninstallError after 354ms was, and adds ExtendedErrorCode to the detail.
* Say what a winget failure actually was
The command line prints a sentence for a failure; the client module returns only
an HRESULT, so the same failure read as "COMException (0x8A15007D)". Both report
the same number, so one table serves both paths.
- Get-WinUtilWinGetErrorMessage explains the codes WinUtil hits, and gives the
hex plus the return-code reference for anything else
- 0x8A15007D now reads: installed for a single user, cannot be removed while
running as administrator, remove it from Settings > Apps
- unsigned HRESULTs are wrapped rather than cast, which overflowed Int32
- a shared failure reason is repeated in the thrown message
- the banner wraps at 76 columns instead of drawing a box wider than the console
* Keep the run position in the progress text during a package
- the bar itself was already whole-workflow: 0-12, 25-37, 50-62, 75-87, 100
- but the status read "A.A - 50% downloaded", dropping the (n/total) the old
per-package messages carried
- callers pass a label, so it now reads "A.A (1/4) - 50% downloaded"
* Pulse the progress bar in place instead of filling it
- IsIndeterminate makes WPF discard Value and stretch the indicator across the
whole track: measured 398px of a 400px track at value 40, against 159px correct
- the pulse is driven by Tag instead, so the bar keeps the progress it reached
- RemoveStoryboard on exit, because Stop left the indicator at whatever opacity
the pulse happened to be on
* perf: cut Install tab build and keep the interface answering during warmup
- look apps up by hashtable index, not dynamic member: Install tab app area 361ms -> 91ms
- cap a render pass at 25 apps so a large category cannot stall the interface
- yield between batches when building speculative tab content
- claim a tab as initialized before building it, so a click during a yield cannot double build
- time each step of a tab switch
* xaml: drop layout elements and styles that do nothing
- remove 8 single child wrappers from control templates, one per instance of every button, toggle and tweak switch
- delete unreferenced labelfortweaks and ScrollVisibilityRectangle styles
- verified pixel identical across all five tabs
* fix: bring the last workflows into the job layer
- upgrade all runs package by package on the worker instead of spawning a console
- PS profile setup runs pwsh with output captured, not a Windows Terminal tab
- resolve the PS7 profile path from pwsh, so remove targets the file install wrote
- treat winget exit 3010 and 1641 as success; a reboot requirement is not a failure
- drop the power plan success popups, the job layer already reports the result
- load PresentationFramework before a message box on a worker, and log if it cannot show
- probe optional commands with -ErrorAction so a missing choco does not throw
- refresh PATH after installing chocolatey
* fix: keep the runspace handle out of the console and the pipeline
- suppress the IAsyncResult Start-WinUtilJob got back, which printed a table on every button press
- test fails if any caller leaves Invoke-WPFRunspace unassigned
* fix: choco parity with winget, and prompts that cannot hang or assume consent
- choco runs one package per call, so progress moves and a failure names the package
- add an Upgrade action; upgrade all was building "choco install all", which is not a package
- explain a choco failure from its own output instead of reporting a bare exit code
- pass a progress slice to choco from install and uninstall, as winget already gets
- never show a message box without a window: a modal there never returns
- an unanswerable prompt answers No, so it can never stand in for consent
- uninstall requires an explicit Yes rather than the absence of a No
* feat: headless runs report and finish on their own
- progress goes to the console when there is no window, throttled so downloads do not bury it
- one entry point for preset and config; a preset can now be a baseline a config adds to
- apply selected toggles, which only ever applied themselves from the window
- exit code carries the outcome: 0 clean, 1 problems, 2 nothing selected
- elevation waits for the elevated run and hands its code back
- per step timeout, so an installer that never returns cannot hang the run for good
- a step that throws no longer abandons the remaining steps
- name an unrecognised config entry instead of failing on a null list, and ignore duplicates
- import with no window logs instead of throwing on a message box type it cannot load
- temp file cleanup skips files in use rather than reporting each as an error
* perf: stop the interface stalling while the app list loads
Measured with a new input-priority heartbeat: 2669 ms unresponsive across 18
stalls, worst 399 ms. Now 502 ms across 5, worst 151 ms, and nothing after the
first three seconds.
- cache app icons on disk and fetch the missing ones on a worker; assigning a
remote address to an Image made WPF fetch and decode it, landing back on the
interface thread whenever the network answered, for a minute after startup
- share the six app entry event handlers instead of building them per entry:
3.18 ms to 1.36 ms per entry, measured
- slice rendering and tab building by a deadline rather than an entry count, so
a slower machine cannot turn a batch into a stall
- yield while building the tab opened at startup, as the warmup already did
- reject a ParameterList that is a flattened pair; it silently ran the worker
with two characters of the parameter name and did nothing
- add Start-WinUtilUIHeartbeat behind WINUTIL_TRACE_UI to measure any of this
* fix: build the other tabs before finishing the app list
Tab warmup was queued at idle priority while app rendering was queued at
background priority, so no tab was warmed until every render pass had run.
For the first few seconds every tab except the open one was empty, and
clicking one paid for its whole build: Tweaks measures 400-530 ms.
Verified from the log: all tabs were ready after 32 of 32 background steps
before, and after 5 of 32 now.
- warm tabs at background priority so they are not starved by the app list
- hold app rendering while any tab is still unbuilt
- stand aside for 400 ms after input, and skip drawing entries for a tab that
is not on screen, resuming when it is
- report the whole latency distribution rather than only gaps over 60 ms; a
thread kept busy by short pieces of work showed no stall while every
interaction still waited behind the piece in flight
* fix: ask before closing over running work, and shut down in order
Closing while a job ran tore the worker pool down underneath it. A queued
instance then started on a runspace already in Closing, threw on a thread
pool thread where nothing catches, and ended the process with an unhandled
InvalidRunspaceStateException instead of exiting.
- ask whether to wait for the running job or stop it, and keep the window
open if the close is cancelled
- track what is running so it can be stopped, and stop it before closing the
pool rather than pulling the runspaces away from it
- refuse to queue new work once shutdown has begun
- close the window by itself once an awaited job finishes
- bound the wait, so a worker that cannot be stopped does not keep the window
open for ever
- phrase the question so the buttons answer it in any language; Windows labels
them itself and text naming them Yes and No does not match Ja and Nein
- treat a missing collection as empty; @($null) is a one element array, which
read as one running item when nothing was running
* fix: let a running job finish in the console after the window is closed
Waiting kept the window open until the job ended, which is not what closing
it means. The window now goes at once and the run continues where it can
still be seen, ending the process when it is done.
- close the window immediately and leave the job on the worker pool
- skip the pool shutdown on that path; it would stop the work just kept
- wait for the job on the main thread, then close the pool and exit
- treat a shut down dispatcher as no window, so progress reaches the console
instead of being posted to a dispatcher that drops it
- bound that wait, so a job that never returns cannot hold the process open
- guard the close post against a window that has already gone
* feat: pause button, and progress that rewrites its line
- add a pause button beside the progress bar; a run holds at the point every
loop reports progress, since a command already started cannot be suspended
- hold only inside a job worker: whoever presses pause reports it through the
same progress call and would otherwise wait on itself
- clear the pause once the body returns, or the finish reporting pauses too and
the job stays marked running for ever
- release it when the window is closed over the job, since there is then no
button left to resume with
- rewrite the console progress line in place rather than adding one line per
update; a single install scrolled a screenful
- keep one line per update when output is redirected, where there is no cursor
to move, and throttle harder there
- say goodbye where the process actually ends; closing can be declined or leave
a job running
* feat: stop button for the running action
- add a stop button beside pause; it asks first, since stopping an install
halfway is not undoable
- end the run at the same safe point a pause holds at, so anything already
started finishes and nothing is cut in half
- report it as stopped rather than failed, through its own cancellation
- clear the stop before reporting it, or the report throws it again from inside
the handler doing the reporting
- release a pause when stopping, or the run would never reach the point where
it notices
- cut the worker off after a grace period, since a single long step reaches no
safe point until it returns
- reset both flags per job, so a late stop cannot end the next run
* fix: use a stop glyph that does not look like a missing one
U+E71A is a hollow square at button size and reads as the empty box a font
shows for a code point it lacks. U+E73B is the filled square.
- add a test that every private use code point in the markup and the scripts
exists in Segoe MDL2 Assets
* fix: icon buttons render in the icon font, not a fallback
A char assigned to Content is not laid out with the control's own font. It
falls back to whatever font claims the code point and is drawn in that font's
colour and metrics, which is why the pause icon came out teal and a different
size from the cross beside it. Add-SelectedAppsMenuItem already cast to string
for the same reason.
- cast every icon assignment to string: pause, play, the app entry popup and
the theme button
- use the cancel cross for stop; a square is a blank block at button size
whether it is filled or hollow
- test that no icon reaches Content as a char
* fix: the font scaling slider goes where it is clicked
WPF leaves IsMoveToPointEnabled off, so a click on the track pages by
LargeChange instead of moving the thumb to the pointer. LargeChange defaults
to 1.0, which over a range of 0.75 to 2.0 is most of the track, so clicking
anywhere toggled between 100% and 200%.
- follow the click, and page by one tick rather than a full unit
- test that every slider does both
* fix: draw the logo at the size it is asked for, and give the window its own icon
The rasteriser built the bitmap from the canvas rather than the requested
size, so every render came out 100 by 100 whatever was asked for: a 32px icon
was that downscaled, and anything larger was an upscale. The canvas was also
smaller than the artwork, whose paths run to about 108 by 110, so the right
and bottom edges were cut off. Between the two, the logo covered about a third
of the icon it was drawn into.
- rasterise from the geometry's real bounds into a bitmap of the requested
size, centred and filling the frame
- keep the shapes in one place, so the control and the bitmap draw the same art
- set the window icon at the sizes the system reports for this display, small
and large, instead of leaving Windows to scale one bitmap
- hold the icon handles for the life of the window and free the ones replaced
* fix: give the nav logo its square box back
Fitting the box to the artwork left no room around it. The logo is taller than
it is wide, so a square control filled by it edge to edge sat against the tab
buttons next to it.
- centre the artwork in a square box with a small margin, so the control fills
the size it was given rather than the artwork's own proportions
- the bitmap form is unchanged and still fills the frame, which is what an icon
wants
* refactor: one background queue, one UI-alive check, drop the stall tracer
- move the DPI window icon work out to feat/dpi-window-icon and revert it here
- drop Start-WinUtilUIHeartbeat: it found the startup stalls, it is not
something a normal run should carry
- add Start-WinUtilBackgroundQueue and put tab warmup and app-entry rendering
on it; they were the same pump written twice
- add Test-WinUtilUIAlive, replacing the same dispatcher guard hand-written
eleven times in three spellings
- carry the queue name as the dispatcher's own argument, not a captured
variable, so the posted step still resolves where it was written
- give Invoke-WinUtilWhenIdle an -Argument for the same reason
- load the WPF assemblies in preferences-theme tests instead of relying on
logo-render having loaded them first
* docs: bring the agent rules in line with the job layer
Section 13 still pointed at Set-WinUtilTweaksProgressIndicator, which the job
layer removed, and said nothing about the pieces that replaced it.
- point the UI-helper rule at Write-WinUtilJobProgress and Test-WinUtilUIAlive
instead of the deleted progress indicator
- say that long operations go through Start-WinUtilJob, and that a job body
owns neither the busy flag, the banner, nor its own interface handling
- send deferred interface work through Start-WinUtilBackgroundQueue rather
than a fourth hand-rolled pump
- record how values reach a posted scriptblock, and why a closure is the wrong
answer: it carries the value but binds command lookup to a copied scope
- state that diagnostic scaffolding is measured with and then deleted
- require each Pester file to load what it needs; several passed only because
an earlier file in alphabetical order had loaded it
* refactor: trim the package work back to what the pipeline needs
The winget client module earns its place: winget.exe hides its progress bar
once its output is redirected, so the CLI can never say how far along an
install is and the progress bar has nothing to show. What rode in behind it
did not.
- keep the module, the per-package progress and the result objects both
managers now return; those are what the job layer reports from
- take the action from the caller instead of probing the machine first:
Install, Uninstall and Upgrade each pick their own cmdlet and verb, matching
the set Install-WinUtilProgramChoco already takes
- drop the Get-WinGetPackage probe with it, which cost a nested call per
package and made the module path mean "install or upgrade" while the command
line path still meant "install"
- send the upgrade workflow through -Action Upgrade, which is what it was
always asking for
- revert Invoke-WinUtilCurrentSystem to the command line; reading which apps
are installed has nothing to do with reporting progress
- cut the winget and choco error tables: the hex code and Microsoft's own list
say the same thing without a copy to keep in step, and choco's reason is
already in the output that gets logged
* test: drop the tests that assert on source text
Sixty-six tests read a function file and regex-matched its contents, so they
failed on edits that changed no behaviour: ten of them broke during a refactor
that only moved code between files. A test that pins how something is spelled
is an edit detector, not a test.
- remove the It blocks that Get-Content a .ps1 and match against it, and the
Describe blocks left with nothing in them
- keep the ones that read source to check a set rather than a spelling, such as
every WPF handler resolving to a defined function and every $sync member
being declared; those catch a real mistake
- drop Get-WinUtilFunctionFile, which had no callers left
* fix: make the active job slot safe to claim and release
Three defects in the job layer's shared state, all of them races that the
dispatcher happened to hide.
- claim $sync.ActiveJob under the collection's own lock. Interface events are
serialised by the dispatcher, but a headless run, a scheduled caller and a
job body starting another job are not, and a test-then-assign there let two
jobs both believe they owned the slot
- identify a run by token rather than by name. A worker the stop watchdog cut
off can still be unwinding when the next job starts, and its finally block
released the slot unconditionally, wiping the claim the next job had just
made. Both the worker and the watchdog now release only what they still own
- rename Write-WinUtilJobProgress to Step-WinUtilJob. Write- in PowerShell
means adds to a stream, and this blocks while paused and throws
OperationCanceledException on stop. The trap was already live: the job layer
has to clear both flags before its own finish reporting or that reporting
re-raises the stop it is reporting
Also corrects the cross-runspace cost noted in the tests. Marshalling a
scriptblock is not "roughly twenty times" dearer; measured over 400 command
invocations it is 5354 ms against 3 ms rebuilt from text, because every command
the body invokes is resolved back through the originating runspace.
* refactor: drop the WinGet client module from this branch
The module is the one thing here that adds a runtime dependency: 53 MB from
PSGallery on first use, fetched at elevated privilege. It buys progress
movement inside a single package and nothing else. That is a different kind of
change from the rest of this branch, which only moves work between threads, and
it deserves to be judged on its own.
- remove Install-WinUtilWinGetClient and Invoke-WinUtilWinGetCommand
- collapse Install-WinUtilProgramWinget onto the command line path it already
had underneath, keeping the per-package result objects, so a failed package
still fails the job
- drop ProgressBase, ProgressSpan and Label from the winget path, which only
existed to slice the bar inside one package. Chocolatey keeps its own: it
reports per package from its own loop and never needed the module
- read upgradable packages from the winget command line output again
Kept on feat/winget-client-module, which branches from here.
* docs: follow the Write-WinUtilJobProgress rename in the architecture page
* fix: address the review findings on the job layer
Ten findings held up on inspection. The first two were breakage this branch
introduced.
- run a nested Start-WinUtilJob inline instead of refusing it. Install Features
reaches the job layer twice, once through its feature.json entry and again
from Invoke-WPFFeatureInstall, so the inner call was refused and features
never installed
- read the package manager preference rather than ChocoRadioButton.IsChecked in
Invoke-WPFInstallUpgrade, which runs on a worker where touching a control
throws
- pass parameters and return values through the Invoke-WPFUIThread fallback.
[action] carries neither, so Show-WinUtilMessage lost both its arguments and
its answer whenever the dispatch delegate was not yet built
- register a shell after BeginInvoke, not before: a NotStarted instance looks
finished to the pruning pass and could be dropped before shutdown saw it
- clear $sync.ActiveJobToken wherever the slot is released, through one
Clear-WinUtilActiveJob helper. Clearing only the name left a token that could
match a later run
- clear $global:WinUtilIsJobWorker when a worker finishes. Pool runspaces are
reused, so the flag outlived the job that set it
- set the pause and stop button state through the dispatcher
- take a locked snapshot of $sync.ActiveShells before enumerating it, and create
the collection under the lock
- stop the watchdog waiting on the interface thread. It issues the stop and
watches later ticks instead of sleeping for up to ten seconds
- throw rather than return on the ISO failure paths, which the job layer was
reporting as finished, and check exit codes in Invoke-WPFSystemRepair and
Invoke-WPFUltimatePerformance so a failed step fails the job
- give the pause and stop buttons AutomationProperties.Name; their content is a
private-use glyph
* fix: address the remaining review findings
Seven more held up. The first is the one that mattered most and this branch
introduced it.
- separate recycling a runspace pool from shutting down. Initialize-WinUtilRunspacePool
replaces a pool that is no longer open by calling Close-WinUtilRunspacePool,
which set $sync.ShuttingDown. Nothing resets it, so a single recycle made the
job layer refuse every later action for the rest of the session
- bound the ISO mount wait at 60 seconds. The loop had no exit but success, and
one job runs at a time, so a damaged or already-mounted image blocked every
other action and the shutdown wait
- check robocopy exit codes in both ISO and USB workflows through one
Invoke-WinUtilRobocopy. Codes of 8 and above mean files were not copied, which
produced media that reported complete and did not boot
- read oscdimg stderr as it arrives. Draining stdout first and stderr afterwards
deadlocks once the stderr pipe fills
- look for oscdimg on PATH, in both ADK roots and in the per-user WinGet package
root, using the same search before and after the install attempt
- set the child error preference to Stop for the profile setup, and fail on
captured error records. A non-terminating failure exited zero and was reported
as installed
- confirm each id parsed out of the winget upgrade table against winget before
upgrading it, and keep stderr out of the parse. The table is localised and
truncated, so a wrapped version or a translated header matched the same shape
* fix: address the minor and nitpick review findings
The review body carried 38 findings beyond the 20 inline ones, in collapsed
sections. These are the ones that held up.
Real defects:
- the theme hook assigned to $handled instead of $handled.Value, so a
WM_SETTINGCHANGE was never marked handled
- a declined UAC prompt left $elevated null and exited 0, which a headless
caller reads as a successful run
- Start-Transcript ran before the log directory existed, so the first run failed
before logging started
- Write-WinUtilLog appended even when the mutex wait timed out, which is the
case with the most contention and the one that interleaves lines
- Wait-WinUtilRemainingWork took [int] minutes, so any fractional timeout
truncated to zero and the bound meant "do not wait"
- the console progress throttle only applied when the text was unchanged, so a
job reporting per package wrote on every call
- an undecodable icon response stayed in the cache and was skipped on every
later run
- Start-WinUtilAssetRendering leaked its dedicated STA runspace; the cleanup
callback now disposes a runspace it was given
- Invoke-WPFFixesWinget could not repair a broken WinGet, because
Install-WinUtilWinget returns early when winget is detected. Added -Force
- winget's own reboot-required and reboot-initiated codes counted as failures
Smaller:
- set TLS 1.2 and a timeout before fetching the Chocolatey bootstrap
- let the headless queue drain survive one failing item, as the dispatcher path
already does
- bind the idle timer to the interface dispatcher explicitly
- route the export message through Show-WinUtilMessage like the import branch
- suppress New-Item output that was reaching the job's output stream
Tests that could not fail:
- the bounded-wait test passed a fractional timeout that truncated to zero, so
the wait never ran
- the slider tests cast possibly-absent attributes to [double], and compared two
empty strings for the regression they exist to catch
* fix: startup crash and icons that only appeared once the list was drawn
Both were mine, and both were introduced while addressing review findings.
The crash. Register-WinUtilRunspaceCleanup compiles its helper type once and
guards that with a type-exists check, so a session already holding the old
shape keeps it. Adding a Runspace property to that type therefore failed on
every later run with "The property 'Runspace' cannot be found". The type is
back to exactly what it was, and the asset rendering runspace lives until the
process exits: one STA runspace for the lifetime of the app is the cheaper
trade against a helper type that cannot be changed safely in a session.
The icons. Start-WinUtilIconFetch ran from Complete-WinUtilInstallAppRendering,
so nothing was fetched until every entry had been drawn and no icon appeared
for the first several seconds. Upstream assigned a remote address per Image and
let WPF fetch them all at once, which is what made them appear promptly.
- fetch after each render batch instead, gated so one fetch runs at a time and
whatever queues up meanwhile is taken when that fetch ends
- clear the gate when the runspace refuses the work, or no fetch would ever run
again for the rest of the session
- request each wave together rather than one icon after another
Measured on a cold cache in a VM: first icons at 1.35s and all of them by 4.0s,
against nothing before roughly 7s previously.
* fix: address the second review pass
All three are in code this branch added.
- accept DISM exit code 3010 as success. It is ERROR_SUCCESS_REBOOT_REQUIRED,
meaning the image was repaired and the change lands on restart, so the exit
code check added in the last pass turned a successful repair into a failed
job. Carried per step, since the same number from chkdsk or sfc does not mean
that, and logged as a warning so the restart is not silent
- say what is happening on the WinGet repair path. Install-WinUtilWinget -Force
runs while WinGet is installed, and printed "WinGet is not installed"
- append to PATH after installing Chocolatey rather than replacing it.
Overwriting with the machine and user values drops whatever this process
added earlier in the session
* fix: treat chkdsk's own exit codes as the outcomes they are
- chkdsk /scan reports 1 and 2 as ordinary results, so aborting the whole
repair on them skipped sfc and dism for no reason
- 3 stays a failure: the disk could not be checked, and the later steps are
not worth running on a disk in that state
- drive the decision off each step's SuccessCodes instead of a branch
hard-coded to DISM's 3010, so the same number means what that step means
- cover chkdsk 0, 1, 2 and 3, and 3010 from the wrong step
* fix: address the Codex review findings on the job layer
- send WPFPanel buttons that carry a function through the job layer: the
system corruption scan waited on chkdsk, sfc and dism on the interface
thread, with nothing to pause, stop or report it
- stop a timed out headless step before starting the next one, and abandon
the run if it will not stop, rather than changing the machine from two
runs at once
- keep the stop watchdog's slot claimed until the worker has actually gone
- skip the install summary reset when the Install tab has not been built,
which offline startup does by opening Tweaks first
- keep Win11ISO out of the tab warmup: building it runs the existing work
check, which reports or prompts while the user is on another tab
- pass --include-unknown when upgrading, since the scan that found the
packages used it
* fix: stop the tab warmup dying on a sync that is still growing
- Reset-WPFCheckBoxes enumerated $sync live while setting IsChecked, whose
handlers add to $sync, and while the warmup was building controls into it;
either one invalidated the enumerator and the warmup reported "Collection
was modified"
- snapshot both loops
- cover it with a checkbox that grows $sync from its own handler, which
reproduces the failure without the fix
* fix: shrink the pause and stop buttons and take them away when idle
- they were sized like the title bar icons, next to a 6px progress bar
- give them a size of their own and the smaller icon font, which font
scaling still applies to
- collapsed unless a job is running: they were only ever disabled, so a
finished or failed run left two dead buttons on screen
- the progress bar still stays to report how the run ended
* fix: colour the progress bar by how the run ended
- only the taskbar item carried the state, so a run that finished with
errors left a full bar in the normal colour, reading as a clean finish
- the fill now follows the bar's Foreground, which the job layer points at
an error or warning colour and back again
- by resource reference, so switching theme repaints it
- add error and warning colours to both themes
* fix: stop rendering the app navigation twice, which left Install dead
- upstream moved the app navigation render into Initialize-WPFUI, and this
branch still rendered it beforehand, so it was built twice
- the second pass clears the target grid, and the "already wired" guard goes
by name, so the replacement buttons counted as wired and never got a click
handler: Install and Uninstall did nothing, with no error anywhere
- leave that render to Initialize-WPFUI
* fix: uninstall apps that belong to the signed in user
- WinGet answers APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_ACTION_PROHIBITED for
anything installed in user scope while it is running elevated, and WinUtil
is always elevated, so those uninstalls did nothing
- a process cannot drop its own elevation, so hand that one command to a
scheduled task running as the interactive user at limited run level
- retry only on that code, so everything else is untouched
* style: keep the ISO variable names the file already used
- new scriptblock parameters were named in PascalCase, which put 20 lines
into the diff that are otherwise identical to main
- nothing about the behaviour changes
* refactor: take the icon cache out of the job layer change
It is its own feature, not something consolidating background work needs.
Entries go back to assigning the favicon address to the image, as main does.
The work is kept on feat/install-icon-cache and follows once this lands.
* refactor: take pause and stop out of the job layer change
They are a new feature of their own, not part of consolidating background
work, and main has nothing like them today.
- drop the two buttons, their routing and the checkpoints that served them
- keep Stop-WinUtilActiveWork: closing the pool and the headless step
timeout both need to end work, and neither is the user pressing a button
The work is kept on feat/job-pause-stop and follows once this lands.
* refactor: drop changes the job layer does not need
- the icon glyph strings are a rendering fix of their own: main renders a
char, and whether that is right has nothing to do with background work.
Reverted here, kept on fix/ui-glyph-strings
- trim the temp cleanup tweak to the part this change forces: errors have to
be suppressed because the job layer counts a logged error as a failed
step, but counting what was removed is unrelated polish
* refactor: cut comments and wrappers that were not earning their place
- the Recycle switch explained itself over four lines; two say what a reader
needs to know
- drop a .PARAMETER that only restated the parameter name
- Unregister-WinUtilActiveShell had one caller and cost twenty lines, so it
is inlined
- the running check was the same try/catch three times over, now one helper
* refactor: comments describe the code, not how it got here
- remove five comments left pointing at code the icon and pause splits took
away, two of them sitting above unrelated lines
- cut the ones that recounted a bug rather than describing behaviour: the
double rendered navigation, the dropped [action] parameters, the shell
registered too early, the Add-Type shape
- shorten the rest to what a reader needs
* refactor: inline the winget error message at its only call site
- Get-WinUtilWinGetErrorMessage was 26 lines for a one line format string
- Its zero guard was dead: exit code 0 is handled by an earlier branch
- Drop the file and the two test dot-sources of it
* refactor: drop the user scope uninstall fix, it has its own branch
- Invoke-WinUtilUnelevated and the elevation retry are self contained
- They ship on fix/winget-user-scope-uninstall, which does not need the job layer
- Removes a file and 3 tests from a PR that is already large
* docs: tighten function help and comments
- Cut narrative framing from the job layer, queue and shutdown doc blocks
- Kept the technical reasons, dropped the restatements around them
* docs: drop comments that restate the code
- Consent check, powercfg exit codes, shortcut guard, rethrow, single reason
- Compact the UI thread helper's help
* fix: address async job layer review findings
* fix: harden job startup and dependency discovery
* fix: close remaining async review gaps
* fix: finalize shutdown and ISO recovery paths
* fix: guarantee job cleanup and error attribution
* fix: preserve job outcomes during cancellation
* fix: close async rendering and logging races
* fix: bound shutdown and surface UI startup failures
* fix: skip blocked user-scope install updates
* fix: address final PR review findings
* fix: warn on blocked user-scope package actions
* fix: propagate file-backed headless exit codes
* Address final async job review findings
* Resolve latest automated review findings
* Preserve clipboard history in activity tweak
---------
Co-authored-by: Chris Titus <contact@christitus.com>
* feat: add title screen generation tooling
Add local automation for capturing WinUtil in Light and Dark themes
and generating the composite title-screen image.
* ci: automate title screen updates
Add a manual Windows workflow that compiles WinUtil, generates the
Light and Dark composite, and opens an image-only pull request.
* docs: update AGENTS.md and SPEC.md with title-screen generation details
* fix: address review comments
Tested image is correct, SHA-256 hash is identical to the previously verified output.
* fix: deselect bitmap before reading capture pixels
* chore(tweaks): clarify Activity History tweak description
* fix(tweaks): preserve clipboard history
---------
Co-authored-by: Chris Titus <contact@christitus.com>
* Add tweak to block Logitech Download Assistant auto-install
Mirrors the WPFTweaksRazerBlock folder-deny approach: clear
C:\Program Files\LogiDownloadAssistant, recreate it empty, and deny
Everyone write access so the Windows Update software-component package
cannot re-deliver the payload. UndoScript removes the deny ACE.
Unlike the Razer tweak, no global SearchOrderConfig/DisableCoInstallers
registry changes: those alter driver search system-wide and do not stop
software-component packages.
Fixes#5029
* Harden Logi block tweak per review: SID, 64-bit path, exit codes
- Use the locale-independent *S-1-1-0 SID instead of the English
"Everyone" name, which fails to resolve on non-English Windows.
- Resolve the 64-bit Program Files directory via ProgramW6432 with a
ProgramFiles fallback, so a 32-bit host cannot target the x86 folder.
- Throw when icacls exits non-zero so Invoke-WinUtilScript logs the
failure instead of reporting the tweak as completed.
- Skip the undo icacls call when the folder no longer exists.
* fix(iso): inject drivers per package
Add each root package folder separately so one bad driver cannot fail the rest. The batch form is roughly four times faster. That cost is accepted for one deterministic code path
* Preserve retained nested driver packages
* Discard partial driver injection attempts
* Remove excluded nested driver INFs
---------
Co-authored-by: Chris Titus <contact@christitus.com>
* docs: fix typo in contributing image asset and markdown references
* docs: fix typos and grammatical phrasing in documentation
* fix(config): correct typographical errors in tweak and app descriptions
* fix(scripts): resolve typos and grammatical errors in PowerShell sources
* fix: address review feedback on Outlook description, WinUtil capitalization, and grammar
* fix(win11-creator): filter unserviceable and stale drivers before injection
Add-Driver aborts the whole batch when one exported package is bad,
which broke ISO creation for anyone with an Extension-class or stale
duplicate driver in their store (#4971, #4982). Exclude both before
the single Add-Driver call instead of guessing per-vendor.
* fix(win11-creator): address driver filtering review feedback
Accept date-only DriverVer entries instead of treating them as
unknown. Use the full folder path as the dedup fallback key so two
unrelated packages can't collide on a shared leaf name. Discard the
logger's own output inside the selector so an emitting -Log callback
can't inflate the survivor count. Skip driver injection instead of
failing the whole ISO build when nothing is left to inject.
Also extracts the repeated DISM mock setup in the driver tests into
one shared harness, and asserts that excluded packages are actually
deleted from the export root before Add-Driver runs, not just logged.
* fix: address remaining code review comments
* feat(github): Prettify bug report template
* Let the people know that if they include anything private and share it, that makes them stupid, because AI told me to do that and that's NOT THAT OBVIOUS(sarcasm sign)
* Fix backward compatibility for old-style JSON config imports
* Refactor sidebar UI generation and add tests for Get-WinUtilVariables
* Fix appnavigation config test following UI generation refactor
* Restore global sync state in variables tests
* fix(impex): migrate supported legacy selections
Keep modern imports strict while allowing legacy backups to skip retired entries with clear logging and user feedback. Add fixtures covering partial and all-retired imports.
* test: strengthen UI and global state coverage
Exercise app category rendering through Initialize-WPFUI and restore global sync without leaking test state.
* fix(impex): ignore legacy metadata fields
Limit legacy selection migration to supported WPF key families so unrelated string metadata does not produce false retired-setting warnings.
* docs: clarify legacy config imports
Distinguish strict modern flat imports from partial legacy object migration, including single-setting export shape and historical groups.
---------
Co-authored-by: Chris Titus <contact@christitus.com>
* Add Get-WinUtilEntryToolTip helper for preset-key tooltips
* Show preset JSON key in app and tweak tooltips
* Match apps search against preset key for consistency with tweaks
* Limit preset-key tooltips to preset-representable controls
Comboboxes and package-manager radio buttons cannot appear in a preset
file. Update-WinUtilSelections routes a flat list of keys by the
WPFInstall/WPFTweaks/WPFToggle/WPFFeature/WPFAppx prefixes, so a preset
can carry neither a combobox selected value nor a radio group choice;
WingetRadioButton and ChocoRadioButton do not even carry a WPF prefix.
Advertising those control names as preset keys invited users to add keys
that fall through the switch and abort the whole import.
Revert the combobox label and radio button tooltips to the plain
description, and add tests that fail if the key is reattached to a
control the preset importer cannot accept.
* fix: address preset key review feedback
* docs: clarify preset key search scope
---------
Co-authored-by: Chris Titus <contact@christitus.com>
* fix(apps): WARP prettify
Updated Cloudflare WARP entry with new key and modified description and link.
* Fine, Chris, let's leave property name as full
* Rename 'cloudflare-warp' to 'CloudflareWARP'
---------
Co-authored-by: Chris Titus <contact@christitus.com>
* feat(github): revival of triage tools
* fix(github): workflow doc fix
* fix(github): PR detection
* fix(gh): wontfix preserve bug label
Removed state_reason from issue update when adding wontfix label.
* fix(gh): doc cleaning
* one more thing
* fix(gh): prevent Gabi moment
That's the only thing I will agree with this piece of hardware
* Fix syntax error in triage-tools.yaml
* fix(github): restrict triage commands to issues
---------
Co-authored-by: Chris Titus <contact@christitus.com>
* Add EmulationStation and Tailscale entries to applications.json
* Update config/applications.json
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* Update config/applications.json
I'm so sorry I'm late I've had to take a break from this for a bit of health related stuff.
Co-authored-by: Ivan Lepekha <57459428+FluffyPunk@users.noreply.github.com>
---------
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Ivan Lepekha <57459428+FluffyPunk@users.noreply.github.com>
* feat(apps): Added Battle.net
Just Blizzard. Well, we can hate them, but their launcher is unique
* You know, being smart is a hard work...
* titusTypo
* fix: link apps to their own site instead of a repo or store page
- the icon comes from the link's domain, so every GitHub hosted app showed
the same octocat and the two store apps showed the Microsoft Store tile
- point 16 apps at the project's own site, and LibreWolf at librewolf.net
rather than its old gitlab.io address
- left on GitHub where the site turned out to redirect back to the repo
(fnm, MSEdgeRedirect, Deskflow) or has no icon to serve (gsudo,
simplewall, ungoogled-chromium, DISMTools)
* fix: point Teams at teams.live.com so it shows the Teams logo
- the marketing page on microsoft.com serves the generic Microsoft favicon,
which Edge already uses
* fix: keep GlazeWM and Lua on the pages that match what is installed
- glazewm.com is not the project's own site: the repo declares no homepage,
glzr.io never references it, and it runs on unrelated hosting
- lua.org is the language, while the package is Lua for Windows
* fix: keep Teams on the Microsoft product page
- the package is Microsoft.Teams, the work product, while teams.live.com is
the consumer edition
FontScalingSlider had no AutomationProperties.Name, so screen readers
announce it as an unnamed slider with only a raw numeric value.
Completes the accessible-name coverage of the font scaling popup.
ThemeButton, FontScalingButton, SearchBar, and SearchBarClearButton had
no AutomationProperties.Name, so screen readers (Narrator, NVDA)
announce them as raw glyph text ("N/A", Unicode E8D3), an unnamed edit
field, and a button called "X". Completes the top-bar naming pass
started for SettingsButton and the window controls.
* Containerize the docs site's npm tooling
Run Astro/Starlight dev, build, and preview commands through Docker
(docs/Dockerfile, docker-compose.yml, service winutil-astro) instead
of bare npm on the host, and document the required commands and
rationale in docs/README.md.
* Document Docker-only npm policy for agents
Add a Dependency Installs, Builds, And Dev Servers section to
AGENTS.md requiring docs/ tooling to run through Docker rather than
directly on the host, point SPEC.md's Docs Site section at the new
Dockerfile/docker-compose.yml, and renumber the remaining AGENTS.md
sections to stay sequential.
* Harden docs Docker dev environment
Tightened docs-container safety and clarified contributor workflow. The docs Docker image now switches to the non-root `node` user after setting ownership, and compose now binds Astro to `127.0.0.1` instead of all interfaces. Updated AGENTS and docs README instructions to explain the security boundary of the bind mount and to require rebuilding plus `docker compose down -v` after dependency changes so `node_modules` is reseeded correctly.
* Clarify docs secret handling in AGENTS
Updates AGENTS.md to tighten docs security guidance: secrets must not be stored anywhere under `docs/`, because `docs/.dockerignore` only affects image build context and does not protect files from the Docker Compose bind mount used for docs dev/build commands.
* Fix preview command to expose port in Docker
The previous preview command didn't expose the port outside the container. Adding --service-ports and binding to 0.0.0.0 makes the preview server accessible from the host.
* Update dns.json
* Fix Mullvad DNS review issues
* Add Mullvad secondary resolvers
* Register DoH before changing adapter DNS
* Report DNS failures to tweak workflow
* Handle non-terminating DNS assignment failures
* Preserve DNS fallback and document Mullvad options
---------
Co-authored-by: Chris Titus <contact@christitus.com>
* Fix UI automation peer wrapping and category focusability
* Add accessibility name to SettingsButton
* Avoid nesting ScrollViewer in generated panels with outer viewers
Inspect targetGrid and its parent hierarchy in Invoke-WPFUIElements to avoid adding an inner ScrollViewer when an outer ScrollViewer already exists.
* Turn the Install category filters into toggle chips
- Replace the filter buttons with toggles that show which ones are active
- Add ctrl click to select more than one category
- Keep the search box and the category filter independent of each other
- Expand matching categories while a filter is active
* Fix the filter interactions the category chips missed
- Pass the selected categories to the lazy rendering batches, the old call
used a parameter that no longer exists and threw while typing
- Keep the category filter when switching tabs, the chips stayed checked
while the filter itself was dropped
- Put a category back to collapsed once the filter that expanded it is gone
* Document the Install category filters
- Add a guide section for the chips, ctrl click and clearing the filter
- Note that search and the category chips apply together
* Correct the category filter guide wording
- Clearing by clicking the chip only applies when it is the only one selected
- Only categories with matches expand, and only auto expanded ones re-collapse
* Point OpenSSH key setup at the file sshd actually reads
The Remote Access feature created %USERPROFILE%\.ssh\authorized_keys and
told the user to put their public keys there. sshd does not read that
file for a member of the administrators group; the "Match Group
administrators" block in sshd_config sends those logons to
C:\ProgramData\ssh\administrators_authorized_keys instead. WinUtil always
relaunches itself elevated, so the account it was setting up is always an
administrator, and key auth for it never worked.
The function tried to work around that by commenting the block out, but
those regexes anchor on $, and .NET puts $ before the \n of a CRLF pair.
The sshd_config Windows ships is CRLF throughout, so the replace was a
silent no-op on a stock install. On an sshd_config with LF endings it did
apply, and that is worse than not working: sshd gives an administrator
logon a full token with no UAC prompt, which is why Windows keeps those
keys in ProgramData behind an ACL that requires elevation to write.
Moving the lookup into the profile lets anything running as the user at
medium integrity append a key and get an elevated shell unprompted.
Use administrators_authorized_keys and give it the ACL sshd requires
(inheritance off, Administrators and SYSTEM only, by SID so localized
installs work). Where the sshd_config edit did land, undo it and copy any
keys out of the profile file first so key auth is not cut off mid-session.
Keys are only copied when the block needs restoring, so a default config
never grants access sshd was not already granting.
Also stop creating the profile .ssh directory: under elevation it was the
elevating administrator's profile, not necessarily the caller's.
* Document where to put SSH keys for the OpenSSH server feature
* Make UI helpers no-op when no window exists
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.
* Suppress stray console output from automation runs
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.
* Add project learning for window-free UI helpers
Tabs other than Install build their checkboxes lazily on first visit.
Import populates $sync.selected* and calls Reset-WPFCheckBoxes, but
that only touches checkboxes that already exist in $sync, so any tab
not yet visited gets its controls built later with default (unchecked)
state and never reflects the import. Re-apply Reset-WPFCheckBoxes right
after a tab's controls are built so it picks up existing selections.
mpv has no official Windows installer; winget id shinchiro.mpv is the
de facto standard Windows build. Choco mpv/mpv.install are flagged
"Possibly broken" upstream, so winget is the sole install source.
* Add SEO metadata to docs home
Add a robots.txt sitemap directive and JSON-LD SoftwareApplication metadata to the docs homepage to improve search engine discovery and rich results.
* Add social preview metadata
Add Open Graph and Twitter image metadata for the docs site and include a new shared social preview image. Also set the homepage title metadata to match the documentation branding.
* docs: correct Win11 Creator behavior after the ISO workflow refactor
The creator stopped stripping unused editions, loading offline registry
hives, and running component store cleanup in #4862. It now copies the
ISO contents and applies the selected edition through sources\ei.cfg and
autounattend.xml, leaving install.wim alone unless drivers are injected.
Update the guide and the architecture reference to match, and note that
the output ISO is close to the size of the source.
* docs: align disk space, driver injection, and stuck-run guidance
The disk space block still carried the pre-refactor 10-15 GB workspace,
2.5-3.5 GB output, and ~25 GB total, which contradicted the copy-based
numbers in the data flow. Restate them against the source ISO size.
Driver injection never touches boot.wim. It adds every exported driver
to the selected install.wim index and stages only the storage packages
in $WinpeDriver$ for WinPE, so correct both the guide and the reference.
The stuck-run troubleshooting entry pointed at a WIM dismount that only
happens when driver injection is enabled.
* docs: narrow this PR back to the edition and output size claims
The earlier commits rewrote the surrounding Win11 Creator sections,
which went well beyond what #4918 was about and presented the rest of
the section as reviewed when it had not been.
Restore the untouched text and keep only the claims that promised a
size reduction or edition removal: the strip-editions and component
store bullets, the two data flow lines, and the modified ISO size.
* Replace FOSS dot with a corner badge on app entries
- Add New-WinUtilFossBadge, the open source keyhole on a green backdrop
- Mark FOSS apps with a corner triangle instead of a dot after the name
- Give the FOSS legend a circle badge and move it below the buttons
- Sort Note entries last, they shared a rank with checkboxes before
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Update functions/private/Initialize-InstallAppEntry.ps1
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* Centralize agent instructions in SPEC
Move the full coding-agent contract and repository guidance from `AGENTS.md` into `SPEC.md`, and reduce `AGENTS.md` to a simple pointer to the canonical instructions. Add lightweight `CLAUDE.md`, `GEMINI.md`, and `.github/copilot-instructions.md` files so different assistants consistently bootstrap through the same repo-specific guidance.
* Clarify auto-generated docs path scope in SPEC
Narrowed rule #3 to specify exact auto-generated subdirectories (tweaks/ and features/) rather than the entire code-reference/ directory, and clarified that other hand-written pages (e.g. architecture.mdx) may be edited directly.
* Remove archived docs from gitignore
Drop the old `docs-old` Hugo ignore rules from `.gitignore`, reflecting that the archived docs build artifacts no longer need special handling there.
* Add local env files to docs .gitignore
Add .env.local and .env.*.local patterns to prevent local environment files from being committed.
* Clarify test and git hygiene guidance
Update `SPEC.md` to run Pester in CI mode and tighten repository hygiene instructions around ignored files. The git guidance now points contributors to the actual `.gitignore` files and clarifies that `docs/public/` contains tracked static assets rather than generated output.
* Split agent workflow from project spec
Move repository working instructions into `AGENTS.md` and refocus `SPEC.md` on the stable WinUtil project contract. This separates agent-specific guidance from architecture, build, runtime, docs, testing, and release details so both documents have clearer ownership.
* docs: clarify Pester, ScriptAnalyzer, and source-of-truth rules
Expand AGENTS.md with context on why -SkipPublisherCheck is needed for Pester installation, why winutil.ps1 should be deleted before running ScriptAnalyzer, and clarify that the source-of-truth rule applies only to compiled-script behavior — repository metadata files are edited directly.
* Clarify Pester install command and -SkipPublisherCheck reason
Expands the explanation for why -SkipPublisherCheck is needed (catalog-signed vs Authenticode-signed), clarifies it does not skip download integrity, and adds -Repository PSGallery to pin the trusted source explicitly.
* feat: restore document category
Moves existing document-focused apps into the Document category and adds its filter chip.
* feat: restore document applications
Restores reviewed document-focused apps removed in PR #4451.
* chore: update apps list as per code review comments
This also updates the link for PDF24 Creator
* test: verify document category filter wiring
Covers the Document filter handler and config category presence.
* fix: categorize Calibre as multimedia
Keeps the e-book reader in Multimedia Tools.
* fix: repair popup install binding, dialog titles, presets switch, radio groups, JSON, tests
- Invoke-WPFInstall: add param block so the right-click app popup binds
-PackagesToInstall. It previously dropped the argument into $args and
installed the whole selected list instead of the clicked app (or warned
that nothing was selected).
- Replace undefined $AppTitle with "WinUtil" in Install/UnInstall warning
boxes, matching the XAML window title and sibling dialogs.
- tweaks.json: remove stray nested "link" inside the WPFToggleScrollbars
registry entry (dead property; registry entries expose only the six
consumed fields).
- Invoke-WPFPresets: fix dead switch cases to the wildcard-pattern
convention so selectedFeatures/selectedToggles clear correctly.
- Invoke-WPFUIElements: store the group StackPanel in radioButtonGroups so
grouped radio buttons share one container (else branch was unreachable).
- sanity.Tests.ps1: pass the Windows PowerShell parser script via
-EncodedCommand; -Command string marshaling corrupted backticks/quotes and
produced false parse failures.
- install-workflow.Tests.ps1: remove the AppTitle workaround and update
title assertions to "WinUtil"; drop orphaned cleanup lines.
- Add CHANGES.md with technical and plain-language explanations.
Verified: each bug reproduced against HEAD, fixes demonstrated to change
behavior, full Pester suite 471/471.
* Delete
* test: cover the explicit popup install parameter path
Add a regression test invoking Invoke-WPFInstall -PackagesToInstall with a
package different from the default sync.selectedApps selection and assert
the runspace receives the explicit object, mirroring the Initialize-WPFUI
popup call shape. Existing default-path coverage is unchanged.
Add the `YouTubeEmbed` component import to `docs/src/content/docs/guides/tweaks.mdx` so the guide can use embedded YouTube content without missing-component build issues.
Update image links in `.github/CONTRIBUTING.md` and `.github/READMEITALIAN.md` from `../docs/...` to `/docs/...` so screenshots resolve correctly when viewed from the `.github` directory context on GitHub.
The homepage uses Starlight's splash template, which has no sidebar and
therefore never gets Starlight's own mobile menu toggle - on narrow
viewports there was no way to reach any nav link at all. Add a small
toggle + panel in the header (only rendered on pages without a sidebar)
exposing the same links as the desktop nav, and group it with the
search icon so mobile actions sit together instead of spread across
the header.
Also add the Store and Forums links to the real sidebar config so
they're reachable on mobile on every other page too (previously only
in the desktop-only top nav), and pull both URLs from a shared
site-links.ts so the sidebar config and header can't drift apart.
* Update Applications tab screenshots
Replace outdated install/uninstall/show-installed screenshots and add
new ones for upgrade-all, clear-selection, selected-apps counter, and
collapse/expand categories.
* Update Applications guide with new screenshots and sections
Point each tab at its refreshed screenshot, reorder tabs to match the
sidebar button order in the app, and document the Upgrade All,
Selected Apps counter, and Collapse/Expand Categories controls.
* Add missing Updates tab screenshot
The image was referenced by the Updates guide but was never checked
into the repo.
* Reorder screenshots to follow their intro paragraph
Move the tab screenshot below the introductory text on the Tweaks and
Updates guide pages, matching the paragraph-then-screenshot layout
used elsewhere in the docs.
---------
Co-authored-by: Chris Titus <contact@christitus.com>
* Fix the same arrow-icon alignment issue on the "Read the Docs" button
Same root cause as the hero/final-CTA buttons: the label was plain
markdown text, which MDX wrapped in a <p>, and the icon was missing
the size/nudge applied elsewhere. Match it to the other two buttons.
* Rename known-issues page slug to knownissues
Update the sidebar config, nav dropdown, and every internal link that
pointed at /known-issues/ to match.
* Fix image paths in root-level markdown to match the new docs site
The Hugo docs site (docs/assets/images/...) has been replaced by the
Astro site (docs/src/assets/...); update CONTRIBUTING.md,
READMEITALIAN.md, and README.md to point at the new asset locations.
* Remove unused houston.webp asset
Leftover Starlight default-theme asset with no references anywhere
in the docs site.
* Fix docs site reference in generator docs
Updates `tools/devdocs-generator.md` to describe link generation correctly: links now point to the Astro/Starlight documentation site instead of the old Hugo site.
* Point docs homepage CTAs to guides hub
Updates the docs landing page CTA links to use `/guides/` instead of `/guides/getting-started/` in the hero action and both “Get Started”/“Read the Docs” buttons, routing users to the guides index.
* Add reusable YouTube embeds to docs guides
Introduces a new `YouTubeEmbed.astro` component for responsive 16:9 video embeds using the privacy-enhanced `youtube-nocookie.com` domain. The User Guide index and Tweaks guide now import and use this component instead of plain YouTube links, and the component includes styling that overrides Starlight’s iframe reset so embedded videos render at the correct size.
* Fix YouTube embed iframe height via not-content
Add the `not-content` class to the embed wrapper so Starlight's markdown reset no longer overrides `iframe { height: auto }`. Drop the `@layer starlight.core` wrapper and `!important` overrides that were compensating for the same issue.
* Clarify docs comments across Astro components
Adds and refines inline comments in the docs site config, custom Starlight components, and theme styles to better explain layout structure, navigation dropdown behavior, hero composition, and theme initialization. Also normalizes a few multi-line comments into single-line form for readability without changing functionality.
* Set secondary docs button background color
Update `.wu-btn-secondary` in the docs theme to use `var(--sl-color-gray-6)` instead of a transparent background, improving button visibility and contrast while keeping existing border and hover behavior.
* Move User Guides into docs dropdown
Updates the docs header navigation so the “User Guides” link is grouped under the existing “Documentation” dropdown instead of appearing as a separate top-level nav item. This keeps related documentation links together and simplifies the top navigation.
* Fix hero margin and center trust section
Remove the automatic 1.5rem top margin Starlight adds between the hero and the content column. Also flex-center the trust section so its content is properly aligned.
* Disable edit links in generated dev docs
Update `tools/devdocs-generator.ps1` to include `editUrl: false` in the frontmatter for generated tweak and feature docs pages. This prevents Starlight from showing edit links on these auto-generated pages.
* Move docs update PRs to pre-release flow
The docs Pages workflow is simplified to only build and deploy the docs site: it now triggers only on docs changes, drops extra permissions/default shell settings, and removes the dev-docs generation + auto-PR steps. The pre-release workflow now owns that automation by expanding its PR step to include generated code-reference docs alongside JSON link updates, with updated commit/title/body text and a documentation label.
* Shorten generated docs source note
Simplify the autogenerated DevDocs notice so generated pages only warn against direct edits while still linking back to the source file.
* Improve docs header dropdown behavior
Reworked the header nav dropdown triggers to use native `<details>/<summary>` instead of buttons, then updated menu visibility logic to use `details[open]` for no-JS support on touch and keyboard while preserving hover behavior. Also renamed the “Developer Docs” link to “Code Reference” and tightened related CSS comments.
* Refresh contribution and docs workflow docs
Updated contributor-facing docs to use consistent Markdown admonition blocks, cleaned Mermaid fence formatting, and fixed minor wording/capitalization in contribution steps. Clarified `devdocs-generator` behavior so workflow ownership is explicit: generation now documented as part of `pre-release.yaml` (via docs-update PR), while `docs.yaml` is documented as build/deploy only.
* Fix contributing guide grammar
Correct the "it's" to "its" typo in both contributing guides so the pull request guidance is consistent across the GitHub and docs versions.
* feat: Add DoH templates for DNS providers in configuration
* feat: Implement DoH support for DNS configuration and management
* feat(pester): enhance DNS tests to include DoH server address handling and template application
* fix: address codex feedback
* fix: update existing DoH server entries
* fix: enable DoH auto-upgrade
* fix: handle DNS setup failures
* fix: remove DoH state on DHCP reset
* refactor: simplify Win11 Creator ISO workflow
Keep no-driver ISO generation on the fast copy-only path.
Inject exported drivers with one selected-index WIM mount, one Add-Driver pass, and one commit.
Limit WinPE staging to boot-storage drivers and improve workflow validation coverage.
* refactor: stage Win11 setup script fallback
Copy the prepared setup script payloads into sources/$//Setup/Scripts so setup media does not depend solely on answer-file extension extraction.
Keep the existing autounattend execution path unchanged and cover the fallback files in the Win11 Creator tests.
* refactor: address codex feedback
* refactor: run ISO verification in runspace
Rename the ISO logger to Write-WinUtilISOLog so the shared runspace pool imports it automatically. Run Mount & Verify off the WPF thread with dispatcher-safe UI updates, and add Pester coverage for the runspace and logger contract.
* refactor: address ISO review feedback
Keep the selected ISO stable during background verification. Apply ContentDeliveryManager settings to both the first account and the default profile. Block FAT32 USB creation when install.esd exceeds the supported file size.
* refactor: address ISO setup edge cases
Set BypassNRO before OOBE so local account setup is available during Windows Setup.
Detect registered DISM mounts during driver-injection cleanup so partial mount failures are discarded.
* refactor: address ISO review edge cases
Use the actual FAT32 file limit for install.esd USB checks.
Disable ISO modification while mount verification is running.
Apply unsupported-hardware notice suppression to the first user profile.
* refactor: stage ISO setup safeguards earlier
Set device encryption and reserved-storage registry guards before OOBE.
Enable the OEM configuration-set fallback when setup scripts are staged there.
* Update functions/private/Invoke-WinUtilISOUSB.ps1
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
---------
Co-authored-by: Chris Titus <contact@christitus.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* refactor: implement FilterChipStyle for Install tab category filters
- Created a dedicated FilterChipStyle style block with custom padding, cursor, and height definitions
- Applied FilterChipStyle to all category filter buttons
- Added a new 'Filters' TextBlock section title aligned with the sidebar actions
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Chris Titus <contact@christitus.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Add Quick Category Search Chips to Install tab
Add category shortcut chips on the Install tab that insert the category
name into the existing search box. Append each app's category as a
description tag so the existing description search filters by category.
Reuses the standard button style and existing search; no search, install,
or preset logic is changed.
* Fix install category chip filtering
* Fix category filter refresh behavior
---------
Co-authored-by: Chris Titus <contact@christitus.com>
* fix(search): make search bar stretch instead of overflowing at narrow window sizes
* fix(ui): sync runtime MinWidth override with XAML value
Add_Loaded was hardcoding $sync.Form.MinWidth back to 1000 after
load, overriding the 1150 set in inputXML.xaml and letting the
window shrink below the space the search bar/toolbar layout needs.
* fix(ui): address responsive search review feedback
* fix(ui): enforce usable toolbar width
---------
Co-authored-by: Chris Titus <contact@christitus.com>
* Add maximize button logic and update assets
* Add maximize button with theming
* delelte logo
* Center and restyle the window control buttons
* Fix window state test setup
---------
Co-authored-by: aran628 <aran2014+@gmail.com>
Co-authored-by: Chris Titus <contact@christitus.com>
* feat(tweaks): add window-level progress indicator for Run Tweaks and Undo
* Hide completed tweaks progress on next action
---------
Co-authored-by: Chris Titus <contact@christitus.com>
* fix: resolve silent failures in AppX removal and PS7 DISM bugs
- Fixed a bug where Remove-AppxPackage failed silently in PowerShell 7 due to pipeline binding issues. We now explicitly loop through Get-AppxPackage results and pass PackageFullName directly.
- Added wildcard matching to Get-AppxPackage and Get-AppxProvisionedPackage to reliably locate packages regardless of short name vs family name variations.
- Offloaded Get-AppxProvisionedPackage and Remove-AppxProvisionedPackage execution to Windows PowerShell 5.1 (powershell.exe) to bypass PowerShell 7 DISM COM exceptions ("Class not registered") and performance hangs.
- Deduplicated AppX removal logic by calling Remove-WinUtilAPPX directly from Invoke-WPFAppxRemoval instead of copying the implementation.
* refactor(Remove-WinUtilAPPX): use script block to improve readability
* feat: enhance AppX removal process and add provisioned package removal function
* fix(tests): update AppX removal tests
This also adds tests for the new Remove-WinUtilProvisionedAPPX functionality
* Move AppX removal under Tweaks
* Address AppX removal review feedback
* Complete AppX removal error handling
* Prevent AppX cleanup confirmation prompts
---------
Co-authored-by: Chris Titus <contact@christitus.com>
* feat(legacy-panels): add Firewall, Security, Programs, and Mouse to Legacy Windows Panels
- Added `firewall.cpl`, `wscui.cpl`, `appwiz.cpl`, and `main.cpl` to `config/feature.json`
* chore(legacy-panels): alphabetize panel entries
---------
Co-authored-by: Chris Titus <contact@christitus.com>
* Create app suggestion discussion template
Add a discussion template for app suggestions with rules and input fields.
* Add application suggestion link to issue template
Added a link for suggesting applications to the issue template.
* Update app suggestion discussion template
* Add checkbox for WinGet application presence check
* Changed name of file, didn't change name of link XD
* Add WinUtil version input to bug report template
Added input field for WinUtil version in bug report template.
* whoops :^)
* Someone forgot that label is single value parameter
* GH Wiki is a liar
* GH Docs are looking awful
* Talk about "PowerShell 7 from the Microsoft Store" inside of KnownIssues.md
* Update Invoke-WinUtilInstallPSProfile.ps1
* Update KnownIssues.md
* Update KnownIssues.md
* Update KnownIssues.md
* feat: Enhance Win11 Creator with edition ID handling and update autounattend.xml generation
* Strengthen Pester coverage for config and function files
* Create app suggestion discussion template
Add a discussion template for app suggestions with rules and input fields.
* Add application suggestion link to issue template
Added a link for suggesting applications to the issue template.
* Update app suggestion discussion template
* Add checkbox for WinGet application presence check
* Changed name of file, didn't change name of link XD
* Replace FOSS highlight toggle with inline label
Remove the separate "Highlight FOSS" toggle and its theming logic, and instead append an inline "- FOSS" label to FOSS app entries. Changes: remove WPFToggleFOSSHighlight from app navigation and related event handlers, delete FOSSColor resource updates in theme code and button handler, simplify checkbox setup in Invoke-WPFUIElements by removing the special-case toggle logic, and update Initialize-InstallAppEntry to build a horizontal panel containing the app name plus a small green "- FOSS" TextBlock for FOSS items. This consolidates FOSS indication into the item UI and cleans up toggle/theme handling.
* Add inline FOSS label to app entries
Replace the separate StackPanel/TextBlock used for the FOSS indicator with an inline System.Windows.Documents.Run appended to the app name. The run is styled (green RGB(76,175,80) and FontSize=10) and the checkbox content assignment was moved outside the conditional so the appName (with optional FOSS run) is always used. Cleaned up related comments to reflect the FOSS label change.
* Add FOSS note type with green bullet
Added FOSS notes in the UI and FOSS labels with a green bullet instead of the text '#FOSS'
* Use larger circle glyph; remove italic text
Replaced the FOSS and list bullet glyphs with a larger circle for better visual consistency and removed the italic styling
* Update FOSS/bullet glyphs, colors and sizes
Updated FOSS/list bullet styling by switching glyphs from U+2B24 to U+25CF, increasing size (10→11.5), and adjusting bullet/text colors for better visibility and consistency across.
* Fix slop remains
Chris likes to vibe code. AI likes to use different types of dashes (em-dashes, en-dashes), instead of regular dashes (-).
On PowerShell 7 this appears to work. The parser doesn't care. But, what about PowerShell 5? It begins throwing errors about the ampersand. Loading it in the ISE reveals how it stops parsing the syntax correctly when it encounters em-dashes. Rather than displaying them as such, the ISE displays them as —.
Chris, if you want to vibe code, I don't mind. But at least use regular dashes.
Anyway, I won't fix the AI code. I just want to make it work on my beloved powershell 5.
* Apparently this file is important
* Create READMEITALIAN.md for Italian users
Added Italian translation for the README file, detailing the functionality and usage of WinUtil.
* Fix formatting in READMEITALIAN.md
* Rename READMEITALIAN.md to .github/READMEITALIAN.md
* Added Delivery Optimization tweak to Standard Tweaks preset
* Added Delivery Optimization tweak to Standard Tweaks preset and Advanced preset to match Gabi PR
* Update CONTRIBUTING.md
* updated the apps page and pics
* updated tweaks tab pic
* updated features page with pic
* updated update page with pic
* update win 11 page with pic
* update pics for app page
* deleted old pics
* fix favicon from not showing
* added forums link in help
* Update hugo.toml
* updated title screen pic and added it to the main screen
* Update _index.md
* added the recommended section to the user guild tweeks
* Update _index.md
* added the new advance preset
* clean up pages
* fix links
* fixed wikipedia link
* Update _index.md
* Less radical removal of apps
* Moving some applications to Selfhosted category
* Some more chess-boxing
* Document section is too small
* TG forks are too niche
* Floorp should be good
* feat: Add new multimedia tools and update existing application entries in applications.json
---------
Co-authored-by: Chris Titus <contact@christitus.com>
* Add checkbox for known issues acknowledgment
Some people do not check the link
* typo fix
Updated checkbox type to checkboxes in bug report template.
* :zombie_sound:
* :zombie_sound_2:
* AAAAAAAAAAAAAAA
* I'm tired of testing commits
* :zombie_sound_1337:
Added a description to the read issues checkbox.
* my bad
* Test the latest changes to WinUtil by running the pre-release and reporting issues you are encountering to help us continually improve WinUtil!
#### **Run the latest pre-release**
```ps1
irm https://christitus.com/windev | iex
```
!!! bug "Keep in mind"
```ps1
irm https://christitus.com/windev|iex
```
This is a pre-release and should be treated as such. It exists for developers to test the utility and report or fix bugs before they get added to the stable release. Don't use it in production!
> [!CAUTION]
> **Keep in mind:** This is a pre-release and should be treated as such. It exists for developers to test the utility and report or fix bugs before they get added to the stable release. Don't use it in production!
## Issues
@@ -23,19 +23,17 @@
* If you're doing code changes, then you can submit a PR to the `main` branch.
!!! warning "Important"
Do not use a code formatter, make massive amounts of line changes, or make multiple feature changes. EACH FEATURE CHANGE SHOULD BE IT'S OWN PULL REQUEST!
Do not open a pull request that adds support for other languages to WinUtil for now, until we decide how we want to move forward with language support.
> [!IMPORTANT]
> Do not use a code formatter, make massive amounts of line changes, or make multiple feature changes. EACH FEATURE CHANGE SHOULD BE ITS OWN PULL REQUEST!
>
> Do not open a pull request that adds support for other languages to WinUtil for now, until we decide how we want to move forward with language support.
* When creating pull requests, it is essential to thoroughly document all changes made. This includes, but is not limited to, documenting any additions made to the `tweaks` section and corresponding `undo tweak`, so users are able to remove the newly added tweaks if necessary, and comprehensive documentation is required for all code changes. Document your changes and briefly explain why you made your changes in your Pull Request Description. Failure to adhere to this format may result in the denial of the pull request. Additionally, any code lacking sufficient documentation may also be denied.
* By following these guidelines, we can maintain a high standard of quality and ensure that the codebase remains organized and well-documented.
!!! note
When creating a function, please include "WPF" or "WinUtil" in the file name so it can be loaded into the runspace.
> [!NOTE]
> When creating a function, please include "WPF" or "WinUtil" in the file name so it can be loaded into the runspace.
While you can make your changes directly through the Web, we recommend cloning the repo to your device using the application GitHub Desktop (available in WinUtil) to test your fork easily.
> [!TIP]
> While you can make your changes directly through the Web, we recommend cloning the repo to your device using the application GitHub Desktop (available in WinUtil) to test your fork easily.
* Install GitHub Desktop if it is not already installed.
* Log in using the same GitHub account you used to fork WinUtil.
@@ -96,22 +91,26 @@ graph TD
* Run the following command to compile and run WinUtil:
* After seeing that your changes work properly, feel free to commit the changes to the repository and make a PR. For help on that, follow the documentation below.
### Committing the changes
* Before committing your changes, please discard changes made to the `winutil.ps1` file, like the following:
* To make a PR on your repo under a new branch linking to the main branch, a button will show and say Preview and Create pull request. Click that button and fill in all the information that is provided on the template. Once all the information is filled in correctly, check your PR to make sure there is no WinUtil.ps1 file attached to the PR. Once everything is good, make the PR and wait for Chris (the maintainer) to accept or deny your PR. Once it is accepted by Chris, you will be able to see your changes in the "/windev" build.
description:"Suggest a new feature or improvement for the project."
title:"[Feature Request] - <your summary here>"
labels:["enhancement"]
body:
@@ -11,7 +12,7 @@ body:
## ⚠️ **IMPORTANT**
- 🛠️ **Supported environments only:** We only support Windows 11.
- 💡 For general questions, use the [Discussions section](https://github.com/Christitustech/winutil/discussions) or join our Community-driven [Discord Server](https://discord.gg/RUbZUZyByQ).
- 💡 For general questions, use the [Discussions section](https://github.com/ChrisTitusTech/winutil/discussions) or join our Community-driven [Discord Server](https://discord.gg/RUbZUZyByQ).
Questa utility è una raccolta di attività Windows che eseguo personalmente su ogni sistema che utilizzo. È progettata per snellire le *installazioni*, rimuovere i componenti superflui tramite *ottimizzazioni*, risolvere problemi tramite la *configurazione*, e riparare *aggiornamenti* di Windows. Sono estremamente selettivo riguardo ai contributi per mantenere questo progetto pulito ed efficiente.
Winutil deve essere eseguito con privilegi di amministratore, poiché apporta modifiche all'intero sistema. Per farlo, avvia PowerShell come amministratore. Ecco alcuni modi per procedere:
1.**Metodo del menu di Start:**
- Fai clic con il tasto destro sul menu Start.
- Scegli "Windows PowerShell (esegui come Amministratore)" (per Windows 10) o "Terminale (esegui come Amministratore)" (per Windows 11).
2.**Metodo tramite ricerca:**
- Premi il tasto Windows.
- Digita "PowerShell" o "Terminal" (per Windows 11).
- Premi `Ctrl + Shift + Invio` oppure fai clic con il tasto destro e seleziona "Esegui come amministratore" per avviarlo con privilegi elevati.
### Comando di avvio
#### Branch stabile (Consigliato)
```ps1
irm "https://christitus.com/win"|iex
```
#### Branch Sviluppatore
```ps1
irm "https://christitus.com/windev"|iex
```
### Automazione
Winutil supporta anche preset predefiniti che applicano automaticamente configurazioni comuni:
In caso di problemi, consulta i [Problemi noti](https://winutil.christitus.com/knownissues/) o [Apri una segnalazione](https://github.com/ChrisTitusTech/winutil/issues)
## 🎓 Documentazione
### [Documentazione ufficiale di WinUtil](https://winutil.christitus.com/)
### [Tutorial su YouTube](https://www.youtube.com/watch?v=6UQZ5oQg8XA)
### [Articolo su ChrisTitus.com](https://christitus.com/windows-tool/)
## 🛠️ Build & Sviluppo
> [!NOTE]
> Winutil è uno script piuttosto esteso, per questo è suddiviso in più file che vengono combinati in un unico file `.ps1` tramite un compilatore personalizzato. Questo rende la manutenzione del progetto molto più semplice.
Ottieni una copia del codice sorgente. Puoi farlo tramite l'interfaccia di GitHub (**Code** > **Download ZIP**), oppure clonando (scaricando) la repo tramite git.
Se git è installato, esegui i seguenti comandi in una finestra PowerShell per clonare e accedere alla directory del progetto:
Per compilare il progetto, esegui lo script di compilazione in una finestra PowerShell (i permessi di amministratore NON sono richiesti):
```ps1
.\Compile.ps1
```
Troverai un nuovo file chiamato `winutil.ps1`, creato dallo script `Compile.ps1`. Ora puoi eseguirlo come amministratore e apparirà una nuova finestra. Goditi la tua versione compilata di WinUtil :)
> [!TIP]
> Per ulteriori informazioni sull'utilizzo di WinUtil e su come contribuire allo sviluppo, ti invitiamo a leggere le [Linee guida per i contributi](https://winutil.christitus.com/contributing/). Se non sai da dove iniziare o hai domande, puoi chiedere sul nostro [Server Discord della community](https://discord.gg/RUbZUZyByQ); i membri attivi del progetto risponderanno appena possibile.
## 💖 Supporto
- Per sostenere il progetto moralmente e mentalmente, non dimenticare di lasciare una ⭐️!
- Wrapper EXE a 10$ su https://www.cttstore.com/windows-toolbox
## 💖 Sponsor
Questi sono gli sponsor che aiutano a mantenere vivo il progetto con contributi mensili.
If you find a security issue please make post it in the issues tab. If you think it should be private you can email me at contact@christitus.com.
If you find a security issue, please post it in the Issues tab. If you think it should be private, you can email me at contact@christitus.com.
For immediate response check out our discord server @ [](https://discord.gg/RUbZUZyByQ)
For immediate response check out our Discord server:
Drop-in operating instructions for coding agents. Read this file before every task.
**Working code only. Finish the job. Plausibility is not correctness.**
`SPEC.md` in the repository root is the project contract — read it for what WinUtil is and how it's architected. This file covers how to work on it.
## 0. Non-Negotiables
These rules override everything else in this file when in conflict:
1.**Do not edit `winutil.ps1` directly.** It is generated build output (see SPEC.md's Build Model). Change source files and compile.
2.**Do not commit `winutil.ps1`.** It is ignored locally and generated by GitHub Actions for releases.
3.**Never touch `docs/src/content/docs/code-reference/tweaks/` or `docs/src/content/docs/code-reference/features/`.** Both are auto-generated (see SPEC.md's Docs Site). Edit the source JSON (`config/tweaks.json`, `config/feature.json`) or the relevant PowerShell function file instead. Other hand-written pages under `code-reference/` (e.g. `architecture.mdx`) are not touched by the generator and may be edited directly.
4.**Never fabricate.** Do not invent file paths, function names, command output, test results, commit hashes, or API behavior. Read the file or run the command.
5.**Disagree when the premise is wrong.** Say what is wrong before acting on it.
6.**Stop when genuinely ambiguous.** If two interpretations would produce materially different diffs, ask before editing.
7.**Touch only what the task requires.** No drive-by refactors, formatting sweeps, or unrelated cleanup.
8.**Verify before saying done.** A plausible-looking diff is not proof.
## 1. Key Commands
- Compile:
```powershell
.\Compile.ps1
```
- Compile and run GUI:
```powershell
.\Compile.ps1 -Run
```
- Install the supported Pester version (one-time). `-SkipPublisherCheck` is required because Windows ships an inbox Pester 3.4.0 that is catalog-signed, and PowerShell Gallery's Pester 5.8.0 is Authenticode-signed — `Install-Module` refuses the upgrade without it. This does not skip download integrity (still HTTPS + NuGet package hash verification); `-Repository PSGallery` pins the trusted source explicitly rather than relying on whatever repositories happen to be registered:
- Run Script Analyzer with project settings when available. If a locally compiled `winutil.ps1` exists, delete it first — `lint/PSScriptAnalyser.ps1` only excludes rules, not files, so `-Recurse` would also lint the generated script and produce noise against line numbers that don't map to any source file:
- Docs site dev server (run from `docs/`; see Section 2 for why this goes through Docker):
```powershell
docker compose up winutil-astro
```
- Docs site production build (run from `docs/`):
```powershell
docker compose run --rm winutil-astro npm run build
```
Prefer the narrowest useful verification while iterating. Use the full relevant check before finishing.
## 2. Dependency Installs, Builds, And Dev Servers
Given the current wave of npm/pnpm/yarn supply-chain worms (malicious postinstall/preinstall scripts, credential-stealing packages): **never run npm/pnpm/yarn/npx directly on the host, full stop.** The docs site (`docs/`) is the only npm-based project in this repo; always run its tooling inside Docker via `docs/Dockerfile` and `docs/docker-compose.yml` (service `winutil-astro`).
- Never run `npm install`, `npm run <script>`, `npx <pkg>`, `pnpm`, or `yarn` directly on the host shell in `docs/`. Use `docker compose run --rm winutil-astro <command>` / `docker compose up winutil-astro` instead (see Section 1 for the exact commands).
- If a task needs a new docs dependency, add it to `docs/package.json` yourself, then rebuild the image and drop the `node_modules` volume so it repopulates from the new image (run from `docs/`): `docker compose build winutil-astro`, then `docker compose down -v`. Docker only seeds a named volume from the image the first time it's created, so a plain rebuild silently leaves the old `node_modules` in place. Don't install packages on the host, even temporarily, "just to check something."
- If Docker isn't available on the host, propose the install command for the current OS and wait for confirmation before running it — don't fall back to running npm on the host instead. If the daemon just isn't running (Docker is installed but not started), tell the user rather than trying to start it yourself.
- Treat any `postinstall`/`preinstall` lifecycle script in a new dependency as worth flagging to the user before installing — summarize what it does.
- Don't put real secrets anywhere under `docs/`. `docs/.dockerignore` only trims what `docker build` copies into the image — it does not affect the `docker compose` bind mount, which exposes the entire `docs/` directory (including any `.env` file) inside the container for every dev/build/preview command (see the next bullet). There is no "keep it out unless mounted" middle ground here.
- The container mounts `docs/` as a volume, so file edits on the host are reflected inside the container immediately — no rebuild needed for normal code changes, only when `docs/package.json`/`docs/package-lock.json` change (see the rebuild-and-drop-volume steps above).
- This Docker requirement is specific to `docs/`. PowerShell tooling runs directly on the host per Section 1. The Python project under `tools/title-screen/` runs with uv as documented in its README.
## 3. Source Of Truth
For changes that affect the compiled WinUtil script, make them only in the source files described in SPEC.md's Repository Layout — never in `winutil.ps1` itself. If behavior changes require the compiled script to change, update the source files and run `.\Compile.ps1` only to verify generation.
This scoping applies to compiled-script behavior only. Repository metadata — `AGENTS.md`, `SPEC.md`, `CLAUDE.md`/`GEMINI.md`/`.github/copilot-instructions.md`, `.github/workflows/`, and the root `.gitignore` — is edited directly when a task requires it, per the other sections of this file.
## 4. Before Editing
- State the plan in one or two sentences before editing. For non-trivial work, include the verification you intend to run.
- Read the files you will touch and the files that call them.
- Match existing patterns even when a different greenfield design would be cleaner.
- Surface assumptions when they affect behavior, compatibility, or user data.
- If two approaches have meaningful tradeoffs, name them before choosing. Trivial tasks can proceed directly.
## 5. Coding Guidelines
- Prefer the minimum code that solves the stated problem.
- Keep PowerShell functions in one function file when practical, with the file name matching the primary function name.
- Use approved PowerShell verb-noun names and follow the existing `WPF` / `WinUtil` naming conventions; keep UI event handler names aligned with XAML element names per SPEC.md's UI And Event Contract.
- Use `$sync` for shared state and UI references, consistent with SPEC.md's Runtime Model.
- Update WPF controls through the UI dispatcher when running work in a background runspace.
- Keep config-driven features in JSON when they fit the existing schema instead of hard-coding lists in PowerShell; follow SPEC.md's Configuration Contract for required fields and key-renaming rules.
- Preserve undo/original-state data for tweaks so users can reverse changes.
- Do not add abstractions, configurability, hooks, or "future extensibility" unless the task needs them now.
- Clean up orphans created by your own changes, such as unused variables or functions made obsolete by the edit.
- Avoid broad formatting-only edits, especially in JSON config files, XAML, docs, and generated output.
## 6. Runtime And Safety Rules
- WinUtil performs system-level Windows changes; treat registry, services, AppX removal, package manager, Windows Update, ISO, and unattended setup changes as high-risk (see SPEC.md's Safety Requirements).
- Prefer existing helper functions for WinGet, Chocolatey, registry, services, progress, and UI updates.
- Keep tweaks reversible where the schema supports it by including original values or original states.
- Never modify a user's original ISO in-place; follow existing copy/mount/export patterns.
- Avoid storing credentials, secrets, or machine-specific paths in repo files.
- Preserve logging and user feedback patterns for long-running or destructive operations.
## 7. Surgical Changes
- Do not improve adjacent code, comments, formatting, imports, or docs unless required.
- Do not refactor working code because you are already in the file.
- Do not delete pre-existing dead code unless asked; mention it in the summary if relevant.
- Keep diffs reviewable. Every changed line should trace to the user's request.
- If a change starts spreading across unrelated areas, pause and reassess the plan.
## 8. Verification
Define success in terms that can be checked, then check it.
- For compile/build changes, run `.\Compile.ps1`.
- For GUI behavior changes, run `.\Compile.ps1 -Run` when practical and verify the affected path manually.
- For config changes, run the compile check and relevant Pester tests.
- For function changes, run the relevant Pester tests or add/update focused tests when practical.
- For docs-only changes, proofread the changed files and skip runtime tests unless docs generation is affected.
- Read command output. Do not report tests as passing unless they actually passed.
- If verification fails, fix the cause rather than weakening the test.
If a check cannot be run, say exactly why and what residual risk remains. See SPEC.md's Testing And CI for what GitHub Actions runs on every push.
## 9. Generated Files And Git Hygiene
- Treat local `winutil.ps1` changes as disposable compile output.
- Never stage or commit `winutil.ps1`, `binary/`, or anything else ignored by the root `.gitignore` or `docs/.gitignore` — read those files rather than assuming. `docs/public/` is tracked source for static assets, not generated output.
- `docs/src/assets/branding/title-screen.png` is a tracked generated asset. Do not edit it manually. Update `tools/title-screen/` or run the title-screen workflow.
- Do not remove `.gitignore` rules that keep generated artifacts out of Git.
- Before finishing, check `git status --short` and separate your changes from pre-existing user changes.
- Do not revert user changes unless explicitly asked.
- Commit messages, when requested, should be descriptive: short subject under 72 characters, body explaining why when needed.
- When committing, split changes into small, logical commits rather than one large commit, so each commit's diff is reviewable as a single group of related changes.
## 10. Documentation Expectations
- Update `docs/src/content/docs/guides/` when user-facing behavior changes.
- Update `docs/src/content/docs/code-reference/architecture.mdx` and other hand-written developer docs when architecture, build flow, config schema, or contribution workflow changes — but never hand-edit the auto-generated `code-reference/tweaks/` or `code-reference/features/` subfolders (see Non-Negotiables).
- Keep sidebar entries in `docs/astro.config.mjs` in sync with page slugs (see SPEC.md's Docs Site).
- Keep README changes brief and high-level.
- Put detailed user and developer documentation under `docs/`.
- Keep SPEC.md aligned with project/architecture changes, and this file aligned with process changes.
## 11. Communication Style
- Be direct and concise. Start with the answer or action.
- No flattery, filler, ceremonial closings, or fake certainty.
- Use bullets only when they improve scanning.
- Report what changed, how it was verified, and anything not done.
- If the user asks for a review, lead with findings and file/line references.
## 12. When To Ask
Ask before proceeding when:
- The request has two plausible interpretations and the choice materially changes behavior or files touched.
- The change affects release generation, generated artifacts, migrations, or high-risk Windows behavior in a way the user did not specify.
- You need credentials, secrets, production resources, or access you do not have.
- The user's stated goal conflicts with the literal request.
Proceed without asking when:
- The task is trivial and reversible.
- Ambiguity can be resolved by reading the code or running a local command.
- The user already answered the question in this session.
## 13. Project Learnings
When the user corrects an agent approach, add or tighten one concrete rule here before ending the session. Keep this section short and prune rules that no longer matter.
- Keep `winutil.ps1` generated-only: change source files, compile to verify, and never stage the generated script.
- Keep WinUtil runtime logging in the existing timestamped `%LocalAppData%\winutil\logs\winutil_*.log` session file; do not create a separate root `winutil.log`.
- Import Pester 5.8.0 before running tests so `Invoke-Pester -Output Detailed -CI` does not resolve to Windows' inbox Pester 3.4.0.
- Keep package install/uninstall process launches simple unless explicitly requested; do not add a separate stdout/stderr process logging helper for winget or Chocolatey.
- When the active log file is owned by `Start-Transcript`, do not call `Add-Content` against that file; write to host output so the transcript captures the line in the same log file without recording a terminating-error diagnostic.
- Keep UI helpers such as `Invoke-WPFUIThread` and `Step-WinUtilJob` safe to call without a window; the `-Preset` and `-Config` paths run the workflows before the form is created and before PresentationCore is loaded. Ask `Test-WinUtilUIAlive` rather than writing the `$sync.Form` / dispatcher / `HasShutdownStarted` check out by hand.
- Put long operations on the job layer with `Start-WinUtilJob` and report from them with `Step-WinUtilJob`; a job body must not set the busy flag, print its own banner, or carry its own try/catch/finally around the interface. `Invoke-WPFRunspace` directly is for fire-and-forget work that is not a job.
- Drain interface work that nobody is waiting for through `Start-WinUtilBackgroundQueue`; do not hand-roll another dequeue-and-re-post pump.
- Values a posted scriptblock needs travel as the dispatcher's argument or through `-Parameters`, never captured from the caller: a plain block resolves them when the dispatcher gets to it, and `GetNewClosure` binds command lookup to a copied scope. For the same reason, prefer a compiled `[action]` over `Invoke-WPFUIThread -Async` on hot re-posting paths, which marshals its body as text and recompiles it per post.
- Diagnostic scaffolding does not ship. Measure with it, then delete it.
- Have each Pester file load the assemblies and dot-source the functions it needs; several passed only because an earlier file in alphabetical order happened to load them.
- Log install/uninstall package names and package-manager IDs before queuing background runspace work; do not rely on runspace host output for the package identity.
- For Win11 Creator, start each new ISO modification in a fresh `WinUtil_Win11ISO_*` temp directory; existing-work detection is only for resuming/exporting already modified media.
- For Win11 Creator driver injection, inject storage controllers (`SCSIAdapter` / `HDC`) into `boot.wim` index 2 with DISM, not `$WinpeDriver$`. Keep `install.wim` on its own per-package `/Add-Driver` retry loop. For each image, add each root package folder with its own `/Add-Driver /Recurse` so a single bad driver cannot fail the rest, and skip any folder whose ancestor is already in the set, since that ancestor's `/Recurse` covers it. Warn per failure and commit only when at least one package was added; when none were, warn and discard rather than throwing, so the run still produces an ISO. The discard in the cleanup block carries both orphaned mounts and that intentional zero-added case; keep it. Do not export editions or run unrelated WIM cleanup, and reject damaged metadata before ISO export. Use `-LiteralPath` for driver export paths, since `%TEMP%` can contain wildcard characters.
- For Script Analyzer cleanup, fix actionable source warnings first and do not globally suppress accepted convention warnings such as plural names, `ShouldProcess` on UI helpers, `$global:sync`, or compile-time cross-file false positives.
- For DNS DHCP reset, keep the cmdlet reset and explicitly set IPv4 and IPv6 DNS source to DHCP.
- Public pull-request diffs may be sent to configured external review services without a separate privacy approval; do not block the review loop on upload authorization for this public repository.
- Keep install-tab favicon loading overlapped with app-entry rendering; do not replace native WPF loading with a deferred second phase unless visible completion time is proven no slower than `main`.
This utility is a compilation of Windows tasks I perform on each Windows system I use. It is meant to streamline *installs*, debloat with *tweaks*, troubleshoot with *config*, and fix Windows *updates*. I am extremely picky about any contributions to keep this project clean and efficient.
A curated compilation of Windows system tasks streamline **installs**, debloat with **tweaks**, troubleshoot with **config**, and configure **Windows updates**. Run it fresh on every new Windows install.
Winutil must be run in Admin mode because it performs system-wide tweaks. To achieve this, run PowerShell as an administrator. Here are a few ways to do it:
---
1.**Start menu Method:**
- Right-click on the start menu.
- Choose "Windows PowerShell (Admin)" (for Windows 10) or "Terminal (Admin)" (for Windows 11).
## Quick Start
2.**Search and Launch Method:**
- Press the Windows key.
- Type "PowerShell" or "Terminal" (for Windows 11).
- Press `Ctrl + Shift + Enter` or Right-click and choose "Run as administrator" to launch it with administrator privileges.
> **WinUtil must be run as Administrator** because it performs system-wide changes.
### Launch Command
#### Stable Branch (Recommended)
Open PowerShell or Terminal as admin, then run:
**Stable Branch (recommended)**
```ps1
irm "https://christitus.com/win"|iex
```
#### Dev Branch
```ps1
irm "https://christitus.com/windev"|iex
irm https://christitus.com/win|iex
```
If you have Issues, refer to [Known Issues](https://winutil.christitus.com/knownissues/) or [Create Issue](https://github.com/ChrisTitusTech/winutil/issues)
## 🎓 Documentation
### [WinUtil Official Documentation](https://winutil.christitus.com/)
> Winutil is a relatively large script, so it's split into multiple files which're combined into a single `.ps1` file using a custom compiler. This makes maintaining the project a lot easier.
Get a copy of the source code. This can be done using GitHub UI (**Code** > **Download ZIP**), or by cloning (downloading) the repo using git.
If git is installed, run the following commands under a PowerShell window to clone and move into the project's directory:
You'll see a new file named `winutil.ps1`, which was created by `Compile.ps1` script. Now you can run it as admin, and a new window will pop up. Enjoy your own compiled version of WinUtil :)
| Preset | Description |
|--------|-------------|
| `Standard` | Balanced defaults for most users |
| `Minimal` | Minimal changes to suit every user |
| `Advanced` | Deep tweaks for power users |
> [!TIP]
> For more info on using WinUtil and how to develop for it, please consider reading [the Contribution Guidelines](https://winutil.christitus.com/contributing/). If you don't know where to start, or have questions, you can ask over on our [Discord Community Server](https://discord.gg/RUbZUZyByQ), and active project members will answer when they can.
Project contract for WinUtil — what the project is, how it's built, and how it runs. Written for anyone, human or AI, who needs to understand the project itself.
`AGENTS.md` in the repository root points here for these facts, and separately covers how an agent should behave while working in this repo. This file does not change based on who's reading it.
## Project Context
WinUtil is a Windows PowerShell utility with a WPF interface. The repository is maintained as modular source, but the distributed artifact is one compiled PowerShell script.
### Stack
- Language: Windows PowerShell / PowerShell.
- UI: WPF via `xaml/inputXML.xaml`.
- Configuration: JSON files under `config/`.
- Tests: Pester tests under `pester/`.
- Lint: PowerShell Script Analyzer with settings in `lint/PSScriptAnalyser.ps1`.
- Docs: Astro + Starlight site under `docs/`, built independently of `Compile.ps1` (its own `package.json`/`node_modules`).
- Release artifact: generated root `winutil.ps1`.
### Repository Layout
-`Compile.ps1`: build script that creates `winutil.ps1`.
-`scripts/start.ps1`: startup/bootstrap segment used at the beginning of the compiled script.
-`scripts/main.ps1`: main entrypoint appended at the end of the compiled script.
-`tools/title-screen/`: uv project that captures WinUtil's Light and Dark themes and generates the title-screen composite.
-`docs/`: Astro + Starlight documentation site, with its own `package.json` and build independent of `Compile.ps1`.
-`winutil.ps1`: ignored generated build artifact.
## Goals
- Provide a single-script Windows utility that can be launched from PowerShell.
- Keep development modular enough for contributors to work on functions, config, UI, docs, and tooling independently.
- Make install, tweak, feature, repair, update, and ISO workflows discoverable from the WPF UI.
- Keep common lists and options declarative in JSON config where possible.
- Preserve a repeatable compile process so local builds and GitHub Actions builds produce the distributable script from the same inputs.
## Non-Goals
-`winutil.ps1` is not hand-maintained.
- The project is not structured as a PowerShell module at runtime.
- The GUI is not a separate packaged desktop application in this repository's normal release path.
- Generated files should not be reviewed as source changes.
## Build Model
`Compile.ps1` combines the repository sources into `winutil.ps1` in this order:
1. Read `scripts/start.ps1` and replace `#{replaceme}` with the current `yy.MM.dd` build date.
2. Append every file under `functions/` recursively.
3. Convert each `config/*.json` file into embedded `$sync.configs` objects.
4. Special-case `config/applications.json` so keys receive the `WPFInstall` prefix in compiled config.
5. Embed `xaml/inputXML.xaml` into `$inputXML`.
6. Embed `tools/autounattend.xml` into `$WinUtilAutounattendXml`.
7. Append `scripts/main.ps1`.
8. Write the result to root `winutil.ps1`.
Because the final script is concatenated, code cannot rely on runtime module imports or source-relative dot-sourcing unless the compiled script will also contain the required code/data.
## Runtime Model
- WinUtil runs in PowerShell on Windows and uses WPF for the UI.
- Shared mutable state is stored in `$sync`, including configs, UI element references, runspace state, selections, and progress.
- Long-running operations use runspaces or existing async patterns so the UI remains responsive.
- UI updates from background work are dispatched back to the WPF UI thread.
- Declarative features such as apps, tweaks, presets, DNS providers, and navigation stay in `config/*.json` unless code is required.
## UI And Event Contract
- UI layout lives in `xaml/inputXML.xaml`.
- Named WPF controls are discovered and stored in `$sync`.
- Button/action wiring follows a naming convention: an element named like `WPFThingButton` maps to a function named like `Invoke-WPFThingButton`.
## Configuration Contract
- Config files must remain valid JSON and compile cleanly through `ConvertFrom-Json`.
-`config/dns.json` opts unfiltered providers into Fastest selection with `BenchmarkEligible: true`; missing or false values exclude a provider from the TCP latency benchmark.
-`config/applications.json` defines installable applications; each entry includes the fields expected by tests and UI code, such as package manager IDs, category, display content, description, and link.
-`config/tweaks.json` defines Windows tweaks; registry and service changes include original values or original states when applicable so undo workflows can restore user systems.
- Preset and navigation files reference valid config keys. Renaming a config key requires updating all presets, UI references, docs, and code paths together.
## Safety Requirements
- Registry, service, package manager, Windows Update, AppX removal, and ISO operations affect the host system and are treated as high-risk.
- Tweak changes include undo metadata when the schema supports it, so changes stay reversible.
- ISO workflows never modify the user's original ISO file; they work on copied/mounted content.
## Docs Site (Astro)
-`docs/` is an Astro + Starlight site, independent of `Compile.ps1`'s build (its own `package.json`/`node_modules`, deployed via the `docs.yaml` GitHub Actions workflow to GitHub Pages).
- Pages live under `docs/src/content/docs/` (`.mdx`), organized into `guides/`, `code-reference/`, plus top-level pages like `faq.mdx`, `knownissues.mdx`, `contributing.mdx`, `index.mdx`.
-`docs/src/content/docs/code-reference/tweaks/` and `.../features/` are auto-generated by `tools/devdocs-generator.ps1` from `config/tweaks.json`/`config/feature.json` and the relevant PowerShell function files. Other pages under `code-reference/` (e.g. `architecture.mdx`) are hand-written and untouched by the generator.
- Sidebar entries in `docs/astro.config.mjs` must match actual page slugs under `docs/src/content/docs/`.
-`docs/public/` is tracked source for static assets (favicons, etc.), not generated output. Generated/ignored paths are listed in `docs/.gitignore` (`dist/`, `.astro/`, `node_modules/`, local env files).
-`docs/src/assets/branding/title-screen.png` is a tracked generated image used by the repository README and docs homepage. Its raw Light and Dark captures are temporary.
-`docs/Dockerfile` and `docs/docker-compose.yml` (service `winutil-astro`) containerize the site's npm tooling; see AGENTS.md's Dependency Installs, Builds, And Dev Servers for why and how agents must use them instead of running npm on the host.
## Testing And CI
-`.\Compile.ps1` verifies the compiler can generate `winutil.ps1`.
-`.\Compile.ps1 -Run` compiles and launches the generated utility for manual GUI verification.
- Pester 5.8.0 runs the suite under `pester/*.Tests.ps1`. GitHub Actions (`unittests.yaml`) installs Pester 5.8.0 fresh and runs with `-CI`, which produces `testResults.xml` and exits non-zero on failure.
- GitHub Actions also runs PowerShell Script Analyzer with `lint/PSScriptAnalyser.ps1` on every push.
- The generated `winutil.ps1` may appear locally after compile. It remains ignored build output (see root `.gitignore`) and must not be committed.
- The manually triggered title-screen workflow compiles WinUtil from `main` and opens an image-only pull request when the generated composite changes. These pull requests require manual review. Failed runs retain diagnostics for 14 days.
## Release Artifact
GitHub Actions is responsible for producing the release `winutil.ps1` from repository sources. A release is considered valid only if the generated script came from the compile process, not from direct manual edits to `winutil.ps1`.
"Description":"Allows users to submit bug reports, feature suggestions, and diagnostic data directly to Microsoft.",
"Panel":"0",
"PackageId":"Microsoft.WindowsFeedbackHub",
"StoreId":"9NBLGGH4R32N"
},
"WPFAppxMicrosoft_GetHelp":{
"Category":"Microsoft Apps",
"Content":"Get Help",
"Description":"Provides access to automated troubleshooting guides, support documentation, and direct Microsoft customer assistance.",
"Panel":"0",
"PackageId":"Microsoft.GetHelp",
"StoreId":"9PKDZBMV1H3T"
},
"WPFAppxMicrosoft_OutlookForWindows":{
"Category":"Microsoft Apps",
"Content":"Outlook for Windows",
"Description":"Provides modern email management, calendar scheduling, and contact organization features.",
"Panel":"0",
"PackageId":"Microsoft.OutlookForWindows",
"StoreId":"9NRX63209R7B"
},
"WPFAppxMSTeams":{
"Category":"Microsoft Apps",
"Content":"Microsoft Teams",
"Description":"Facilitates instant messaging, video conferencing, file sharing, and workspace collaboration.",
"Panel":"0",
"PackageId":"MSTeams",
"StoreId":"XP8BT8DW290MPQ"
},
"WPFAppxClipchamp_Clipchamp":{
"Category":"Utilities & Productivity",
"Content":"Clipchamp",
"Description":"Provides a user-friendly video editor with built-in templates, effects, and timeline editing tools.",
"Panel":"0",
"PackageId":"Clipchamp.Clipchamp",
"StoreId":"9P1J8S7CCWWT"
},
"WPFAppxMicrosoft_MicrosoftOfficeHub":{
"Category":"Microsoft Apps",
"Content":"Microsoft 365",
"Description":"Serves as a centralized launcher and dashboard for accessing cloud-based Microsoft 365 apps and recent documents.",
"Panel":"0",
"PackageId":"Microsoft.MicrosoftOfficeHub",
"StoreId":"9WZDNCRD29V9"
},
"WPFAppxMicrosoft_ZuneMusic":{
"Category":"Utilities & Productivity",
"Content":"Media Player",
"Description":"Plays local audio and video files with modern playlist management and casting capabilities.",
"Panel":"0",
"PackageId":"Microsoft.ZuneMusic",
"StoreId":"9WZDNCRFJ3PT"
},
"WPFAppxMicrosoft_BingSearch":{
"Category":"Bing & Web Services",
"Content":"Bing Search",
"Description":"Integrates Microsoft Bing search capabilities and web services directly into the operating system.",
"Panel":"1",
"PackageId":"Microsoft.BingSearch",
"StoreId":"9NZBF4GT040C"
},
"WPFAppxMicrosoftCorporationII_QuickAssist":{
"Category":"Utilities & Productivity",
"Content":"Quick Assist",
"Description":"Enables secure remote technical support and screen sharing over an internet connection.",
"Panel":"0",
"PackageId":"MicrosoftCorporationII.QuickAssist",
"StoreId":"9P7BP5VNWKX5"
},
"WPFAppxMicrosoft_WindowsDevHome":{
"Category":"Developer Tools",
"Content":"Dev Home",
"Description":"Provides a specialized dashboard for software developer environment setups, repository syncing, and hardware widgets.",
"Panel":"1",
"PackageId":"Microsoft.Windows.DevHome",
"StoreId":"9N8MHTPHNGVV"
},
"WPFAppxMicrosoft_WindowsCrossDevice":{
"Category":"Microsoft Ecosystem",
"Content":"Mobile Devices",
"Description":"Manages system-level background connectivity with paired mobile devices. Removing this may disable cross-device features such as phone screen mirroring, file transfer, and mobile hotspot handoff integrated into Windows Settings.",
"Panel":"0",
"PackageId":"MicrosoftWindows.CrossDevice",
"StoreId":"9NTXGKQ8P7N0"
},
"WPFAppxMicrosoft_Todos":{
"Category":"Utilities & Productivity",
"Content":"To Do",
"Description":"Creates, tracks, and synchronizes personal tasks, smart lists, and daily reminders.",
"Panel":"0",
"PackageId":"Microsoft.Todos",
"StoreId":"9NBLGGH5R558"
},
"WPFAppxMicrosoft_PowerAutomateDesktop":{
"Category":"Developer Tools",
"Content":"Power Automate",
"Description":"Automates repetitive workflows and desktop tasks using low-code visual scripting.",
"Panel":"1",
"PackageId":"Microsoft.PowerAutomateDesktop",
"StoreId":"9NFTCH6J7FHV"
},
"WPFAppxMicrosoft_YourPhone":{
"Category":"Microsoft Ecosystem",
"Content":"Phone Link",
"Description":"Synchronizes text messages, phone notifications, photos, and calls from a mobile device to the desktop.",
"Panel":"0",
"PackageId":"Microsoft.YourPhone",
"StoreId":"9NMPJ99VJBWV"
},
"WPFAppxMicrosoft_MicrosoftStickyNotes":{
"Category":"Utilities & Productivity",
"Content":"Sticky Notes",
"Description":"Creates quick, floating text notes on the desktop that automatically sync across devices.",
"Panel":"0",
"PackageId":"Microsoft.MicrosoftStickyNotes",
"StoreId":"9NBLGGH4QGHW"
},
"WPFAppxMicrosoft_WindowsSoundRecorder":{
"Category":"Utilities & Productivity",
"Content":"Sound Recorder",
"Description":"Records and trims live audio inputs with simple microphone adjustment controls.",
"Panel":"0",
"PackageId":"Microsoft.WindowsSoundRecorder",
"StoreId":"9WZDNCRFHWKN"
},
"WPFAppxMicrosoft_WindowsAlarms":{
"Category":"Utilities & Productivity",
"Content":"Clock",
"Description":"Features world clocks, alarms, countdown timers, stopwatches, and dedicated focus session tracking.",
"Panel":"0",
"PackageId":"Microsoft.WindowsAlarms",
"StoreId":"9WZDNCRFJ3PR"
},
"WPFAppxMicrosoft_Paint":{
"Category":"Utilities & Productivity",
"Content":"Paint",
"Description":"Provides built-in digital sketching, basic image editing, and pixel-level graphic manipulation tools.",
"Panel":"0",
"PackageId":"Microsoft.Paint",
"StoreId":"9PCFS5B6T72H"
},
"WPFAppxMicrosoft_WindowsNotepad":{
"Category":"Utilities & Productivity",
"Content":"Notepad",
"Description":"Provides a lightweight text editor with multi-tab support for plain text files and code snippets.",
"Panel":"0",
"PackageId":"Microsoft.WindowsNotepad",
"StoreId":"9MSMLRH6LZF3"
},
"WPFAppxMicrosoft_ScreenSketch":{
"Category":"Utilities & Productivity",
"Content":"Snipping Tool",
"Description":"Captures screenshots or screen recordings with built-in markup, image cropping, and optical character recognition (OCR).",
"Panel":"0",
"PackageId":"Microsoft.ScreenSketch",
"StoreId":"9MZ95KL8MR0L"
},
"WPFAppxMicrosoft_Copilot":{
"Category":"Bing & Web Services",
"Content":"Copilot",
"Description":"Launches the Microsoft AI companion for contextual answers, creative writing assistance, and intelligent web search.",
"Panel":"1",
"PackageId":"Microsoft.Copilot",
"StoreId":"9NHT9RB2F4HD"
},
"WPFAppxMicrosoft_WindowsCalculator":{
"Category":"Utilities & Productivity",
"Content":"Calculator",
"Description":"Performs standard arithmetic, scientific operations, programming calculations, and unit conversions.",
"Panel":"0",
"PackageId":"Microsoft.WindowsCalculator",
"StoreId":"9WZDNCRFHVN5"
},
"WPFAppxMicrosoft_WindowsCamera":{
"Category":"Utilities & Productivity",
"Content":"Camera",
"Description":"Captures photographs and records video files via connected webcams or imaging hardware.",
"Panel":"0",
"PackageId":"Microsoft.WindowsCamera",
"StoreId":"9WZDNCRFJBBG"
},
"WPFAppxMicrosoft_WindowsPhotos":{
"Category":"Utilities & Productivity",
"Content":"Photos",
"Description":"Organizes, views, and crops local images with basic color adjustment and album creation tools.",
"Panel":"0",
"PackageId":"Microsoft.Windows.Photos",
"StoreId":"9WZDNCRFJBH4"
},
"WPFAppxMicrosoft_BingNews":{
"Category":"Bing & Web Services",
"Content":"News",
"Description":"Aggregates breaking news headlines, personalized article feeds, and world current events.",
"Panel":"1",
"PackageId":"Microsoft.BingNews",
"StoreId":"9WZDNCRFHVFW"
},
"WPFAppxMicrosoft_BingWeather":{
"Category":"Bing & Web Services",
"Content":"Weather",
"Description":"Displays local real-time weather tracking, radar maps, and historical meteorological forecasts.",
"Panel":"1",
"PackageId":"Microsoft.BingWeather",
"StoreId":"9WZDNCRFJ3Q2"
},
"WPFAppxMicrosoft_GamingApp":{
"Category":"Xbox & Gaming",
"Content":"Xbox App",
"Description":"Serves as the primary gaming library manager, social community interface, and PC Game Pass dashboard.",
"Panel":"1",
"PackageId":"Microsoft.GamingApp",
"StoreId":"9MV0B5HZVK9Z"
},
"WPFAppxMicrosoft_XboxGamingOverlay":{
"Category":"Xbox & Gaming",
"Content":"Xbox Game Bar",
"Description":"Provides customizable in-game status widgets, audio balancing sliders, system monitoring tools, and gameplay recording.",
"Panel":"1",
"PackageId":"Microsoft.XboxGamingOverlay",
"StoreId":"9NZKPSTSNW4P"
},
"WPFAppxMicrosoft_XboxIdentityProvider":{
"Category":"Xbox & Gaming",
"Content":"Xbox Identity Provider",
"Description":"Manages Xbox network user authentication and background account validation for connected titles. Warning: removing this may break Microsoft account sign-in for non-Xbox games and apps that rely on this authentication pipeline.",
"Panel":"1",
"PackageId":"Microsoft.XboxIdentityProvider",
"StoreId":"9WZDNCRD1HKW"
},
"WPFAppxMicrosoft_XboxSpeechToTextOverlay":{
"Category":"Xbox & Gaming",
"Content":"Xbox Speech To Text Overlay",
"Description":"Provides system-level live accessibility captions and voice-to-text translation for gaming chat networks.",
"Panel":"1",
"PackageId":"Microsoft.XboxSpeechToTextOverlay"
},
"WPFAppxMicrosoft_Xbox_TCUI":{
"Category":"Xbox & Gaming",
"Content":"Xbox TCUI",
"Description":"Provides core account connection UI modules for single sign-on flows within game titles. Warning: removing this may break Microsoft account authentication in games and apps that do not otherwise require the Xbox app.",
"Panel":"1",
"PackageId":"Microsoft.Xbox.TCUI"
},
"WPFAppxMicrosoft_StartExperiencesApp":{
"Category":"Bing & Web Services",
"Content":"Start Experiences App",
"Description":"Powers the Windows Widgets board, delivering a personalized feed of news, weather, sports, and finance content.",
"Panel":"1",
"PackageId":"Microsoft.StartExperiencesApp",
"StoreId":"9PC1H9VN18CM"
},
"WPFAppxMicrosoft_MicrosoftSolitaireCollection":{
"Category":"Xbox & Gaming",
"Content":"Solitaire Collection",
"Description":"Bundles built-in card game modes including Klondike, Spider, FreeCell, Pyramid, and TriPeaks alongside daily challenges.",
[](https://starlight.astro.build)
Documentation site for [WinUtil](https://github.com/ChrisTitusTech/winutil), built with [Astro](https://astro.build) and [Starlight](https://starlight.astro.build). Served at [winutil.christitus.com](https://winutil.christitus.com/).
## 🚀 Project Structure
```
.
├── public/
├── src/
│ ├── assets/
│ ├── components/
│ ├── content/
│ │ └── docs/
│ ├── styles/
│ └── content.config.ts
├── astro.config.mjs
├── docker-compose.yml
├── Dockerfile
├── package.json
└── tsconfig.json
```
Starlight looks for `.md` or `.mdx` files in the `src/content/docs/` directory. Each file is exposed as a route based on its file name.
Images can be added to `src/assets/` and embedded in Markdown with a relative link.
Static assets, like favicons, can be placed in the `public/` directory.
## 🧞 Commands
All commands run in a Docker container — there's no need to install Node or npm dependencies on your host. This is deliberate, not just convenience: npm/pnpm/yarn have seen a steady stream of supply-chain attacks (malicious `postinstall`/`preinstall` scripts, credential-stealing packages), so `npm install` and friends never run directly on a contributor's machine here. Note the container still has read-write access to this `docs/` directory (it's bind-mounted for live reload), so this only contains a compromised package to the project folder plus the container itself — it doesn't reach the rest of your host (SSH keys, other repos, cloud credentials elsewhere on disk). Don't keep real secrets in `docs/` as a result.
[Docker](https://www.docker.com/) (with Compose) is required — install Docker Desktop (or Docker Engine + the `docker compose` plugin on Linux) and make sure the daemon is running before using any of the commands below.
All commands are run from the `docs/` directory, from a terminal:
| `docker compose build` | Builds the dev image (needed after Dockerfile or dependency changes) |
| `docker compose up winutil-astro` | Starts local dev server at `localhost:4321` |
| `docker compose run --rm winutil-astro npm run build` | Build the production site to `./dist/` |
| `docker compose run --rm --service-ports winutil-astro npm run preview -- --host 0.0.0.0` | Preview the build locally, before deploying |
| `docker compose run --rm winutil-astro npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
| `docker compose down` | Stop and remove the dev container |
Source files are bind-mounted into the container, so edits on the host are picked up immediately by the dev server — no rebuild needed for normal content or code changes. After changing `package.json`, `package-lock.json`, or the `Dockerfile`, rebuild the image *and* drop the `node_modules` volume, since Docker only seeds a named volume from the image the first time it's created — a plain rebuild leaves the old `node_modules` in place:
```sh
docker compose build
docker compose down -v
docker compose up winutil-astro
```
The first `docker compose up` (or any command before an image exists) builds the image and runs `npm install` from scratch, which can take a few minutes. Subsequent runs reuse the cached image and start almost immediately.
## 👀 Want to learn more?
Check out [Starlight's docs](https://starlight.astro.build/), read [the Astro documentation](https://docs.astro.build), or jump into the [Astro Discord server](https://astro.build/chat).
If it still isn't working in your region, it may be due to temporary ISP or network filtering of GitHub content domains. This has been reported by some users in India in the past. See: [Times of India](https://timesofindia.indiatimes.com/gadgets-news/github-content-domain-blocked-for-these-indian-users-reports/articleshow/96687992.cms).
If you are still having issues, try using a **VPN**, or changing your **DNS provider** to one of the following two providers:
| Provider | Primary DNS | Secondary DNS |
| :--------: | :---------: | :-----------: |
| Cloudflare | `1.1.1.1` | `1.0.0.1` |
| Google | `8.8.8.8` | `8.8.4.4` |
### Script Won't Run
If you run WinUtil and get the error:
`"WinUtil is unable to run on your system, powershell execution is restricted by security policies,"`
this means that your PowerShell session is in **Constrained Language Mode**, which prevents WinUtil from running.
Welcome to the official documentation for Winutil, your go-to utility for optimizing and managing your Windows environment. Whether you’re an IT professional, power user, or regular user, Winutil provides a comprehensive set of tools to enhance your Windows experience.
## Running the latest release of Winutil
* You will first need to start a PowerShell terminal **as Admin**.
* Now you can run the following command:
```
irm "https://christitus.com/win" | iex
```
> [!IMPORTANT]
> Winutil is updated frequently as of the time of writing. Consequently, features and functionalities may evolve, and the documentation may not always reflect the most current images or information.
"Description": "This tweak controls the Resume function in Windows 11 24H2 and later, which allows you to resume an activity from a mobile device and vice-versa.",
Applications and System Components store and retrieve configuration data to modify Windows settings, so we can use the registry to change many settings in one place.
You can find information about the registry on [Wikipedia](https://www.wikiwand.com/en/Windows_Registry) and [Microsoft's Website](https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry).
Applications and System Components store and retrieve configuration data to modify Windows settings, so we can use the registry to change many settings in one place.
You can find information about the registry on [Wikipedia](https://www.wikiwand.com/en/Windows_Registry) and [Microsoft's Website](https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry).
Applications and System Components store and retrieve configuration data to modify Windows settings, so we can use the registry to change many settings in one place.
You can find information about the registry on [Wikipedia](https://www.wikiwand.com/en/Windows_Registry) and [Microsoft's Website](https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry).
"Description": "Creates an Environment Variable called 'POWERSHELL_TELEMETRY_OPTOUT' with a value of '1' which will tell PowerShell 7 to not send Telemetry Data.",
if (-not (powercfg /list | Select-String "ChrisTitus - Ultimate Power Plan")) {
if (-not (powercfg /list | Select-String "8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c")) {
powercfg /restoredefaultschemes
if (-not (powercfg /list | Select-String "8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c")) {
Write-Host "Failed to restore High Performance plan. Default plans do not include high performance. If you are on a laptop, do NOT use High Performance or Ultimate Performance plans." -ForegroundColor Red
if (-not (powercfg /list | Select-String "ChrisTitus - Ultimate Power Plan")) {
if (-not (powercfg /list | Select-String "8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c")) {
powercfg /restoredefaultschemes
if (-not (powercfg /list | Select-String "8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c")) {
Write-Host "Failed to restore High Performance plan. Default plans do not include high performance. If you are on a laptop, do NOT use High Performance or Ultimate Performance plans." -ForegroundColor Red
"Description": "Reduces user interruptions by selectively blocking connections to Adobe's activation and telemetry servers. Credit: Ruddernation-Designs",
"Description": "Disables FSO in all applications. NOTE: This will disable Color Management in Exclusive Fullscreen.",
"category": "z__Advanced Tweaks - CAUTION",
"panel": "1",
"registry": [
{
"Path": "HKCU:\\System\\GameConfigStore",
"Name": "GameDVR_DXGIHonorFSEWindowsCompatible",
"Value": "1",
"Type": "DWord",
"OriginalValue": "0"
}
],
```
## Registry Changes
Applications and System Components store and retrieve configuration data to modify Windows settings, so we can use the registry to change many settings in one place.
You can find information about the registry on [Wikipedia](https://www.wikiwand.com/en/Windows_Registry) and [Microsoft's Website](https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry).
# Restarting Explorer in the Undo Script might not be necessary, as the Registry change without restarting Explorer does work, but just to make sure.
Write-Host Restarting explorer.exe ...
Stop-Process -Name \"explorer\" -Force
"
],
```
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.