How to package a QT application into an MSIX
QT is a very powerful framework. Allow you to develop and release cross-platform applications. But there comes a point, at the end of development, you need to distribute your application. And that's where MSIX comes in.
MSIX is a Windows app packaging format developed by Microsoft, intended to modernize and improve upon older formats like MSI (Microsoft Installer). MSIX is designed to simplify deployment, management, and security of applications on Windows platforms (and beyond). It is also mandatory if you want to release your application on the Microsoft Store.
More about MSIX
Okay, MSIX seems good. But you've never used it or very little. Why? Already, because some developers are resistant to changes, especially in native software. But also because some limitations really exist. Unlike MSI, it is not possible to propose different installation profiles. A code signing certificate is also mandatory (except for the Microsoft Store). And the cherry on top is that you and the user cannot choose where they want to install your app. Nor the path and the drive (post installation or if the user, in Windows settings, set it explicitly). Not a big deal for me, but in specific scenarios, it can be.
By default, your application is installed at the root of the C drive, under the "WindowsApps" folder, protected by Windows (even an admin can't access it easily). The advantage, Windows registry is simulated inside it, so when the user uninstall your app no trace rest.
This is not really a problem anymore, but a few years ago, your application was mandatory sandbox and you had to define permissions, like for a UWP application. Today, this is no longer the case and it is possible to start in "runFullTrust", so like any win32 application.
So, finally, here are the advantages of MSIX (I recommend you this post from Serif, the developer of Affinity Designer & co who migrated the v2 to msix), but in short :
- MSI installations achieve around an 85% success rate, often leading to user-reported installation issues with version 1. In contrast, MSIX aims to deliver a 99.9% installation success rate. MSI requires administrative privileges for installation, while MSIX installs are per-user and prevent redundant files, conserving disk space.
- With MSI, applications are not isolated from other software, which means other programs can potentially disrupt MSI-installed apps—a problem seen with some third-party software. MSIX, however, uses sandboxing to keep applications isolated and safe from interference.
- Updating MSI apps involves downloading the entire 550 MB installer and manually installing it. MSIX supports smaller, in-app delta updates, making updates faster and more efficient. MSI also limits updates to one app at a time, whereas MSIX can update multiple apps simultaneously.
- Finally, uninstalling an MSI app may leave remnants behind, unable to fully restore the pre-installation state. MSIX installations, however, are compartmentalized, bypassing the need for registry dependencies or unique filesystem locations. This ensures a clean uninstall, with installation and app data directories fully removed.
So, how to package a QT application?
Here we go! It's time. First of all, you need to setup your QT project with MSVC (the Visual Studio compiler) and CMake on a Windows 10 or 11 machine.
The AppxManifest
I recommend you to create, at the root of your project a new folder called "package" (the name doesn't care, but I use it in the future path). Now, inside it, create a new file "AppxManifest.xml". It is the most important file, here we defined permissions, icons, file type association if necessary (without using the Windows Registry !) and also dependencies.
Here the most simple one :
<?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/2010/manifest">
<Identity Name="Microsoft.SDKSamples.HelloWorldPackage"
ProcessorArchitecture="neutral"
Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
Version="1.0.0.0"
ResourceId="en-us"
/>
<Properties>
<DisplayName>HelloWorld</DisplayName>
<Description>Sample package containing a "Hello World!" application</Description>
<Logo>AppTile.png</Logo>
<PublisherDisplayName>Microsoft</PublisherDisplayName>
</Properties>
<Resources>
<Resource Language="en-us"/>
</Resources>
<Applications>
<Application Id="HelloWorld" StartPage="Default.html">
<VisualElements DisplayName="Hello World Sample App"
Logo="AppTile.png"
SmallLogo="AppTile.png"
Description="Hello world package!"
BackgroundColor="#AA00AA"
ForegroundText="light"
>
<DefaultTile WideLogo="AppTile.png" />
<SplashScreen Image="AppTile.png" />
</VisualElements>
</Application>
</Applications>
<Prerequisites>
<OSMinVersion>6.2.1</OSMinVersion>
<OSMaxVersionTested>6.2.1</OSMaxVersionTested>
</Prerequisites>
</Package>
I will not dwell on the Identity part, Windows version and other. But on the part that matters to us for MSIX. The rest depends on your application and Microsoft's documentation is very useful for that! You will also need to generate the "assets" that represent the different icon sizes that Windows will need, a simple .ico like for a win32 application will not be enough.
So what do we need for QT? As you know, once released, QT needs us to run the vc_redist.x64.exe file when installing on the user's machine because it depends on some DLLs for that. But like I say before, we can't to that with MSIX, we don't have "steps" during the installation process. But good news, Microsoft have a solution, just add :
<Dependencies>
<PackageDependency Name="Microsoft.VCLibs.140.00" MinVersion="14.0.24217.0" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
<PackageDependency Name="Microsoft.VCLibs.140.00.UWPDesktop" MinVersion="14.0.24217.0" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
</Dependencies>
In your AppxManifest.xml. This code automatically download (if necessary) from the Microsoft Store the vc_redist.x64.exe dependency. And voilà!
Another parameter to setup inside the manifest, it is the permissions. Because QT is a win32 application, we choose runFullTrust like so :
<Capabilities>
<rescap:Capability Name="runFullTrust"/>
</Capabilities>
And we are good for the AppxManifest.xml.
The CMake part
Now you have inside your package folder another folder called "assets" (check the Microsoft documentation for that, Visual Studio can generate them for you) and a AppxManifest.xml. Maybe, you can need a priconfig.xml and resources.pri files more advanced stuff, you can check the official documentation for that.
Now, we need to do some stuff inside your CMakeLists.txt in order to generate the MSIX installer. You can create a new add_custom_target
for packaging, you have to copy the AppxManifest and assets to your dist folder. For that, it is easy :
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/package/AppxManifest.xml ${CMAKE_SOURCE_DIR}/dist/AppxManifest.xml
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/package/assets ${CMAKE_SOURCE_DIR}/dist/assets
Then, we don't want to package the vc_redist.x64.exe because we don't use it, so you can do the following :
COMMAND ${CMAKE_COMMAND} -E remove -f ${CMAKE_SOURCE_DIR}/dist/vc_redist.x64.exe
And the most important part, you just have to run a command with the makeappx cli shipped with Visual Studio (if you don't have it, check in the Visual Studio installer if you have it).
COMMAND ${MAKEAPPX} pack -d ${CMAKE_SOURCE_DIR}/dist -p ${CMAKE_SOURCE_DIR}/dist/MaComptaAutoEntreprise.msix
So here, the full code :
find_program(MAKEAPPX NAMES makeappx)
find_program(SIGNTOOL NAMES signtool)
add_custom_target(Script-PackageMSIX
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/package/AppxManifest.xml ${CMAKE_SOURCE_DIR}/dist/AppxManifest.xml
COMMAND ${CMAKE_COMMAND} -E copy
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/package/assets ${CMAKE_SOURCE_DIR}/dist/assets
COMMAND ${CMAKE_COMMAND} -E remove -f ${CMAKE_SOURCE_DIR}/dist/vc_redist.x64.exe
COMMAND ${CMAKE_COMMAND} -E remove -f ${CMAKE_SOURCE_DIR}/dist/MyAppName.msix
COMMAND ${MAKEAPPX} pack -d ${CMAKE_SOURCE_DIR}/dist -p ${CMAKE_SOURCE_DIR}/dist/MyAppName.msix
COMMAND ${SIGNTOOL} sign /f ${CMAKE_SOURCE_DIR}/selfsignedcert.pfx /p password /fd SHA256 /td SHA256 /tr http://timestamp.comodoca.com ${CMAKE_SOURCE_DIR}/dist/MyAppName.msix
COMMENT "Creating MSIX package........."
SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
Code signing
Maybe you noticed, but there is a step of signing the MSIX. It is mandatory, but in our case, not having a certificate on hand, we will self-sign it.
It's this line inside the CMake configuration :
COMMAND ${SIGNTOOL} sign /f ${CMAKE_SOURCE_DIR}/selfsignedcert.pfx /p password /fd SHA256 /td SHA256 /tr http://timestamp.comodoca.com
Here, we use self signed certificat with a password "password". To create your pfx certificate file, you can use this commands :
$cert = New-SelfSignedCertificate -Type CodeSigning -Subject "CN=YourCompanyName" -CertStoreLocation "Cert:\CurrentUser\My"
And
Export-PfxCertificate -Cert "Cert:\CurrentUser\My\$($cert.Thumbprint)" -FilePath "C:\path\to\selfsignedcert.pfx" -Password (ConvertTo-SecureString -String "password" -Force -AsPlainText)
Be warned, this is only to be able to test our MSIX without having a paid certificate. But it is not production ready. We will even have to use a command line to install it (because of the self signed certificate).
So, if all goes well, you now have an MSIX file of the name of your application in your distribution folder. But surprise, if you try to launch it, the installer refuses by telling you that the MSIX does not have a valid signature "This app package’s publisher certificate could not be verified. Contact your system administrator or the app developer to obtain a new app package with verified certificates. The root certificate and all immediate certificates of the signature in the app package must be verified (0x800B010A)".
And this is normal, for security reasons, we would need a certified signature certificate (which you can buy from suppliers or for free by uploading your MSIX directly to the Microsoft Store with our self-signed certificate).
But don't worry, we are developers, and for testing purpose (to try the MSIX, if your program run smoothly once packaged) it is possible to force de installation from the command line :
Add-AppPackage -Path ".\MyAppName.msix" -AllowUnsigned
And voilà, you can now find your application inside the Start Menu of Windows and run it.
Last words regarding the Microsoft Store, it is better to self-sign your MSIX anyway. You can then put it online via the Microsoft Partner Center and they will take care of re-signing it with a Microsoft certificate.