Pakko — Developer Deployment Scripts
These scripts handle local MSIX signing and sideloading during development.
They are not part of the build pipeline — run them manually from a PowerShell 7 (pwsh) terminal.
Prerequisites
- Windows 10/11 with Developer Mode enabled, or sideloading allowed via Group Policy
- .NET 10 SDK
- PowerShell 7 (
pwsh): the developer scripts have#Requires -Version 7.0, and the Release post-build deploy in Visual Studio runspwsh.exe(T-F371). The two end-user scripts in "Permission repair for archives opened by older Pakko versions" below stay on Windows PowerShell 5.1. - Visual Studio 2026 with Desktop C++ and the MSVC v143 x64/ARM64 build tools (the scripts find
MSBuild.exeviavswhere -latest; it builds only the C++ shell extension)
Step 1 — Set up the developer certificate (once)
.\scripts\Setup-DevCert.ps1
This will:
- Relaunch itself as Administrator if needed
- Create a self-signed
CN=Pakko Devcode-signing certificate inCert:\CurrentUser\My - Export it as
scripts/PakkoDev.cer(gitignored) - Install it into
Cert:\LocalMachine\TrustedPeopleso Windows trusts signed packages
At the end it prints the certificate thumbprint — copy it for use with Deploy.ps1.
You only need to run this once per machine (or when the certificate expires).
Step 2 — Build and install (after every change)
Full build + deploy (terminal workflow):
# Auto-detect the CN=Pakko Dev certificate (x64 default):
.\scripts\Deploy.ps1
# ARM64 build:
.\scripts\Deploy.ps1 -Architecture arm64
# Or pass the thumbprint explicitly:
.\scripts\Deploy.ps1 -Thumbprint "ABCDEF1234567890ABCDEF1234567890ABCDEF12"
This will:
- Publish
Archiver.Shell,Archiver.OperationUiandArchiver.CLIas Native AOT exes (T-F355) for the target architecture, into thebinfoldersArchiver.App.csprojpackages from. Needs the Visual Studio C++ build tools for that architecture; the script putsvswhere.exeonPATH, which ILCompiler uses to find them - Build
Archiver.ShellExtension.dll(MSBuild.exedirectly on the.vcxproj, with/p:SolutionDirpassed explicitly — seeDECISIONS.mdfor why) - Write
src/Archiver.App/obj/PakkoDev/Package.appxmanifest, a copy of the tracked manifest whose revision is one past the installed dev package (X.Y.Z.0with-SkipVersionBump), and pass it as/p:PakkoAppxManifest(T-F368: the tracked file stays atX.Y.Z.0, the Store rule) - Run
dotnet publishonArchiver.App.csprojwithGenerateAppxPackageOnBuild=trueandAppxPackageSigningEnabled=true+PackageCertificateThumbprint=<thumbprint>— packaging and signing happen in this one step.Content Includeitems inArchiver.App.csproj(conditioned onGenerateAppxPackageOnBuild=true) declare the three satellite exes andArchiver.ShellExtension.dllas package content (the App itself is Native AOT too, so the package has no .NET runtime files), sodotnet publishincludes them automatically — there is no separateArchiver.Package.wapprojand no manualSignTool.execall (a manualSignToolcall on an MSIX producesERROR_BAD_FORMAT; seeDECISIONS.md"MSIX Signing") - Uninstall any existing Pakko package
- Install the new
.msixfromsrc/Archiver.App/AppPackages/ - Print the installed version
-Architecture — "x64" (default) or "arm64". Derives the MSBuild Platform and runtime identifier automatically.
There is no
Archiver.ProgressWindowproject — it was removed (seeDECISIONS.md, T-F65). Shell-triggered operations show progress via the Windows Shell's built-inIProgressDialog, in-process, no second.exe.
Deploy only (skips build — installs the most recently built .msix):
.\scripts\Deploy.ps1 -DeployOnly
Visual Studio post-build event — Release builds in Visual Studio run
Deploy.ps1 -DeployOnlyautomatically after the build completes, so no manual script invocation is needed when building from VS.
Step 3 — Test the Explorer hand-off
After installing, run the installed Archiver.Shell.exe the way Explorer's "Extract files..." does
(it opens Archiver.App through ActivateApplication; Pakko registers no URI scheme, T-F232):
$shell = (Get-AppxPackage PavloRybchenko.Pakko).InstallLocation + '\Archiver.Shell.exe'
Start-Process -FilePath $shell -ArgumentList @('--open-ui', '--extract', '"C:\path\to\file.zip"')
Pakko should open with the archive in its list. --browse instead of --extract opens it in the
Archive Browser.
Publishing the standalone CLI (Archiver.CLI, T-F09)
Publish-Cli.ps1 is independent of everything above — the zip needs no dev-signing
certificate. (Separately, since T-F317 Deploy.ps1 and CI-Build-Msix.ps1 also publish
Archiver.CLI and package its pakko.exe into the MSIX for the pakko execution
alias; CI-Build-Msix.ps1 -CliVersion X.Y.Z stamps a release version, and the script fails if the
built package lacks one of the three satellite exes or the alias, or carries coreclr.dll or a
satellite .dll - a sign a non-AOT build slipped in. It also copies the four exes' native .pdb
files to artifacts/pdb/<arch>/, which CI keeps as the pakko-pdb-<arch> artifact for crash
dumps.)
.\scripts\Publish-Cli.ps1 # both architectures (default)
.\scripts\Publish-Cli.ps1 -Architecture x64 # one architecture only
Publishes a Native AOT build per architecture to artifacts/cli/<rid>/ (gitignored; T-F355, needs
the C++ build tools of each architecture) — the built exe is pakko.exe (AssemblyName, distinct
from the Archiver.CLI project/folder name) — zips pakko.exe alone as pakko-<rid>.zip (its
pakko.pdb stays in the folder), and writes a SHA256SUMS file covering both zips — ready to
attach directly to a GitHub Release. See CLI.md's "Distribution" section for why no tar.exe
copy is bundled alongside it.
-OutputRoot <dir> publishes elsewhere. The folder is wiped first, so the script accepts only a
new or empty folder, the default, or one it created before (it leaves a .pakko-cli-output
marker); any other non-empty folder is refused (T-F259).
winget manifest for the CLI zip (T-F317)
New-WingetManifest.ps1 -Version X.Y.Z [-Sha256SumsPath <release SHA256SUMS>] [-ReleaseDate yyyy-MM-dd] writes the three manifest files for PavloRybchenko.PakkoCLI (moniker pakko-cli)
into artifacts/winget/<version>/. A tag build's release job runs it too and uploads the result
as the pakko-winget-manifest artifact. Per release, by hand (it publishes under the project's
name):
winget validate --manifest <folder>; optionallywinget install --manifest <folder>(needswinget settings --enable LocalManifestFiles, admin) andpakko -vin a new terminal.- Open a PR to
microsoft/winget-pkgsadding the folder undermanifests/p/PavloRybchenko/PakkoCLI/<version>/(from thepakkoapp-ossaccount).
The manifest uses ArchiveBinariesDependOnPath: true — winget's Links symlink broke the
pre-AOT apphost; a symlink is possible now (T-F361, see docs/CLI.md, Distribution).
PAR2 test oracles (T-F275)
.\scripts\Get-Par2Oracles.ps1 # this machine's architecture
.\scripts\Get-Par2Oracles.ps1 -Architecture arm64
Downloads par2cmdline 1.4.0, par2cmdline-turbo 1.5.0 and MultiPar 1.3.3.6 (x64 only) into
artifacts/par2-oracles/<tool>/, each checked against a pinned SHA-256 (the digest GitHub
publishes for the release asset); a mismatch stops the script and leaves nothing behind. A tool
already present at the pinned hash is not downloaded again. They are GPL-2.0, so they are never
committed. What the tests use each for: docs/TESTING.md, "PAR2 Oracles".
Store listing text into a Partner Center export (T-F332)
py -3 scripts\Fill-StoreListing.py <export.csv> checks a listing CSV exported from Partner
Center (submission overview, "Export listing") against docs/store-listing/<locale>.txt: every
language column has a file and every file a column. With --write --output <filled.csv> it writes
the description, short description, features, search terms, "What's new" and the copyright line
from the files, copies the 300 x 300 icon's URL to every language, leaves every other row as
exported, then reads the result back and compares it. --add-locale <tag> adds a language the
export lacks (title and image rows of en-us); --keep-lists <tag> leaves one language's
features and search terms as exported. The import itself ("Import listings") is done by hand.
Continuous Integration (T-F122)
.github/workflows/build.yml builds both artifacts automatically — it is a separate, CI-only
path alongside everything above, not a replacement for local Deploy.ps1/Publish-Cli.ps1 use:
- On every push to
mainand on pull requests intomain: runsdotnet test --filter "Category!=Slow&Category!=VeryLarge"plus the C++Archiver.ShellExtension.Testssuite. A red suite blocks every downstream job. - On every push to
main(after tests pass): builds and signs the MSIX for bothx64andarm64via a new CI-only script,scripts/CI-Build-Msix.ps1(aDeploy.ps1sibling covering just the build+sign steps — no install, no version bump), and publishespakko.exefor both architectures via the existingPublish-Cli.ps1unchanged. Both are uploaded as workflow artifacts. - On a version tag push (
v*): additionally creates a real GitHub Release for that tag and attachespakko-win-x64.zip,pakko-win-arm64.zip, andSHA256SUMSvia theghCLI. This is now the only planned CLI-Release publication path — there is no separate manual publish step to remember. The MSIX is not attached to the public Release (it's still signed with a sideload-only self-signed cert — see below); it stays a workflow-run artifact, downloaded manually to hand to testers, same as today.
Signing identity: CI signs with the exact same local CN=Pakko Dev dev cert Deploy.ps1
uses (thumbprint D2EC5F2C451ED0EBE94B8168A68E5B813954CC75), exported once as a PFX and stored
as two repo secrets, PAKKO_DEV_CERT_PFX_BASE64 and PAKKO_DEV_CERT_PASSWORD. See
build.yml's header comment for the exact swap point once T-F10 (SignPath Foundation) issues a
real certificate — only those two secrets (and the thumbprint constant next to them) need to
change, nothing else in the workflow.
Pinned SDK and lock files (T-F364)
global.json pins the exact SDK (rollForward: disable) and every project has a committed
packages.lock.json; CI restores in locked mode (CI=true), so a changed package graph fails
there instead of building. Native AOT puts the SDK's own ILCompiler/ILLink version into the locks,
which is why the SDK is pinned as well (docs/DECISIONS.md, T-F364).
- Changing a package: edit the
.csproj, rundotnet restore, commit the changedpackages.lock.jsonfiles with it. - A new SDK patch (the canary's
canary-dotnetfails with "A newer SDK changes the lock files"): install that SDK, setglobal.json'sversionto it, rundotnet restore windows-archiver-wrapper.sln --force-evaluate, run the tests, commitglobal.jsonand the locks together. - A Visual Studio update removed the pinned SDK (every
dotnetcommand says the SDK was not found): install exactly that version from the .NET download page, or do the bump above. - Dependabot's NuGet PRs: a week with none may mean its updater failed (e.g. it lacks the pinned SDK), not that nothing is outdated; read its job log under Insights, Dependency graph, Dependabot, nuget "/".
SBOM per artifact (T-F365)
New-Sbom.ps1 -Artifact Msix|Cli -Architecture x64|arm64 -Version <v> -OutputPath <file> writes
the CycloneDX 1.6 SBOM of one shipped artifact. It restores that artifact's projects with
--locked-mode, runs the CycloneDX tool pinned in .config/dotnet-tools.json, removes what the
artifact does not ship (build tools, Windows ML, other architectures' runtime) and checks the
result (docs/DECISIONS.md, T-F365). CI runs it in the sbom job, which has no secrets; the
build jobs attest each SBOM against its artifact, and a release carries pakko-msix-<arch>.cdx.json
and pakko-win-<arch>.cdx.json.
- Check a downloaded artifact's SBOM attestation:
gh attestation verify pakko-win-x64.zip -R pakkoapp-oss/pakko --predicate-type https://cyclonedx.org/bom(the same without--predicate-typechecks the SLSA provenance). - Bumping the tool: Dependabot is not known to update
.config/dotnet-tools.json; change thecyclonedxversion there by hand, run the script for all four artifacts and diff the component lists before committing.
Canary build (T-F187)
A separate workflow, .github/workflows/canary.yml, runs daily (cron: "17 6 * * *") plus
workflow_dispatch on demand. Unlike build.yml above, it runs on a deliberately floating
toolchain (windows-latest, a floating .NET 10 SDK, T-F364) instead of the pinned
windows-2022/MSVC v143 combination build-msix relies on for stability. The goal is to catch
SDK/NuGet/MSVC toolset drift on the day it actually happens, rather than waiting for someone to
eventually bump build.yml's pin and discover the break then (the exact class of surprise T-F122
already produced once).
canary-dotnet runs the same dotnet test --filter "Category!=Slow&Category!=VeryLarge" command
as build.yml's own test job; canary-shellext compiles Archiver.ShellExtension.vcxproj
directly, x64 only — ARM64-on-windows-latest is already a known, unrelated, unfixed failure
(MSB8020, see build.yml's own header comment / T-F122), so it's deliberately excluded here to
avoid escalating a permanent condition as if it were new drift. No signing, no MSIX packaging —
that stays covered entirely by build.yml's own pinned path.
Escalation, not immediate alerting: every step in both build jobs is wrapped in
continue-on-error: true, so a transient network/runner blip is retried in-run (2 attempts) and
never reddens the workflow by itself. A genuine failure only shows as a ::warning:: log
annotation for its first two occurrences — no red X, no email. Only the 3rd consecutive
scheduled-day failure fails the run for real (GitHub's default failure email) and creates/updates
one de-duplicated tracking Issue titled CI Canary: build failing for 3+ consecutive days, which
auto-closes on the next green run. Once escalated, the run keeps failing daily (Issue updated via
comment) until the underlying build is actually fixed — it doesn't go quiet just because the Issue
already exists.
The 3-day streak is reconstructed from a dedicated sentinel job, canary-failed-day (runs only
when a day fails; its own recorded conclusion across past runs, queried via the Actions REST API,
is the one bit of state this workflow persists) — not from the workflow run's own conclusion,
which is deliberately masked on days 1-2 and so cannot double as the failure signal. canary-status
computes today's result and the streak but always exits 0 itself; canary-alert is the only job
that can actually fail or touch the tracking Issue — check its log first when investigating an
escalation. See docs/DECISIONS.md's T-F187 entry for why the more obvious single-job approach
doesn't work.
Note: GitHub auto-disables scheduled workflows after 60 days of repository inactivity — if the canary goes silent, check whether it's actually still enabled (Actions tab) before reading silence as "everything's fine."
Store-submission builds (build-store-msix, T-F129)
A separate, workflow_dispatch-only job in the same build.yml, for packages actually uploaded
to Partner Center — kept out of the automatic push/tag pipeline since a real Store submission is
a deliberate, occasional act, not something that should fire on every commit.
Why a separate job at all: dotnet publish's MSIX packaging rewrites the built package's
Identity/Publisher to match whatever certificate signs it. Every package built with the
tester-facing CN=Pakko Dev cert above therefore ships with Publisher="CN=Pakko Dev" — not
Pakko's reserved Partner Center identity (CN=EF3EC84C-8287-4FC3-BB4F-FCCEBA116BCE) — and Partner
Center rejects it outright. Confirmed against a real submission attempt, 2026-08-01: both
Invalid package publisher name and the derived Invalid package family name errors clear once
signed with the correct-Subject cert. See docs/DECISIONS.md's T-F129 entry for the full trail.
Run it: Actions tab → Build → Run workflow, against the tag/ref you're about to submit. Two
architecture legs (x64, arm64) build+sign on windows-2022 (build-store-msix, same
ARM64-v143-toolset reasoning as build-msix) as single-architecture .msixbundles, then a third
job (bundle-store-msix) unbundles both, merges them into one real multi-architecture
.msixbundle, and re-signs it — download that single pakko-store-msixbundle artifact from the
run's Summary page and upload it to Partner Center's Packages step. Not attached to the public
GitHub Release: a differently-signed package with a different Publisher would be confusing for
end users to stumble onto, and Microsoft re-signs for real distribution anyway once certification
passes, so the local signature here only needs to make packaging/upload succeed.
Why the merge step exists: a .msixbundle's own Identity carries no ProcessorArchitecture
attribute — only the packages inside it do. Two separately-built single-architecture bundles
(one containing only the x64 .msix, one containing only the arm64 .msix) therefore compute to
the exact same full package name (..._Neutral_...) despite having different contents byte-for-
byte, and Partner Center rejects the upload outright: "All .msix and .appx packages ... must be
uniquely identified by their full names." Confirmed against a real submission, 2026-08-01. The fix
is makeappx unbundle on each, makeappx bundle /bv <version> on the combined inner .msix
files, then signtool sign again (bundling strips the original per-bundle signature) — this is
also Microsoft's own documented shape for a multi-architecture Store app, not a workaround.
Local machines are not the reference for this build. scripts/Setup-StoreCert.ps1 exists so a
developer can build and smoke-test a Store-identity package locally (useful for verifying the
fix before relying on CI, or if the cert ever needs rotating), but the artifact actually uploaded
to Partner Center should always come from this CI job, not a local Deploy.ps1 run — confirmed
2026-08-01 that a real dev machine may simply be missing the ARM64 v143 C++ toolset MSVC needs
(a genuine, not-uncommon local environment gap; windows-2022 runners have it out of the box).
Signing identity: a second self-signed cert, Subject CN=EF3EC84C-8287-4FC3-BB4F-FCCEBA116BCE
(thumbprint CD8DE1646CBF5A52046001FB32B0B60B797E7497), created via Setup-StoreCert.ps1 and
exported once as a PFX into two more repo secrets, PAKKO_STORE_CERT_PFX_BASE64 and
PAKKO_STORE_CERT_PASSWORD — same pattern as the dev cert pair, just a different Subject/secret
names. This is also a self-signed cert, not a "real" purchased one: Partner Center accepts
self-signed-cert-signed uploads routinely, since Microsoft re-signs on successful certification —
the Subject matching the reserved Publisher is what actually matters here, not the cert's issuer.
SonarCloud static analysis (T-F135)
The test job runs a SonarCloud scan wrapped around the existing dotnet test invocation (JDK
setup → dotnet-sonarscanner begin → dotnet test → dotnet-sonarscanner end), on every push to
main/tags and on same-repo pull requests. Free tier — SonarCloud analysis is free for genuinely
public repositories, which this one is. Fork-originated PRs skip the Sonar steps entirely (no
SONAR_TOKEN secret available to them) rather than failing.
Real organization/project keys (confirmed via SonarCloud's public API, 2026-07-27) — note the
-1 suffix on the org key, since the plain pakkoapp-oss key was already taken on sonarcloud.io
when the project was created, so it does NOT match the GitHub org/account name:
- Organization key:
pakkoapp-oss-1 - Project key:
pakkoapp-oss-1_pakko
Both are wired into build.yml's SONAR_PROJECT_KEY/SONAR_ORGANIZATION env vars and into
README.md's badge — don't reuse pakkoapp-oss/pakkoapp-oss_pakko (an earlier guessed
placeholder that turned out wrong) anywhere new.
Coverage: the org's default "Sonar way" quality gate requires new code coverage ≥ 80% — with
no coverage data submitted, that condition reads as "no data" and fails the gate outright. The
test job's dotnet test step therefore also passes --collect:"XPlat Code Coverage" (using the
coverlet.collector package every test project already references — no new dependency), and the
dotnet-sonarscanner begin step passes
/d:sonar.cs.cobertura.reportsPaths="**/coverage.cobertura.xml" to pick up the resulting
per-test-project Cobertura reports. This makes the condition evaluate against a real number — it
does not guarantee the actual number clears 80%; if the real coverage on new code comes in lower,
the gate can still fail, and that's a legitimate signal to act on (write more tests), not a CI bug.
One-time setup (already done 2026-07-27 — kept here for reference / re-setup):
- Sign in to sonarcloud.io, create the org, import the
pakkoapp-oss/pakkorepository as a new project (done manually, without GitHub-App binding, since the importing account had no Admin on the repo — seeDECISIONS.md's T-F135 entry). - Generate an analysis token (My Account → Security) and add it as a repo secret named
SONAR_TOKEN(Settings → Secrets and variables → Actions) — done. - Analysis Method should already be "CI-based" — the manual/"Other CI"
dotnet-sonarscannersetup snippet SonarCloud generated (see below) only exists for CI-based projects; Automatic Analysis needs the GitHub App installed with repo Admin, which was never available for this import (step 1). Not independently eyeballed on the Administration → Analysis Method page, but this project structurally couldn't have ended up in Automatic mode. - Once a real scan has run, confirm the quality-gate results against the actual SonarCloud project dashboard (https://sonarcloud.io/project/overview?id=pakkoapp-oss-1_pakko), not just a green GitHub Actions run — same rule this project already applies to every other CI change (T-F122/T-F125 precedent).
The C++ Archiver.ShellExtension/Archiver.ShellExtension.Tests projects are not covered by
this scan (SonarCloud's C/C++ analysis needs a separate build-wrapper tool and has its own
licensing terms — not yet evaluated for the free tier). Only the .NET projects are analyzed for
now.
Measuring start-up (T-F346)
.\scripts\Measure-Startup.ps1 # all scenarios, 8 launches each
.\scripts\Measure-Startup.ps1 -Runs 12 -Scenario App,Open
Times the installed package (run Deploy.ps1 first): App start to visible window with its
memory, threads and CPU; Explorer's "Open" (Archiver.Shell.exe --open-ui --browse) to visible
App window; Explorer's "Extract here" on a generated 200-file ZIP (files written, Shell exit);
pakko --help and pakko l; the operation window on a generated 4000-file ZIP and on a conflict
prompt (Shell start to visible Archiver.OperationUi window). Prints median, minimum and maximum; the first launch of each
scenario is dropped. There is no pass/fail threshold — the numbers depend on the machine, so
compare a run before a change with a run after it on the same machine. It opens and closes Pakko
windows on the desktop while it runs.
Agent hook (T-F370)
.claude/settings.json (tracked) runs hooks/Test-AgentBashCommand.ps1 before every Bash-tool
call Claude Code makes in this repo. It blocks bare python/python3 (use py -3 script.py) and
dotnet with a /p: switch (Git Bash rewrites /p: into a path; use the PowerShell tool), at
command position only, so quoted text and heredoc bodies pass. Exit 2 blocks; a payload that is
not JSON exits 1 and does not block. Tests: AgentBashHookTests.
Notes
PakkoDev.cerandPakkoStore.cerare gitignored — never commit certificates to the repository.- All paths in the scripts are resolved relative to
$PSScriptRoot, so they work regardless of your current working directory. - The self-signed certificate is for local development only. Store/release builds require a trusted EV certificate (see T-F10).
Permission repair for archives opened by older Pakko versions (T-F233)
Pakko versions before fix phase 4 (2026-09-25) changed the permissions of tar-family archives (.tar, .gz, .7z, .rar, ...) they opened: an entry for the sandbox was added, and the entries the file inherited from its folder were replaced. Two scripts, for users who ask (Windows PowerShell 5.1 is enough):
.\scripts\Find-PakkoSandboxAce.ps1 # read-only: lists affected files
.\scripts\Find-PakkoSandboxAce.ps1 -Path D:\Shared -AllFiles
.\scripts\Repair-PakkoSandboxAce.ps1 -Path '<file>' -WhatIf
.\scripts\Repair-PakkoSandboxAce.ps1 -Path '<file>' # backs up with icacls /save first
The repair removes only Pakko's own entries and re-inherits from the file's folder; entries set
for anyone else are kept (it is not icacls /reset). See docs/DECISIONS.md's fix-phase-4
entry.
Invoke-Smoke.ps1 — the on-device checks (T-F378)
Runs rows of docs/SMOKE.md against the installed package. The catalogue says what each row does
and expects; this script is a filter over it.
.\scripts\Invoke-Smoke.ps1 # the quick tier: 25 rows, about 2.5 minutes
.\scripts\Invoke-Smoke.ps1 -Tier release # quick and release rows: about 26 minutes
.\scripts\Invoke-Smoke.ps1 -Tier release -Only CLI-0* # the console rows: about a minute
.\scripts\Invoke-Smoke.ps1 -Tier release -Only PAR-* # the PAR2 rows: about a minute
.\scripts\Invoke-Smoke.ps1 -Only PAR-*,CLI-004 # some rows of the chosen tier
.\scripts\Invoke-Smoke.ps1 -Tier store -List # show the rows, run nothing
# Before a tag: the release tier with the update rows (<run> = the build run of the release commit)
gh run download <run> -n pakko-msix-x64 -D $env:TEMP\new; gh release download <last tag> -D $env:TEMP\last
.\scripts\Invoke-Smoke.ps1 -Tier release -PackageLifecycle -ReleaseFiles $env:TEMP\new -PreviousRelease $env:TEMP\last
# After it: everything, on the release's own files
gh release download <tag> -D $env:TEMP\new
.\scripts\Invoke-Smoke.ps1 -Tier store -PackageLifecycle -Tag <tag> -ReleaseFiles $env:TEMP\new -PreviousRelease $env:TEMP\last
- Install the build first with
Deploy.ps1; the script tests what is installed.-PakkoExe <path>tests anotherpakko.exe(the one from a release zip) in the CLI, PAR2 and speed rows. - A row a script can run has a step in
scripts/smoke/<Surface>.ps1, registered withAdd-SmokeStep '<ID>' { ... };Common.ps1holds the helpers, andtools/the scripts a step starts as a process of their own (Send-CtrlC.ps1: Ctrl+C goes to a console, so the sender joins the target's). A step throws to fail (Assert-Smoke), callsSkip-Smoke '<reason>'when it cannot run here, and may return a note. Rows without a step are printed asmanualwith their action and expectation. - Fixtures go into a new folder under
%TEMP%and are removed at the end; the folder is kept when a row fails. The result table is printed and written to%TEMP%\pakko-smoke-report.md; paste it into the release record. - Windows open and close on the desktop during a run, and the speed rows compare times: leave the
machine alone meanwhile. The PAR2 rows need
Get-Par2Oracles.ps1to have been run once, and SHL-008 writes a ZIP with the antivirus test string into the run's folder: the antivirus may report it. REL-001 isdotnet test --filter "Category=Slow"on the checked-out commit and takes about twenty minutes; REL-002 reads the canary run on that commit and is skipped when there is none. - The
POLrows andPKG-005need an administrator: the first of them raises one elevation prompt forscripts/smoke/elevated/PolicyHost.ps1, which sets Pakko's machine policies and the Windows FIPS flag for one row at a time, runsappcert.exe, and puts the registry back. Decline the prompt and those rows are skipped.-Holdkeeps each row's policy set until a file namedgois created in the run's folder: the time to look at the App and the Explorer menu under it (POL-009, POL-010). PKG-006..009remove and replace the installed package, so they are skipped without-PackageLifecycle. With it they run last, and the run ends with the package it began with (its file must still be undersrc/Archiver.App/AppPackages); the package's data is lost, as on everyDeploy.ps1.PKG-006..008update from one bundle to another and also need-PreviousRelease <folder>(gh release download <last tag> -D <folder>) and-ReleaseFiles <folder>: before the tag, the bundle CI built on the release commit (gh run download <run> -n pakko-msix-x64 -D <folder>), after it the release's own files. With-Hold,PKG-009waits with the package removed (PKG-010).- The store rows (
CLI-017,REL-003..007,STO-001..003) check the release's files against what-Tagstands for: its version, its commit, itsCHANGELOG.md. A whole-Tier storerun refuses to start without-Tag,-ReleaseFiles,-PreviousReleaseand-PackageLifecycle. They needgh(signed in) and the network;REL-007installs the CLI with winget and removes it, and is skipped when the CLI is already installed that way.STO-001andSTO-002read the Store bundle of a dispatch run on the tag and never start one. - Shell and App windows speak the user's language, so steps match numbers, names and hashes in their text, never words.
- Adding a check: add the row to
docs/SMOKE.md(the next free ID of its surface), then its step.SmokeCatalogueTestsfails when a row markedscriptoruachas no step or a step has no row. - A step that starts
pakkogives it a time limit (Invoke-Tool -TimeoutSeconds, 15 minutes unless set): a hang fails the row instead of the run.