Administering ClickOnce Deployments

 

Brian Noyes
Microsoft Regional Director and MVP

June 2006
Revised October 2006 (sample download added)

Applies to:
   .NET Framework 2.0
   Smart client application deployment
   Windows Forms

Summary: ClickOnce provides an easy-to-use and flexible deployment mechanism for smart client applications built for the .NET Framework 2.0. ClickOnce offers end-user deployment, ready-made installation dialogs, a built-in security model, and automatic or on-demand updates. But while it's great to have so many built-in features, one area that users often request more control is in tracking who uses which applications, controlling access to those applications, and knowing when something goes wrong. You may just need to keep track of who is using which versions of each application at the user level. You may want to restrict access to certain applications and updates based on a user's identity or their association with a role or group. You may want to know when launch errors have occurred for particular users so that you can troubleshoot deployment problems if they exist.

This whitepaper discusses all of these scenarios. It identifies the places you can insert your own code or tools into the process for control or monitoring purposes. Several alternative solutions will be discussed, allowing you to pick the approach that provides the best trade-off of complexity and control given your requirements. This whitepaper also identifies potential hazards of certain solutions that may seem like a good idea, but that can lead to fragility or unexpected results. Along the way, I give you insight into what artifacts are being placed where when you publish and deploy a ClickOnce application. (29 printed pages)

Download the associated code sample.

Contents

Introduction
   Publishing and Deploying a ClickOnce Application
   Installing and Launching a ClickOnce Application
Tracking ClickOnce Application Usage
   Using Windows Authentication
   Using Custom Application Authentication
   Using Query String Parameters
   Using a Custom Client Proxy to Establish an Application Session
   User Authentication Summary
Limiting Access to ClickOnce Applications
   Using Windows Authorization
   Custom Authorization
   Using Query String Parameters
   Using a Custom Client Proxy
   User Authorization Summary
Dealing with Launch Errors
Summary
Additional Resources
Appendix
   Publish Directory Structure
   Setting and Changing the Deployment Provider

Introduction

ClickOnce allows you to easily deploy smart client applications through a simple client-pull model that is part of the .NET Framework 2.0. To use ClickOnce, you build and publish your application files and ClickOnce metadata to a deployment server. End users install the application via a URL, which you provide. When the user clicks the link, the application will download to and launch from a local cache on the user's machine. Depending on the permissions that it requests, the application can run in a secure sandbox provided by the .NET Framework 2.0. The application launches from the local cache each time it is run, and the ClickOnce runtime in .NET Framework 2.0 can be configured to automatically check for updates on each launch. The application can also be updated on demand using the deployment APIs under the System.Deployment namespace in the .NET Framework's base class libraries (BCL). ClickOnce applications can be run as online-only applications, where they can only be activated via URL, or as installed applications, where they can be activated via the Start menu and managed by the user via the Add and Remove Programs dialog. ClickOnce deployment has several configurable settings, including the update semantics, the update location of the application, which files are included in the application, and the security requirements the application has at runtime. All of these capabilities are covered in various other articles that can get you up to speed on the basics of ClickOnce (see the Additional Resources section).

Once architects and developers get familiar with the basics of ClickOnce, the next thing they usually try to figure out is how to make it work for their particular production environment. They also try to figure out how they will satisfy all of their related administration and management requirements. Specifically, some of the most common questions that arise with respect to administering ClickOnce applications are the following:

  • How can I track which users have which versions of which applications?
  • How can I control which users have access to which applications?
  • How can I know when an application has failed to launch on a client and how can I diagnose the problem?

How to address these questions is not readily apparent because ClickOnce does not provide a means to centrally manage applications that have been deployed. There is no natively supported way to know who has downloaded and launched which ClickOnce applications, and whether the end-users had any problems doing so. There are also no built-in mechanisms for restricting which users have access to particular applications or versions of applications.

These scenarios could be addressed in ClickOnce by combining existing IT infrastructure provided by the Windows platform and some custom solutions. This paper will explore the various tracking and access control options, including using Windows credentials, using a custom application API, using query string parameters, and using a custom client side proxy. Diagnosing application launch failures will be discussed at the end of the paper.

Publishing and Deploying a ClickOnce Application

To understand how to satisfy tracking and control requirements, it is necessary to have a good understanding of exactly what ClickOnce is doing under the covers when you publish and deploy an application. To publish a ClickOnce application, you must generate two manifests, which are XML files containing ClickOnce metadata. These files are called the deployment manifest (<appname>.application file) and the application manifest (<appname>.exe.manifest file). As its name suggests, the deployment manifest describes the aspects of the deployment, including publisher, installation mode (online only or online/offline), and update policy. The deployment manifest also contains the deployment provider (the hard coded URL to the deployment manifest that the ClickOnce runtime uses to avoid spoofing attacks) and a path to the location of the application manifest. For a typical ClickOnce deployment, the deployment manifest, the application manifest, and the application files will all reside on the same deployment server. In these cases, the deployment provider URL will match the URL used to launch the application from the client machine.

The application manifest contains a list of files that compose the application and the security permissions required by the application. Both the deployment and application manifests must be signed with an Authenticode publisher certificate to ensure authenticity of the application at deployment time.

Note The publish command in Visual Studio 2005 takes care of creating and signing the deployment and application manifests for you, based on the settings you've specified in the Publish, Signing, and Security tabs of the project property designer.

If you want to generate or update the manifests manually, you can do so using the SDK tool Mage, which has both a Windows application version (mageUI.exe () and a command-line version (mage.exe). (These SDK tools will be called Mage collectively throughout this paper.) It is important to note that Mage only allows you to edit or generate manifests, but does not support publishing; you'll have to copy the manifests and the application files to the deployment server or to a local directory for publishing via CD. The application files include the Windows executable itself, any dependent DLLs, and any additional files such as resources, XML, database, or document files the application uses.

Installing and Launching a ClickOnce Application

The installation and launch of a ClickOnce application occurs when an end-user clicks on a link to the deployment manifest. That link may be provided to the user through a number of means, including a URL in e-mail, a link on a company portal Web page, or a Windows Shortcut link (.lnk file) placed on their desktop. When the user clicks on the link, the application files are downloaded into the ClickOnce application cache on the client machine. The cache location is isolated per user, per application, and per version. This cache is independent of the Internet Explorer cache or any other application cache.

Once the application has been downloaded, it is launched from the ClickOnce cache. Depending on the security settings of the application, it may be run in a secure sandbox enforced by the .NET Framework 2.0 Code Access Security (CAS) mechanisms. A sandbox refers to a particular set of actions, called permissions, which the application is permitted to do. If the application tries to do something it does not have permission to do, the Framework blocks the action and throws a security exception.

The Framework determines the degree of an application's sandbox at install time. ClickOnce applies security policy at the application level rather than at the assembly level. Based on the target machine's security policy and the installation location, the .NET Framework will allow the application a default set of permissions. For its part, the application requests a set of permissions, as specified in its application manifest. If application's request exceeds the default set, then the application must gain elevated trust in order to run. This can be done either by the user explicitly agreeing in a security prompt shown at install time or through policy present on the user's machine.

Note   The important takeaway here is that the security protections provided by ClickOnce are based only on CAS mechanisms and are not tied to the identity of the user running the application in any way. ClickOnce applications can be installed and run by least-privilege users on the client machine (non-administrators), and there are no built-in security mechanisms specific to ClickOnce for identifying who the user is on the client or server side.

Each time the ClickOnce application is launched there are zero to many requests for files being sent to the deployment server to ensure that the client machine has the appropriate version of the application in the cache when it launches the application. For an initial deployment, this includes downloading the deployment manifest, application manifest, and all of the application files for the current version.

On subsequent launches, basically the deployment manifest is downloaded from the deployment server and the version number within it is compared to the locally cached version. If it's determined an update is available, only those files in the update that are different than the local copies are downloaded from the deployment server to the client. Once the update has been applied then the application is launched.

Tracking ClickOnce Application Usage

One common requirement for enterprise usage of ClickOnce is the need to track which users have launched or installed which applications. An example of this is querying the specific version of an application most recently launched by the user. Tracking of this sort will likely need to be done on the deployment server to avoid the need to monitor potentially large numbers of client machines distributed over the network. Tracking on the client side would also require custom software development and installation because the ClickOnce runtime does not expose any hooks to plug in a custom launch event tracking solution.

In order to track which users have installed or launched which applications from the server side, you need to authenticate the user's identity and to detect and intercept file download requests. Your ability to do these things is affected by a number of factors.

One key factor is the network protocol used. ClickOnce supports HTTP (and HTTPS) or network file sharing (UNC path). HTTP provides the most flexibility because you can easily intercept inbound file requests on the server. You can still achieve some level of tracking with Windows file auditing and event logs, however these options are difficult to set up.

Another important factor is whether the ClickOnce application is being launched from inside your network, or from outside. From inside your network there are ways to control user accounts and to have some degree of configuration control over the client machines. From outside the network, you can't count on Windows networking to identify the user or machine because any number of proxies, firewalls, and layers of interception can be between your deployment server and the end user.

Even though there are no built-in mechanisms with ClickOnce to determine which users are trying to deploy your applications, there are a number of approaches you can employ to obtain this information. These include:

  • Windows authentication over HTTP(S).
  • Custom authentication designed into the application.
  • Passing query string parameters when launching the application (requires dynamic manifest generation with embedded user credentials for installed applications).
  • Custom client proxy and custom HTTP module authentication.

Identification can also be done at the machine level instead of the individual user if you are within a controlled Windows network where there are no proxies or firewalls in the way that disguise the address or identity of the calling machine, but identifying the individual user is a more common requirement, and harder to achieve, so I will focus at the latter.

Using Windows Authentication

When the user launches a ClickOnce application and is online, a set of requests are sent by the ClickOnce runtime on the client to the deployment server to obtain the files necessary to deploy or update the application. If the client is part of your Windows network, the logged in identity of the user on the client machine is passed along with those file requests via Windows networking to the deployment server. HTTP is the preferred protocol to allow you to intercept those requests and log them for tracking purposes. Depending on which Web server version you are using, at a minimum you will be able to let the Web server log visits to your ClickOnce application deployment site, and should be able to determine the IP address or machine name the request came from. If you are running a more sophisticated Web server, such as Internet Information Services (IIS) 6.0 on Windows Server 2003, you can get extended log file formats that include the authenticated user identity. If this is the case you can simply use these log files directly and do some data-mining on the logged entries to summarize who is accessing which applications and versions.

If you want a greater degree of control over the logging format and location, such as pumping only requests for the deployment and application manifests into a custom database per application, version, and user, you can use the request processing pipeline of ASP.NET to easily facilitate this. What you need to do is publish your ClickOnce application to an ASP.NET site. As discussed earlier, this involves simply copying the manifests and application files to a folder under the root folder of the site and updating the deployment manifest to reflect the appropriate deployment provider URL.

You need to configure IIS to pass requests for .application, .manifest, and .deploy files to ASP.NET instead of servicing those directly in IIS. You also want to turn off anonymous access to the site, and only allow Integrated Windows Authentication control. To do so, follow these steps:

  1. Open the IIS management console.
  2. Right-click on the site or virtual directory under which you have copied the ClickOnce application files and select Properties.
  3. In the first tab of the properties dialog (the Directory tab), press the Configuration button at the bottom right under Application Settings. This brings up the Application Configuration dialog (see Figure 1).
  4. Select one of the existing application mappings assigned to ASP.NET, such as the .ascx mapping and press the Edit button. You do this just to get the full path to the ASP.NET ISAPI extension.
  5. Copy the Executable path from the Add/Edit Application Extension Mapping dialog to the clipboard (CTRL-C doesn't work in this dialog, but CTRL-Insert does).
  6. Cancel out of editing the extension selected in step 4.
  7. Click the Add button to bring the Add/Edit Application Extension Mapping dialog for the new mapping.
  8. Paste the Executable path for the ASP.NET ISAPI extension into the Executable input box (Shift-Insert).
  9. Enter an extension of .application and press OK.
  10. Repeat step 7-9 for the .manifest and .deploy file extensions.
  11. Click OK to exit the Application Configuration dialog.
  12. Select the Directory Security tab in the virtual directory properties dialog.
  13. Click the Edit... button in the Anonymous access and authentication control section.
  14. Clear the Anonymous Access check box at the top of the Authentication Methods dialog and click OK.
  15. Click OK to exit the Properties dialog.

Figure 1. Passing .application, .manifest, and .deploy requests to ASP.NET

Once you have the site configured to pass ClickOnce file requests to ASP.NET, as well as only allowing authenticated Windows users into the site, you can now capture the user identity with each file request using Windows authentication in the ASP.NET application that shares the site. To do this, you need to write a custom HTTP module that is configured to handle requests coming into the site. The custom module extracts the user information and which application is being requested, and can log this information as needed. An example HTTP module that performs this kind of custom tracking is shown in Figure 2.

An HTTP module is a class that implements the IHttpModule interface. This interface includes two methods, Init and Dispose. In Init you usually subscribe to the application level events you are interested in. These events fire for each request processed by the application. In this case, we want to handle the AuthorizeRequest event because it is fired after the user has been successfully authenticated. In that handler, we just check the request properties to extract the user and URL being requested. If the request is for a ClickOnce resource (.application, .manifest, or .deploy file), then we log it.

Figure 2. Custom ClickOnce application tracking module

public class DeploymentTrackingModule : IHttpModule
{
   #region IHttpModule Members

   public void Dispose()
   {
   }

   public void Init(HttpApplication app)
   {
      app.AuthorizeRequest += OnAuthorizeRequest;
   }

   #endregion

   private void OnAuthorizeRequest(object sender, EventArgs e)
   {
      // Gather context information
      HttpRequest request = HttpContext.Current.Request;
      string user = request.LogonUserIdentity.Name;
      string resource = request.Url.ToString();
      string localPath = request.Url.LocalPath;
      string resourcePath = 
         HttpContext.Current.Server.MapPath(localPath);
      string fileExtension = Path.GetExtension(resource);
      // Log deployment manifest requests
      if (fileExtension == ".application")
      {
         // Get version
         string version = 
            ManifestHelper.GetManifestVersion(resourcePath);
         // Log potential deployment / update check
         Debug.WriteLine(
            string.Format(
               "Deployment manifest request: {0}\n" +
               "User: {1}\nVersion: {2}\n",
               resource, user, version));
      }
      // Log application manifest requests
      if (fileExtension == ".manifest")
      {
         // Get version
         string version = 
            ManifestHelper.GetManifestVersion(resourcePath);
         // Log deployment/update in progresss
         Debug.WriteLine(
            string.Format(
               "Application deployment or update in progress:\n" +
               "Manifest: {0}\nUser: {1}\nVersion: {2}\n",
               resource, user, version));
      }
      if (fileExtension == ".deploy")
      {
         // Log app file retrieval
         Debug.WriteLine(
            string.Format(
               "Application file download: {0}\nUser: {1}",
               resource, user));
      }
   }
}

The module shown in Figure 2 intercepts requests for .application, .manifest, and .deploy files and simulates logging activity with Debug.WriteLine statements. The requests for .application may be caused by an initial installation/launch, or can be caused by an update check at subsequent application launch. As a result, .application requests can't be used as an indication that an update is in progress. A request for a .manifest file does indicate an installation or update to the corresponding version, so that should be used as the primary indicator of what version the user currently has. The .deploy file requests are the details of what files are being installed or updated on the client, and can be ignored or also logged as tracking details.

To extract the version from the deployment and application manifests in the tracking module, a ManifestHelper class is called to extract the manifest version using the APIs defined in the Microsoft.Build.Tasks.Deployment namespace (see Figure 3). The output of an installation request is shown in Figure 4.

Figure 3. Checking manifest version

using Microsoft.Build.Tasks.Deployment.ManifestUtilities;

public static class ManifestHelper
{
   public static string GetManifestVersion(string path)
   {
      Manifest fest = ManifestReader.ReadManifest(path, false);
      return fest.AssemblyIdentity.Version;
   }
}

Figure 4. Tracking Output for Sample Application Launch

Deployment manifest request: http://xps600/CODeploymentTrackingSite/CustomerManager/CustomerManager.application
User: XPS600\Brian Noyes
Version: 1.0.0.1

Deployment manifest request: http://xps600/CODeploymentTrackingSite/CustomerManager/CustomerManager.application
User: XPS600\Brian Noyes
Version: 1.0.0.1

Application deployment or update in progress:
Manifest: http://xps600/CODeploymentTrackingSite/CustomerManager/CustomerManager_1_0_0_1/ CustomerManager.exe.manifest
User: XPS600\Brian Noyes
Version: 1.0.0.1

Application file download: http://xps600/CODeploymentTrackingSite/CustomerManager/CustomerManager_1_0_0_1/ CustomerManager.exe.config.deploy
User: XPS600\Brian Noyes

Application file download: http://xps600/CODeploymentTrackingSite/CustomerManager/CustomerManager_1_0_0_1/ CustomerManager.exe.deploy
User: XPS600\Brian Noyes

To add this module to an ASP.NET Web site that contains ClickOnce application deployment files, you first add this source code to a file and place it in the App_Code subfolder of the ASP.NET application root folder. This allows the code to be dynamically compiled by the ASP.NET runtime. You will then need to identify the module to the application so that it can be called by the runtime. You do this with the web.config file entry shown in Figure 5.

Figure 5. Web.config file entries to hook up custom module

<?xml version="1.0"?>
<configuration>
  <system.web>
    <httpModules>
      <add name="COTracking" type="DeploymentTrackingModule"/>
    </httpModules>
    <!-- Other system.web elements -->
  </system.web>
</configuration>

Using Custom Application Authentication

If Windows authentication is not an option for you, then there are other ways to identify and track the user. One straightforward way of doing this for primarily online applications is to build a custom authentication mechanism into the application itself. Using this method, the user is allowed unauthenticated access to install the application from the deployment server; however, the user is forced to log in to the application at start up in order to gain access to any of the application's functionality. This scheme is best suited for applications that need to connect to a back-end server anyway to perform its primary functions (e.g. a distributed client application).

To accomplish this, the client application needs to communicate remotely with the back-end server for authentication purposes. Web services are ideal for ClickOnce application because they are loosely coupled and easy to implement. (See the Web Services Basics page on MSDN.)

No matter which remote communication technology you choose, you will want to expose a way for the client to authenticate the user credentials with the back-end server. It may be sufficient for tracking purposes just to identify the user at launch, in which case a single method call to the server API that accepts the user identification would work. As an example, consider the following simple Web service:

public class AppService : System.Web.Services.WebService
{
   [WebMethod]
   public void TrackUser(string username, string password)
   {
      // Log application launch
   }
}

This can be called by the client application at launch, after a user supplies credentials through a login dialog (with an appropriate Web reference added to the client application project):

private void OnLogin(object sender, EventArgs e)
{
   AppService service = new AppService();
   service.TrackUser(m_UsernameTextBox.Text, m_PasswordTextBox.Text);
}

If you need to track application usage beyond just the launch, or if you need to authorize access to portions of the application functionality based on user identity and privileges, you will want to look into passing a user token back and forth with each call. Using Web Services Enhancements 3.0 is the best way to do that with currently released .NET technology. Using custom authorization is discussed more in a later section.

Using Query String Parameters

Another way to authenticate a user for tracking or authorization purposes is to pass the user's credentials as query parameters back to the server when the application is launched. ClickOnce supports passing query string parameters to an application when it is launched through an HTTP URL to the deployment manifest. For example, you can use a launch URL, as shown below:

http://someserver/SomeClickOnceApp.application?username=Joe&password=CORocks

When you pass query string parameters in the URL through a request from the browser, the parameters are accessible both to the launching client application and to the deployment server when it is contacted to check for updates. If you pass the user's credentials as query string parameters when the app is launched (while online), then you can intercept those credentials on the server and log them or make decisions about what applications and versions that user should be given access to.

When you launch an online-only application, you do so through a URL to the deployment manifest on the deployment server. As a result, you can embed user credentials as query string parameters in that URL. The user can get this custom URL in a number of ways. One way is to generate a separate URL for each user and send it to them in an email. Another way is to have the user log in to a portal Web site and dynamically generate the link to the application with the user's login credentials.

On the server side, you will need to intercept the request, strip off the query string parameters, and optionally log the information for tracking purposes. As shown earlier, by using an HTTP module you can inspect every request as it comes into the application, and then based on what is found in each request, take appropriate action. Figure 6 shows a different version of an AuthorizeRequest event handler in an HTTP module that extracts query string parameters and simulates logging them.

Figure 6. HTTP Module with Query String Extraction

private void OnAuthorizeRequest(object sender, EventArgs e)
{
   // Gather context information
   HttpRequest request = HttpContext.Current.Request;
   NameValueCollection parms = 
      HttpUtility.ParseQueryString(request.Url.Query);
   string username = parms["username"];
   string password = parms["password"];
   if (username != null && password != null)
   {
      // Log app launch with credentials
      Debug.WriteLine("App launched by " + username +
        " with password " + password);
   }
}

Using an HTTP module, you can inspect the URL for each request entering the ASP.NET application. If the request is for a deployment manifest, you can look for URL parameters such as a username token and possibly a password. Using that information, you log the launch of each application or update on a per user basis. You can also validate the user name and password, and if they were invalid, you would return an error message instead of returning the application file itself.

This addresses identifying the user for online-only applications. But installed applications will normally be launched through their Start menu shortcut after initial deployment. So how can you get user-specific URL parameters embedded in the shortcut that is used by ClickOnce to launch an installed application? The answer is fairly complicated and still has a number of limitations you should be aware of.

Getting user-specific URL parameters passed when the ClickOnce application is launched from the Start menu shortcut requires the deployment provider on the client machine contain the custom parameters. To achieve this, you need to dynamically generate a manifest for each user at the point when they first try to launch the application from the server.

You can do this by using a custom HTTP handler. A custom HTTP handler can be configured in an ASP.NET Web application to be the endpoint that receives requests for a given file type. Similar to a custom HTTP module, a custom handler is just a class that implements the IHttpHandler interface. The handler is passed the request by the ASP.NET processing pipeline and can do whatever custom processing is required in its IHttpHandler.ProcessRequest method implementation. In the case of dynamic manifest generation, the custom processing generates a custom user-specific manifest on the fly and return that instead of the non-user-specific one requested.

Setting up dynamic manifest generation for the purpose of embedding user information in the launch URL involves the following steps:

  • Set up an ASP.NET Web site under which the application deployment files reside.
  • Modify the application in IIS to route .application file requests to ASP.NET as shown earlier.
  • Add a custom HTTP handler to the ASP.NET application and map the .application request path to that handler through the web.config file.
  • Add a login page to the site to authenticate users before the first time they try to launch an application.
  • After they have logged in, present them with a link to launch the application.
  • In the custom handler, ensure that the user has been authenticated or that the request already contains user credentials.
  • Load the deployment manifest for the application being requested, dynamically modify the deploymentProvider URL contained within the manifest to include the user credentials as query string parameters, return that from the request, and finally re-sign the manifest.

By doing this, the files that get created on the client machine will include the query string parameters in the provider URL, and those will be passed any time the application is launched on the client side through the Start menu shortcut and the application sends deployment manifest requests to the deployment server to check for updates.

The sample code for this paper includes a simple sample called CODynamicManifestGeneration that demonstrates dynamic manifest generation to include a user token. That sample includes an ASP.NET project with a custom handler to perform the manifest generation when a request comes in for a deployment manifest. The code for this custom handler is shown in Figure 7.

Figure 7. Custom HTTP Handler for dynamic manifest generation

public class DynamicManifestHandler : IHttpHandler
{
   #region IHttpHandler Members

   public bool IsReusable
   {
      get { return true; }
   }

   public void ProcessRequest(HttpContext context)
   {
      string requestUrl = context.Request.Url.ToString();
      string localPath = context.Request.Url.LocalPath;
      string resourcePath =
         context.Server.MapPath(localPath);
      string providerUrl = context.Request.Url.AbsoluteUri + 
         "?username=Joe&password=CORocks";
      string certPath = 
         Path.Combine(Path.GetDirectoryName(resourcePath), "cert.pfx");
      string tempCertPath = 
         context.Server.MapPath("~/temp.application");
      // Output request URLs for diagnostic purposes
      Debug.WriteLine(requestUrl);
      // If it is a deployment manifest
      if (Path.GetExtension(requestUrl).Contains(".application"))
      {
         ManifestHelper.ModifyDeploymentProviderAndSign(
            resourcePath,tempCertPath, providerUrl,certPath,null);
         // Write generated file into response stream
         context.Response.WriteFile(tempCertPath);
         // Set the appropriate contentType header 
         // for a deployment manifest
         context.Response.ContentType = "application/x-ms-application";
      }
   }

   #endregion
}

As you can see from Figure 7, the custom handler extracts the request information for to see if the request is for an .application file. If so, it calls into the ManifestHelper method ModifyDeploymentProviderAndSign to dynamically modify the deployment provider URL in the requested manifest and then re-signs the manifest with a specified certificate.

The helper method looks like this:

public static void ModifyDeploymentProviderAndSign(
   string manifestPathIn, string manifestPathOut, string providerUrl, 
   string certPath, string certFilePassword)
{
   DeployManifest fest = ManifestReader.ReadManifest(
      manifestPathIn, false) as DeployManifest;
   fest.DeploymentUrl = providerUrl;
   ManifestWriter.WriteManifest(fest, manifestPathOut);
   X509Certificate2 cert = new X509Certificate2(
      certPath, certFilePassword);
   SecurityUtilities.SignFile(cert, null, manifestPathOut);
}

The custom handler gets hooked up in the web.config file in a similar manner to the way you hook up custom modules. You add an <httpHandlers> element to the <system.web> element and place <add> elements under that with path, verb, and type information (see Figure 8).

Figure 8. Web.config file to hook up custom HTTP handler

<configuration>
  <system.web>
    <httpHandlers>
      <add path="*.application" verb="*" 
           type="DynamicManifestHandler"/>
    </httpHandlers>
  </system.web>
</configuration>

Obviously this sample has a number of limitations that prevent you from wanting to use it directly for production sites. For this code to run, the ASP.NET worker process identity (ASPNET local machine account by default) will have to have read/write access to the folder where the manifest resides. The temporary file created by Mage can be accessed by multiple requests concurrently, and this represents a scalability concern even if you can ensure that only one user at a time can access it through dynamic file path generation. To make this more production ready, you will need to write out a separate file per client request to avoid concurrency issues. You also need to get the user credentials from somewhere instead of having them hard-coded (i.e. a forms authentication ticket). You will also probably want to drive things like the certificate file location based on configuration settings.

Besides just the complexity involved, this approach has some significant limitations. The first is that whatever form of user credential you choose to use, those credentials will be passed in plain text as part of the request URL from the client to the deployment server whenever the application is updated. As a result, you will definitely want to consider using SSL for your deployment site. Additionally, those credentials will be stored in plain text files under the user's profile on the client machine (the deployment manifest). The user's profile should be protected by default from other users (other than Administrators), so this is not a large vulnerability, but you should be aware of it. Additionally, anyone can impersonate any other user if they can obtain their credentials just by manually constructing the URL to the deployment manifest in a browser. You also might want to make user credentials expire on the server side as an additional security measure to limit the window of vulnerability for compromised credentials. To do so, you would require that the user login to the site periodically and launch the application through the manifest generation process to issue a new set of credentials.

On the server side, in order to dynamically generate the manifest, you will have to have the Authenticode publisher certificate used to sign the manifests physically present and accessible to the Web application to sign the manifest after modification. That represents a bit of a security concern because if someone can compromise the file access protections of your site, they can steal your certificate and start issuing applications that look like you signed them. Windows physical file security should be sufficient to protect you against this, but putting a publisher certificate that close to the front line boundary of your network is a bit of a concern for some organizations (depending on the certificate being used for application signing).

Probably the biggest limitation is that there is still no way to secure access to the actual application files (the application manifest or any of the application files suffixed with .deploy). The requests for these files come in as separate individual file requests and are not associated with the request for the .application file in any way that is apparent to the server. So even if you put this mechanism in place, someone who can figure out your directory structure could issue unauthenticated requests for the .deploy files. Once obtained, they can manually remove the .deploy extensions and try to run the application locally without going through the ClickOnce launch mechanism.

Using a Custom Client Proxy to Establish an Application Session

The problem with the custom approaches discussed so far is tied to the fact that when a ClickOnce application is installed or updated, a series of separate file requests come in to the deployment server from the client machine. There is no way for the deployment server to identify who those calls are coming from and that they are all part of a single logical installation or update "session" from the perspective of the user and the client application. The only way to solve this problem is to introduce something onto the client that can make an association between the individual requests and introduce information into the calls leaving the client to identify the user to the deployment server.

This kind of client side interception of requests is exactly what an HTTP proxy was designed for, even though they are usually used for other purposes related to network infrastructure security. If a proxy can be introduced on the client that intercepted requests destined for one or a set of ClickOnce deployment servers, and added user credential information to those requests before they left the client machine, then those credentials can be used on the server not only for authenticating the deployment manifest requests, but for all file requests on the server. The proxy adds a custom cookie to the HTTP requests that contained the user credentials. You then need some code on the server side, such as another HTTP module, that checks for the presence of those cookies and only allows authenticated access to the server (see Figure 9).

Figure 9. Client proxy user credentials injection

This solution is complicated because the proxy that injects the user identity is difficult to create. To implement the proxy, you can use the HttpListener and HttpWebRequest classes in the .NET Framework 2.0 to create a proxy that intercepts and forwards all calls to a particular site after adding user credentials. The proxy needs to be configurable so that you can target multiple different deployment server locations. You need to decide how you want to gather user credentials on the client machine, associate them with the application, and use them for future manifest and application file requests leaving the server.

You also need to protect the credentials in transit, probably by using SSL. You need to intercept the inbound requests on the server side and extract the user credentials from the request to perform logging or to make authorization requests. This approach is most easily accomplished by using a custom HTTP module as discussed earlier as depicted in Figure 2.

Once the proxy is written and tested, you need a mechanism to deploy the proxy to the client. Since the proxy can interfere with other applications on the client machine, it is not really a candidate for ClickOnce deployment itself. You can use the Bootstrapper feature in Visual Studio 2005 and the .NET Framework 2.0 SDK to install the proxy as a pre-requisite for your application. Only one proxy can be installed on the client, so if the client machine required a different proxy for the network infrastructure or other applications, you cannot use this approach.

However, despite the complexity, this solution is the only way other than using Windows authentication to achieve user authentication and to be able to use that authentication information to make authorization decisions down to the individual application file level on the server side. Because the custom proxy can inject user credentials in each request leaving the client for .application, .manifest, and .deploy files, those credentials are available on the server side and the server can reject any requests for those files that did not include valid credentials.

Like most of the other approaches discussed in this paper, you will want to use SSL to protect the information in transit from the client to the server to ensure that no one can steal the credential information leaving the client and use it to impersonate the client in the future.

User Authentication Summary

As you can see from the preceding sections, identifying the user for the purposes of tracking alone can be a sticky problem with ClickOnce. Windows authentication provides the easiest solution, but is limited to applications that will only be deployed on the local intranet. A custom authentication API built into a distributed client application is straightforward and will work fine as well, but will only give you tracking information after the application is launched. Custom query string parameters can provide you with user information, but require a fairly complicated solution on the server side to dynamically generate custom manifests for each user. Finally, a custom proxy on the client side can support tracking down to the individual file level, but is very complicated to implement and has a number of limitations that will make that approach difficult for many applications.

Limiting Access to ClickOnce Applications

In order to limit access to an application, you have to first identify the user. Once you have identified the user, you can decide what you want to allow them to do. In the context of ClickOnce applications, access control can come in one of several forms:

  • Only allow authenticated users to access any of the application files or manifests on the deployment server.
  • Only allow authenticated users to access application functionality after the application has been launched.
  • Direct selected users to a particular version of a given application for Beta testing or policy/subscription purposes.

To address these scenarios, the authentication mechanisms discussed in the Tracking ClickOnce Application Usage section are still the primary choices for how to identify the user. For each of those choices, this section will discuss how to also make authorization decisions based on that user identity information. It will also highlight the advantages, limitations and considerations for each approach.

Using Windows Authorization

Using Windows authorization is definitely the easiest way to achieve access control to the manifests and application files. Because it is built in at the operating system level and constantly scrutinized for vulnerabilities, it is considered the most secure option for access control to files on a Web server. Its main downside is that it is not an option for many applications that are exposed through the Internet to their end users, or when firewalls or separate networks stand in the way of accessing the Windows user identity.

If you want to use the user's Windows credentials to secure access to the application, or to specific versions of the application, instead of just tracking usage, the most straightforward way is to simply set Access Control List (ACL) permissions to restrict access to the ClickOnce application files to only authorized users.

If you are trying to accomplish an initial deployment of an application through a URL to the deployment manifest (.application file), and the user does not have permission to access that file based on its ACLs, the user will be prompted with a standard Windows login dialog. This gives them the opportunity to enter other credentials that do have access to that file. If the user has permission to access the deployment manifest, but is denied access to any of the application files (e.g. if access was revoked to a specific version's subfolder), then an error will be displayed by ClickOnce as part of the attempt to launch the application as shown in Figure 10.

Figure 10. Authentication error dialog

The Details... button will load the error log into Notepad, and allows you to direct a user to send you that detailed error information to help diagnose a problem. In this case, the details log includes the error summary section shown in Figure 11.

Figure 11. Launch Error Details Error Summary

ERROR SUMMARY
   Below is a summary of the errors, details of these errors are listed later in the log.
   * Activation of https://localhost/ContactManager/ContactManager.application resulted in exception. 
 Following failure messages were detected:
      + Downloading https://localhost/ContactManager/ContactManager.application did not succeed.
      + The remote server returned an error: (401) Unauthorized.

The error log contains other detailed information including a stack trace from the exception and platform version info. In this case, the HTTP 401 error indicates unauthorized access to a resource as part of a request. This error is returned by one of the ClickOnce file requests for the application manifest or application files, which the user's credentials to do not have authorization to access.

If the user has a previous version of an installed application on their machine, and they are denied access to the deployment manifest after they have already run the application before, the locally cached version will continue to run just as if the application is offline.

One scenario where you might want to explicitly control access to specific application files based on the user's Windows permissions is to support deployment of a Beta version of your application. In this case, you might allow a select group of users to access a Beta version of your application, while the remainder can only access the current production version of your application. You can do this by configuring the ACLs for the Beta version folder to only allow users in the Beta group access and give the Beta testers a different URL to a deployment manifest that refers to the Beta version. However, that has the disadvantage of needing to constantly configure Windows accounts and ACLs to manage the set of people allowed access to the Beta version.

You can also address this scenario by using an HTTP module similar to the one shown in Figure 2, but change the functionality of the AuthorizeRequest handler to decide whether to let the user access the requested files based on some dynamic criteria, such as a custom database of Beta testers. To make the permission decisions, you can call out to some custom code, or you can tie it in with the new Membership and Role provider infrastructure in ASP.NET 2.0. If a request comes in for a deployment manifest or application files that the user does not have permissions to access, you can just return an appropriate error such as an HTTP 401 Unauthorized error. When ClickOnce sees this status code in the response for a file, it will present an authentication error dialog as shown in Figure 10.

An example of blocking access is shown in Figure 12.

Figure 12. Custom HTTP Authorization Module AuthorizeRequest Handler

void OnAuthorizeRequest(object sender, EventArgs e)
{
         // Gather context information
   HttpRequest request = HttpContext.Current.Request;
   string user = request.LogonUserIdentity.Name;
   string resource = request.Url.ToString();
   string localPath = request.Url.LocalPath;
   string resourcePath = 
      HttpContext.Current.Server.MapPath(localPath);
   string fileExtension = Path.GetExtension(resource);
   // Log deployment manifest requests
   if (fileExtension == ".application")
   {
      // Get version
      string version =
         ManifestHelper.GetManifestVersion(resourcePath);
      // Lookup access rights
      if (!GrantAccess(user, version))
      {
         HttpContext.Current.Response.StatusCode = 401;
      }

   }
}

You can also do a dynamic routing to the appropriate deployment manifest based on the user's role. The best way to do this is with a custom HTTP Handler with a ProcessRequest method like the one shown in Figure 13.

Figure 13. Custom HTTP Handler ProcessRequest Method

public void ProcessRequest(HttpContext context)
{
   string resource = request.Url.ToString();
   string fileExtension = Path.GetExtension(resource);
   // Log deployment manifest requests
   if (fileExtension == ".application")
   {
      if (HttpContext.Current.User.IsInRole("BetaTester"))
      {
         HttpContext.Current.Server.Transfer(
           betaVersionDeployManifest);
      }
   }
}

Custom Authorization

If you cannot use Windows identities to control access to the application files, you can use a custom authorization approach that is built in to the client application. This is especially relevant for a distributed client application. Using this approach, you require the user to login immediately after the application starts or before accessing privileged portions of the application. The login gets passed to and validated at the server. You then return a set of roles to the client application identifying what the user is allowed to do. You start the application with most or all of the user interface elements disabled until the user's roles have been retrieved, selectively enabling functionality based on the user roles.

The advantage of this approach is that you have explicit control over what the user is allowed to do with your application based on their identity and associated roles. The downsides are mainly related to the fact that the authentication and authorization occurs after application has already been installed. This does not provide any file-level access controls and therefore does not prevent someone from obtaining your application files, even though they may not be authorized to use the application. It also requires that the user be online in order to run the application.

A simple example is shown in Figure 14. The MainForm class in this example has its main user interface elements disabled by default. At form load, it pops up a login dialog. The login dialog (LoginForm) calls out through a Web service to validate the user. If the login dialog returns OK, indicating that the user was validated, then the main form enables the rest of the UI.

Figure 14. Enabling Client UI after Authentication

public partial class MainForm : Form
{
   public MainForm()
   {
      InitializeComponent();
   }

   private void MainForm_Load(object sender, EventArgs e)
   {
      LoginForm login = new LoginForm();
      DialogResult result = login.ShowDialog();
      if (result == DialogResult.OK)
      {
         m_MainMenu.Enabled = true;
         m_MainToolStrip.Enabled = true;
         m_MainContentPanel.Enabled = true;
      }
   }
}

For this kind of authorization scheme, just disabling UI functionality based on the identity of the user or the roles with which they are associated is probably not a secure enough solution. You will also want to protect the access to the back-end services that the application uses, to prevent someone from spoofing their own un-authenticated client to call those services. To do this, you need to have authenticated access controls on the back-end that limit access to the APIs based on the identity of the caller. This implies that you need to maintain the identity on the client side and maintain a log in session with the back-end services for subsequent calls beside the initial log in.

One of the best ways to do this with currently released technology is to use Web Services Enhancements (WSE) 3.0 with an ASP.NET Web service as the back-end API you are calling into. WSE 3.0 provides flexible security mechanisms for authenticating every call coming into the service, and ASP.NET allows you to plug in HTTP modules, handlers, and custom providers to extend the processing of requests to accomplish whatever you need to do. WSE 3.0 implements the WS-Security standards and allows you to protect message level information including username and password embedded in the messages. It can be combined with transport-level security such as SSL or used on its own with equal levels of protection. WSE 3.0 is a free extension to ASP.NET that you can download from MSDN. The download includes documentation and samples to get you up and running with WSE 3.0. WSE 3.0 also has the advantage of being wire compatible with Windows Communications Foundation (WCF), which will be the infrastructure of choice for remote communications in the future.

Using Query String Parameters

An earlier section described the process required to use user-specific query string parameters to track application usage. For installed applications, it requires you to dynamically generate the returned manifest for the user each time they request a deployment manifest. By doing so, subsequent requests for the same manifest can carry along the query string parameters you embedded in the returned manifest's deploymentProvider element.

If you embedded user credentials such as a user name and password in the query string parameters, you can also use this information as a weak form of access control on the server side. When the request for a deployment manifest comes in to the server, you can inspect the query string on the request to see if a valid user name and password is present, and if not, you can return an authorization error (HTTP 401 status code). This blocks the application from launching on the client. You do this in an HTTP Module as shown in Figure 12. This solution assumes that the user is online when application launches.

The problem with using this approach for authorization is that it provides a very weak form of access control. The credentials are included in the request query string, so they are vulnerable in transit to someone sniffing the network traffic, unless transmitted over HTTPS. The query strings are stored in plain text on the client machine, so if someone can get to the manifest files under a user's profile, they can steal that person's identity for using the application. And the only thing really being checked is the .application file access. The query string parameters are only transmitted for the initial request of the deployment manifest. The subsequent requests for application manifest and application files will not include the query string parameters, so there is no way to block access to them using this method.

Using a Custom Client Proxy

As described in an earlier section, if you can introduce a custom proxy on the client side, you can ensure that a user token is included with every request for ClickOnce manifests or application files that leave the client machine for a particular site or set of sites. As a result, you can use the user token to make authorization decisions on the server side to determine whether to return the requested file or not. This can again be done using a custom authorization HTTP module that extracted the user token (typically in cookie form) and used it to decide whether to satisfy the request.

The downsides to this approach are the complexity in setting up the custom proxy as discussed earlier, as well as coming up with a way to store and associate user tokens on the client side that is secure. To protect the token in transit, SSL would work fine, and token lookup on the server side could be done using the ASP.NET Membership API or some other custom database lookup. A solution for this approach is beyond the scope of this whitepaper, and the complexity of implementing this approach would overcome the simplicity of using ClickOnce as a deployment mechanism compared to other options.

User Authorization Summary

As with tracking application usage, using Windows credentials inside the intranet will be the easiest and most secure solution. It will allow you to control access down to the file level, and when combined with HTTP modules and handlers on the server, will enable dynamic role checking and routing to different versions of an application based on user identity. Custom authorization built in to the application is an easy solution for limiting access to application functionality based on the logged in user identity, and can be used instead of or in addition to Windows authorization. Custom query string parameters can provide an identity at the server to base authorization decisions on, but it is a complex and fragile solution that does not really provide any file level protections, so in general this approach is not recommended. A custom proxy solution can address maintaining a login context across the multiple file requests that get passed for an application installation or update, and thus can be part of a solution that provides protection down to the file level. However, the custom proxy approach requires a complex implementation on the client side, matched with custom security mechanisms on the server side and will likely be too difficult for most applications.

Dealing with Launch Errors

ClickOnce is highly reliable; however, in rare instances the application may fail to install or launch correctly. When these situations arise, it is helpful to have a log to diagnose the problem.

Whenever a launch failure occurs, the end user will get an error dialog like the one shown in Figure 10. If they press the Details… button in that dialog, they will get a deployment log for the failed deployment. The log includes information such as the URLs used to launch the application, and specifically what caused the failure.

Figure 11 shows the error log summary for an installation that failed due to an authentication issue.

Whenever a user reports that they are having trouble getting a ClickOnce application to launch, the first troubleshooting step is to get them to copy the information from the error log and have them send it to you for analysis. The vast majority of launch failures can be diagnosed from the information in the deployment logs.

ClickOnce log files for each installation are written to the WinInet cache (the Temporary Internet Files folder), for both failed and successful deployments and launches. These files are just written as text files to the Internet temporary files cache, and you can harvest information from there directly if you are troubleshooting a persistent problem on a client machine. These files are unfortunately named with obfuscated file names that make it fairly difficult to identify which log file belongs to which application. You will also have to sort them out from the hundreds or thousands of other files that end up in most users' Internet caches. The naming convention for these files is the prefix System_Deployment_Log_XXX, where XXX is the obfuscation. The best way to deal with these is to clear the cache, reproduce the problem, and then copy the log files out of the cache for analysis.

Another option is to set a custom log file location. When you choose to do this, all errors are written to a single file in the location that you specify, and each activation appends its log entries into the end of the file. So this approach can give you a more easily analyzed running commentary of multiple launch attempts.

To set this up, under the registry key:

HK_Current_User\Software\Classes\Software\Microsoft\Windows\CurrentVersion\Deployment

Add a string value named LogFilePath, and set its value to a fully qualified path to where you want the log entries written (i.e. C:\temp\ClickOnceErrors.txt). Once you do that, all errors will be written there. I would use this more as a diagnostic and troubleshooting aide rather than as a normal configuration. Note that when you set a log file like this, there is no longer any separation of entries, and the entries cannot be purged using the normal cleanup process of the internet temporary files cache.

Summary

ClickOnce provides a simple mechanism for getting rich interactive user interface applications into the hands of users. Unfortunately, it does not provide much support for administering an application or controlling access to it. This paper has discussed several approaches to identifying the application user for the purposes of usage tracking and access control. It covered some of the most promising approaches for identifying the user, but highlighted the fact that most of them have some limitations that you need to be aware of before moving forward with that approach. It is fairly easy to get some user information passed from the client to the server when the user launches the application. It is fairly difficult to transmit that information from client to server in a way that is not vulnerable to attack There are only a few ways to truly control access to all of your ClickOnce application files, with Windows authentication and authorization being the cleanest and most secure choice.

By understanding the techniques discussed in this paper, you will be better equipped to decide whether ClickOnce can satisfy all of your deployment and administration requirements, and how you might be able to achieve them through some custom means. If you have advanced administration and security requirements, then ClickOnce might not be the best solution for you. There are a number of very good third party managed client deployment solutions, such as Windows Installer and SMS that provide you much more control of your application's deployment.

Additional Resources

Appendix

Publish Directory Structure

The folder and file structure created by Visual Studio 2005 includes a root folder for the application, a default deployment manifest, a version specific deployment manifest, and a subfolder for each version of the application. The subfolder contains the application files suffixed with a file extension of .deploy and the application manifest for that version. The .deploy extension is added to all of the application files by default when publishing from Visual Studio to simplify the configuration required on the deployment server. Doing so ensures that the only file extensions you need to set up mime-type mappings for are the .application, .manifest, and .deploy files. The runtime on the client will remove the .deploy file extension after download to restore the original file name.

Additionally, a Setup.msi file for the configured pre-requisites is placed in the root-publishing folder, along with a publish.htm file that can be used to test the deployment. The default deployment manifest (the one without a version number embedded in its name) in the root folder is updated each time you publish a new version so that it always refers to the application manifest in the most recently published version. Figure A-1 depicts this folder structure for an application published to the local machine instance of IIS.

The structure used by Visual Studio is arbitrary and does not have to be followed explicitly if you manually publish your applications. The deployment manifest contains a path to the application manifest, and the application manifest contains relative paths to the application files. As a result, they can be placed in whatever folder structure you choose if you create or edit your manifests with Mage.

Figure A-1. Deployment server folder/file structure

Setting and Changing the Deployment Provider

The installation URL or update location that is specified through the Visual Studio 2005 publishing properties is saved into the deploymentProvider element within the deployment manifest generated when you publish the application. If you need to update the URL, you can do so through the mageUI.exe (Windows application) or mage.exe (command-line) manifest generating and editing tools available in the .NET Framework SDK. In the Mage UI, this setting is labeled Start Location. For the mage.exe command line tool, the corresponding command line parameter is –ProviderURL.

About the Author

Brian Noyes is a Microsoft Regional Director and MVP and a speaker, trainer, writer and consultant with IDesign, Inc. (www.idesign.net). He speaks at Microsoft TechEd US, Europe, and Malaysia, Visual Studio Connections, SDC Netherlands, DevTeach and other conferences, and is a top-rated speaker on the INETA Speakers Bureau. He has published numerous articles on .NET development for MSDN Magazine, MSDN Online, The Server Side .NET, CoDe Magazine, Visual Studio Magazine, asp.netPRO, .NET Developer's Journal, and other publications. Brian latest book, Data Binding with Windows Forms 2.0, part of the Addison-Wesley .NET Development Series, came out in January 2006, and will be followed by Smart Client Deployment with ClickOnce in the third quarter of 2006.