Accessing to the files in the installation folder in a Desktop Bridge application
We already had the chance to discuss this scenario previously on this blog, specifically when we have talked about the option to include multiple Win32 processes in the same app package or how to handle data in a converted desktop application. If you open the documentation with the guidelines on how to prepare a classic desktop application to be converted, you will find the following item:
Your app uses the Current Working Directory. At runtime, your packaged desktop app won't get the same Working Directory that you previously specified in your desktop .LNK shortcut. You need to change your CWD at runtime if having the correct directory is important for your app to function correctly.
Translated into code, this means that in your application, no matter which platform is based on or which programming language has been used to implement it, when you will try to get access to the folder where the app has been installed, you won’t get the real path, but another one. However, before starting to understand how to handle this requirement, please remember that all the approaches we are going to talk about in this post refers only to read-only mode scenarios, like launching another executable or reading the content of a file stored in the app package. Any tentative of writing in the installation folder would lead to an exception, since it isn’t supported by the Desktop Bridge (since packaged apps always run in user mode, while the installation folder requires admin rights to get write access). If you want to learn how to handle the creation of files at runtime, you can read the following blog post: https://blogs.msdn.microsoft.com/appconsult/2017/03/06/handling-data-in-a-converted-desktop-app-with-the-desktop-bridge/.
Let’s continue the conversation about the original topic of the post (accessing to the files in the installation folder) by using a real example with a very simple Windows Forms app, that I’ve added to a solution which contains also the usual JavaScript project that makes easier to deploy the same app, but running as converted thanks to the Desktop Bridge.
The application contains just a button that uses the .NET API Environment.CurrentDirectory to display, in a label, the path of the Current Working Directory by using the following code:
private void OnDisplayCurrentWorkingDirectory(object sender, EventArgs e)
{
labelCWD.Text = Environment.CurrentDirectory;
}
When we execute this code on the application running as a classic desktop application, what we get is the right path where the process is being executed from. In this case, since we’re launching the app directly from Visual Studio, you’ll get the path of the folder where the output build gets created ( /bin/Debug).
However, if we try to do the same when the app is running as converted (so by deploying the JavaScript project), this is what we get:
As you can notice, this isn’t really the directory where the executable is running from. As a side note, we’re getting this result because the app package has been compiled for the x86 architecture. If we would have compiled it for the x64 architecture, we would have received C:\Windows\SysWOW64 as a result.
This change, compared to a classic desktop app, can create some issues if you try to access to any of the file that is included in the app package, like another executable to launch or a text file to read. To show this behavior, let’s update our sample Visual Studio solution, by adding another Windows Forms project and, by following the same guidance we used to generate the JavaScript project, making sure that it gets included in the same app package. This is how the final result should look like:
The second Windows Forms application does nothing else than displaying a message to the user using a Label control. However, as you can see, both processes (CurrentWorkingDirectory.exe and CurrentWorkingDirectory2.exe) are copied inside the same app package, which means that they are deployed in the same folder when the converted version is installed. If we were talking about a regular desktop application, to launch the second app from the first one it would be enough to add a new button and connect it to the following event handler:
private void OnLaunchSecondApp(object sender, EventArgs e)
{
string secondApp = $"{Environment.CurrentDirectory}\\CurrentWorkingDirectory2.exe";
Process.Start(secondApp);
}
However, if we start the app as converted, we will get the following exception:
If we have carefully read the introduction of this post, the reason of this error should be clear. However, just to be sure we are on the same page, let’s open again the converted version of the application and attach a debugger to it (from Visual Studio, choose Debug –> Attach to process and select from the list the process called CurrentWorkingDirectory.exe). Place a breakpoint right before invoking the Process.Start() method and this is what you will see:
This shouldn’t be a surprise: sincethe API Environment.CurrentDirectory isn’t returning the real installation folder, but a system folder, the operation fails because the app is looking for the second process in the path C:\Windows\system32\CurrentWorkingDirectory32.exe, which is clearly wrong .
Fixing the problem in the best way
The best way to fix this scenario is to change code in a way that can work fine no matter how the app is running. This way, you are in total control of what’s happening and you can be sure that, when your app will continue evolving with new features, everything will keep working as excepted. The way how to fix the code is based on the technology used to write the application. For example, we have already seen a potential solution when we’re talking about a .NET based application, like a Windows Forms one or a WPF one. Thanks to the reflection APIs, we can get access to the path of the assembly which is being executed and, from there, we can retrieve the path of the real installation folder and of the file want to get access. As such, in our sample scenario, here is how we need to change the code to invoke the second application contained in the package:
private void OnLaunchSecondApp(object sender, EventArgs e)
{
string result = System.Reflection.Assembly.GetExecutingAssembly().Location;
int index = result.LastIndexOf("\\");
string processPath = $"{result.Substring(0, index)}\\CurrentWorkingDirectory2.exe";
Process.Start(processPath);
}
With the API System.Reflection.Assembly.GetExecutingAssembly().Location we get the full path of the running process (in this case, CurrentWorkingDirectory.exe). Then, by using some string manipulation, we remove the name of the process from the path so that we can get the installation folder and add, at the end, the name of the new process we want to launch (CurrentWorkingDirectory2.exe). This time, if we repeat the process of deploying the converted version of the app, attaching the debugger and putting a breakpoint right before calling the Process.Start() method, we’ll see that the path of the second process is now correct:
Remember, in fact, that C:\Program Files\WindowsApps is the protected folder where all the app packages (either downloaded from the Store or manually sideloaded) are deployed. The final result, in fact, will be that this time the second application stored in the same app package will run just fine:
An alternative approach to fix the problem
There may be some scenarios where your application is very complex or you’re trying to bring to the Store a very old application that , originally, hasn’t even been developed by you but you have been put in charge of the project at a later stage. In these cases, changing every part of your code when you need to access to the current working directory might be complicated if not even impossible. An alternative solution is to leverage a launcher: a middle man process that is invoked by the converted desktop app and that takes care, at runtime, to intercept all the calls to retrieve the current working directory and redirect them to the right path and not to the system one. I would like to stress out that this should be considered as a workaround, not as an optimal solution, because you “lose” control over your code and there may be scenarios where the launcher you have written plays well with some parts of your application and some others where, instead, the launcher approach doesn’t work at all.
As an overview, generally a launcher is an application written in C++ (because you need direct access to the native Win32 APIs) which:
- Identifies the process you want to launch
- Identifies the real current working directory
- Calls the native Win32 APIs to create the process by passing the real current working directory, which gets used every time an application tries to access to the installation folder using a managed API (like the Environment.CurrentDirectory one in the .NET Framework).
Luckily, we don’t have to start from scratch to create the launcher, because the Desktop Bridge team has published a sample launcher that it works out of the box, but that you can also tweak if you have custom requirements. You can download the launcher from here: https://github.com/Microsoft/DesktopBridgeToUWP-Samples/tree/master/Samples/LauncherSample
Once you have downloaded it (either because you have compiled the source code or directly downloaded the executable), you will end up with two files:
- The main executable, called launcher.exe
- A configuration file, called launcher.cfg, where you need to specify in two separate lines:
- The name of the win32 process of your main application.
- The current working directory as a relative path.
Let’s see a real example by reusing the previous sample app we have created. First, we need to add a new JavaScript project to our solution. It will be exactly like the other one (CurrentWorkingDirectory.Package), but with just one difference: the entry point of the app won’t be anymore the CurrentWorkingDirectory.exe process, but the launcher.exe one. As such, repeat all the steps described in this post and create a new JavaScript project (in my sample, I have called it CurrentWorkingDirectory.Launcher) that, again, will contain a win32 folder with all the Windows Forms applications that are part of the solution. The difference is that, this time, we will place also the launcher.exe and the launcher.cfg in the root of the project. This is how your final JavaScript project will look like:
Now we need to configure the launcher.cfg file, to specify which is the main executable that we want to launch when the app is started and which is the current working directory. In our case, this is how the content of the launcher.cfg file looks like:
CurrentWorkingDirectory.exe
win32\
As mentioned before, the first line contains the name of the executable that must be launched when the app starts. In our scenario, it’s CurrentWorkingDirectory.exe. The second line, instead, is the path of the current working directory, expressed as a relative path starting from the root of the app package. In our scenario, it’s the win32 folder, which contains both our executables.
Now we need to change the package.appxmanifest file manually (by right clicking on it and choosing View code) and changing the entry point of the application: it shouldn’t be anymore the win32/CurrentWorkingDirectory.exe file, but the launcher.exe file in the package root. As such, the following line that we had in the manifest of the other JavaScript project:
<Application Id="CurrentWorkingDirectory" Executable="win32\CurrentWorkingDirectory.exe" EntryPoint="Windows.FullTrustApplication">
must become:
<Application Id="CurrentWorkingDirectory" Executable="launcher.exe" EntryPoint="Windows.FullTrustApplication">
The last step to test if our launcher works as expected is to change back the code that, in the CurrentWorkingDirectory project, launches the second process. Now, instead of using the reflection APIs, we use again the same approach we tried in the beginning and we treat the app like if it’s a classic desktop one, so by directly leveraging the Environment.CurrentDirectory API:
private void OnLaunchSecondAppWithLauncher(object sender, EventArgs e)
{
string path = $"{Environment.CurrentDirectory}\\CurrentWorkingDirectory2.exe";
Process.Start(path);
}
Now deploy the new converted app (in the sample on GitHub, it’s the project called CurrentWorkingDirectory.Launcher) and press the button that invokes the second executable using the classic code instead of the reflection APIs. Surprise! The second process will start just fine. If you will try again to attach the Visual Studio debugger you will notice that this time, despite the app is running as converted, the path variable will contain the proper path with the installation folder of the app package and not the C:\Windows\System32 one.
Wrapping up
In this blog post we have learned how to deal with the installation folder of a desktop app packaged with the Desktop Bridge, to overcome the limitation that we can’t use the traditional APIs exposed by many languages and frameworks to get access to the current working directory of the executable. The best solution is to adapt your code, so that you have full control over the changes you might make over time. However, we have seen also an alternative approach based on a “middle man”, a second process that acts as intermediate with your real application, in case you don’t have the chance to change your code in an easy way. Before wrapping up, remember that all the approaches we have talked about in this post refers only to read-only mode scenarios. If you need to generate files at runtime, please refer to the approach described in the following blog post: https://blogs.msdn.microsoft.com/appconsult/2017/03/06/handling-data-in-a-converted-desktop-app-with-the-desktop-bridge/
You can find the sample Visual Studio solution used in this blog post on my GitHub repository, as usual: https://github.com/qmatteoq/DesktopBridge/tree/master/Extras/CurrentWorkingDirectory
Comments
- Anonymous
November 15, 2018
Hello Alejandro,the best solution is to write the files in another location, like the AppData folder or the local app data store. Writing files in the installation folder, in fact, isn't a best practice, since the application may be installed in a folder which is admin protected. In alternative, you can leverage the Package Support Framework, which allows to change this behavior without modifying the code. You can learn more here: https://blogs.msdn.microsoft.com/appconsult/2018/07/18/solving-common-desktop-bridge-blockers-with-the-package-support-framework/- Anonymous
November 15, 2018
you forgot to leave the url- Anonymous
November 15, 2018
Sorry, it seems Wordpress didn't recognize the link. I've updated my comment to include it.
- Anonymous
- Anonymous