Source: ViewEdit

Windows Auto-Updates

Goal

Windows users receive newer builds without manually hunting installers.

Why

Automatic updates.

Pick a primary path

Path When to use

Microsoft Store / MSIX

You can meet Store policies; want OS-managed updates

winget-upgradable installer

Classic desktop app; you already ship MSI/EXE; want discovery in winget

In-app updater (download-page installs)

Need fast channels, multiple tracks, or non-Store distribution with guided UX

Combining winget (install/discover) with in-app updates (speed) is common—if About stamps the install channel and in-app apply skips Store/winget-managed installs (deep-link winget upgrade or open the Store listing instead).

Signed update feed

For download-page / in-app updates:

  • Host a stable feed URL on the marketing site, e.g. https://example.com/updates/latest.json (or Sparkle-style latest.yml / appcast).

  • Pin that stable URL in the binary (enterprise may override via policy).

  • When CI cuts a tag, also attach a versioned feed copy as a GitHub Release asset so history stays verifiable.

  • The running app fetches only the stable URL for "is there a newer build?"

Minimum feed fields: version, channel, per-OS/arch artifact URL, signature/hash, min OS, release notes, mandatory flag.

In-app updater checklist

  1. Publish the signed feed from CI when you cut a release (stable URL + versioned release asset)

  2. Code-sign installers and verify signature/hash before apply

  3. Download to a staging directory—never unzip over a running install folder

  4. Prefer an in-place setup EXE (Inno Setup / NSIS / similar) or MSIX over zip-replace

  5. Use a tiny one-shot updater only at apply time—not a long-lived separate launcher process

  6. Apply order: download + verify → write "update pending" → quit the app completely → one-shot waits until PID is gone → run installer / replace → refresh Start Menu shortcuts → relaunch

  7. Keep last-known-good when feasible

  8. Honor a disable switch for enterprise (UpdatePolicy=Disabled or env var)

  9. Show status on the About dialog (last check, available version, channel, disabled-by-policy)

Apply sequence (download-page installs)

Shut the app down first. Never start replacing files while the main process (or tray helper) is still running.

  1. App finishes download and verifies signature/hash

  2. App writes an "update pending" flag (and paths to the staged package)

  3. App exits completely (process tree, including tray helpers)

  4. One-shot helper waits until that PID is gone

  5. Helper runs the in-place installer (preferred) or performs the file swap

  6. Helper refreshes Start Menu / desktop shortcuts via the installer, not by hand-editing a live .lnk

  7. Helper relaunches the app (optional --updated to show What’s new)

Check / download / prompt may run inside the still-running app. Apply work starts only after exit.

Channel detection (do not self-replace)

Detect how this binary was installed. If the channel is Store, winget, or another package manager:

  • Do not run the self-replace / setup-EXE path

  • Offer Check for updates that deep-links the native upgrade (winget upgrade Your.Package, Store product page, or "Updates arrive via …")

  • Still show update status on About so support can see the channel

winget path

  1. Ship a stable, versioned installer URL per release

  2. Submit or PR manifests to microsoft/winget-pkgs (or your private source)

  3. Verify winget upgrade Your.Package works after publish

Pitfalls

  • Updating while files are locked — quit first; schedule swap / installer only after PID exit

  • Unzip over a running folder — locked binaries and broken shortcuts; use an in-place installer instead

  • Rewriting Start Menu .lnk while Explorer holds it — classic failure mode (Start Menu icon vanishes until reboot). Let the installer refresh shortcuts after the app has exited; avoid ad-hoc shortcut rewrites from a still-running process

  • Long-lived separate launcher that owns updates — unnecessary complexity; prefer a one-shot apply helper

  • Per-machine install updated by a per-user process — align install scope

  • Unsigned downloads — SmartScreen and enterprise policy will block you

  • In-app self-replace on a winget/Store install — fights the package manager; detect channel and defer