Teslo.
Teslo.

Reputation: 501

Packaged WPF app returns wrong AppData path when used as argument for a new process

I have a WPF .NET Core 3.1 app packaged as an MSIX app. The app downloads some assets from S3 to the AppData folder and at some point, it starts another process (another app) with one of the arguments being the path to one of the downloaded assets (a Settings.xml file).

I'm facing two problems:

  1. The app sometimes downloads the assets to the "real" AppData path (C:\Users\my_user\AppData\Local\some_created_folder), sometimes to the virtualized path (C:\Users\my_user\AppData\Local\Packages\package_id\LocalCache\Local\some_created_folder). I noticed the latter only recently in 3 different releases (3 consecutive versions): 1st used "real", 2nd used virtualized, 3rd used "real" once again. I'm pretty sure there was no code change that could cause this.

  2. I'm composing the download paths using Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData). When the assets are being downloaded to the virtualized path, the 2nd app is not starting correctly, because the settings file path set as an argument when starting the process is pointing to the "real" path (always!). No exceptions or errors are thrown!

var appData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
var settingsFilePath = Path.Combine(appData, "Settings", "Settings.xml");

...

var settingsFile = new FileInfo(settingsFilePath);
if (settingsFile.Exists)
{
    var arguments = $"-l \"{settingsFile.FullName}\"";
    var fileInfo = new FileInfo(_options.ExePath);
    var process = new Process
    {
        StartInfo = new ProcessStartInfo
        {
             FileName = fileInfo.FullName,
             WorkingDirectory = fileInfo.DirectoryName ?? string.Empty,
             Arguments = arguments
        }
    };

    if (process.Start())
    {
        process.WaitForInputIdle();
    }

     _logger.LogDebug("Started {name} {arguments}", fileInfo.FullName, arguments);
}
else
{
     throw new FileNotFoundException($"Settings file not found at path '{settingsFile.FullName}'!", Path.GetFileName(settingsFile.Name));
}

I read this, but I don't understand why the app is acting this unpredictable. Or am I missing something? The package manifest file has the EntryPoint="Windows.FullTrustApplication". I'm also aware that UWP Desktop Bridge virtualizes some File System paths, but I'm expecting it to be predictable.

Questions

  1. How can I make sure that my downloaded assets are always on the same path, either the "real" one or the virtualized one?
  2. How can I set the argument for the 2nd app to always point to where the file really exists ("real" vs virtualized)?

enter image description here

Upvotes: 4

Views: 1148

Answers (2)

Mark Ingram
Mark Ingram

Reputation: 73605

I read this, but I don't understand why the app is acting this unpredictable. Or am I missing something? The package manifest file has the EntryPoint="Windows.FullTrustApplication". I'm also aware that UWP Desktop Bridge virtualizes some File System paths, but I'm expecting it to be predictable.

From that link you provided, the details for the reasons why it appears "unpredictable" (note: the behaviour is slightly different before and after Windows 10 1903):

In response to a file open command, the OS will open the file from the per-user, per-package location first. If this location doesn't exist, the OS will attempt to open the file from the real AppData location. If the file is opened from the real AppData location, no virtualization for that file occurs.

And

All newly created files and folders in the user's AppData folder (e.g., C:\Users\user_name\AppData) are written to a private per-user, per-app location but merged at runtime to appear in the real AppData location. ... Modifications to existing files under the user's AppData folder is allowed to provide a higher degree of compatibility and interactivity between applications and the OS. ... State separation also allows packaged desktop applications to pick up where a non-packaged version of the same application left off.

The affected directories are:

Local
Local\Microsoft
Roaming
Roaming\Microsoft
Roaming\Microsoft\Windows\Start Menu\Programs

So, to answer you questions:

  1. How can I make sure that my downloaded assets are always on the same path, either the "real" one or the virtualized one?

To always use the virtualized directories, look at the Windows.Storage.ApplicationData API. The function Local[Cache]Folder will always return the virtualized path.

Failing that, you must make sure that you (or a user) have never written to the non-virtualized AppData locations (e.g. C:\Users\user_name\AppData\MyCompany) as that will always be used in preference to the virtualized location (e.g. C:\Users\user_name\AppData\Local\Packages\MyCompany.MyApp_PublisherId\LocalCache\). That way, you will only ever read and write to the virtualized location.

  1. How can I set the argument for the 2nd app to always point to where the file really exists ("real" vs virtualized)?

The virtualized path to the file is the "real" file.

However, files in this location are only visible to the packaged app itself (see https://learn.microsoft.com/en-us/windows/msix/desktop/flexible-virtualization):

Files in the virtualized location are visible only to the app.

One final thing to note. When a user uninstalls an MSIX package, it will delete all of the virtualized AppData that your application wrote.

Upvotes: 0

Bogdan Mitrache
Bogdan Mitrache

Reputation: 10993

I don't have an anwer yet, but I need more details from you to further investigate this:

  1. Are you sure the files under the "real" appdata folder are not created accidentally by you when debugging the app from VS?

From what I know, packaged apps work with the plain, real AppData resource and path when they open a folder or file already present in that location. If the Appdata folder/files are created from scratch then it should use the virtualized location.

This behavior is useful when migrating from MSI to MSIX, until the user chooses to uninstall the MSI version, the MSI and MSIX variant can be used in parallel both having access to the same AppData files.

Then again, if this is not the first version of your app, and you have previously deployed it as an MSI to your users, you will not be able to know for sure which AppData folder you are working with when running from inside the container.

C:\Users\my_user\AppData\Local\some_created_folder

However, while researching this I found that you can also use Windows.Storage.ApplicationData.Current.LocalFolder.Path to write your appdata. Maybe this would work?

  1. The second app, is that an EXE from the same MSIX package or another separate application (deployed with its own MSI/MSIX) from the machine? I might be wrong, but if remember correctly the virtualized AppData folder is accessible only inside your app container, so other apps from the machine can't access those files.

If that is the case, you could try saving the data in the CommonApplicationData folder, if this isn't user-specific data.

Related question:

Upvotes: 3

Related Questions