Skip to content

Preserve MSIX app data (LocalState) across winapp run re-deploys#403

Merged
nmetulev merged 2 commits intomainfrom
preserve-localstate-on-redeploy
Apr 7, 2026
Merged

Preserve MSIX app data (LocalState) across winapp run re-deploys#403
nmetulev merged 2 commits intomainfrom
preserve-localstate-on-redeploy

Conversation

@nmetulev
Copy link
Copy Markdown
Member

@nmetulev nmetulev commented Apr 6, 2026

Problem

winapp run called RemovePackageAsync without RemovalOptions, which wiped the package's LocalState, RoamingState, and Settings folders on every re-deploy. Apps writing to ApplicationData.Current.LocalFolder or LocalApplicationData would lose all persisted data between runs.

This was reported ~15 times as a pain point — developers waste 10-30 minutes debugging why data doesn't persist across restarts.

Fix

  • UnregisterAsync now passes RemovalOptions.PreserveApplicationData by default when removing dev-mode packages before re-registration. This is a Windows API flag specifically designed for packages registered in development mode.
  • Added --clean flag to winapp run for when a fresh start is needed (e.g., reset corrupted state or test first-run behavior).
  • Explicit unregister (winapp unregister, --unregister-on-exit) still removes application data, since those are intentional cleanup actions.

Changes

Area Files
Core fix PackageRegistrationService.cs, IPackageRegistrationService.cs
Plumbing MsixService.Identity.cs, IMsixService.cs, RunCommand.cs, UnregisterCommand.cs
npm SDK winapp-commands.ts
Tests FakeMsixService.cs, FakePackageRegistrationService.cs
Docs usage.md + autogenerated cli-schema.json, npm-usage.md, SKILL.md

Behavior matrix

Scenario App data
winapp run ./bin/Debug (re-deploy) Preserved ✅ (new default)
winapp run ./bin/Debug --clean Wiped
winapp unregister Wiped
winapp run --unregister-on-exit Wiped on exit

Previously, winapp run called RemovePackageAsync without RemovalOptions,
which wiped the package's LocalState, RoamingState, and Settings folders
on every re-deploy. Apps writing to ApplicationData.Current.LocalFolder
would lose all persisted data between runs.

Now UnregisterAsync passes RemovalOptions.PreserveApplicationData by
default when removing dev-mode packages before re-registration. This
preserves application data across winapp run invocations.

Added --clean flag to winapp run for when a fresh start is needed (e.g.,
reset corrupted state or test first-run behavior).

Explicit unregister (winapp unregister, --unregister-on-exit) still
removes application data, since those are intentional cleanup actions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 6, 2026 20:40
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Updates winapp run re-deploy behavior so MSIX app data (e.g., LocalState / settings) is preserved by default, with an explicit --clean flag to opt into wiping state when needed. This reduces the “data disappears between runs” pain point while keeping explicit cleanup commands destructive.

Changes:

  • Add preserveAppData support to package removal and default it to preserving application data for run re-deploy scenarios.
  • Introduce --clean on winapp run (and plumb it through CLI + npm wrapper) to force a fresh state.
  • Update docs/schema and test fakes to reflect the new flag/signatures.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/winapp-npm/src/winapp-commands.ts Adds clean option and forwards --clean to the CLI.
src/winapp-CLI/WinApp.Cli/Services/PackageRegistrationService.cs Implements RemovalOptions.PreserveApplicationData via new preserveAppData parameter.
src/winapp-CLI/WinApp.Cli/Services/MsixService.Identity.cs Plumbs clean into identity creation and uses it to control app-data preservation on unregister.
src/winapp-CLI/WinApp.Cli/Services/IPackageRegistrationService.cs Extends unregister API with preserveAppData.
src/winapp-CLI/WinApp.Cli/Services/IMsixService.cs Extends loose-layout identity API with clean.
src/winapp-CLI/WinApp.Cli/Commands/UnregisterCommand.cs Ensures explicit unregister wipes app data.
src/winapp-CLI/WinApp.Cli/Commands/RunCommand.cs Adds --clean option and passes it through to MSIX service.
src/winapp-CLI/WinApp.Cli.Tests/FakePackageRegistrationService.cs Updates fake signature for new unregister parameter.
src/winapp-CLI/WinApp.Cli.Tests/FakeMsixService.cs Updates fake signature for new clean parameter.
docs/usage.md Documents new default persistence + --clean behavior and examples.
docs/npm-usage.md Documents npm RunOptions.clean.
docs/cli-schema.json Adds --clean to generated CLI schema.
.github/plugin/skills/winapp-cli/setup/SKILL.md Updates skills doc to include --clean option.
Comments suppressed due to low confidence (2)

src/winapp-CLI/WinApp.Cli/Services/PackageRegistrationService.cs:95

  • UnregisterAsync currently matches packages using only p.Id.Name == packageName and then removes every match. This can unintentionally target packages from other publishers (including Store/MSIX installs) that share the same identity name. It also means preserveAppData=true may be applied to non-development-mode packages, even though RemovalOptions.PreserveApplicationData is only supported for dev-mode registrations, potentially causing removals to fail or behave unexpectedly. Consider tightening the removal criteria (e.g., require publisher/family name, and/or only remove pkg.IsDevelopmentMode packages when preserving data) so winapp run/unregister cannot affect unrelated installed apps.
    public async Task<bool> UnregisterAsync(string packageName, bool preserveAppData = true, CancellationToken cancellationToken = default)
    {
        var pm = new PackageManager();

        // FindPackagesForUser with name+publisher requires both to match.

src/winapp-CLI/WinApp.Cli.Tests/FakeMsixService.cs:28

  • FakeMsixService doesn’t record the clean argument passed to AddLooseLayoutIdentityAsync, which makes it hard for tests to validate --clean behavior end-to-end. Consider storing the full call (including clean) rather than only the manifest path.
    public Task<MsixIdentityResult> AddLooseLayoutIdentityAsync(
        FileInfo appxManifestPath,
        DirectoryInfo inputDirectory,
        DirectoryInfo outputAppXDirectory,
        TaskContext taskContext,
        bool clean = false,
        CancellationToken cancellationToken = default)
    {
        AddLooseLayoutCalls.Add(appxManifestPath.FullName);
        if (ExceptionToThrow != null)

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

Comment thread src/winapp-CLI/WinApp.Cli/Commands/RunCommand.cs
Comment thread src/winapp-CLI/WinApp.Cli.Tests/FakePackageRegistrationService.cs
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 6, 2026

Build Metrics Report

Binary Sizes

Artifact Baseline Current Delta
CLI (ARM64) 28.46 MB 28.46 MB 📈 +1.5 KB (+0.01%)
CLI (x64) 28.91 MB 28.91 MB 📈 +1.5 KB (+0.01%)
MSIX (ARM64) 12.05 MB 12.05 MB 📈 +2.1 KB (+0.02%)
MSIX (x64) 12.81 MB 12.81 MB 📈 +0.4 KB (+0.00%)
NPM Package 25.07 MB 25.07 MB 📈 +0.9 KB (+0.00%)
NuGet Package 25.15 MB 25.15 MB 📈 +1.6 KB (+0.01%)

Test Results

651 passed out of 651 tests in 337.4s (+4 tests, +14.5s vs. baseline)

Test Coverage

30.8% line coverage, 42.4% branch coverage · ✅ no change vs. baseline

CLI Startup Time

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


Updated 2026-04-06 21:19:56 UTC · commit bcc2b09 · workflow run

- FakePackageRegistrationService now records (packageName, preserveAppData)
  tuples so tests can assert on data preservation behavior.
- FakeMsixService now records (manifestPath, clean) tuples.
- Added option parsing tests for --clean flag.
- Added handler tests verifying clean=true/false is passed through to
  the MSIX service.
- Updated existing tests to use new tuple accessors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@nmetulev nmetulev merged commit 4aa2b11 into main Apr 7, 2026
11 checks passed
@nmetulev nmetulev deleted the preserve-localstate-on-redeploy branch April 7, 2026 20:34
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.

3 participants