* fix(packages): install every ID in semicolon-separated choco entries applications.json githubdesktop has choco "git;github-desktop", but Get-WinUtilSelectedPackages passed the whole string to choco as one package name, so install and uninstall always failed under the Choco preference. Detection in Invoke-WinUtilCurrentSystem already splits on ";". Split in Add-PackageId so install and detection agree. * fix(tweaks): undo services only when still at applied type KeepServiceStartup skipped a service on apply when its startup type was changed by the user, but undo forced the static OriginalType back regardless, overwriting the user setting that apply deliberately left alone. Undo now uses the same guard in reverse: only restore OriginalType when the service still sits at the applied StartupType. * fix(iso): treat a failed source-ISO dismount as an error Two Dismount-DiskImage calls in the modify path lacked -ErrorAction Stop, unlike the existing call sites at lines 185 and 291. A non-terminating dismount failure (device busy) was swallowed, $modified was set anyway, and the finally-block dismount cleanup was skipped, leaving the source ISO mounted with no feedback. * fix(features): continue past a failed optional feature Enable-WindowsOptionalFeature ran with -ErrorAction Stop and no per-feature catch, so one feature missing on the current edition (e.g. Windows Sandbox on Home) aborted the whole selection. Each feature now fails independently and logs an error. * fix(tweaks): drop nonexistent wermgr service, use REG_SZ types Set-Service wermgr targets a service that does not exist. Invoke-WinUtilScript runs the script block with -ErrorAction Stop, so the failing Set-Service aborted the rest: apply never set POWERSHELL_TELEMETRY_OPTOUT or removed PeriodInNanoSeconds, and undo never restored them. Also fix MouseSpeed/MouseThreshold1/MouseThreshold2 and KeyboardDelay to Type String: Windows stores these Control Panel values as REG_SZ, and writing them as DWord changed the value type, which the settings UI can ignore. * fix(tweaks): guard diagtrack, drop taskbar keys from Visual Effects WPFTweaksTelemetry disabled diagtrack through InvokeScript/UndoScript, bypassing the KeepServiceStartup guard that service entries get, and undo forced Automatic regardless of what the user had set. diagtrack is now a service block entry, so apply and undo go through Set-WinUtilService with the guard. WPFTweaksDisplay (Visual Effects - Set to Best Performance) also set ShowTaskViewButton and SearchboxTaskbarMode to 0 with OriginalValue 1, while WPFToggleTaskView/WPFToggleTaskbarSearch own the same values with the opposite originals: one undo path always wrote a guessed value over the user setup, and taskbar visibility is not a visual effect. Removed both entries from WPFTweaksDisplay; the toggles remain the sole owners. Adds a configs test asserting no two tweaks claim the same registry value with conflicting OriginalValue or Type. * fix(tweaks): guard Set-Service calls in config scripts WPFTweaksWindowsAI and WPFTweaksRemoveOneDrive called Set-Service unguarded inside scripts that Invoke-WinUtilScript runs with -ErrorAction Stop. WSAIFabricSvc only exists on some Windows 11 builds, so on machines without it the failing Set-Service aborted the block and Disable-WindowsOptionalFeature for Recall never ran. Each Set-Service is now wrapped in a Get-Service existence check, and a configs test keeps future config scripts guarded. * fix(appx): report an install with no source as a failed result Install-WinUtilAPPX threw when a package had no local manifest and no StoreId, and Invoke-WPFAppxInstall had no per-package catch: selecting any of the three appx.json entries without a StoreId aborted the whole job, skipped every remaining selected package, and never reached Complete-WinUtilPackageRun. The no-source case now returns a Failed result like the choco and winget paths, so the batch completes, the summary names the package, and Complete-WinUtilPackageRun still fails the job. Unexpected errors continue to surface to the job layer. * Document partial failures and service undo limits accurately * Keep dependencies installed when uninstalling their app * Skip dependent scripts when a Windows feature fails * Check OneSyncSvc template existence through the registry --------- Co-authored-by: foxbyte <198093090+adiraj66132@users.noreply.github.com> Co-authored-by: Chris Titus <contact@christitus.com>
WinUtil Docs
Documentation site for WinUtil, built with Astro and Starlight. Served at 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 (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:
| Command | Action |
|---|---|
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:
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, read the Astro documentation, or jump into the Astro Discord server.