Process Identity and Working Directory for Partial Trust ClickOnce Apps

People always ask me “How can you write a whole book on ClickOnce?” because they envision the standard 5 minute demo of what ClickOnce is and does and think that is all there is to it. The fact is there is just a plethora of variations, hidden behaviors, specialized scenarios, and things people want to do with ClickOnce that are far more than 5 minute answers. The book keeps growing the more I get into it.

One of these things that snuck up and bit me recently (unfortunately in a live demo at VS Connections) due to a gap in my knowledge was the way partial trust apps run on the client machine.

First some background on ClickOnce and application files. Any file you add to your project and set the file Build Action property to Content will be added to the Application Files (under the Publish tab in project properties) with a Publish Status of Include, depending on the file type. MDF files (SQL Express), mdb, and XML files will get marked as Data Files instead of Include. Include means the file will be deployed to the application client cache folder under the user profile (C:\Documents and Settings\\Local Settings\Apps\), and Data File means it will be deployed to a separate data folder associated with that app, also buried in the obfuscated goo under the user profile. The data files are treated differently for updates (beyond the scope of this post) and the folder is accessible through the ApplicationDeployment.DataDirectory property.

If you deploy an app with ClickOnce, and the app manifest requests Full Trust, then when the app runs it simply gets launched by the runtime directly – the app executable is the executable process that runs. It runs from the deployed client cache directory. As a result, with a full trust app, you can access files that you deploy with your app, marked with a Publish Status of Include, with a relative path such as “.\MyImage.jpg”. The current working directory when your executable starts is the folder it was launched from, and so everything works out.

Then you decide that you want to be more security concious, and switch your ClickOnce security settings to only require partial trust (lets say LocalIntranet zone). Suddenly your app stops working complaining that it can’t find the file.

So what’s going on there??

The problem is that the executable process is actually different when you run a ClickOnce app under partial trust. When you configure a ClickOnce deployed app to request less than full trust, the process that actually launches is AppLaunch.exe. This process loads your executable assembly into an AppDomain which it has cranked down the CAS security on to your requested permissions, and your app runs from that appdomain under partial trust. This is similar to what Visual Studio 2005 does to enable partial trust debugging with the .vshost.exe that is the debug process by default.

So how does that screw up your paths? AppLaunch.exe is running from the .NET directory under C:\WINDOWS\Microsoft.Net\Framework\v2.0.50727, and so the current working directory for your app loaded into that host process is that folder. Naturally your application files have not been deployed there, so your relative paths for locating the files fail.

OK, so next thought is “there’s gotta be an API that I can call to say ‘give me my app’s deployed directory'”. Unfortunately, that thought would be incorrect.

So what’s the solution? Simple – don’t ever deploy a file to the application directory (Publish Status = Include) that you need to access explicitly through a path (i.e. to load that file as a bitmap, xml file, etc.). If you need to do that, you should mark it as a Data File, and access it by adding the file name to the ApplicationDeployment.DataDirectory path. You can also use Application.UserAppDataPath property, results in the same thing when you are ClickOnce deployed.

Just wish I had known that before doing that demo on the fly in a way I had not done it before in front of a live audience… :)