This is the manual runbook for shipping a WinClean release. It is deliberately manual: an automated release.yml workflow is a future improvement and is intentionally deferred for now, so every release goes through the checklist below and the machine gate Invoke-ReleaseCheck.ps1.
WinClean is distributed through several channels, and they must all show the same version at the same time:
mainholds the source of truth for the code.- README badges advertise the current version. The version badge is dynamic (it reads the latest GitHub Release via shields.io), so it follows the release automatically; the version that still needs a manual bump is the flow-diagram heading in each README.
- GitHub Release is what the one-line bootstrap scripts consume: both
get.ps1andinstall.ps1downloadWinClean.ps1from the latest GitHub Release and verify it against the publishedWinClean.ps1.sha256asset. They are fail-closed: a missing asset or a hash mismatch aborts the run, and there is no fallback to a mutable branch. - PSGallery is what the script's built-in update check uses:
Test-ScriptUpdatecompares the running version against the PowerShell Gallery. An installed copy only learns about a new version if it was published there. - CHANGELOG.md is the human record of what changed.
If these drift, users get confused or, worse, run an old version while the README promises a new one. A git push alone does not publish a release: the bootstrap scripts keep serving the previous GitHub Release until you cut a new one.
The version appears in several places inside WinClean.ps1 (the $script:Version variable, the PSScriptInfo .VERSION, .RELEASENOTES, the .SYNOPSIS, and the NOTES block) and in the docs (the flow diagram heading in both READMEs, and the CHANGELOG entry). The README version badge is dynamic and needs no bump; the test-count lines in CONTRIBUTING and CLAUDE are a separate counter the gate also checks. Line numbers move between releases, so find each spot by content (grep), not by a fixed line number.
You do not verify these by hand. The machine gate does it for you.
pwsh tools/Invoke-ReleaseCheck.ps1This is fail-closed and checks, at minimum:
- the version is consistent across every place it appears;
- there are no em-dashes or en-dashes anywhere in the tracked text (only hyphen-minus is allowed);
- the documented test counters match the real Pester count;
- PowerShell syntax parses;
- PSScriptAnalyzer is clean over exactly the files and severities CI enforces (both call the same
tools/Invoke-Lint.ps1, so the gate cannot be greener than CI); - Pester runs with no skipped tests (a skipped integration suite is a silent gap);
- the smoke test passes (a real
-ReportOnlyrun, console box geometry, result JSON); - the working tree is clean and pushed.
Optional flags:
-IncludeStandalso runs one real cleanup on the stand (Invoke-StandTest.ps1 -Mode Full -Source localagainst the default config). The broader RU + EN andReportNoCleanupmatrix below is run manually.-VerifyPublishedis run after the GitHub Release exists: it confirms both assets are present and that the publishedWinClean.ps1.sha256matches the releasedWinClean.ps1.
Compute the hash, write the sidecar file, and create the release with both assets:
$hash = (Get-FileHash .\WinClean.ps1 -Algorithm SHA256).Hash
"$hash WinClean.ps1" | Out-File "$env:TEMP\WinClean.ps1.sha256" -Encoding ascii -NoNewline
gh release create vX.Y `
".\WinClean.ps1#WinClean.ps1" `
"$env:TEMP\WinClean.ps1.sha256#WinClean.ps1.sha256" `
--title "WinClean vX.Y" `
--notes "..."Both assets are mandatory. A release that publishes WinClean.ps1 without WinClean.ps1.sha256 is refused by get.ps1 (it will not run unverified code elevated). If you have to correct the script inside an already-published release, re-upload with --clobber and recompute the hash so the two stay in sync:
$hash = (Get-FileHash .\WinClean.ps1 -Algorithm SHA256).Hash
"$hash WinClean.ps1" | Out-File "$env:TEMP\WinClean.ps1.sha256" -Encoding ascii -NoNewline
gh release upload vX.Y ".\WinClean.ps1#WinClean.ps1" "$env:TEMP\WinClean.ps1.sha256#WinClean.ps1.sha256" --clobberFor a tool that runs elevated, predictability is worth more than a long feature list, so every release uses the same headings. Keep them even when a section is short - a reader looking for "did this change what gets deleted?" should find the answer in the same place every time.
## What changed
Grouped as Fixed / Changed / Added, one line each, in user terms rather than internal
task numbers. Say what was wrong, not only what was done.
## Safety notes
Anything that alters what is deleted, what is reported, or how the script decides it is
finished. Say "nothing in this release changes what gets deleted" when that is the case -
its absence is what people check for.
## Verification
Release gate, Pester count, PSScriptAnalyzer, smoke, and the RU/EN stand runs, plus the
end-to-end run through the published one-liner. State what was actually run for THIS
release, not what the process says in general.
## Assets
`WinClean.ps1` and `WinClean.ps1.sha256`. Both are required: the bootstrap scripts verify
the hash fail-closed and refuse to run without it.
## Upgrade
Which of the four installation methods needs an action, and which update themselves.
## Known limitations
Anything deliberately not fixed in this release, with the reason. This section is the one
most worth writing honestly - it is where trust is earned or lost.Publish-PSResource -Path .\WinClean.ps1 -Repository PSGallery -ApiKey $env:PSGALLERY_API_KEYThe API key lives in the PSGALLERY_API_KEY environment variable. Publishing here is what lets already-installed copies discover the update through Test-ScriptUpdate; skipping it leaves PSGallery users behind even though the GitHub one-liners are current.
Publish to PSGallery last. It is the only irreversible step in this runbook: a published version cannot be deleted or replaced, only unlisted, and the same version number can never be re-used. Everything else can be corrected (assets re-uploaded with --clobber, a tag deleted, a commit reverted). So run the post-release verification first and publish to PSGallery only once the GitHub Release is confirmed good.
Every release is validated on real Windows 11 VMs before and after publishing, run manually with tools/proxmox/Invoke-StandTest.ps1:
- RU (VM 190) and EN (VM 191) (via
-ConfigPath), so locale-dependent parsing is exercised in both cultures. - Full runs (real cleanup) plus
-Mode ReportNoCleanup(a preview with-SkipCleanup, which verifies end-to-end that the whole cleanup group is suppressed and reported as skipped). - The nightly matrix additionally runs a quick pass with
-Source release, which downloadsWinClean.ps1from the latest release tag (raw.githubusercontent.com/.../<tag>/WinClean.ps1). This catches a broken release with a healthymain; note it exercises the tag's script content, not the release asset download or theget.ps1one-liner (those are covered by-VerifyPublishedand a manual one-liner run). The nightly also has a dead-man heartbeat check: an independent, later cron reports if the matrix never ran, so a silent night is not mistaken for a healthy one.
- Land all code and doc changes on the release branch; update
CHANGELOG.mdwith the new version section. - Bump the version in every place inside
WinClean.ps1and the docs (verify by grep, not line number). - Run
pwsh tools/Invoke-ReleaseCheck.ps1until green. - Run
pwsh tools/Invoke-ReleaseCheck.ps1 -IncludeStand(one Full/local pass), and manually run the RU + EN stand (Invoke-StandTest.ps1with each-ConfigPath) in Full and-Mode ReportNoCleanup. - Merge to
mainand push. - Compute the SHA256, write
WinClean.ps1.sha256, andgh release create vX.Ywith both assets. - Run
pwsh tools/Invoke-ReleaseCheck.ps1 -VerifyPublished(confirms both assets exist and the SHA256 matches), then a manual one-liner run (irm https://raw.githubusercontent.com/bivlked/WinClean/main/get.ps1 | iex) to confirm the release asset and theget.ps1path serve the new version. Run that one-liner on a stand VM, never on your own workstation: it is a real elevated cleanup run, not a preview. (Invoke-StandTest.ps1 -Source releaseon the stand is a separate check - it runs the tag's rawWinClean.ps1, not the release asset or the one-liner.) Publish-PSResourceto PSGallery - last, because it is the one step that cannot be undone.- Confirm README badges, the GitHub Release, PSGallery and the CHANGELOG all show the same version.
- Clean up. Delete the release branch once it is merged (locally and on the remote) and resolve any open dependency PRs, so that
mainis the only branch left. The repository has "automatically delete head branches" enabled, so a branch merged through a PR removes itself; a branch merged any other way does not. The tag and the release keep the history, so a merged branch carries no information that is not already inmain.