Programmatically launching ClickOnce applications

An in-depth look at the various ways that ClickOnce applications can be programmatically launched.


It's not always easy to programmatically launch a ClickOnce application. The installation is not particularly transparent, and it's tricky to find exactly where on your local machine a ClickOnce application has been installed. This blog post will provide some detail on the ways in which a ClickOnce application can be launched programmatically.

Launching from the deployment manifest (.application file)

The first, and most obvious way of launching a ClickOnce application is from its deployment manifest. In fact, this is how most users would usually launch a ClickOnce application - by downloading and running the .application file.

To do this, simply run the .application file using the windows shell. This can be done by invoking ShellExecute from your language of choice. In .NET/C#, just use Process!

C# example:

/* Launch from URL */
Process.Start("http://example.com/ExampleClickOnceApp.application");
/* Launch from .application file */
Process.Start("file://C:/ExampleClickOnceApp.application");

Launch with the ClickOnce Application Deployment Support Library

The technique detailed above might seem somewhat "magical". What's going on behind the scenes? Well, there's a file association for .application files, which directs them to the 'ClickOnce Application Deployment Support Library' (dfshim.dll). This does all the heavy lifting; downloading the application, checking for updates, and working out where on your local machine the application files should be located. Anyway, if you prefer to have things be a little more robust and a little less magical, you can launch a ClickOnce application using dfshim.dll directly.

C# example:

/* Launch from URL */
string app = "http://example.com/ExampleClickOnceApp.application";
Process.Start("rundll32.exe", "dfshim.dll,ShOpenVerbApplication " + app);
/* launch from .application file */
string app = "file://C:/ExampleClickOnceApp.application";
Process.Start("rundll32.exe", "dfshim.dll,ShOpenVerbApplication " + app);

Launching from an appref-ms file

If your ClickOnce application includes a start menu shortcut, you may have noticed that the shortcut points to an 'Application Reference' (.appref-ms) file, rather than your application's executable. The application reference file contains all the information needed to find the locally installed app, or download and install it if it doesn't already exist.

An application reference file contains a single line of text (Unicode, LE) in the following format:

{ApplicationUrl}#{ApplicationFileName}, Culture={Culture}, PublicKeyToken={PublicKeyToken}, processorArchitecture={ProcessorArchitecture}

For a real-world example, consider the appref-ms file for my SkypeQuoteCreator app:

http://mking.s3.amazonaws.com/SkypeQuoteCreator.application#SkypeQuoteCreator.application, Culture=neutral, PublicKeyToken=0000000000000000, processorArchitecture=msil

You can generate an application reference file at runtime and then launch it using the following code snippet:

void LaunchClickOnceApplication(string url, string queryString)
{
  // Call this whatever you want.
  string applicationReferencePath = "reference.appref-ms"; 

  // Only bother generating the .appref-ms file if we haven't done it before.
  if (!File.Exists(applicationReferencePath))
  {
    byte[] applicationManifest = null;
    string applicationReference = null;

    // Download the application manifest.
    using (WebClient wc = new WebClient())
    {
      applicationManifest = wc.DownloadData(url);
    }

    // Build the .appref-ms contents.
    using (MemoryStream stream = new MemoryStream(applicationManifest))
    {
      XNamespace asmv1 = "urn:schemas-microsoft-com:asm.v1";
      XNamespace asmv2 = "urn:schemas-microsoft-com:asm.v2";
      XDocument doc = XDocument.Load(stream);
      XElement assembly = doc.Element(asmv1 + "assembly");
      XElement identity = assembly.Element(asmv1 + "assemblyIdentity");
      XElement deployment = assembly.Element(asmv2 + "deployment");
      XElement provider = deployment.Element(asmv2 + "deploymentProvider");
      XAttribute codebase = provider.Attribute("codebase");
      XAttribute name = identity.Attribute("name");
      XAttribute language = identity.Attribute("language");
      XAttribute publicKeyToken = identity.Attribute("publicKeyToken");
      XAttribute architecture = identity.Attribute("processorArchitecture");

      applicationReference = String.Format(
        "{0}#{1}, Culture={2}, PublicKeyToken={3}, processorArchitecture={4}",
        codebase.Value,
        name.Value,
        language.Value,
        publicKeyToken.Value,
        architecture.Value);
    }

    // Write the .appref-ms file (ensure that it's Unicode encoded).
    using (FileStream stream = File.OpenWrite(applicationReferencePath))
    {
      using (StreamWriter writer = new StreamWriter(stream, Encoding.Unicode))
      {
        writer.Write(applicationReference);
      }
    }
  }

  // Launch the .appref-ms file.
  Process.Start(applicationReferencePath, queryString);
}

Conclusion

This blog post has given a brief description of how ClickOnce applications are launched, and has outlined a number of ways to launch your ClickOnce apps programmatically. My recommendation is to use the Application Reference (.appref-ms) method, as it most closely mimics how the user of an installed app would launch their application.


Posted by Matthew King on 27 May 2014
Permission is granted to use all code snippets under CC BY-SA 3.0 (just like StackOverflow), or the MIT license - your choice!