Developer hints to getting Win32/.Net code to work under MSIX
[NOTE: Last Updated Dec 13, 2019: Check back for updates]
What are the top things to consider when looking into building your older applications into an MSIX release format?
You should check out Microsoft's answer here: https://docs.microsoft.com/en-us/windows/msix/psf/package-support-framework but I think we can do better than that.
I'm not going to focus here on the tooling of how to build the project, but on the things you need to consider because of restrictions that exist when running your code inside of the MSIX runtime container. Generally, this involves finding coding that may have been "best practice" in the past but will need to be updated to newer practices today. You will have two choices to address the issues:
- Update the coding to new practices. Most of the time these changes work both inside and outside of the container.
- Add the Package Support Framework for MSIX to your installer package and configure the PSF to remediate the issues.
Here are my top items to look into:
What do you have?
Do you really know all the things that your existing installer does to integrate your application to the OS, the end-user, and to other applications? Probably not. And you need to understand all of these to know what to adjust.
It pisses off my MSIX developer friends at Microsoft every time I say this, but my best advice to figure this out is to use a different Microsoft product, Microsoft App-V! With almost 20 years of experience and tooling built around that technology, which is ultimately doing something similar to the MSIX container, we have gotten very good at taking your existing installer, watching it install and running that output through an analyzer to tell you exactly the how, where, and why of all the integrations that you have today. So I suggest this because it is the best way to get the information that you need before you even start on MSIX. And you don't even need to worry about getting the App-V package working!
Here is what you do:
- On a clean machine/VM, install the App-V Sequencer from the latest Windows ADK. You only want the Sequencer (not the Auto Sequencer). After, disable the WIndows Search and Update services.
- Start up the Sequencer.
- Select the menu "Tools-->Options", click on the "Exclusion Items" tab. Locate the item" [{Local AppData}]" and delete it. Close the options dialog with the "OK" button..
- Start a new Package capture with the "Create a New Virtual Application Package" item. This starts a capture wizard.
- Use the "Create Package" option (which is the default) and click the "Next" button.
- Click "Next" on Prepare computer", click the "Next" button..
- Use the "Standard Application" option (which is the default) and click the "Next" button.
- Select "Perform a custom installation" and click the "Next" button.
- Enter a name for the package, and click the "Next" button.
- When the "Install your applications now" dialog appears, manually install your installer. You don't need to use the "Run" button on the dialog to do so, you can use the start menu or file explorer.
- When installation is done, check the "I am finished installing" box and click the "Next" button.
- On "Configure Software", ignore instructions and just click the "Next" button.
- Ignore the Installation Report and click the "Next" button.
- On "Customize", just click the "Next" button.
- On "Create Package" just click the "Create" button. This will create a folder on you desktop using the package name you entered earlier in the wizard. Close the sequencer.
- Download the free AppV_Manage, or licensed TMEdit programs from this website. Open up the .AppV file in your desktop folder.
- In AppV_Manage you configure the tool to look at the folder, then go to the publishing tab and click the refresh button, then right click on the package and select analyze.
- In TMEdit you just open the .AppV file and it automatically analyzes.
AppV_Manage analyzer provides details on your shortcuts, FTAs, URLs, Shell Extensions, ActiveX, and many, many, more integration points. TMEdit doesn't call out that detail, but does summarize things it sees against MSIX compatibility.
[Small plug: If all that sounds to hard, you can consider engaging us to help.]
Where are your Settings and Data?
Over the years, there have been many recommendations for application settings and data, the registry, files in the program folder, the AppData Local and Roaming folders, C:\ProgramData (Common AppData), and more. Some of these work under MSIX and some do not. Let's look at the particular cases:
- Registry HKLM: This is appropriate for things that the installation lays down, and maybe configured by IT personnel in the Enterprise, but is never changed by the end-user. Your application will be able to read these values, but review your code to make sure it does not try to acquire Write or Full access to the registry kyes under the local machine hive. With a native (MSI) this can lead to UAC prompts for credentials/elevation, but under the MSIX runtime the call will just outright fail. So if you have this in your code you'll need to change it to ask only for read permissions on the key. Currently there is no PSF fix available for this.
- Registry HKCU: This is appropriate for settings that an end-user might want to change, or things that might be different for different users on the same machine. Multi-user versions of the OS are quite popular among your larger customers! Settings in the HKCU hive also naturally follow the user as they roam from machine to machine. When working with HKCU kyes and items, under MSIX (currently) you cannot get permission to delete the key or item. Code that asks for Full access includes the delete capability and therefore will be denied when the call is made under the MSIX runtime. Change the code to request specific read/write access as appropriate. Currently there is no PSF fix available for this.
- Files in the Program Folder: Under the MSIX runtime, attempts to open files under the program folder for the purpose of anything other than read will be denied. So if you have a settings file here, it should be moved to a safe location (such as in the user's profile). You can change that location in your code, or you can leave it there include the PSF along with the FileRedirectionFixup (FRF) and configure it to take care of the file. The FRF would perform an automatic copy-on-access, managing a writable copy in the user profile whenever you code opens the file using the program folder path.
- Files in AppData Local and Roaming and Common: While you may include files for these locations in your installer, without the PSF the application will not see these files running in the MSIX Runtime. Microsoft has newer APIs that you can change your code to use (TBD), but these APIs do not work outside of the container. The other option is to include the PSF along with the FileRedirectionFixup (FRF) and configure it to take care of the file. The FRF would perform an automatic copy-on-access, managing a writable copy in the user profile whenever you code opens the file using the program folder path.
NOTE: As of the OS versions for 1909 (I think, I have not nailed down exactly when this changed), the MSIX runtime changed affecting this without using the PSF. Depending on how the app accesses files in the package VFS folders for AppData and LocalAppData, the runtime might automatically make a copy into the user profile package folder LocalCache. This is a copy-on-access event that occurs only if the app directly tries to open the file. If your application first tries to open the folder to see if the app is there, it will not see the folder and your application might never try to open the file. So there might be a few apps that can now get away without the PSF, but then you'll have try about installs to older OS versions, so maybe use the Psf for this anyway.
What are your Entry-points?
Take inventory of the ways that end-users start your software once installed. Because MSIX apps run in a container, some of these may need adjustments. The primary sources of these are:
- Shortcuts
- File Type Associations
- URL Protocol Handlers
Ultimately, the methods used to expose these to the system is different in the modern app. Your AppXManifest.xml file will contain the references that define these entry points and those references will be used during the installation to provide the integration. Once you identify these, review the following for differences in what you could do in the past that might cause you to make adjustments.
Shortcuts
Traditional shortcuts are .lnk files with a slew of properties. On older operating systems these are gathered up by the copy of Windows Explorer that creates the user's desktop and populates the start menu. On Windows 10, the process is different and the Start Menu consists of tiles that are generated from the manifest. These tiles can come from UWP and MSIX apps, but are augmented by existing lnk files deployed by traditional apps.
Your modern app installation will not deploy those traditional lnk files; you will need to make adjustments. In addition to how you define the start menu entry-point, there are restrictions in capabilities that need to be reviewed and possibly worked around.
The following table highlights features/properties of the lnk file versus those of the start menu integration for modern apps:
Property | LNK file | MSIX/AppX Modern Start Menu Entry-point |
---|---|---|
Display Name | The display name shown in the start menu is the name of the lnk file without the file extension. | The display name shown in the start menu is taken from a field in the manifest. |
Target: reference to a program to be run. | May be either the path to an exe file, or a file of any type that can be resolved to an executable using the local file type association (sometimes referred to as a shell launch). Using a target other than an exe is popular to display a file without your installer needing to know what the user's preference is to display files of hat type. For example, you might have a shortcut to a pdf or rtf/doc/docx file. Shortcuts to cmd or other script files are sometimes also used. | May only reference an exe directly. |
Command Line Arguments: | Additional arguments that are given as command line arguments to the target program. If you program uses argv/c then you are supporting this. | (Currently) not supported. |
Working Directory | The lnk file may set the current working directory of the target process explicitly. If not specified in the lnk file, it will be set to the directory containing the target file. This affects how the application references files relative to the working directory, and potentially how it finds dlls it needs. | (Currently) may not be specified. The working directory of the target process will be set to the Windows System32 folder. |
Icon | Used for display in the target. When not specified, the first icon in the target file is used when the target in WinPE formatted, otherwise the icon used for the file association of the target is used. Otherwise the path to an icon file may be specified, or the path to a WInPE file containing icons may be specified, optionally followed by a comma and number which indicate which icon to extract from the resources of the WinPE file (defaulting to the first). | Explicit Assets are used. These will be a series of graphic files in the package with various sizes and ratios (to support different sized tiles), handle different DPI settings, and, if the image includes text as graphics, different text scaling options. |
Description | Can be seen by the user when hovering over the shortcut. | I am not aware that this is supported, but it really isn't that important. |
Additionally there are more restrictions (currently) affecting the start menu entry-points in a given package:
- You may not have two entry points to the same target file. The most common use today are many existing apps that use different shortcuts to the same exe with different command line arguments.
- There are limitations on folders. Many existing apps use a folder structure for the shortcuts. Your package may only have one folder (no nested folders) for the shortcuts. On Windows 10, I find that also also prefer to make sure the shortcuts to all start with the same name as well ("vendor do this", "vendor set that" rather than "do this" and "set that").
- Placement of the lnk file affected who would see the shortcut. It could be placed in the common start menu, default start menu, or current user start menu. As MSIX only supports per user installations, all entry-points for the start menu in MSIX are for the current user only.
If you are faced with issues from these limitations, your first course of action would be to alter your application entry-points to comply with the new capabilities. Short of that, you can leverage the same techniques we are using to repackage existing installers into MSIX without having source code access.
We can overcome most all of these challenges using the PSFLauncher. In essence, you add a copy of PsfLauncher into your package for each of your existing shortcuts and then configure that PsfLauncher copy to do what you used to do for targets, arguments, and working directory. By changing the name of each launcher copy in the package, you can overcome the issue of having multiple shortcuts that ultimately run the same program with different arguments.
You should still create the Assets as individual graphic files of the different sizes. These may be ico files, but png is also supported (and there is much better tooling to help you create good ones).
PsfLauncher executable comes in a 32 and 64 bit version, so you will have to match the "bitness" (that is my term, Microsoft prefers to use the overloaded term "Architecture" that I find confusing) to that of your target. Place a renamed copy of this into the root folder of your project for each start-menu entry-point. You will also need one bit-specific copy of PsfRuntime dll placed in the same folder that is used by the launchers (and will be injected into your target process in case you need additional PSF fixups). You don't rename the PsfRuntime dll as you would the launcher. Finally, you will need a config.json file that instructs the launcher what to do. It is in this config file that we can put the ultimate target (exe for other file to be run using shell launch), command line arguments, and working directory.
Later on you will see that there are other uses of the launcher and the config file for making use of PSF capabilities.
File Type Association
The traditional installer implements a file type association by writing into either the Local Machine or Current User registry Classes key. In your MSIX package you will define the association in the Manifest, and the Current User classes key are will be updated based on that information. The differences in (current) support are summarized in this table:
Item | Traditional | Manifest |
---|---|---|
Icon | Defines how explorer displays the file. | Same |
Shell/Verb/Command | Defines a verb to add to the right click menu on the file. Supports a target command line and arguments. If the arguments does not include a "%1", the name of the file will automatically be appended. | Same |
ShellEx | Various Shell Extensions that affect explorer functionality. | Some types of shell extensions are supported, some not. |
ProgID | Used to separate out/consolidate Shell/ShellEx definitions, and also support user selectable FTA conflicts. |
Not supported via repackaging, but those shell and shellex entries that would have worked directly under the FTA should work. User selectable FTAs when there is conflict is handled "differently" |
If you needed to use PsfLauncher only to solve start-menu issues, you only need to adapt the fta definition into the manifest per the above.
If you needed to use PsfLauncher to inject a fixup dll or script, and the fta points to the same target exe, then an additional adjustment will be needed. You can either:
- Adjust the target on the FTA to be the PsfLauncher copy. Command line arguments can flow through, but if you moved arguments into the config.json file you will end up with the FTA launch seeing anything you put in the arguments in the FTA plus those of the config.json file, so you might need to trim them here.
- Make use of the CommandAlias feature of the manifest. This is an entry-point that tells the system that if X is asked for, start Y instead. With this, you can leave the target command of the FTA pointing to the ultimate target, but the alias will redirect it to PsfLauncher. You still need to wory about the arguments as above.
URL Protocol Handler
Advice here parallels that of FTAs. URL Protocol handlers are rare, so we have little experience in dealing with them; there may be hidden challenges we have not come across yet.
Where are your Dlls?
Dlls are traditionally located by looking in the current working directory, followed by folders listed in an App Paths registration (if any), and finally by folder in the Path variable. Traditional installers might alter the Path variable to add more folders, but this was deprecated back in Vista and the best practice became adding an AppPath registration for the specific exe process (so as to not affect other apps). Additionally, the code can add additional folders to the dll search at runtime.
Under the MSIX runtime, the current working directory might be different, and you cannot alter the Path variable (or create/change any environment variable), and AppPath declarations are ignored. Using the PSF you can specify the working directory used by the PsfLauncher to run your exe, and you can inject a DllLibraryFixup shim to find the dlls wherever they are in your package.
Windows Services
Starting in 2020H1 version of the OS, Services may now be included. Be aware that prior versions of WIndows 10 don't support them under MSIX!
The service will require setting a new capability in the MSIX Capabilities list.
If the purpose of the service is an updater, you might be better off removing the service from your MSIX build. Other mechanisms will be used.