Figuring out why a traditional package recaptured into MSIX does not work can be a bit of art. Of sure, we have some standard things we look for and fixups that we can use, but what about when it isn’t one of those?
The case today is about an application for which the standard fixups solved one problem, but didn’t help in building a deployable app. It makes a great case to show one of the different tools/ways to debug, and demonstrates how you really need to learn how these systems work at a deep level to be most successful.
The app? LibreOffice 7. Yes, that old yet free “open” version of a Microsoft Office alternative. LibreOffice is a large suite of applications, but because it is written to run under multiple operating systems, it doesn’t contain too many deep integrations into the OS, making it a likely candidate to work under MSIX.
The application stores configuration in ini files, which includes a setting that we want to use as part of the package to disable the autoupdater (as it can’t update itself under MSIX). It makes use of both the local and roaming parts of the Users AppData folder, so right from the start we know that we need the FileRedirectionFixup or else the app cannot see these files in the MSIX package without it. We know that we want the app pre-configured to avoid the updaters, and in most large organizations the package will be further customized as well.
Our initial attempt is to install Libre Office using the Microsoft MSIX Packaging Tool, disabling the autoupdater configuration and making a few other changes in the initial configuration that seem appropriate. We also use PsfTooling to inject and configure the FileRedirectionFixup.
But when we try to launch one of the applications under MSIX, we get the following error:
Even knowing the name of the file (and it is a file not a folder), I couldn’t figure out what the problem was from a Process Monitor trace. So I rebuilt the package using the Debug build of the Psf (by using the PsfToolingD shortcut to start PsfTooling). I then used DebugView to monitor the debug output from the FileRedirectionFixup (FRF). And that led to a resolution.
Ultimately, the smoking gun is the apparent success of this operation that the application made:
I check the package, and indeed there is a 1kb VFS\AppData file in the equivalent package location. We know that we can’t actually delete the immutable files inside the package, but that isn’t the issue; it is far more complicated than that.
Given the filename, I suspected that perhaps the app was using this 1kb file as a marker for some type of synchronization activity between threads or processes, similar to way app built specifically for Windows might use a mutex, semaphore, or named pipe. As such, it is a temporary marker, and although left behind when we configured the application, it probably should not be part of the package.
Further reading of the logs help to confirm this theory. The app firsts looks to see if the file exists, writes to it, and then later we see the deletion request above, and then finally it searches for the file again and finds it. But the detail available in DebugView against the added information supplied by using the Debug build of the FRF, we can see this in more detail:
- The app does a FindFirstFile against the “normal” appdata/roaming folder for the app. The FRF intercept Find code must consider three possible sources of files for this request, “as requested”, “an equivalent VFS path inside the package”, and a “redirected location under the WritablePackageRoot” folder where any file changes might live. It layers these three possible locations (with the latter winning in the case of conflict) to return it’s result. As can be seen in the full debug log, the presence of the file in the package automatically triggers a copy-on-access to the WritablePackageRoot. And it is this copy that is returned in the Find operation.
- The application open the file for write using the “normal” path, and writes to it. The FRF CreateFile intercept finds that the path is covered by the package structure also and finds and open the WritablePackageRoot copy.
- The app does a DeleteFile operation, the one shown above. The FRF intercept code deletes the copy in WritablePackageRoot and returns success.
- But then a the app uses FindFirstFile to check if the file has been cleared. This finds the file in the package (creating another copy in the process), causing the app to display the error message as it was supposed to have been deleted.
I don’t have this problem with the app packaged for and running under App-V. So understanding a subtle difference in how those runtimes work is important to solving the problem. MSIX does not support the concept of deletion markers the way App-V did, so this is a new kind of challenge to us. I can’t just eliminate all of the AppData folder from the package, as we need the configuration ini (and other) files.
So I recreated the package without the synchronization files (there are two of them in different folders) but keeping all others. Now when the app runs, the FRF will initially tell the app there is no file. When the app creates it, because the equivalent folder exists in the package (without the file) the file is created under the MsixWritableRoot. When the file is deleted, it is now fully gone and the app will not see it when it queries the directory again. With this change, the app is fully functional.
Or at least it will be fully function just as soon as I figure out how to make a “framework” Java package as a dependency for the database app. But everything else looks good and if necessary I could just include a Java runtime it in this package.