If you ship a Windows desktop application, the packaging step is usually the least interesting part of your pipeline and the one most likely to break it. The build itself is clean. Then the packaging stage needs makeappx.exe, makeappx.exe lives in the Windows SDK, and suddenly your build agent needs a multi-gigabyte SDK install pinned to a specific version just to turn a folder into a package.

There is a way around that, and it does not involve giving up the tooling conventions your pipeline already uses.

Why the SDK dependency is the actual problem

The Windows SDK is a developer workstation tool. Putting it on a build agent creates three recurring costs.

The first is image size and provisioning time. A hosted agent that has to install the SDK before it can package pays that cost on every clean run. A self-hosted agent that has it baked in carries a much larger image.

The second is version drift. Packaging behaviour can differ between SDK versions, so the moment one agent has a different SDK than another, you get a build that passes on one machine and fails on another for reasons that have nothing to do with your code.

The third is the awkward one. The SDK is a large surface to justify to whoever signs off on what goes onto build infrastructure, and it is doing one job for you: producing a package from a folder.

The packaging engine is already on the machine

The important detail is that MSIX packaging is not something the SDK does by itself. The AppxPackaging engine is a component of Windows. makeappx.exe is a command line wrapper around it that happens to ship in the SDK.

That means a tool can call the same operating system component directly. forge_msix, the command line interface in EtherApps Forge, does exactly that. It is a drop-in replacement for makeappx.exe: the same verbs, the same switches, the same expectations about what goes in and what comes out. If your pipeline currently calls makeappx pack, you change the executable name and the stage keeps working.

The makeappx-compatible verbs are pack, unpack, bundle, unbundle, validate and make-pri. If all you need is the SDK behaviour without the SDK on the agent, that set covers it.

A minimal pipeline stage

The shape of the thing is unremarkable, which is the point.

# Package a build output folder into an MSIX
forge_msix pack /d .\publish\win-x64 /p .\artifacts\ContosoApp.msix
# Validate before anything downstream touches it
forge_msix validate /p .\artifacts\ContosoApp.msix

Two things are worth knowing about validate. It returns a non-zero exit code when the package is not valid, which is what lets you fail the build rather than discover the problem at deployment. And exit code 2 specifically means the package was analysed successfully but is invalid, as opposed to the tool failing to run at all. Treating those two cases differently in your pipeline is the difference between a useful gate and a confusing one.

Signing without a manual step

Packaging and signing tend to get separated because they use different tools, which is how unsigned packages end up reaching a test ring.

Signing from the certificate store by thumbprint keeps the private key where it belongs and keeps the pipeline declarative:

forge_msix sign /p .\artifacts\ContosoApp.msix `
  /thumbprint <certificate-thumbprint> `
  /timestamp http://timestamp.digicert.com

Use an RFC 3161 timestamp server. Without a timestamp, your package stops validating the moment the signing certificate expires, even though it was legitimately signed while the certificate was live. With one, the signature stays valid beyond the certificate's own lifetime.

Note that sign is one of the value-added verbs rather than one of the makeappx-compatible ones, so it needs an active Forge licence. The same applies to create, test, assess, the cert- verbs and the dev- verbs.

Shortening the inner loop

The slowest part of MSIX work is usually not the packaging. It is the loop: package, install, discover something is wrong, uninstall, change one line, repeat.

The dev-register and dev-test verbs let you register a package from a folder without building and installing a full package each time. For anyone iterating on manifest capabilities, file type associations or entry points, this removes most of the waiting.

Publishing updates

Once packages are being produced and signed by the pipeline, distribution is the remaining question. make-appinstaller generates an App Installer update feed, which gives you a URL that installed clients check for new versions. For an ISV shipping outside a managed estate, that is often the whole distribution story: publish the package and the feed, and existing installs update themselves.

Where this fits

This is a different problem from the one most application packaging content addresses. The usual scenario is an IT team facing an application whose installer is long gone and whose original vendor may no longer exist, which is a capture and remediation problem. That work lives on our MSIX packaging and deployment route.

What is described here assumes the opposite: there is a healthy build folder, nothing needs capturing, and the friction is everything that happens after the build succeeds. That is the MSIX packaging for developers and ISVs route, and it is a genuinely separate audience with a separate set of problems.

If you also need compatibility fix-ups for an application that packages cleanly but misbehaves inside the container, the Package Support Framework staging added in Forge 1.0.6 is covered in the 1.0.6 release notes.

Try it against your own pipeline

The honest test is whether your existing packaging stage keeps working when you change the executable name. EtherApps Forge is a Windows desktop application with a free 7-day trial, so you can run that test against a real build before deciding anything.

Explore MSIX packaging for developers and ISVs