Search code examples
pythonwixpyinstalleruninstallationmsix

How can I programmatically uninstall my MSIX python app?


I've just written my first MSIX app in python 3. I use pyinstaller to generate an EXE. Then I use WiX Toolset to generate an MSI. I then use MSIX Packaging Tool to create the MSIX. There's probably an easier way to go from code to MSIX, but that's what I've gotten to work so far.

Ideally, I'd like to capture an onuninstall event and throw a GUI prompt asking the user why they are uninstalling. I can do this in the MSI. However, my understanding is that MSIX offers no onuninstall event. Please let me know if you know differently!

Since I apparently can't catch the MSIX uninstall event, my next preference is to offer the user a way to uninstall the app from the tray icon. The user selects an uninstall tray menu button from my app's icon, which pops up a window where the app then asks them why they are uninstalling. They type in an answer, and then click the submit button. Then, the app should completely uninstall itself. This also works well in the MSI. However, I can't get it to work in the MSIX.

Here's what works in python with the MSI installed:

subprocess.call('msiexec.exe /x {' + myguid + '}', shell=True)

However, the MSIX, which is built from the MSI, throws this popup error message when that line runs, and never actually uninstalls the app:

This action is only valid for products that are currently installed.

I've tried using the GUID from my WXS file's <Product> entry, hard-coded, just to see if it would work. That one worked for uninstalling the MSI, but not the MSIX. I also tried getting the GUID dynamically, but that didn't work for either MSI or MSIX, both producing the same error as above. Here is how I got the GUID dynamically:

from System.Runtime.InteropServices import Marshal
from System import Reflection

myguid = str(Marshal.GetTypeLibGuidForAssembly(
    Reflection.Assembly.GetExecutingAssembly()
)).upper()

While running the MSI (where I have far better logging than in the MSIX), it appears as though GetExecutingAssembly() gets an assembly with a FullName of Python.Runtime, which is certainly something I don't want to uninstall. GetCallingAssembly() produces the same result. GetEntryAssembly() produces a null value.

I looped through AppDomain.CurrentDomain.GetAssemblies() to see what was listed and my app was not listed, though I saw many libraries that it uses.

So, any thoughts on how I can get the app to programmatically uninstall? Maybe a suggestion on how I can get the correct GUID for the MSIX app, if that's the problem? DotNet code should be fine. I can probably figure out how to translate it to python.

Or better yet, any idea how I can catch the MSIX uninstall event and run some custom code?

Thanks in advance!


Solution

  • From what I know at this moment catching the uninstall event is not possible. I don't recommend implementing the approach you suggested (the tray icon) but to ask your question, you can use the MSIX PowerShell commandlets to install and uninstall MSIX packages programmatically.

    Also, I noticed you are really torturing yourself to create the MSIX package.

    The MSIX Packaging Tool was created by Microsoft for IT professionals that don't have access to the source code. Developers can either use the Windows Application Packaging Project project template (if they use Visual Studio) or other third-party tools, like Advanced Installer or Wix (from what I know there was a Wix extension that can be used to build MSIX packages).

    Here is a quick tutorial on how to create an MSIX from scratch, easier with Advanced Installer: https://www.advancedinstaller.com/create-msi-python-executable.html

    Disclaimer: I work on the team building Advanced Installer.