Stages

  1. Validate — product name, version, components; abort on errors.
  2. Provision self-update — StampUpdateSettings writes update-settings.json (from AppUpdatesURL/AppUpdateMode) as a payload file, when a feed URL is authored.
  3. Stage payload — copy component files (or scan the source) into payload/.
  4. Write runtime script + install-config.json beside it.
  5. Build the host EXE — behind IInstallerHostBuilder (real impl runs dotnet publish; tests inject a stub). Uncompressed single-file, so the payload can be appended after the PE.
  6. Embed the icon (Win32 UpdateResource).
  7. Compress payload → payload.zip (optionally solid/deduplicated — see below).
  8. Embed payload into the EXE (appended with a marker footer).
  9. Sign (optional) and/or package MSIX (Track C).

The solid (content-addressed) payload

With solid compression, identical file contents are stored once under _blobs/<sha256> and a _payload-manifest.json maps each path to its blob. This is the same content-addressed store a delta update diffs against — the hard part of delta updates already exists in the build output.

// _payload-manifest.json
{ "solid": true, "entries": [
  { "path": "MyApp.exe", "blob": "A1B2…", "size": 20480 },
  { "path": "docs/readme.txt", "blob": "C3D4…", "size": 512 }
] }

Testable seams

  • Host production is behind IInstallerHostBuilder, so the pipeline (staging, compression, embedding, cleanup) is exercised in tests without a multi-minute publish.
  • PublishTimeout (default 10 min), ReadyToRun (off by default — it costs far more build time than it returns for a run-once installer), and KeepIntermediates are configurable.
  • Signing that silently did nothing, and compression options that were collected then ignored, are wired for real — a configured certificate really signs, and a failure is an error.

Headless build

Beep.Installer.exe /BUILD=MyApp.bsetup /OUT=out            # Track A Setup.exe
Beep.Installer.exe /BUILD=MyApp.bsetup /FORMAT=msix        # Track C .msix
Beep.Installer.exe /VALIDATE=MyApp.bsetup                  # gate in CI
Beep.Installer.exe /BUILD=MyApp.bsetup /REQUIRESIGNED      # refuse unsigned