June 30th, 2026
intriguinglike3 reactions

MSIX Per-User vs All Users: Install, Provision, and Uninstall Packages

Principal Software Engineer

MSIX supports making a package available to all users; the formal term is provisioning. Provisioning a package family makes it available to all users, whereas registration makes a package available to a single user.

Per-User vs All Users

Traditional installers often provide a “per-machine” installation mode to make software available to all users. In MSIX, the equivalent user experience is achieved through provisioning.

To install a package for all users, first stage the package on the machine and then register it for each user. To facilitate this, Deployment manages a ‘list of provisioned package families’ and ensures provisioned packages are automatically registered for all users.

NOTE: Deployment provisions package families, not specific packages. But ‘list of provisioned package families’ is a mouthful, so it’s often referred to as the ‘list of provisioned packages’, or simply ‘provisioned packages’. Likewise, ‘provisioned package family’ is wordy, thus often referred to as ‘provisioned package’. Regardless of the phrasing, package families are what’s provisioned.

User Experience

At logon before the desktop appears, Deployment compares the user’s registered packages against those staged on the machine. If a newer version is present, Deployment registers it for the user.

The provisioned list is also checked at this time. Deployment registers the latest version in each provisioned family if the user has no packages in that family, i.e. add the package for the user. This ensures users have all provisioned packages registered and available for use, regardless of whether it’s a new user’s first logon or previously existing user’s Nth logon.

How does provisioning work?

At logon, Deployment determines what the user should have versus what they currently have, and updates accordingly. The logic amounts to:

Package[] todo = new Package[]

// Are there newer versions of packages the user has registered?
Package[] current = PackageManager.FindPackagesForUser("")
FOREACH (pcurrent IN current)
  Package ptodo = null

  // What packages in the family exist on the machine?
  FOREACH (p IN PackageManager.FindPackages(p))

    // Is this package newer than the registered packaged?
    IF p.Version > pcurrent.Version

      // Is this package a newer version than the previously found package (if any)
      IF (ptodo == null) OR (p.Version > ptodo.Version)
        // New 'better' fit
        ptodo = p
    ENDIF
  ENDIF

  // Did we find a newer package we should register?
  IF (ptodo != null)
    todo += ptodo
  ENDIF
NEXT

// Any provisioned package families the user doesn't have registered?
Package[] provisioned = PackageManager.FindProvisionedPackages()
FOREACH (p IN provisioned)
  IF (p.PackageFamilyName NOT IN current)
    pmax = FindHighestVersionPackageInFamily(p.PackageFamilyName)
    todo += pmax
  ENDIF
NEXT

// Register the newer and provisioned packages the user should have but doesn't
FOREACH (p IN todo)
  PackageDeploymentManager.RegisterPackage(p)
NEXT

Deployment determines the desired packages the user should have by comparing the provisioned list plus packages on the machine versus packages registered for the user:

  • If a desired package is registered => Nothing to do
  • If an older version is registered => Register the desired (newer) package (aka update)
  • If no package in the family is registered => Register the desired package (aka add)

Deployment updates users to the latest version of provisioned packages during user logon, before the desktop appears. From the user’s perspective they’re automagically updated to the latest version of their installed software.

HOWTO Install Per-User vs All Users

Installation for all users differs from installation for the current user.

Per-User

A package needs to be staged and registered to be accessed by a user. This can be simplified as an Add operation, as explained in There is no Install – it’s ‘Stage’ and ‘Register’ and Add vs Stage and Register.

var packageDeploymentManager = new Microsoft.Windows.Management.Deployment.PackageDeploymentManager();
var packageUri = new Uri("https://contoso.com/ContosoParts-v1.2.3.4-x64.msix");
var options = new Microsoft.Windows.Management.Deployment.AddPackageOptions();
var result = await packageManager.AddPackageByUriAsync(packageUri, options);

All Users

You must stage a package on the system before you can provision it. For a new package family not yet present on a system this requires staging a package and then provisioning its package family via ProvisionPackageAsync():

var packageDeploymentManager = new Microsoft.Windows.Management.Deployment.PackageDeploymentManager();
var packageUri = new Uri("https://contoso.com/ContosoParts-v1.2.3.4-x64.msix");
var options = new Microsoft.Windows.Management.Deployment.StagePackageOptions();
var result = await packageDeploymentManager.StagePackageByUriAsync(packageUri, options);

string packageFamilyName = "Contoso.Parts_1234567890abc";
result = await packageDeploymentManager.ProvisionPackageAsync(packageFamilyName);

.ProvisionPackageAsync() will…

  1. Add the package family to the provisioned list
  2. Register the package for the current user, if necessary AND current user is not LocalSystem1

Deployment registers the newly provisioned package family for other users at their next logon.

HOWTO Uninstall Per-User vs All Users

Uninstall for all users vs current user is similar to installation.

Per-User

Uninstalling a package for a user is a simple Remove operation.

var packageDeploymentManager = new Microsoft.Windows.Management.Deployment.PackageDeploymentManager();
string packageFamilyName = "Contoso.Parts_1234567890abc";
var options = new Microsoft.Windows.Management.Deployment.RemovePackageOptions();
var result = await packageDeploymentManager.RemovePackageAsync(packageFamilyName, options);

RemovePackage*Async() has multiple method overloads. The simplest is to remove by package family name, as above.

Alternatively, one could specify a PackageFullName for a specific package to remove, but the package may have been updated since the initial install. RemovePackageAsync(pkgfullname,...) will fail if the exact package is not currently registered.

Remove operations implicitly include ForceTargetAppShutdown, causing running processes to quit, so the package can be cleanly deregistered.

In the simplest case there’s (now) no references to the package, so it will also be destaged.

All Users

To remove a package for all users, first remove the package family from the provisioned list via DeprovisionPackageAsync() AND deregistered for all users. The latter requires RemoveForAllUsers option.

var packageDeploymentManager = new Microsoft.Windows.Management.Deployment.PackageDeploymentManager();
string packageFamilyName = "Contoso.Parts_1234567890abc";
result = await packageDeploymentManager.DeprovisionPackageAsync(packageFamilyName);

var packageDeploymentManager = new Microsoft.Windows.Management.Deployment.PackageDeploymentManager();
var options = new Microsoft.Windows.Management.Deployment.RemovePackageOptions();
options.RemoveForAllUsers = true;
var result = await packageDeploymentManager.RemovePackageByFamilyNameAsync(packageFamilyName, options);

WARNING: .Deprovision...() should be called before .Remove...(). If not, new (unexpected) registrations could occur after .Remove...() but before .Deprovision...() removes the package family from the provisioned list.

Do I need to reprovision after staging a newer package?

If v1 is provisioned and I stage v2, do I need to reprovision?

No.

Provisioning tracks package families, not individual packages. You do not provision a specific package version; you provision a package family. As a result, newer versions do not need to be reprovisioned. Simply stage the newer package. Because provisioning tracks package families rather than package versions, Deployment will register the highest available version in the family at user logon.

Security

MSIX allows users to query and change their package inventory, but requires admin privilege to see or impact other users or machine-wide data.

Thus ProvisionPackageAsync(), DeprovisionPackageAsync() and RemoveForAllUsers require admin privilege.

Recap

The canonical operations are:

  • Per-user: Add → Remove
  • All users: Stage + Provision → Deprovision + RemoveForAllUsers

Provisioning is the MSIX mechanism that provides the traditional “install for all users” experience.


1 Deployment can register packages for any user except LocalSystem. This is a known limitation, and lifting this restriction is tracked in our backlog.

Author

Howard Kapustein
Principal Software Engineer

MSIX Development Engineer/Architect

4 comments

Sort by :
  • Koby Kahane

    Howard, this is a nice series of posts so far.

    I’d love if a future post in the series could deep dive into why AppX/MSIX installation currently doesn’t work within a Windows Server Container (Docker).

    • Howard KapusteinMicrosoft employee Author

      Glad you’re enjoying them. Always good to hear feedback (for good or ill; hard to improve if we don’t know 🙂

      Good suggestion! I’ve added Docker to the topic list. Thanks

  • Lucas A. 1 day ago

    The package state lands in the StateRepository (AppRepository), and unfortunately it isn't entierly cleaned up when User Profiles are deleted. On normal end‑user machines this barely matters, but on RDS farms with thousands of monthly users it becomes a real problem: the StateRepository accumulates large amounts of stale entries for users that no longer exist, and because this data is replicated into the registry, the system starts exhibiting odd delays and sluggish behavior.
    It would be cool if you could purge old references from the StateRepository when user profiles are deleted (I'm referring to the proper DeleteProfile winapi of course).

    Read more
    • Howard KapusteinMicrosoft employee Author

      >accumulates large amounts of stale entries for users that no longer exist

      That's unexpected. Please use FeedbackHub to file a bug with more details about problematic data not purged when a user profile is deleted. Thanks.

      Some data follows a garbage collection (GC) model so it's cleaned up eventually, but that's very small e.g. a 'user' definition that's a few dozen bytes (mainly a SID). Only potentially notable data I can think of is a log of activity. That's GC'd, but larger systems with more activity will have more data. This shouldn't be a problem but if so, well, there's always a...

      Read more