Archive for March, 2010

How to pass arguments to an offline ClickOnce application

March 21, 2010

In ClickOnce Deployment, it is a common belief that you can not pass arguments to an application unless:

  1. The application is deployed to a web server, and
  2. The application is online-only.

If you are interested in passing query parameters to an online-only application deployed to a web server, check out the article on MSDN. About offline applications, or applications deployed via a file share, that page says this:

 

However, this is no longer the case. I suspect it was changed when they added the ability to do file associations in .NET 3.5 SP-1. It turns out that you can now pass parameters to:

  • an offline ClickOnce application,
  • a ClickOnce application deployed to a file share, and even to
  • an offline ClickOnce application deployed to a file share.

And of course you can pass parameters to an online-only application using query parameters, but we already knew that (see article referenced above).

Here’s how you call the application and pass the arguments:

System.Diagnostics.Process.Start(shortcutPath, argsToPass);

This is how you read the argument string:

//Get the ActivationArguments from the SetupInformation property of the domain.
string[] activationData = 
  AppDomain.CurrentDomain.SetupInformation.ActivationArguments.ActivationData;

Here are three different ways to pass and receive arguments:

  1. Pass a file path and name as a URI. This mimics what happens when you set up a file association and double-click on a file with that extension.  The argument will start with “file:”.
  2. Pass a query string with key-value pairs in it. This is the same way you pass and parse query strings for an online-only application. The argument will start with a question mark.
  3. Pass one value or a list of comma-delimited values.

Locate the shortcut for the application.

The first thing you need to do is locate the shortcut in the start menu for the application you want to run — this is made up of the Publisher Name and Product Name. Here is how to find the location of the application’s shortcut:

StringBuilder sb = new StringBuilder();
sb.Append(Environment.GetFolderPath(Environment.SpecialFolder.Programs));
sb.Append("\\");
//publisher name is Nightbird
sb.Append("Nightbird");  
sb.Append("\\");
//product name is TestRunningWithArgs
sb.Append("TestRunningWithArgs.appref-ms ");  
string shortcutPath = sb.ToString();

Call the application with an argument string:

System.Diagnostics.Process.Start(shortcutPath, argsToPass);

The argument string that you pass can not have spaces or double-quotes in it. If you pass [a b c], you will only get [a] on the receiving side. If you pass [“a”,”b”,”c”], you will get [a]. If you need to pass arguments with spaces, pass your arguments as a query string.

How to create the argument string to be passed, and how to parse it on the receiving side.

Case 1: Sending file path and name. This mimics what happens when you use the ClickOnce properties to set up a file association, and the user double-clicks on an associated file.

Build the argument string:

//If you have file associations, 
//and you double-click on an associated file,
//it is passed in to the application like this: 
//file:///c:/temp/my%20test%20doc.blah
//So format it as a URI and send it on its way.
string fileName = @"D:\MyPictures\thePicture.jpg";
Uri uriFile = new Uri(fileName);
string argsToPass = uriFile.ToString();

On the receiving side, retrieve the file path from the URI:

//This is what you get when you set up a file association 
//  and the user double-clicks on an associated file. 
Uri uri = new Uri(activationData[0]);
string fileNamePassedIn = uri.LocalPath.ToString();

Case 2: Sending a querystring

Define the argument string:

//querystring 
string argsToPass = "?state=California&city=San%20Francisco";

Parse the string on the other side:

//NameValueCollection is like a dictionary.
NameValueCollection nvc = 
  System.Web.HttpUtility.ParseQueryString(activationData[0]);
//Get all the keys in the collection, 
//  then pull the values for each of them.
//I know I'm only passing each key once, with one value, 
//  in the querystring.
string[] theKeys = nvc.AllKeys;
foreach (string theKey in theKeys)
{
  string[] theValue = nvc.GetValues(theKey);
  //key is theKey
  //value is theValue[0]
  System.Diagnostics.Debug.Print("Key = {0}, Value = {1}", 
    theKey, theValue[0]);
}

Case 3: Pass a list of comma-delimited values

Define the argument string:

//pass a comma-delimited list of values
//don't use any spaces
string argsToPass = "arg1,arg2,arg3,arg4";

Parse the string on the other side:

//I've only ever seen activationData have one entry,
//  but I'm checking for multiples just in case. 
//This takes each entry and splits it by comma 
//  and separates them into separate entries.            
char[] myComma = { ',' };
foreach (string arg in activationData)
{
  string[] myList = activationData[0].Split(myComma, 
    StringSplitOptions.RemoveEmptyEntries);
  foreach (string oneItem in myList)
    System.Diagnostics.Debug.Print("Item = {0}", oneItem);
}

If you only want to send one argument, just use activationData[0].

Summary and Code Samples

This showed three ways to pass arguments to an offline ClickOnce application, and how to parse them on the receiving side.

Code samples can be found here. They are available in both C# and VB. They were built with VS2008 and they target .NET 3.5 SP-1, which is the minimum version for which this will work.

The first solution (RunningWithArgs) receives the arguments, parses them, and displays them in a listbox. You need to deploy this one to a webserver or a file share and then install it. To determine which kind of argument it is receiving, it checks the first characters of the argument string.

The second solution (CallRunningWithArgs) calls the first one and passes arguments to it. It uses the conventions mentioned previously to determine the kind of arguments being passed. If the first application is installed, you can just run this one out of Visual Studio.