# Dual-ship runbook — NSIS + full MSI (side-by-side) **Status:** operator validation while NSIS remains the **primary** full-product Windows download and the elevated MSI (`Product-Full.wxs` + `build-full-msi.ps1`) is **unsigned** and not yet CA-complete. **Do not** thin or deprecate NSIS until the **thin-NSIS exit criteria checklist** (below) is fully checked — clean-VM gates, live Authenticode, silent/fleet proof, and site cutover (see [COMMERCIAL-INSTALLER-BACKLOG.md](../../../docs/COMMERCIAL-INSTALLER-BACKLOG.md), [CI-SIGN-NOTARY.md](CI-SIGN-NOTARY.md)). **Related:** [Product-Full.wxs](Product-Full.wxs) · [SILENT_PROPS.md](SILENT_PROPS.md) · [VM-DUAL-SHIP-CHECKLIST.md](VM-DUAL-SHIP-CHECKLIST.md) (printable A/B/C) · [`dual-ship-smoke.ps1`](../scripts/dual-ship-smoke.ps1) (offline checklist runner + optional local MSI/NSIS hash) · [../installer-windows.nsi](../installer-windows.nsi) · [PACKAGING.md](../../../docs/PACKAGING.md) · site [DOWNLOADS-MANIFEST.md](../../../site/printit.verae.ai/DOWNLOADS-MANIFEST.md) (0.3.6 TBD rows) · [`fill-manifest-sha256.ps1`](../scripts/fill-manifest-sha256.ps1) (hash built MSI/NSIS → manifest row; no upload). --- ## Why dual-ship | Channel | Role today | Gap vs commercial | |---------|------------|-------------------| | **NSIS** `VeraePrintStamp-windows-*-setup.exe` | Primary GUI install (printers, helper task, key wizard, ARP) | Not MSI; Authenticode still open | | **Full MSI** `VeraePrintStamp-windows-*-.msi` | Fleet path authoring (`msiexec /qn` props) | Printer deferred CAs **commented**; unsigned; not Intune-proven | | **Tray MSI** `VeraeTray-*-peruser.msi` | Optional non-elevated presence only | Not full product; distinct UpgradeCode | Dual-ship means **both** NSIS and full MSI may be built from the same `HelperVersion` tree and validated on separate clean VMs **without** uninstalling the other channel’s install mid-test (use two VMs or one channel per VM). --- ## Version rule - Source of truth: `internal/job.HelperVersion` (currently **0.3.6**). - NSIS, `build-full-msi.ps1`, and site product strings must stamp that value (or explicit `VERSION=`). - Site `DOWNLOADS-MANIFEST.txt` may still list **last-published** artifact filenames (historical checksums). Do not rewrite checksum lines until new binaries are published. --- ## Build both artifacts (same tree) From repo root on a **Windows** host with Go + NSIS + WiX v3 (`candle`/`light`) on PATH: ```powershell # 1) Native binaries + NSIS setup.exe powershell -NoProfile -ExecutionPolicy Bypass -File platform\windows\build-windows.ps1 # 2) Elevated full MSI (StageFull → candle/light) powershell -NoProfile -ExecutionPolicy Bypass -File platform\windows\scripts\build-full-msi.ps1 ``` Expected outputs (names vary by arch): | Artifact | Typical path | |----------|----------------| | NSIS setup | `dist/` or build output `VeraePrintStamp-windows-amd64--setup.exe` | | Full MSI | `platform/windows/wix/out/VeraePrintStamp-windows-amd64-.msi` | Local SHA-256 for site manifest rows (**no upload**): ```powershell powershell -NoProfile -ExecutionPolicy Bypass -File platform\windows\scripts\fill-manifest-sha256.ps1 ` -Path .\dist\VeraePrintStamp-windows-amd64--setup.exe, .\platform\windows\wix\out\VeraePrintStamp-windows-amd64-.msi ``` Paste printed digests over **TBD** in [DOWNLOADS-MANIFEST.txt](../../../site/printit.verae.ai/DOWNLOADS-MANIFEST.txt) only after the blob is on the CDN. See [DOWNLOADS-MANIFEST.md](../../../site/printit.verae.ai/DOWNLOADS-MANIFEST.md). If WiX is missing, `build-full-msi.ps1` **exits 1** with an install tip — dual-ship **fails closed** for the MSI leg (do not claim MSI ready). Cross-host NSIS-only: `platform/windows/build-installer.sh` (needs prebuilt Windows binaries). ### Manifest checksum rows (no upload) After artifacts exist locally (and only after they are on the CDN when rewriting site TBD): ```powershell powershell -NoProfile -ExecutionPolicy Bypass -File platform\windows\scripts\fill-manifest-sha256.ps1 ` -Path .\dist\VeraePrintStamp-windows-amd64--setup.exe, .\platform\windows\wix\out\VeraePrintStamp-windows-amd64-.msi ``` Prints `DOWNLOADS-MANIFEST.txt` lines (` `). `-Format md` prints Markdown table rows for [DOWNLOADS-MANIFEST.md](../../../site/printit.verae.ai/DOWNLOADS-MANIFEST.md). **Does not upload** or edit the site files. --- ## Clean-VM matrix (minimum) **Printable tick sheet:** [VM-DUAL-SHIP-CHECKLIST.md](VM-DUAL-SHIP-CHECKLIST.md) (shared pre-checks + VM A/B/C steps + side-by-side table). **Offline checklist runner** (prints the same A/B/C steps; optional local artifact existence + hash via fill-manifest — does **not** install or drive a VM): ```powershell powershell -NoProfile -ExecutionPolicy Bypass -File platform\windows\scripts\dual-ship-smoke.ps1 powershell -NoProfile -ExecutionPolicy Bypass -File platform\windows\scripts\dual-ship-smoke.ps1 -VerifyArtifacts ``` Use two fresh Windows 10/11 **x64** VMs (or reset snapshots). Do **not** mix channels on one machine until Upgrade/ARP coexistence is explicitly tested. | VM | Install | Goal | |----|---------|------| | **A — NSIS primary** | setup.exe (GUI) | Baseline product parity | | **B — MSI files-only** | MSI with `VERAE_SKIP_PRINTERS=1` | File + ARP path without deferred printer CA | | **C — MSI printers** (when CAs uncommented) | MSI default / `VERAE_SKIP_PRINTERS=0` | Printer + helper parity with NSIS | Until printer CAs are scheduled and tested, treat **VM C as blocked**; dual-ship gate for “MSI can replace NSIS” stays **open**. ### Shared pre-checks 1. Snapshot or sysprep-clean OS; admin account available. 2. Note `HelperVersion` on both artifacts (NSIS version resource / MSI `ProductVersion`). 3. No prior Verae Print Stamp under Program Files or ARP. 4. Spooler running (`Get-Service Spooler`). --- ## VM A — NSIS validation ```text 1. Run setup.exe elevated; complete key wizard or skip per test plan. 2. Confirm Program Files\Verae\PrintStamp\bin\verae-print.exe + helper. 3. Confirm printers (IPP and/or Local Port) and scheduled task "Verae Print Helper". 4. Print a one-page PDF → stamp path succeeds (or hold id only — never treat jobId as evidence). 5. Start Menu: Setup signing key, Start print helper, Collect diagnostics. 6. Uninstall via ARP; confirm printers/task removed; reboot optional. ``` Record: install log path, printer names, task principal, any SmartScreen blocks (unsigned expected). --- ## VM B — MSI files-only (safe today) Printer deferred CAs are **commented** in `Product-Full.wxs`. Validate payload + silent props without expecting queues: ```bat msiexec /i VeraePrintStamp-windows-amd64-0.3.6.msi /qn /norestart ^ VERAE_SKIP_KEY_SETUP=1 VERAE_SKIP_PRINTERS=1 VERAE_CHANNEL=zapier ``` Interactive UI install is fine for smoke; `/qn` proves fleet syntax ([SILENT_PROPS.md](SILENT_PROPS.md)). ```text 1. msiexec as above (or UI). 2. Confirm InstallDir files: bin\, scripts\ (incl. install-printer.ps1), LICENSE, README. 3. Confirm ARP entry (Publisher Verae, DisplayVersion = HelperVersion). 4. Confirm Start Menu shortcuts resolve to helper / diag (may fail if helper missing — fail the build). 5. Optional: elevated manual run of scripts\install-printer.ps1 -InstallDir "…" to prove script parity outside MSI CA. 6. msiexec /x {ProductCode} /qn — files and Start Menu removed; no orphan HKLM Software\Verae\PrintStamp if components clean. ``` **Pass criteria (files-only):** light-built MSI installs/uninstalls cleanly; version matches; no reboot required; props accepted (even if not yet honored by CAs). **Fail:** missing StageFull binaries at light time; wrong UpgradeCode collision with tray MSI; elevation refused unexpectedly. --- ## VM C — MSI printer CA (still left) When uncommenting CAs in `Product-Full.wxs`: 1. Schedule stubs (see commented `InstallExecuteSequence` in Product-Full) after `InstallFiles`. 2. Conditions: `NOT Installed` and `VERAE_SKIP_PRINTERS<>"1"` for install; `REMOVE~="ALL"` for uninstall. 3. Deferred elevated: `install-printer.ps1 -InstallDir "[INSTALLFOLDER]"`; rollback/uninstall → `uninstall-printer.ps1`. 4. Re-run dual-ship **A vs C** parity: same printers, same helper task name, same IPP loopback `127.0.0.1:8631`. 5. Silent: `VERAE_SKIP_PRINTERS=1` must skip CA; default must install printers. Do **not** mark dual-ship complete until VM C matches VM A on a clean image. --- ## Side-by-side comparison checklist | Check | NSIS (A) | MSI files (B) | MSI+CA (C) | |-------|----------|---------------|------------| | Version = HelperVersion | ☐ | ☐ | ☐ | | Program Files layout | ☐ | ☐ | ☐ | | Start Menu shortcuts | ☐ | ☐ | ☐ | | ARP uninstall clean | ☐ | ☐ | ☐ | | IPP + Local Port printers | ☐ | N/A (skip) | ☐ | | Scheduled task helper | ☐ | N/A (skip) | ☐ | | Key wizard / first-run key | ☐ | skip prop | ☐ / first-run | | `msiexec /qn` | N/A | ☐ | ☐ | | Authenticode (signtool) | ☐ still left | ☐ still left | ☐ still left | --- ## Coexistence / upgrade notes - Full MSI **UpgradeCode** `F8B2A4C6-…` must stay distinct from tray `A7C3E91B-…`. - NSIS uses its own `UNINST_KEY`; long-term migrate ARP to MSI only after dual-ship pass — avoid dual ARP entries for the “same” product on one machine in production. - Do not install tray per-user MSI and full MSI as substitutes for each other; tray is presence-only. - MajorUpgrade on full MSI: same-version upgrades allowed (`AllowSameVersionUpgrades=yes`); test repair after dual-ship CA enablement. --- ## Exit: when to thin NSIS (MSI becomes primary) Thin or demote NSIS to “key-wizard offline only” (or drop from primary download) only when **every** box below is checked. Partial passes do **not** promote MSI. Until then: **ship NSIS as primary**; MSI is dual-ship candidate only. ### Thin-NSIS exit criteria checklist #### A — Product parity (clean VMs) - [ ] **VM A** NSIS baseline signed off (files, printers, helper task, key path, ARP uninstall). - [ ] **VM B** MSI files-only (`VERAE_SKIP_PRINTERS=1`) install/uninstall clean; version = `HelperVersion`. - [ ] **VM C** MSI with printer/helper deferred CAs **uncommented, scheduled, and tested** — parity with VM A: - [ ] Same Program Files layout (`bin\`, `scripts\`, docs). - [ ] IPP loopback `127.0.0.1:8631` and/or Local Port printers match NSIS names/behavior. - [ ] Scheduled task **Verae Print Helper** (same principal / trigger class). - [ ] Start Menu shortcuts resolve; one-page PDF stamp path works (receipt/CID evidence — never jobId alone). - [ ] `VERAE_SKIP_PRINTERS=1` skips CAs; default installs printers. - [ ] Uninstall (`msiexec /x`) removes printers, task, files, Start Menu; no orphan HKLM `Software\Verae\PrintStamp`. - [ ] Side-by-side comparison table (this doc) fully checked for columns A and C. - [ ] Repair / same-version upgrade (`AllowSameVersionUpgrades`) smoke on MSI after CA enablement. #### B — Trust / signing - [ ] Live **Authenticode** on `verae-print.exe`, `verae-printhelper.exe`, and full `.msi` (CI or release host). - [ ] CI sign step enabled only with org secrets — **never** PFX/passwords in git (names in [CI-SIGN-NOTARY.md](CI-SIGN-NOTARY.md) / `windows-msi.yml` comments). - [ ] SmartScreen: signed install path validated on a clean VM (warn-only unsigned is **not** enough to promote). - [ ] Timestamp counter-signature present (`WINDOWS_SIGN_TIMESTAMP_URL` / equivalent TSA). #### C — Fleet / silent contract - [ ] Public silent props in [SILENT_PROPS.md](SILENT_PROPS.md) **honored by scheduled CAs** (not docs-only): - [ ] `VERAE_SKIP_PRINTERS`, `VERAE_SKIP_KEY_SETUP`, channel / API URL props as authored. - [ ] `msiexec /qn /norestart` one-liner documented with **real** exit codes from clean-VM runs. - [ ] Intune and/or SCCM sample deployment recorded (success + known failure codes); no reboot required for default path unless documented. - [ ] UpgradeCode stays distinct from tray MSI; no dual “same product” ARP entries in production guidance. #### D — Channel / site cutover - [ ] Site **primary** Windows download points at signed full MSI (`VeraePrintStamp-windows-*-.msi`). - [ ] NSIS `setup.exe` linked as **alternate** only (or removed from primary CTA) — not the default button. - [ ] `DOWNLOADS-MANIFEST.txt` checksums refreshed for the promoted MSI (and remaining NSIS if kept) — use [`fill-manifest-sha256.ps1`](../scripts/fill-manifest-sha256.ps1) after upload (script does **not** upload). - [ ] Release notes / PACKAGING state MSI as primary full-product Windows install. - [ ] OPEN-ISSUES Issue 1 **Still left** no longer lists dual-ship gate / thin-NSIS / “NSIS still primary”. #### E — Explicit non-goals until checked Do **not** thin NSIS if any of these remain true: - Printer CAs still commented or untested (VM C blocked). - CI only uploads **unsigned** MSI (`VeraePrintStamp-windows-msi-unsigned`). - Operators still need NSIS for key wizard with no MSI-equivalent first-run path. - Tray per-user MSI is being mistaken for full-product MSI. **When all of A–D are checked:** demote NSIS (thin installer or archive-only), keep dual-ship docs as historical validation, and treat full MSI as the commercial Windows path. --- ## CI artifact (unsigned full MSI) GitHub Actions workflow [`.github/workflows/windows-msi.yml`](../../../.github/workflows/windows-msi.yml) builds the elevated full MSI on `windows-latest` when WiX v3 (`candle`/`light`) is available: 1. Checkout + Setup Go (`go-version-file: go.mod`). 2. Install WiX Toolset v3 (Chocolatey) and put `candle`/`light` on `PATH`. 3. Run `platform/windows/scripts/build-full-msi.ps1 -Arch amd64` (StageFull → candle/light). 4. Upload **unsigned** artifact `VeraePrintStamp-windows-msi-unsigned` (`platform/windows/wix/out/VeraePrintStamp-windows-*.msi`). | Fact | Detail | |------|--------| | Trigger | `workflow_dispatch`; push/PR on Windows packaging + Go paths | | Signed? | **No** — artifact is dual-ship candidate only; SmartScreen will warn | | Use for | Clean-VM B (files-only) smoke; CI proof that authoring lights | | Not for | Production download, Intune primary, or “MSI replaces NSIS” claims | **Future sign step (names only — not enabled):** org secrets/vars documented in the workflow comments and [CI-SIGN-NOTARY.md](CI-SIGN-NOTARY.md): | Name | Kind | |------|------| | `WINDOWS_CERT_PFX_BASE64` | secret | | `WINDOWS_CERT_PFX_PASSWORD` | secret | | `WINDOWS_CERT_THUMBPRINT` | secret (optional) | | `WINDOWS_SIGN_TIMESTAMP_URL` | var | | `WINDOWS_TRUSTED_SIGNING_*` | secret (optional Azure path) | Do **not** put PFX bytes or passwords in git. Do not upload the PFX as a workflow artifact. Until sign is live, treat CI MSI like a local `build-full-msi.ps1` output for dual-ship gates only. --- ## Quick links | Doc | Path | |-----|------| | Full MSI authoring | [Product-Full.wxs](Product-Full.wxs) | | Stage/light driver | [../scripts/build-full-msi.ps1](../scripts/build-full-msi.ps1) | | CI unsigned MSI workflow | [../../../.github/workflows/windows-msi.yml](../../../.github/workflows/windows-msi.yml) | | Silent properties | [SILENT_PROPS.md](SILENT_PROPS.md) | | Printable VM A/B/C checklist | [VM-DUAL-SHIP-CHECKLIST.md](VM-DUAL-SHIP-CHECKLIST.md) | | Offline dual-ship smoke (print + optional hash) | [../scripts/dual-ship-smoke.ps1](../scripts/dual-ship-smoke.ps1) | | Sign/notary draft | [CI-SIGN-NOTARY.md](CI-SIGN-NOTARY.md) | | Menubar CI notary example | [../../macos/menubar/ci-notarize.yml.example](../../macos/menubar/ci-notarize.yml.example) | | Site 0.3.6 placeholders | [DOWNLOADS-MANIFEST.md](../../../site/printit.verae.ai/DOWNLOADS-MANIFEST.md) · [DOWNLOADS-MANIFEST.txt](../../../site/printit.verae.ai/DOWNLOADS-MANIFEST.txt) | | Local SHA-256 → manifest rows | [../scripts/fill-manifest-sha256.ps1](../scripts/fill-manifest-sha256.ps1) (hash only; no upload) | | Commercial backlog | [docs/COMMERCIAL-INSTALLER-BACKLOG.md](../../../docs/COMMERCIAL-INSTALLER-BACKLOG.md) |