Repository navigation
Installing Python install manager MSIX for all users #119
Description
Activity
- pinned this issue
on May 21, 2025 So far I believe (but haven't tested at scale) that the best way to do this is to create a Scheduled Task that runs for all users at login that launches:
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -Command Add-AppxPackage <path to MSIX>Or alternatively (without needing a pre-downloaded MSIX):
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -Command Add-AppxPackage -AppInstallerFile https://www.python.org/ftp/python/pymanager/pymanager.appinstallerIt unfortunately pops up a command window briefly, but otherwise it works best for ensuring all the aliases are set correctly and it's ready for the user to use. It doesn't seem to have any problems running again if it's already installed, and won't downgrade (though it will fail in this case).
A close second approach so far is the
Add-AppxProvisionedPackagePowerShell command, which when run as admin with an MSIX file, will provision the package and enable auto-install for all users next time they log in:Add-AppxProvisionedPackage -Online -PackagePath python-manager-25.0.msixUnfortunately it seems to have a bug right now where it doesn't correctly replace existing
python.exeandpython3.exealiases, even though it reports that they've been updated. Users don't get their first-run experience in this model (unless they dopy install --configure) and so won't find out. The other aliases seem to be okay.
Any other approaches? Has anyone tried using any of the management/deployment tools?
Reacted by David DennisonWhy would they do this to us 😂 😂 😂
MSIX supports installing a package for all users; the formal term is you 'provision' the package. This makes the package available to all users, compared to registering a package for a user. Here's the definitive way to install + uninstall a package 'per user' and 'for all users'. The simple explanation is...
- PerUser: Install=Add, Uninstall=Remove
- AllUsers: Install=Stage+Provision, Uninstall=Deprovision+Remove
For a more detailed explanation keep reading, but remember, you asked :P If your coffee's low I suggest a refill before proceeding...
Here's some code snippets with the key APIs. I'l use C# syntax as I'm not as familiar with pywinrt's projections but that's just syntax. It's the APIs that matter:
Per-User
Install
var options = new Windows.Management.Deployment.AddPackageOptions(); var packageManager = new Windows.Management.Deployment.PackageManager(); var packageUri = new Uri("file://c:/blah/foo.msix"); var result = packageManager.AddPackageByUriAsync(packageUri, options).GetAwaiter();
Uninstall
string packageFullName = "foo_1.2.3.4_neutral__1234567890abc"; Windows.Management.Deployment.PackageManager packageManager; var result = packageManager.RemovePackageAsync(packageFullName).GetAwaiter();
All Users
Install
var options = new Windows.Management.Deployment.StagePackageOptions(); var packageManager = new Windows.Management.Deployment.PackageManager(); var result = packageManager.StagePackageByUriAsync(packageUri, options).GetAwaiter(); string packageFamilyName = "foo_1234567890abc"; result = packageManager.ProvisionPackageForAllUsersAsync(packageFamilyName);
Uninstall
string packageFamilyName = "foo_1234567890abc"; result = packageManager.DeprovisionPackageForAllUsersAsync(packageFamilyName); string packageFullName = "foo_1.2.3.4_neutral__1234567890abc"; Windows.Management.Deployment.PackageManager packageManager; var options = Windows.Management.Deployment.RemovalOptions.RemoveForAllUsers; var result = packageManager.RemovePackageAsync(packageFullName, options).GetAwaiter();
The APIs have multiple overloads for behavioral variations but this is representative enough.
Explanation
It helps to understand 'install' isn't actually a proper noun for MSIX. To oversimplify...
- Stage = Lay down the package content on the disk appropriately. Suuuuper oversimplistically:
MKDIR pkgdir & UNZIP package.msix to pkgdir & ACL pkgdir - Register = Wire up the user profile for the package (create tiles on StartMenu, define file type associations, yadda yadda)
Stage is done once per machine, and when complete the files are readable but only the Deployment pipeline has write access. This lets us share the files across all users (no need for unique copies of files).
OTOH Register is a per-user action. To "install" a package for 5 users, ultimately, the package is staged once and registered 5x.
"Add" is a common operation that does both "Stage this package if necessary + Register it for the user".
This requires no special permissions, other than the API is called by a Medium IL process (or an AppContainer with the packageManagement capability).
To 'uninstall' you Remove the package which will Deregister it for the current user and, if there's no other references on the package, it's Destaged (i.e. removed from the system).
Package Lifetime and References
MSIX uses a GC style model for package lifetime. A package can be removed only if there are no 'references' to the package. There's several ways to define a reference e.g. Register'ing a package for a user. If a package is registered for 2 users and one Removes the package it's no longer Registered for that user but can't be removed from disk without breaking the other Registered user. When the 2nd user Removes the package its Deregistered and if that's the last reference on the package it's also automagically removed from disk.
There's several ways a 'reference' can be defined on a package. Register is one. Being listed in the Provisioned list of packages is another. There's a few other ways but this is beyond the scope of this discussion. Just know MSIX uses GC style semantics so packages will be removed from disk when the last reference is removed.
AllUser Explanation
Windows has a list of provision packages, alterable with De/Provision APIs. This list affects more than just the current user so altering it requires admin privilege (aka elevation).
Notice the order of operations. To install we must Stage then Provision because Provision takes a package family name for a package known to Windows at this point. Windows knows about packages once Staged so you can't (successfully) Provision a package before it's Staged (or Added).
Provisioned packages are registered for users at the next opportunity, usually their next login. At login Windows compares what a user has registered vs what the should have e.g. the Provisioned package list has the
Foopackage but when Bob logs in he doesn't have Foo registered, so Windows registers it for him during his login. This is why Calculator and other provisioned packages show up on everyone's StartMenu despite never having done anything to specifically request installation.On the flipside, during uninstall we Deprovision the package first, to make sure no new user gets it Registered (i.e. avoid timing/race conditions). Deprovisioning the package removes it from the Provisioned list. That's all. There's no change to user Registration, it's simply no longer in the list so new users don't get it offered to them.
Then once Deprovisioned we call Remove with the RemoveForAllUsers option. This deregisters the package for the current user, and other users logged in. IF a package is registered for a user and they're not logged in, the remove request is tracked for that user and processed when they next login.*
* Registering a package requires a user be logged in because various resources and APIs used during registration are only available and functional while the user's logged in. And for those thinking of HKCU yes that's one reason, but the simplest one. There's other more complex interactions which can't be done without the user being logged in.
Powershell Cmdlets
You can do this via the command line using Powershell cmdlets
Per-User
Install
Add-AppxPackage c:\blah\foo.msixUninstall
Get-AppxPackage foo | Remove-AppxPackage
All Users
Install
Add-AppxProvisionedPackage -Online -PackagePath c:\blah\foo.msix
Uninstall
Remove-AppxProvisionedPackage -Online -PackageName Foo -AllUsers
The
*-AppxProvisionedPackagecmdlets require admin privilege e.g. run from a command run you launched as an admin prompt.P.S. The cmdlets also support offline operation against a Windows image. You can also un/install packages via DISM.EXE, various other tools or (for the adventurous) use the WinRT API (above) from Powershell v5 via WinRT invocation. In the end it largely boils down to the same thing as these are functional equivalents to the PackageManager API (and in the online cases, are often literally thin wrappers over the API).
Reacted by BobSaidHi and bb7014650-cpuReacted by conioh and Alexander Hassthe best way to do this is to create a Scheduled Task that runs for all users at login that launches:
Ick! No, you don't need to do that.
A close second approach so far is the
Add-AppxProvisionedPackagePowerShell commandThis is the canonical and recommended solution to make a package available for all users.
Unfortunately it seems to have a bug right now where it doesn't correctly replace existing
python.exeandpython3.exealiases, even though it reports that they've been updated.You misunderstand - that's a feature, not a bug.
AppExecutionAliases follow FirstWriterWins semantics. When you install e.g. Python 3.12 as the first Python on the machine the AppExecutionAlias
python.exeis created for the 3.12 package and, since it's not already defined for the user, it's enabled. Now install Python 3.13 and apython.exealias is created for the 3.13 package but, since there's already a 'python.exe' alias the new one is NOT enabled.Same is true of
python3.exe- first installed for a user is enabled, 2nd+ are defined but user must explicitly enable.You can go into Settings > Apps > Advanced app settings > App execution aliases to see which aliases are defined, which are active, disable one if you like, and if there's multiple identical aliases you can choose which one is enabled. That's why you can have 7 versions of Python install with 7 instances of
python.exebut of coursepython.exewill only launch one of them (or non, if they're all disabled).If you desire unique aliases for each version of Python you'll need unique aliases e.g. Python 3.12 can define
python312.exe, Python 3.13python313.exeetc (in addition to the sharedpython.exeandpython3.exe)Reacted by HGP23 and BobSaidHiReacted by conioh and Alexander HassReacted by BobSaidHiYou misunderstand - that's a feature, not a bug.
AppExecutionAliases follow FirstWriterWins semantics. When you install e.g. Python 3.12 as the first Python on the machine the AppExecutionAlias
python.exeis created for the 3.12 package and, since it's not already defined for the user, it's enabled. Now install Python 3.13 and apython.exealias is created for the 3.13 package but, since there's already a 'python.exe' alias the new one is NOT enabled.There's an additional feature known as
uap8:AllowOverridethat is set on the redirector's aliases (we added it to the OS specifically for this scenario), and it disables the semantics you describe. That flag is being ignored for a system-wide provision, but it works great for a per-user install. The inconsistency there is the bug.the best way to do this is to create a Scheduled Task that runs for all users at login that launches:
Ick! No, you don't need to do that.
If your only hammer is Group Policy, what's the better option than this? (I know it's not the only hammer, but I keep encountering "IT" "Professionals" who believe it is, and there's only so much arguing I can do with each of them...)
There's an additional feature known as
uap8:AllowOverridethat is set on the redirector's aliases (we added it to the OS specifically for this scenario), and it disables the semantics you describe.Ahhh. You learn something new every day :-)
That flag is being ignored for a system-wide provision, but it works great for a per-user install. The inconsistency there is the bug.
:-(
Known issue
https://task.ms/57641304Reacted by conioh and AndrewThere's a lack of clear information online about how best to install an MSIX for all users of a machine, and a number of approaches that seem to half work.
This issue is to attract discussion until we can figure out the best ways and document them.
Known issue
https://task.ms/57641304It seems that this link is not available to users that don't have an account (or aren't an external user) in Microsoft's Azure tenant.
Is there some way us non-Microsoft folks can see the status of this issue?
So far I believe (but haven't tested at scale) that the best way to do this is to create a Scheduled Task that runs for all users at login that launches:
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -Command Add-AppxPackage <path to MSIX>
...
It unfortunately pops up a command window briefly...Here's a way around this. AddAppXPackage.js:
var Args = WScript.Arguments; if ( Args.Unnamed.Count == 0 ) { WScript.Echo("Usage: AddAppXPackage.js <MsixFileName>"); WScript.Quit(); } function scriptPath() { return WScript.ScriptFullName.substring(0, WScript.ScriptFullName.length - WScript.ScriptName.length); } var FSO = new ActiveXObject("Scripting.FileSystemObject"); var WshShell = new ActiveXObject("WScript.Shell"); var MsixFileName = Args.Unnamed.Item(0); // Assume script's directory if not specified if ( MsixFileName.indexOf("\\") == -1 ) { MsixFileName = FSO.BuildPath(scriptPath(), MsixFileName); } if ( ! FSO.FileExists(MsixFileName) ) { WScript.Echo("File not found: " + MsixFileName); WScript.Quit(2); // ERROR_FILE_NOT_FOUND } // FSO.GetSpecialFolder(1) is System32 directory var PowerShell = FSO.BuildPath(FSO.GetSpecialFolder(1), "WindowsPowerShell\\v1.0\\powershell.exe"); var CommandLine = PowerShell + " " + "-NoProfile -NonInteractive -Command Add-AppxPackage " + "'" + MsixFileName + "'"; // 0 = run hidden; false = run asynchronously WshShell.Run(CommandLine, 0, false);Run this using
wscript.exeand the MSIX filename as a parameter. You can omit the path of the MSIX file (specify just its name) if the MSIX file sits in the same directory as AddAppxPackage.js.Running this script with
wscript.exemeans there's no initial console/terminal window, and the script itself executespowershell.exein a hidden window.Is there some way us non-Microsoft folks can see the status of this issue?
Afraid not, you just need to poke someone with access and ask them to check. But it makes it easier to connect public issues with the internal work so they don't get lost so easily.
There's a lack of clear information online about how best to install an MSIX for all users of a machine, and a number of approaches that seem to half work.
This issue is to attract discussion until we can figure out the best ways and document them.