DEV Community

Shipping a WPF side-project to winget and the Microsoft Store — and the autostart bug that only happens when packaged

I maintain CapyBro, a small open-source Windows app (.NET 8 / WPF) that rewrites or translates selected text with AI on a global hotkey. This week it picked up two distribution upgrades — and one of them surfaced a bug that's completely invisible until you package the app. Sharing both in case you ship desktop .NET.

1. It's on winget now

winget install RomanTykhonenko.CapyBro
Enter fullscreen mode Exit fullscreen mode

The PR to microsoft/winget-pkgs got merged after the usual automated validation plus a human moderator pass (New-Package submissions always get a person to look). A few notes that might save you time:

  • I generated the manifest with wingetcreate — it handles the schema, the SHA256, and the device-flow auth that opens the PR for you. Far less error-prone than hand-writing the YAML.
  • My NSIS installer is unsigned, and it still passed validation cleanly, no warning labels. winget does not require code signing to get in. (SmartScreen on first run is a separate, end-user thing — not a winget gate.)
  • Turnaround was a few days. The bot does the heavy lifting; you mostly wait on a moderator.

Updates later are a one-liner:

wingetcreate update RomanTykhonenko.CapyBro --version <X> --urls <url> --submit
Enter fullscreen mode Exit fullscreen mode

2. The autostart bug that only exists when packaged

The Microsoft Store build is MSIX-packaged. The plain .exe uses the classic autostart trick: write HKCU\Software\Microsoft\Windows\CurrentVersion\Run. Works fine, shipped that way for ages.

Inside an MSIX package, that silently stops working. Writes to that Run key get virtualized into the package's private view, and Windows won't honor a virtualized Run entry for login startup. So the user flips "Start on login," the app dutifully writes the key, reports success... and never launches on the next login. No error, nothing in the logs.

The fix is to detect at runtime whether you're packaged, and switch backends:

  • Detection: P/Invoke GetCurrentPackageFullName. It returns APPMODEL_ERROR_NO_PACKAGE when unpackaged, success when packaged. That one boolean picks the implementation.
  • Packaged backend: the Windows.ApplicationModel.StartupTask WinRT API, plus a windows.startupTask extension declared in the manifest (with uap10:Parameters to pass the same silent flag the .exe uses). Now the OS owns the startup entry — the user can even toggle it from Settings > Startup apps.
  • Unpackaged backend: the original Run-key writer, untouched.

Wiring it behind an IStartupTaskBackend chosen by a runtime check meant the .exe kept its exact old behavior and only the Store build got the new path — zero risk of regressing the thing that already worked.

A gotcha inside the gotcha: pulling in the WinRT projection (Windows.ApplicationModel.*) bumps your TFM to something like net8.0-windows10.0.17763.0 and drags in a chunky SDK projection. I didn't want the lean .exe carrying that, so the project multi-targets: the plain Win32 build excludes the WinRT files via #if, and only the packaged target compiles them in.

3. Native ARM64

While repackaging I added a native ARM64 build (win-arm64, ReadyToRun, cross-compiled on an x64 host) as a second package in the same Store submission. WPF runs natively on ARM64 .NET 8, so Surface / Snapdragon users skip the x64 emulation layer now. Honest caveat: I don't own ARM hardware, so it shipped smoke-tested on the build side but not on a real device — if you're on Windows-on-ARM, I'd genuinely love a sanity check.


winget for the CLI crowd; a Store build that actually autostarts and runs native on ARM64. It's MIT-licensed if you want to poke at the code: https://github.com/phantasmat2018/capy-bro and https://capybro.app. Happy to answer anything about the winget or MSIX side in the comments.

Top comments (0)