Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
24e02a4
sparse packaging support draft
zateutsch Jul 2, 2026
e8da988
sparse packaging draft
zateutsch Jul 5, 2026
9203649
pr review round 2
zateutsch Jul 8, 2026
e219cc0
Merge branch 'main' into zt/286-sparse-packaging
zateutsch Jul 8, 2026
cac72d7
Potential fix for pull request finding
zateutsch Jul 9, 2026
5de6f86
resolve copilot review
zateutsch Jul 9, 2026
43cd3b2
Merge remote-tracking branch 'origin/main' into zt/286-sparse-packaging
zateutsch Jul 24, 2026
b18bad3
Address PR review: sparse embed-identity fixes
zateutsch Jul 24, 2026
907993a
Address Copilot + code-quality review comments
zateutsch Jul 24, 2026
42e4a89
Address second round of Copilot review comments
zateutsch Jul 24, 2026
692d08d
Address third-wave PR review: sparse doc/notation/sample fixes
zateutsch Jul 24, 2026
4121fef
Address fourth-wave PR review: sparse packaging refinements
zateutsch Jul 24, 2026
97216eb
Address fifth-wave PR review: namespace-scoped msix removal + safer i…
zateutsch Jul 24, 2026
fb2f186
Address sixth-wave PR review: harden sparse output path resolution
zateutsch Jul 24, 2026
b247330
Address seventh-wave PR review: validate sparse init inputs
zateutsch Jul 24, 2026
237c7ae
Address eighth-wave code-quality Path.Combine feedback
zateutsch Jul 24, 2026
616c78f
Address ninth-wave Copilot review feedback
zateutsch Jul 24, 2026
6576748
Address tenth-wave Copilot + code-quality review feedback
zateutsch Jul 24, 2026
e3c11a1
address main two nikola feedback items
zateutsch Jul 24, 2026
781817a
address other nikola feedback items
zateutsch Jul 24, 2026
fa990e3
Address Copilot review: manifest parse errors + sparse init docs
zateutsch Jul 24, 2026
a09a293
Address PR #607 review batch: parsing, catch filter, manifest orderin…
zateutsch Jul 24, 2026
0641a6f
Address PR #607 review: Path.Join, MsixService split, installer prereqs
zateutsch Jul 27, 2026
c91674b
Address PR review batch: sparse option validation, embed-identity sea…
zateutsch Jul 27, 2026
9fd3a2c
Switch remaining Path.Combine calls to Path.Join to silence CodeQL
zateutsch Jul 27, 2026
01bdb6b
Merge origin/main into zt/286-sparse-packaging
zateutsch Jul 27, 2026
5b3896d
Switch all Path.Combine calls in SparsePackagingTests to Path.Join
zateutsch Jul 27, 2026
7a95f8f
Address PR review: sparse manifest validation, repeated --sparse parsing
zateutsch Jul 27, 2026
93eaf84
Address PR review: resilient exe read, atomic manifest save, unsigned…
zateutsch Jul 27, 2026
eaa6dc8
Address PR review: apply sparse rewrite to folder inputs; guide asset…
zateutsch Jul 27, 2026
7759a50
Narrow temp-file cleanup catch to IOException/UnauthorizedAccessExcep…
zateutsch Jul 27, 2026
c5e0ec7
Address PR review: embed-identity validation, version boundary, insta…
zateutsch Jul 27, 2026
acd3159
Potential fix for pull request finding
zateutsch Jul 27, 2026
11e7029
Address PR review: register sparse package via rollback-safe AfterIns…
zateutsch Jul 27, 2026
50caaa8
Fix indentation of staged-manifest File.Copy in sparse packaging
zateutsch Jul 27, 2026
d819ee0
Match asm.v1 namespace for top-level assemblyIdentity checks
zateutsch Jul 27, 2026
e89516f
Harden sparse installer: OS gate, terminating errors, reliable re-reg…
zateutsch Jul 27, 2026
271fafe
Fix empty description fallback, non-destructive re-register, docs index
zateutsch Jul 28, 2026
07d887f
Potential fix for pull request finding 'Nested 'if' statements can be…
zateutsch Jul 28, 2026
85d86c5
Fix CI: ISCC brace comment, sparse guide MS Learn description + TOC
zateutsch Aug 2, 2026
ca1a4e9
Merge origin/main into zt/286-sparse-packaging
zateutsch Aug 2, 2026
4bcd1f2
Only unregister+retry on same-version conflict in sparse register helper
zateutsch Aug 2, 2026
eb26fcc
Normalize direct-file sparse manifests instead of staging verbatim
zateutsch Aug 2, 2026
1e769c3
Potential fix for pull request finding
zateutsch Aug 3, 2026
f3b3188
Raise sparse MinVersion floor in folder-packing rewrite too
zateutsch Aug 3, 2026
6ec90e8
Merge branch 'main' into zt/286-sparse-packaging
nmetulev Aug 4, 2026
e8394b4
Merge origin/main into sparse packaging branch
nmetulev Aug 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .claude/agents/winapp.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ Does the project already have an appxmanifest.xml?
│ │ └─ winapp run <build-output-dir> (registers loose layout + launches)
│ └─ Is the exe separate from your app code? (Electron, sparse package testing)
│ └─ winapp create-debug-identity <exe-path> (registers sparse package)
├─ Need production sparse packaging (ship identity for an unpackaged app)?
│ └─ winapp init --exe <exe> --sparse → winapp pack <manifest> --cert <pfx> → winapp embed-identity <exe>
│ (build a signed identity-only .msix your installer registers with Add-AppxPackage -ExternalLocation)
├─ Need to sign an existing MSIX or exe?
│ ├─ With a local dev/CA certificate (PFX)?
│ │ └─ winapp sign <file> <cert>
Expand Down Expand Up @@ -97,6 +100,7 @@ Want to inspect or interact with a running app's UI?
- `--config-dir` — directory for `winapp.yaml` (default: the selected project directory)
- `--config-only` — only create `winapp.yaml`, skip package installation
- `--no-gitignore` — don't update `.gitignore`
**Sparse mode (`--exe <exe> --sparse`):** generates an identity-only sparse `appxmanifest.xml` (with `AllowExternalContent`) plus placeholder assets for an existing executable, inferring name/publisher/version/description from the exe. Skips all SDK/package installation. `--exe` requires `--sparse`. Additional options: `--name`, `--publisher`, `--output-dir` (default: a `sparse/` folder in the current directory). This is **step 1** of the production sparse packaging workflow.
**Creates:** `winapp.yaml`, `appxmanifest.xml`, `Assets/` folder, `.winapp/` (if SDKs installed)

### `winapp restore [base-directory]`
Expand Down Expand Up @@ -136,6 +140,14 @@ Want to inspect or interact with a running app's UI?
- `--no-install` — create but don't register the package
**Requires:** `appxmanifest.xml` + path to your built `.exe`

### `winapp embed-identity <target>`
**Purpose:** Connect a desktop `.exe` to its sparse identity package by embedding the `<msix>` element into the target's side-by-side (fusion) manifest. This is **step 3** of the production sparse packaging workflow (after `winapp init --exe --sparse` and `winapp pack`).
**When to use:** After building a signed identity-only `.msix` for an unpackaged app, to make Windows associate the exe with that package at runtime.
**Modes:** `.exe` target → embeds via `mt.exe`; `.xml`/`.manifest` target → inserts/replaces the `<msix>` element in an external side-by-side manifest (rebuild the app afterward).
**Key options:**
- `--manifest <path>` — sparse `appxmanifest.xml` to read identity from (defaults to a `sparse/` folder beside the target first, then in the current directory — where `winapp init --exe --sparse` writes it — then beside the target and in the current directory)
**Requires:** a sparse `appxmanifest.xml` + the target `.exe` or `.xml`/`.manifest`

### `winapp run [<input>]`
**Purpose:** Build and/or package a Windows app and launch it — for **packaged** apps this simulates a full MSIX install with package identity; for **unpackaged** apps it launches the built `.exe` directly (no package identity). Returns the launched process ID for debugger attachment. Operates in one of two modes, auto-selected from the input:
- **Folder mode** — input is a build-output folder (contains `Package.appxmanifest`/`AppxManifest.xml`). Creates a loose-layout package, registers it with Windows, and launches it. Original behavior, unchanged.
Expand Down Expand Up @@ -328,6 +340,15 @@ winapp create-debug-identity ./myapp.exe # Register sparse package for exe
# Launch your exe normally — it now has package identity
```

### Ship production sparse identity (unpackaged app + installer)
```bash
winapp init --exe ./bin/MyApp.exe --sparse # Step 1: generate identity-only manifest + assets into ./sparse/
winapp cert generate # dev/test cert (use a trusted cert for production)
winapp pack ./sparse/appxmanifest.xml --cert devcert.pfx # Step 2: build + sign the identity .msix
winapp embed-identity ./bin/MyApp.exe # Step 3: embed <msix> into the exe fusion manifest
# Your installer registers it: Add-AppxPackage -Path MyApp.identity.msix -ExternalLocation <install-dir>
```

### Clone and build existing project
```bash
winapp restore # Reinstall packages from winapp.yaml
Expand Down
36 changes: 36 additions & 0 deletions .claude/skills/winapp-identity/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,26 @@ winapp create-debug-identity .\bin\Debug\myapp.exe

For full details including IDE setup examples, see the [Debugging Guide](https://github.com/microsoft/WinAppCli/blob/main/docs/debugging.md).

## Production sparse packaging (`init --sparse` / `pack` / `embed-identity`)

`create-debug-identity` is for **developer-time debugging** (requires Developer Mode, registers a raw manifest). For **production** — shipping identity to an app distributed by an existing installer (Inno Setup, WiX, NSIS) — use the sparse packaging workflow, which produces a signed identity-only `.msix`:

```powershell
# 1. Create the sparse identity manifest for your exe (skips SDK install)
winapp init --exe ./bin/Release/MyApp.exe --sparse --use-defaults

# 2. Build and sign the identity-only .msix (just the manifest, no binaries)
winapp pack ./sparse/appxmanifest.xml --cert ./devcert.pfx

# 3. Embed the <msix> identity element into the exe's fusion manifest
winapp embed-identity ./bin/Release/MyApp.exe
```

Then your installer registers the package against the install directory:
`Add-AppxPackage -Path MyApp.identity.msix -ExternalLocation <install-dir>`.

Assets are resolved from the external (install) location at runtime, **not** bundled into the `.msix`. `winapp embed-identity` also supports an XML mode (`winapp embed-identity ./app.manifest`) for updating a checked-in side-by-side manifest. See the [Sparse Packaging Guide](https://github.com/microsoft/WinAppCli/blob/main/docs/guides/sparse.md) and the [sparse-app sample](https://github.com/microsoft/WinAppCli/tree/main/samples/sparse-app).

## Related skills
- Need a manifest? See `winapp-manifest` to generate `Package.appxmanifest`
- Need a certificate? See `winapp-signing` — a trusted cert is required for identity registration
Expand Down Expand Up @@ -165,3 +185,19 @@ Enable package identity for debugging without creating full MSIX. Required for t
| `--keep-identity` | Keep the package identity from the manifest as-is, without appending '.debug' to the package name and application ID. | (none) |
| `--manifest` | Path to the Package.appxmanifest or appxmanifest.xml | (none) |
| `--no-install` | Do not install the package after creation. | (none) |

### `winapp embed-identity`

Connect a desktop exe to its sparse identity package by embedding the <msix> element. Reads identity (packageName, publisher, applicationId) from a sparse appxmanifest.xml and writes it into the target's side-by-side (fusion) manifest. EXE targets are updated with mt.exe; .xml/.manifest targets are edited directly. Example: winapp embed-identity ./bin/MyApp.exe. This is step 3 of the sparse packaging workflow (after 'winapp init --exe --sparse' and 'winapp pack').

#### Arguments
<!-- auto-generated from cli-schema.json -->
| Argument | Required | Description |
|----------|----------|-------------|
| `<target>` | Yes | Path to the .exe (embeds identity into its side-by-side manifest via mt.exe) or an .xml/.manifest side-by-side manifest file (inserts/replaces the <msix> element; created if it doesn't exist). |

#### Options
<!-- auto-generated from cli-schema.json -->
| Option | Description | Default |
|--------|-------------|---------|
| `--manifest` | Path to the sparse appxmanifest.xml to read identity from. When omitted, searched in a 'sparse/' folder (where 'winapp init --exe --sparse' writes it by default) beside the target first, then in the current directory, then beside the target and in the current directory. | (none) |
2 changes: 1 addition & 1 deletion .claude/skills/winapp-manifest/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ Key fields to edit:
## Tips

- Always ensure `Identity.Publisher` matches your signing certificate — use `winapp cert generate --manifest` to auto-match
- The `sparse` template adds `uap10:AllowExternalContent="true"` for apps that need identity but run outside the MSIX container
- The `sparse` template adds a `<uap10:AllowExternalContent>true</uap10:AllowExternalContent>` element (under `<Properties>`) for apps that need identity but run outside the MSIX container
- You can manually edit `Package.appxmanifest` after generation — it's a standard XML file
- Image assets must match the paths referenced in the manifest — `update-assets` handles this automatically
- For logos, transparent PNGs or SVGs work best. SVG source images are rendered as vectors directly at each target size, producing pixel-perfect results. Use a square image for best results across all sizes.
Expand Down
17 changes: 16 additions & 1 deletion .claude/skills/winapp-package/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,21 @@ Use the `microsoft/setup-winapp` action to install winapp on GitHub-hosted runne
- The `--executable` flag overrides the entry point in the manifest — useful when your exe name differs from what's in `Package.appxmanifest`
- For production distribution, use a certificate from a trusted CA and add `--timestamp` when signing with `winapp sign`

## Sparse identity packages

To grant identity to an app distributed by an existing installer (not as MSIX), build an **identity-only** sparse package: pass a sparse `appxmanifest.xml` (one declaring `<uap10:AllowExternalContent>true</uap10:AllowExternalContent>` under `<Properties>`) to `winapp pack` instead of a folder.

```powershell
# 1. Generate the sparse manifest for your exe (skips SDK install)
winapp init --exe ./bin/Release/MyApp.exe --sparse --use-defaults
# 2. Build & sign the identity-only .msix (just the manifest)
winapp pack ./sparse/appxmanifest.xml --cert ./devcert.pfx
# 3. Embed identity into the exe, then register in your installer
winapp embed-identity ./bin/Release/MyApp.exe
```

The `.msix` contains only the manifest — binaries and assets are resolved from the external content location at runtime via `Add-AppxPackage -ExternalLocation`. If you pack a folder whose manifest declares `AllowExternalContent`, `winapp pack` warns about any assets/binaries found. See the [Sparse Packaging Guide](https://github.com/microsoft/WinAppCli/blob/main/docs/guides/sparse.md).

## Related skills
- Need a manifest first? See `winapp-manifest` to generate `Package.appxmanifest`
- Need a certificate? See `winapp-signing` for certificate generation and management
Expand All @@ -201,7 +216,7 @@ Create MSIX installer from your built app. Run after building your app. A manife
<!-- auto-generated from cli-schema.json -->
| Argument | Required | Description |
|----------|----------|-------------|
| `<input-folder>` | Yes | One or more input folders with package layout. Pass multiple folders to create an MSIX bundle (e.g., winapp pack ./publish/x64 ./publish/arm64). |
| `<input-folder>` | Yes | One or more input folders with package layout, or a single sparse appxmanifest.xml file (an identity-only package with AllowExternalContent). Pass multiple folders to create an MSIX bundle (e.g., winapp pack ./publish/x64 ./publish/arm64). |

#### Options
<!-- auto-generated from cli-schema.json -->
Expand Down
33 changes: 32 additions & 1 deletion .claude/skills/winapp-setup/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,31 @@ When JS bindings are enabled (via `--add-js-bindings` or by answering yes in int
- `.winapp/bindings/` — generated JS bindings for Windows App SDK APIs (npm-only, Node / Electron)
- `package.json` update — adds the `winapp.jsBindings` namespace and `@microsoft/dynwinrt` dependency (npm-only)

### Initialize a sparse identity package (existing exe)

Use `--sparse` when you have an **already-built desktop exe** (WPF, WinForms, Win32, Electron, etc.) and only want to give it **package identity** — without repackaging the whole app into the MSIX. The app's files stay where they are and are resolved from an *external content location* at runtime.

```powershell
# Generate an identity-only sparse manifest for an existing exe
winapp init --exe ./bin/Release/MyApp.exe --sparse

# Non-interactive, with explicit identity values
winapp init --exe ./bin/Release/MyApp.exe --sparse --name MyApp --publisher "CN=Contoso" --use-defaults
```

`--sparse` requires `--exe`. It skips all SDK/package installation (sparse identity packages have no SDK dependencies) and, by default, writes to a dedicated `sparse/` folder in the current directory (override with `--output-dir`) so the manifest and its `Assets/` stay out of a build-output folder that a rebuild would wipe:
- `appxmanifest.xml` — identity-only sparse manifest (declares `uap10:AllowExternalContent`)
- `Assets/` — placeholder visual assets (extracted from the exe's icon when possible), resolved from the **external location** at runtime — **not** bundled into the `.msix`

If an `appxmanifest.xml` already exists in the target directory, init fails instead of overwriting it; re-run with `--force` to regenerate.

This is step 1 of the sparse packaging workflow. Continue with:
1. `winapp pack ./sparse/appxmanifest.xml --cert ./devcert.pfx` — build the signed identity `.msix`
2. `winapp embed-identity ./bin/Release/MyApp.exe` — connect the exe to the identity package (re-sign the exe afterward)
3. Register in your installer with `Add-AppxPackage -Path <msix> -ExternalLocation <install-dir>`

For the full walkthrough, see the [Sparse packaging guide](https://github.com/microsoft/WinAppCli/blob/main/docs/guides/sparse.md).

### Restore after cloning

```powershell
Expand Down Expand Up @@ -203,10 +228,16 @@ Start here for initializing a Windows app with required setup. Sets up everythin
|--------|-------------|---------|
| `--config-dir` | Directory to read/store configuration (default: the selected project directory, or current directory if no project is detected) | (none) |
| `--config-only` | Only handle configuration file operations (create if missing, validate if exists). Skip package installation and other workspace setup steps. | (none) |
| `--exe` | Path to the application executable. Requires --sparse. Generates an identity-only sparse manifest for the exe instead of a full package/SDK setup. | (none) |
| `--force` | Overwrite an existing appxmanifest.xml in the target directory (sparse only). Without this, init fails instead of replacing existing manifest/asset files. | (none) |
| `--ignore-config` | Don't use configuration file for version management | (none) |
| `--name` | Override the package name (sparse only; default: inferred from the exe) | (none) |
| `--no-gitignore` | Don't update .gitignore file | (none) |
| `--output-dir` | Directory to write the sparse manifest and Assets/ (sparse only; default: a 'sparse/' folder in the current directory) | (none) |
| `--publisher` | Override the publisher CN (sparse only; default: inferred from the exe's company name). Bare names are auto-wrapped as CN=<name>. | (none) |
| `--setup-sdks` | SDK installation mode: 'stable' (default), 'preview', 'experimental', or 'none' (skip SDK installation) | (none) |
| `--use-defaults` | Do not prompt; requires an explicit project directory (e.g., winapp init . --use-defaults) | (none) |
| `--sparse` | Generate a sparse identity manifest (appxmanifest.xml) for an existing desktop exe instead of a full package manifest. Use with --exe. Skips SDK/package installation. | (none) |
| `--use-defaults` | Skip interactive prompts and use default answers. Normal init targets the positional project directory if given, otherwise the current directory (e.g., winapp init . --use-defaults). Sparse init (--exe --sparse) ignores the positional directory and writes to --output-dir instead. | (none) |

### `winapp restore`

Expand Down
21 changes: 21 additions & 0 deletions .github/plugin/agents/winapp.agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ Does the project already have an appxmanifest.xml?
│ │ └─ winapp run <build-output-dir> (registers loose layout + launches)
│ └─ Is the exe separate from your app code? (Electron, sparse package testing)
│ └─ winapp create-debug-identity <exe-path> (registers sparse package)
├─ Need production sparse packaging (ship identity for an unpackaged app)?
│ └─ winapp init --exe <exe> --sparse → winapp pack <manifest> --cert <pfx> → winapp embed-identity <exe>
│ (build a signed identity-only .msix your installer registers with Add-AppxPackage -ExternalLocation)
├─ Need to sign an existing MSIX or exe?
│ ├─ With a local dev/CA certificate (PFX)?
│ │ └─ winapp sign <file> <cert>
Expand Down Expand Up @@ -98,6 +101,7 @@ Want to inspect or interact with a running app's UI?
- `--config-dir` — directory for `winapp.yaml` (default: the selected project directory)
- `--config-only` — only create `winapp.yaml`, skip package installation
- `--no-gitignore` — don't update `.gitignore`
**Sparse mode (`--exe <exe> --sparse`):** generates an identity-only sparse `appxmanifest.xml` (with `AllowExternalContent`) plus placeholder assets for an existing executable, inferring name/publisher/version/description from the exe. Skips all SDK/package installation. `--exe` requires `--sparse`. Additional options: `--name`, `--publisher`, `--output-dir` (default: a `sparse/` folder in the current directory). This is **step 1** of the production sparse packaging workflow.
**Creates:** `winapp.yaml`, `appxmanifest.xml`, `Assets/` folder, `.winapp/` (if SDKs installed)

### `winapp restore [base-directory]`
Expand Down Expand Up @@ -137,6 +141,14 @@ Want to inspect or interact with a running app's UI?
- `--no-install` — create but don't register the package
**Requires:** `appxmanifest.xml` + path to your built `.exe`

### `winapp embed-identity <target>`
**Purpose:** Connect a desktop `.exe` to its sparse identity package by embedding the `<msix>` element into the target's side-by-side (fusion) manifest. This is **step 3** of the production sparse packaging workflow (after `winapp init --exe --sparse` and `winapp pack`).
**When to use:** After building a signed identity-only `.msix` for an unpackaged app, to make Windows associate the exe with that package at runtime.
**Modes:** `.exe` target → embeds via `mt.exe`; `.xml`/`.manifest` target → inserts/replaces the `<msix>` element in an external side-by-side manifest (rebuild the app afterward).
**Key options:**
- `--manifest <path>` — sparse `appxmanifest.xml` to read identity from (defaults to a `sparse/` folder beside the target first, then in the current directory — where `winapp init --exe --sparse` writes it — then beside the target and in the current directory)
**Requires:** a sparse `appxmanifest.xml` + the target `.exe` or `.xml`/`.manifest`

### `winapp run [<input>]`
**Purpose:** Build and/or package a Windows app and launch it — for **packaged** apps this simulates a full MSIX install with package identity; for **unpackaged** apps it launches the built `.exe` directly (no package identity). Returns the launched process ID for debugger attachment. Operates in one of two modes, auto-selected from the input:
- **Folder mode** — input is a build-output folder (contains `Package.appxmanifest`/`AppxManifest.xml`). Creates a loose-layout package, registers it with Windows, and launches it. Original behavior, unchanged.
Expand Down Expand Up @@ -329,6 +341,15 @@ winapp create-debug-identity ./myapp.exe # Register sparse package for exe
# Launch your exe normally — it now has package identity
```

### Ship production sparse identity (unpackaged app + installer)
```bash
winapp init --exe ./bin/MyApp.exe --sparse # Step 1: generate identity-only manifest + assets into ./sparse/
winapp cert generate # dev/test cert (use a trusted cert for production)
winapp pack ./sparse/appxmanifest.xml --cert devcert.pfx # Step 2: build + sign the identity .msix
winapp embed-identity ./bin/MyApp.exe # Step 3: embed <msix> into the exe fusion manifest
# Your installer registers it: Add-AppxPackage -Path MyApp.identity.msix -ExternalLocation <install-dir>
```

### Clone and build existing project
```bash
winapp restore # Reinstall packages from winapp.yaml
Expand Down
Loading
Loading