Skip to content

Add support for sparse packaging workflows - #607

Merged
Nikola Metulev (nmetulev) merged 47 commits into
mainfrom
zt/286-sparse-packaging
Aug 4, 2026
Merged

Nikola Metulev (nmetulev) merged 47 commits into
mainfrom
zt/286-sparse-packaging

Conversation

@zateutsch

Copy link
Copy Markdown
Contributor

Add sparse packaging support to the winapp CLI

fixes #286

Summary

Adds first-class support for the production sparse packaging workflow described in the
MS docs: Grant package identity to non-packaged apps.

Previously, the only sparse-related capability was create-debug-identity — a developer-time
helper that requires Developer Mode and registers a raw manifest. There was no supported path to
produce a signed, identity-only .msix for distribution. This PR covers steps 1–3 of the MS
docs workflow (manifest → signed .msix → embed identity into the exe). Steps 4–5
(register/unregister) remain the installer's responsibility.

The sparse packaging flow

A sparse package is an identity-only MSIX: it contains just a manifest (no binaries, no
bundled assets) and grants Windows package identity to an app that installs normally to the file
system. Identity unlocks modern Windows APIs (notifications, background tasks, share target,
startup tasks, Package.Current, etc.) for otherwise unpackaged Win32/WPF/WinForms apps. Assets
and binaries are resolved from an external content location at runtime, registered via
Add-AppxPackage -ExternalLocation.

The three CLI steps map directly to the MS docs:

Step Command What it does
1 winapp init --exe <exe> --sparse Generate a sparse identity manifest + placeholder assets, inferring defaults from the exe's FileVersionInfo
2 winapp pack <manifest> --cert <pfx> Build & sign the identity-only .msix (infers sparse from AllowExternalContent)
3 winapp embed-identity <exe|xml> Embed the <msix> identity element into the app's SxS/fusion manifest
4–5 (installer) Add-AppxPackage -ExternalLocation / Remove-AppxPackage — not the CLI's job

What this adds

1. winapp init --exe <exe> --sparse

  • Generates a sparse-specific appxmanifest.xml from the template, plus placeholder assets in Assets/.
  • Infers package name, publisher CN, version, and description from the exe via FileVersionInfo, with sensible fallbacks.
  • Interactive prompts to override inferred values; --use-defaults / --no-prompt for CI.
  • Skips SDK/package installation entirely — identity packages have no SDK dependencies.
  • Validates that --exe requires --sparse, with a clear error otherwise.
  • Output clarifies that generated assets live at the external install location, not inside the .msix.

2. winapp pack — sparse-aware

  • Auto-detects sparse mode by checking for AllowExternalContent="true" in the manifest.
  • Accepts an appxmanifest.xml directly (no folder required): stages a manifest-only directory and packs it.
  • Signs only when a cert is supplied (--cert / --generate-cert).
  • When a folder is passed for a sparse manifest, emits warnings (not errors) for stray assets/binaries that shouldn't be in an identity package.

3. winapp embed-identity <exe|xml> (new command)

  • EXE mode: embeds the <msix> element into the exe's RT_MANIFEST via mt.exe.
  • XML mode: inserts/replaces the <msix> element in an external SxS manifest file.
  • Mode auto-detected from the file extension.
  • Reads identity from --manifest (default ./appxmanifest.xml) and rejects non-sparse manifests with a clear error, since identity embedding only applies to external-location packages.

4. Sparse template & code corrections (per MS docs schema)

  • RuntimeBehavior="packagedClassicApp" → "win32App" (correct for plain Win32, not Desktop Bridge).
  • MinVersion raised to 10.0.19041.0 (required for AllowExternalContent / uap10).
  • Added ProcessorArchitecture="neutral" to <Identity> (identity-only packages carry no binaries).

Documentation

  • New guide: docs/guides/sparse.md — overview, prerequisites, end-to-end walkthrough, asset handling, installer integration (NSIS/WiX/Inno), and troubleshooting.
  • Updated: docs/usage.md, docs/npm-usage.md, README, CLI schema, and the Copilot/Claude skill fragments (identity, package, setup).

Sample: samples/sparse-app/

A minimal WPF sample demonstrating the full flow end-to-end, including an Inno Setup
installer (installer/setup.iss) that installs the app, deploys the .msix, and registers the
sparse package on install (and unregisters on uninstall). MainWindow queries
Package.Current and displays the package family name (or "No package identity" when unregistered).
Includes a Pester test.Tests.ps1 and is wired into the test-samples CI matrix.

What the CLI does not do

  • Register/unregister — installer's job (Add-AppxPackage -ExternalLocation).
  • Replace create-debug-identity — kept as-is for the debug workflow.
  • Per-machine vs per-user — installer's concern.

Testing

  • New SparsePackagingTests.cs (+426 lines) covering init inference/validation, sparse pack
    routing (manifest-vs-folder, warnings), embed-identity EXE/XML modes, non-sparse rejection,
    and output-path resolution (including dotted-directory and .msixbundle edge cases).
  • FakeMsixService extended for command-level routing tests.
  • Full sparse suite passes locally (dotnet test ... --filter "FullyQualifiedName~SparsePackaging").

Copilot AI balanced review requested due to automatic review settings July 8, 2026 02:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds first-class support for the production sparse packaging workflow (identity-only MSIX) to the winapp CLI, implementing steps 1–3 of the Microsoft docs flow. It complements the existing developer-time create-debug-identity helper with a supported path to produce a signed, identity-only .msix for distribution, and to embed that identity into an app's side-by-side manifest.

Changes:

  • Adds winapp init --exe <exe> --sparse (generate a sparse identity manifest + placeholder assets, inferring defaults from the exe's FileVersionInfo, skipping SDK install), makes winapp pack sparse-aware (auto-detects AllowExternalContent, accepts a manifest file directly, stages a manifest-only package), and introduces the new winapp embed-identity <exe|xml> command.
  • Corrects the sparse template per the MS docs schema (win32App runtime behavior, MinVersion 10.0.19041.0, ProcessorArchitecture="neutral"), and surfaces the new command/options through the npm SDK and the VS Code extension.
  • Adds a WPF samples/sparse-app/ (with Inno Setup installer + Pester test wired into CI), a new docs/guides/sparse.md, and regenerates docs/schema/skill fragments.

Reviewed changes

Copilot reviewed 50 out of 54 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/winapp-CLI/WinApp.Cli/Services/MsixService.Identity.cs New CreateSparseIdentityPackageAsync, EmbedIdentityAsync, XML-manifest embedding, and sparse output-path resolution
src/winapp-CLI/WinApp.Cli/Services/MsixService.cs Sparse detection/warning helpers and template runtime-behavior handling (minor indentation regression flagged)
src/winapp-CLI/WinApp.Cli/Commands/PackageCommand.cs Routes manifest-file input with AllowExternalContent to the sparse packaging path
src/winapp-CLI/WinApp.Cli/Commands/InitCommand.cs Adds --exe/--sparse options and sparse init flow with validation
src/winapp-CLI/WinApp.Cli/Commands/EmbedIdentityCommand.cs New embed-identity command (EXE/XML modes, non-sparse rejection)
src/winapp-CLI/WinApp.Cli/Templates/appxmanifest.sparse.xml Template corrected to MS docs schema (win32App, MinVersion, neutral arch)
src/winapp-npm/src/winapp-commands.ts npm SDK bindings for embed-identity and new init sparse options
src/winapp-VSC/src/extension.ts, src/winapp-VSC/package.json Registers the winapp.embedIdentity VS Code command
.github/plugin/agents/winapp.agent.md, .claude/agents/winapp.md Agent docs updated for embed-identity (regression: winapp run heading dropped)
samples/sparse-app/* New WPF sample, Inno Setup installer, and Pester test
docs/*, docs/cli-schema.json New sparse guide plus regenerated usage/schema/skill docs
src/winapp-CLI/WinApp.Cli.Tests/SparsePackagingTests.cs, FakeMsixService.cs Tests for init inference/validation, pack routing, and embed-identity modes

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .github/plugin/agents/winapp.agent.md Outdated
Comment thread .claude/agents/winapp.md Outdated
Comment thread samples/sparse-app/installer/setup.iss Outdated
Comment thread src/winapp-CLI/WinApp.Cli/Services/MsixService.cs Outdated
@github-actions

github-actions Bot commented Jul 8, 2026 •

Copy link
Copy Markdown
Contributor

Build Metrics Report

Binary Sizes

Artifact Baseline Current Delta
CLI (ARM64) 37.36 MB 37.59 MB 📈 +241.5 KB (+0.63%)
CLI (x64) 37.52 MB 37.75 MB 📈 +228.0 KB (+0.59%)
MSIX (ARM64) 15.53 MB 15.61 MB 📈 +84.1 KB (+0.53%)
MSIX (x64) 16.50 MB 16.59 MB 📈 +91.8 KB (+0.54%)
NPM Package 32.41 MB 32.57 MB 📈 +160.6 KB (+0.48%)
NuGet Package 32.45 MB 32.62 MB 📈 +172.2 KB (+0.52%)

Test Results

✅ 4218 passed, 5 skipped out of 4223 tests in 878.9s (+51 tests, +219.2s vs. baseline)

Test Coverage

✅ 93% line coverage, 87.2% branch coverage · ⚠️ -0.3% vs. baseline

CLI Startup Time

51ms median (x64, winapp --version) · ✅ no change vs. baseline

Try This Build

Installs the MSIX for your architecture, replacing any previously installed build. Needs the GitHub CLI — the command offers to install it and sign you in if it is missing.

& ([scriptblock]::Create((irm https://raw.githubusercontent.com/microsoft/winappCli/main/scripts/winapp-pr.ps1))) 607
Switching between builds often?

Put the tool on your PATH once:

& ([scriptblock]::Create((irm https://raw.githubusercontent.com/microsoft/winappCli/main/scripts/winapp-pr.ps1))) -AddToPath

Then this build is just:

winapp-pr 607

Run winapp-pr with no arguments to pick from a list of open PRs.


Updated 2026-08-04 19:47:48 UTC · commit e8394b4 · workflow run

Zach Teutsch (zateutsch) and others added 3 commits July 8, 2026 11:53
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

@nmetulev Nikola Metulev (nmetulev) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: sparse packaging workflow

I reviewed this branch across security, correctness, CLI UX, alternative solutions, test coverage, docs/samples, and packaging, then empirically validated the findings by running the real winapp init --sparse -> pack -> embed-identity workflow against three genuine executables: a .NET exe, the actual electron.exe, and a Rust cargo exe.

Bottom line: the feature works end-to-end on all three frameworks — identity embeds correctly, Electron's existing manifest (Common-Controls, trustInfo, dpiAware, supportedOS) is preserved, and quoted publisher DNs (CN="GitHub, Inc.") are XML-escaped. The items below are worth addressing; details are in the inline comments.

Scope note

Some issues live in EmbedMsixIdentityToExeAsync, which pre-exists on main (previously only used internally for debug-identity embedding). This PR doesn't introduce those bugs, but the new embed-identity command routes user-supplied exes into that method for the first time, which materially widens their blast radius. They're flagged at the new call site (MsixService.Identity.cs:181) and labeled accordingly.

Findings

ID Sev Area Where Issue Repro
H1 High security extension.ts:560/567 (sink :66) PowerShell injection: file-picker path interpolated unescaped into terminal.sendText executed arbitrary code
M1 Med correctness Identity.cs (pre-existing, new call :181) Fixed-name temp manifests deleted from target dir -> silent user-file loss deleted planted files
M2 Med cli-ux EmbedIdentityCommand.cs:51 Auto-detect prefers Package.appxmanifest over sparse appxmanifest.xml; fails when both present exit 1
M3 Med docs sparse.md:68 --cert ./dev.pfx but cert generate writes devcert.pfx verified
M8 Med correctness Identity.cs (pre-existing, new call :181) Re-embedding a changed identity hard-fails with cryptic mt.exe c1010001 exit 1
L2 Low correctness Identity.cs:655 (pre-existing) Stray ; in generated fusion manifest (mt.exe strips it -> cosmetic) fires, stripped

Also worth a look (outside the diff hunks, so no inline anchor)

  • Test coverage: the new embed-identity EXE path, the identity-only .msix contents (CreateSparseIdentityPackageAsync), and init --sparse without --exe don't appear to be asserted in SparsePackagingTests.cs. An idempotency/rerun test would have caught M8.
  • README: the PR links the new sample in the samples table (:259) but not the guide in the "Additional guides" section (:130).
  • File size: this change grows MsixService.Identity.cs to ~1,209 lines (was 850) — over the repo's ~1,000-line guideline; a partial-class split would help.

Reviewed with the pr-review skill + a GPT cross-check, then validated empirically on dotnet/electron/rust repro apps.

Comment thread src/winapp-VSC/src/extension.ts Outdated
Comment thread src/winapp-CLI/WinApp.Cli/Commands/EmbedIdentityCommand.cs Outdated
Comment thread src/winapp-CLI/WinApp.Cli/Services/MsixService.Identity.cs Outdated
Comment thread docs/guides/sparse.md Outdated
# Conflicts:
#	docs/npm-usage.md
#	src/winapp-CLI/WinApp.Cli/Services/ManifestService.cs
#	src/winapp-VSC/package.json
#	src/winapp-VSC/src/extension.ts
Copilot AI review requested due to automatic review settings July 24, 2026 00:30
Comment thread src/winapp-CLI/WinApp.Cli/Services/ManifestService.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ManifestService.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ManifestService.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ManifestService.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ManifestTemplateService.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/MsixService.Identity.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/MsixService.Identity.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/MsixService.Identity.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/MsixService.Identity.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/MsixService.Identity.cs Fixed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 50 out of 54 changed files in this pull request and generated 5 comments.

Comment thread src/winapp-CLI/WinApp.Cli/Services/MsixService.Identity.cs Outdated
Comment thread src/winapp-CLI/WinApp.Cli/Commands/EmbedIdentityCommand.cs
Comment thread samples/sparse-app/installer/setup.iss
Comment thread samples/sparse-app/installer/setup.iss Outdated
Comment thread .github/plugin/skills/winapp-cli/setup/SKILL.md
- embed-identity auto-detect now prefers a sparse manifest so a full
  Package.appxmanifest alongside appxmanifest.xml is no longer picked and
  rejected.
- Manifest embedding writes temp files under the system temp directory with
  unique names instead of fixed names beside the exe, avoiding silent
  deletion of user files.
- Strip any existing <msix> from the extracted manifest before the mt.exe
  merge so re-branding an exe is idempotent instead of failing c1010001.
- Remove stray semicolon in the generated assemblyIdentity fusion snippet.
- Fix cert filename in sparse guide (devcert.pfx, matching cert generate).
- Add regression tests for temp-file safety and <msix> stripping.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: de996470-3bd8-45b4-a88d-810d3799467a
Copilot AI review requested due to automatic review settings July 24, 2026 00:46
Comment thread src/winapp-CLI/WinApp.Cli/Commands/EmbedIdentityCommand.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ManifestService.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Commands/EmbedIdentityCommand.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Commands/EmbedIdentityCommand.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Commands/InitCommand.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Commands/PackageCommand.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Commands/PackageCommand.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ManifestService.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ManifestService.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ManifestService.cs Fixed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 51 out of 55 changed files in this pull request and generated 6 comments.

Comments suppressed due to low confidence (1)

samples/sparse-app/installer/setup.iss:26

  • The installer script lives under installer/, and Inno resolves relative [Files] sources from the script directory by default. Consequently this path resolves as installer/bin/...; compiling the documented installer/setup.iss currently fails on line 42 with “No files found matching ...\installer\bin\...”. Set the source root to the sample's parent directory so both PublishDir and MyMsixName resolve to the artifacts produced by the README commands.
#define PublishDir "bin\Release\net10.0-windows10.0.19041.0\win-x64\publish"

Comment thread src/winapp-npm/src/winapp-commands.ts
Comment thread src/winapp-CLI/WinApp.Cli/Commands/InitCommand.cs Outdated
Comment thread src/winapp-CLI/WinApp.Cli/Services/ManifestService.cs
Comment thread src/winapp-CLI/WinApp.Cli/Commands/EmbedIdentityCommand.cs Outdated
Comment thread samples/sparse-app/sparse-app.csproj
Comment thread samples/sparse-app/app.manifest Outdated
Copilot review:
- embed-identity: warn users to re-sign the exe after mt.exe rewrites it
  (invalidates any existing Authenticode signature).
- sparse-app setup.iss: resolve relative [Files] sources from the sample dir
  (SourceDir=..) so PublishDir and the identity .msix resolve correctly.
- sparse-app setup.iss: unregister by exact Identity Name (Get-AppxPackage -Name
  SparseAppSample) instead of a SparseAppSample* wildcard.
- setup skill fragment: document the sparse identity init workflow and outputs;
  regenerate the plugin/claude skill mirrors.

Code quality (CodeQL generic-catch + LINQ/using):
- EmbedIdentityCommand: use LINQ over a static readonly candidate array; narrow
  IsSparseManifest catch; rethrow OperationCanceledException before generic catch.
- PackageCommand: narrow IsSparseManifestAsync catch; rethrow cancellation.
- InitCommand: rethrow cancellation before the sparse-init generic catch.
- ManifestService: using var for the extracted Icon/Bitmap; narrow the
  FileVersionInfo and cleanup catches; rethrow cancellation in logo extraction.

The temp-file data-loss re-flag was already fixed in b18bad3 (temp manifests
live under Path.GetTempPath()); no code change needed.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: de996470-3bd8-45b4-a88d-810d3799467a
Copilot AI review requested due to automatic review settings July 24, 2026 01:08
Comment thread src/winapp-CLI/WinApp.Cli/Services/MsixService.Identity.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/MsixService.Identity.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/MsixService.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Commands/EmbedIdentityCommand.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ManifestTemplateService.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Services/ManifestService.cs Fixed
- init: reject the sparse-only options (--exe/--name/--publisher/--output-dir)
  when --sparse is absent, so scripts fail instead of silently discarding input.
- ManifestService: reject inferred versions whose components exceed 65535 (MSIX
  Identity/@Version is 16-bit) so inference falls back to a packable default;
  add regression cases to NormalizeManifestVersion tests.
- embed-identity: document the actual manifest search order (target dir first,
  then current directory) in the --manifest description and the sparse guide,
  instead of the inaccurate "./appxmanifest.xml" default.
- sparse guide: note that EXE mode invalidates the Authenticode signature.
- sparse-app sample: copy Assets/ to build/publish output so the external
  content location has the logos the manifest references.
- sparse-app app.manifest: fix the invalid XML comment (removed the literal
  "--manifest", which XML comments cannot contain).
- Regenerate docs/cli-schema.json, the identity skill, and the npm
  winapp-commands.ts binding to match (schema version pinned at 0.5.1).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: de996470-3bd8-45b4-a88d-810d3799467a

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 52 out of 56 changed files in this pull request and generated 7 comments.

Comments suppressed due to low confidence (3)

src/winapp-CLI/WinApp.Cli/Commands/InitCommand.cs:148

  • This sparse branch returns before the normal init path, so the positional base-directory and existing options such as --config-dir, --config-only, --setup-sdks, --ignore-config, and --no-gitignore are silently ignored. For example, winapp init ./identity --exe ./app.exe --sparse succeeds but writes beside the exe instead of ./identity. Reject incompatible arguments with an actionable error, or explicitly map the positional directory to sparse output semantics.
    src/winapp-CLI/WinApp.Cli/Commands/EmbedIdentityCommand.cs:27
  • The help text says the default is ./appxmanifest.xml, but the implementation first searches beside the target, then the current directory, and also considers Package.appxmanifest. This can select a different identity than the documented default. Describe the actual precedence here so --help, CLI schema, npm docs, and generated skills remain accurate after regeneration.
    samples/sparse-app/sparse-app.csproj:18
  • The installer copies only the publish directory, but this project does not mark the checked-in Assets/ files for output or publish copying. A normal dotnet publish therefore omits the external assets referenced by appxmanifest.xml, so the production installer cannot deploy the external-content layout it claims to provide. Add copy metadata for Assets/** and assert the published assets exist in the sample test.

Comment thread src/winapp-CLI/WinApp.Cli/Services/MsixService.Identity.cs Outdated
Comment thread src/winapp-CLI/WinApp.Cli/Services/MsixService.Identity.cs Outdated
Comment thread samples/sparse-app/installer/setup.iss Outdated
Comment thread samples/sparse-app/MainWindow.xaml.cs Outdated
Comment thread docs/fragments/skills/winapp-cli/package.md
Comment thread docs/guides/sparse.md Outdated
Comment thread samples/sparse-app/README.md Outdated
Copilot AI review requested due to automatic review settings July 24, 2026 01:17
Comment thread src/winapp-CLI/WinApp.Cli/Services/ManifestService.cs Fixed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 67 out of 72 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

samples/sparse-app/installer/setup.iss:123

  • The fallback unregisters the currently working package after any Add-AppxPackage failure, not only a same-version conflict. A bad/untrusted replacement package, locked files, or a transient deployment error therefore removes the existing registration; when the retry fails, Inno rolls back files but does not restore that registration, leaving the previously installed app without identity. Only remove on the specific conflict this fallback is intended to handle, and preserve/restore the prior registration if the retry fails.

Comment thread docs/guides/sparse.md
The register-sparse.ps1 docs helper and the sparse-app setup.iss inline
registration unregistered the existing package on ANY first Add-AppxPackage
failure, then retried. An untrusted or corrupt new .msix (or an unsupported OS)
would trip the catch, remove a working prior registration, then fail the retry
too — leaving the installed app with no identity.

Gate the unregister+retry on HRESULT 0x80073CFB (ERROR_PACKAGE_ALREADY_EXISTS,
the same-version-already-registered conflict Add-AppxPackage rejects) and
re-throw every other failure so a bad package can never strip existing identity.
Verified the parsing of 0x80073CFB is identical on Windows PowerShell 5.1 and
PowerShell 7, and validated both control-flow paths (conflict -> remove+retry;
untrusted -> abort without removing) with mocked cmdlets.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: de996470-3bd8-45b4-a88d-810d3799467a
Copilot AI review requested due to automatic review settings August 2, 2026 21:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 67 out of 72 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

samples/sparse-app/installer/setup.iss:144

  • RaiseException from this [Files] AfterInstall callback does not fail Setup. I compiled and ran the installer with an untrusted package certificate: RegisterParams returned exit code 1, but Inno logged the exception as a suppressed “Expression error,” continued creating shortcuts/uninstall metadata, exited 0, and left the app installed with no SparseAppSample package registered. This turns a registration failure into an apparently successful identity-less install. Move registration to an Inno execution path whose failure propagates to Setup, and explicitly clean up copied files/registration on failure rather than relying on automatic rollback.

Comment thread src/winapp-CLI/WinApp.Cli/Services/MsixService.SparsePackaging.cs Outdated
The direct-file sparse pack path staged the input manifest with File.Copy,
bypassing the sparse corrections folder packing applies. A manifest from an
older template or hand-edit could ship with RuntimeBehavior=packagedClassicApp,
a missing ProcessorArchitecture, or a MinVersion below the 10.0.19041.0 that
AllowExternalContent requires.

Add MsixService.NormalizeSparseIdentityManifest, applied when staging the
manifest: forces win32App/mediumIL for an .exe app, removes EntryPoint,
defaults a missing ProcessorArchitecture to neutral, and raises any
TargetDeviceFamily MinVersion below 10.0.19041.0. Corrections are surfaced as
status messages. No-op for a freshly generated (correct) manifest.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: de996470-3bd8-45b4-a88d-810d3799467a
Copilot AI review requested due to automatic review settings August 2, 2026 23:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 67 out of 72 changed files in this pull request and generated 1 comment.

Comment thread samples/sparse-app/installer/setup.iss Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 67 out of 72 changed files in this pull request and generated 1 comment.

Comment thread src/winapp-CLI/WinApp.Cli/Services/MsixService.cs
Folder inputs route sparse manifests through UpdateAppxManifestContentAsync,
but that rewrite did not raise TargetDeviceFamily/@MinVersion. A folder created
from an older sparse template kept 10.0.18362.0; because packing uses MakeAppx
/nv, the command could still produce and sign an MSIX deployment rejects, since
AllowExternalContent requires 10.0.19041.0.

Extract the MinVersion flooring from NormalizeSparseIdentityManifest into a
shared RaiseSparseTargetDeviceFamilyMinVersion helper and apply it in the
folder-packing sparse block, so both the manifest-file and folder paths enforce
the same 10.0.19041.0 floor. Corrections are surfaced as status messages. Add a
folder-path regression test.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: de996470-3bd8-45b4-a88d-810d3799467a

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 67 out of 72 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/winapp-CLI/WinApp.Cli/Services/MsixService.SparsePackaging.cs:242

  • This does not actually prevent an invalid identity package from being produced. ParseAppxManifestAsync only checks that Identity.Name, Identity.Publisher, and Application.Id attributes exist; empty values, a missing Identity.Version, and missing required package sections such as Dependencies/TargetDeviceFamily still pass. Because packaging deliberately uses MakeAppx /nv, such a manifest can be packed and signed successfully but then fail during Add-AppxPackage. Please validate the complete deployment-required sparse manifest structure (and non-empty/schema-valid attribute values) before packaging.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 67 out of 72 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/winapp-npm/src/cli-args.ts:94

  • This does not match the native parser for repeated occurrences. The built CLI resolves --sparse=false --sparse to false (the explicit value wins over the bare occurrence), while this returns true; two valued occurrences are rejected by native parsing rather than using the last value. In the npm wrapper, that mismatch selects the sparse fast path and can reject --add-js-bindings even though native init is in normal mode. Track explicit values separately and bypass wrapper hooks for combinations the native parser will reject.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 67 out of 71 changed files in this pull request and generated 1 comment.

Suppressed comments (2)

docs/guides/sparse.md:215

  • The documented retry removes the existing working registration before the replacement is known to succeed. If the second Add-AppxPackage fails, the installer receives an error but the old app has already lost package identity. Avoid unregistering for a same-version reinstall, or preserve and restore the prior registration when the retry fails.
    Get-AppxPackage -Name $PackageName | Remove-AppxPackage -ErrorAction SilentlyContinue
    Add-AppxPackage -Path $MsixPath -ExternalLocation $ExternalLocation -ErrorAction Stop

src/winapp-CLI/WinApp.Cli/Services/MsixService.SparsePackaging.cs:205

  • A missing or malformed MinVersion bypasses this normalization because Version.TryParse returns false. Since sparse packages are packed with MakeAppx /nv, the command can then report success (and even sign the output) for a package that deployment rejects. Treat an unparseable value like a below-floor value, or fail validation explicitly.

Comment on lines +123 to +124
'Get-AppxPackage -Name ''' + EscapePSLiteral('{#MyPackageName}') + ''' | Remove-AppxPackage -ErrorAction SilentlyContinue; ' +
'Add-AppxPackage -Path ''' + MsixPath + ''' -ExternalLocation ''' + EscapePSLiteral(AppDir) + ''' -ErrorAction Stop } ' +
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Add sparse packaging support (winapp sparse command group)

4 participants