Friday 12 March 2010

Launching a ClickOnce Application

Introduction

If you're anything like the me, you won’t be content with knowing that browsing to http://MyServer/MyVirtualDir/MyApplication.application downloads, installs and launches MyApplication. You'll want to know how this is achieved. This post goes under the covers of ClickOnce to show you how it's done. Well, at least how some of it is done.

As this post makes quite a lot of references to registry entries, I'll use the standard abbreviations HKCU and HKCR to represent the HKEY_CURRENT_USER and HKEY_CLASSES_ROOT hives respectively.

Retrieving the Deployment Manifest

What happens when you browse to http://MyServer/MyVirtualDir/MyApplication.application?

The first thing to realise is that no 'magic' is happening behind the scenes. When you send an HTTP GET request to a web server (Internet Information Services, IIS, for example) the web server will typically react in one of two ways. It will either establish that your request must be forwarded to a component on the server for processing (a 'handler' in IIS terminology) or will establish that the request represents a static file which should simply have its contents returned.

IIS stores details of handlers, and the requests which are forwarded to each handler, in its metabase. Handlers can be defined in terms of a script map (potentially in combination with a managed handler) or a module mapping. For example:

  • Requests matching the pattern *.asp are forwarded, via a script map, to the ISAPI module %windir%\system32\inetsrv\asp.dll.
  • Requests matching the pattern *.aspx are forwarded, via a script map, to the ISAPI module %windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll and to the managed handler System.Web.UI.PageHandlerFactory.
  • Requests matching the pattern *.shtml are forwarded, via a module mapping, to the pre-registered ServerSideIncludeModule module.

You can view the current script maps via IIS Manager or by issuing the following statement at the command line whilst in your C:\Inetpub\AdminScripts folder:

cscript adsutil.vbs get W3SVC/ScriptMaps

There is no static map, managed handler, or module mapping for an *.application request. Such requests are picked up by the StaticFileModule module which matches * requests (i.e. requests not processed elsewhere).

Once IIS has established that the request represents a static file which will simply have its contents returned, it looks up the MIME type which it should place in the Content-Type header of the HTTP response. This, and not the name of the file requested, is to be used by the browser to establish what type of data is present within the body of the HTTP response.

IIS stores a map from file extension to MIME type within its metabase. To see the default MIME map for your IIS instance use IIS Manager or issue the following statement at the command line whilst in your C:\Inetpub\AdminScripts folder:

cscript adsutil.vbs enum /MIMEMAP

The MIME map reports than the .application extension maps to a MIME type of application/x-ms-application. This value is therefore placed in the Content-Type HTTP response header. The HTTP response body simply contains the unprocessed contents of the .application file itself.

Upon receiving the HTTP response, Internet Explorer looks up the file extension which should be assumed, given the MIME type specified in the header HTTP response. If this seems a little odd to you, you are not alone. I believe it stems from the fact that within Windows it is file extensions, not MIME types, which are associated with applications. As the HTTP standard defines that the MIME type should be used to determine the type of data, Windows has no real choice but to map the MIME type onto a file extension, to then map from a file extension to an application.

Internet Explorer looks up application/x-ms-application first in the user-specific MIME database at HKCU\Software\Classes\MIME\Database\Content Type and, if it fails to find an entry there, the machine-specific MIME database at HKCR\MIME\Database\Content Type. It reads the Extension sub-key and establishes that the MIME type application/x-ms-application maps to the .application extension. Internet Explorer then saves the file into its Temporary Internet Files folder (typically located at %USERPROFILE%\AppData\Local\Microsoft\Windows\Temporary Internet Files, %USERPROFILE%\Local Settings\Temporary Internet Files or %SYSTEMROOT%\Temp\Temporary Internet Files). Of course, you won't be able to see the file in Windows Explorer as Internet Explorer does its usual cloak-and-dagger trick of preventing you access to files on your own PC. When I tested this, the received file was placed within a hidden system folder called KEH38PJB which was within a hidden system folder called Content.IE5 (I tested on Internet Explorer 8.0) beneath Temporary Internet Files. The file name will be derived from the requested URL, albeit with the name modified to ensure uniqueness and to apply the .application file extension. For example, suppose you create an ASPX page called Default.aspx and have it return an HTTP response with the MIME type "application/x-ms-application" (as I have done) you'll end up with a file beneath Temporary Internet Files called Default[1].application. This again re-enforces the fact that it's the MIME type which drives this process, not the file name extension as stored on the server.

Opening the Deployment Manifest

Once the deployment manifest has been stored in the Temporary Internet Files folder, Internet Explorer then attempts to establish how it should handle the file with the (assumed and actual) .application extension. It checks the user-specific file types at HKCU\Software\Classes and, if it fails to find an .application sub-key there, checks for machine-specific file types at HKCR. Via this means it establishes that the .application extension denotes an Application.Manifest file. It then uses this information to check the user-specific HKCU\Software\Classes\Application.Manifest and machine-specific HKCR\Application.Manifest keys to establish the CLSID of a library which handles Application.Manifest files and yields the result {98af66e4-aa41-4226-b80f-0b1a8f34eeb4}. Finally, it looks up this CLSID in the user-specific HKCU\Software\Classes\CLSID\{98af66e4-aa41-4226-b80f-0b1a8f34eeb4} and machine-specific HKCR\CLSID\{98af66e4-aa41-4226-b80f-0b1a8f34eeb4} paths to establish that .application files are handled by C:\WINDOWS\system32\DFshim.dll.

This is where things start to get a little complicated. I said earlier that no 'magic' was happening behind the scenes. Well, whilst that was true for the process of retrieving the deployment manifest it most certainly is not true of the process of actually launching the ClickOnce application once the deployment manifest has been retrieved and the handler, DFshim.dll, has been identified.

DFshim.dll is described in the registry as the 'Manifest mime handler' although its file properties describe it as the 'Application Deployment Support Library'. It implements the Internet Explorer MIME handler COM interface. It is a native 32-bit DLL, written in Microsoft Visual C++ 2005, and was installed into C:\Windows\system32 when the .NET Framework 2.0 was installed.

DFshim.dll has a hard-coded reference to DFdll.dll, which it locates by checking the values of HKLM\SOFTWARE\Microsoft\.NETFramework\InstallRoot (typically C:\Windows\Microsoft.NET\Framework\) and the keys beneath HKLM\SOFTWARE\Microsoft\.NETFramework\Policy\AppPatch (typically v2.0.50727, even if a later version of the Framework is installed). DFdll.dll too is a native 32-bit DLL written in Microsoft Visual C++ 2005.

DFdll.dll uses COM services exposed by DFsvc.exe, which is also located in the .NET Framework folder. DFsvc.exe is a standard .NET MSIL assembly. DFsvc contains a single Main method (within the System.Deployment.Application namespace) which simply calls the internal System.Deployment.Application.DFServiceEntryPoint.Initialize method within System.Deployment.dll. The Initialize method registers System.Deployment.Application.DeploymentServiceCom with COM via System.Runtime.InteropServices.RegistrationServices (i.e. it performs the equivalent of CoRegisterClassObject in COM) using the CLSID {33246f92-d56f-4e34-837a-9a49bfc91df3}. This is the means by which its services are made available to DFdll.dll.

The COM service exposed by System.Deployment.Application.DeploymentServiceCom delegates the majority of its methods to other non-ComVisible classes within the System.Deployment.Application namespace. For example, the public ActivateDeployment method calls ActivateDeployment on a new System.Deployment.Application.ApplicationActivator instance, the public CheckForDeploymentUpdate method calls CheckForDeploymentUpdate on System.Deployment.Application.SubscriptionStore, etc.

It is clear that the vast majority of the work surrounding the actual installation and launch of the ClickOnce application is undertaken by classes within the System.Deployment namespace, hosted within DFsvc.exe. DFshim.dll and DFdll.dll appear to primarily be responsible for arbitrating between the COM-based world of Internet Explorer and the .NET-based world of ClickOnce.

Summary

Despite appearances, the Web server has very little involvement in the process of launching a ClickOnce application: it simply serves-up the contents of the deployment manifest along with the appropriate MIME type. The real fun happens on the client.

5 comments:

  1. Well done - that was admirably clear.
    I long ago gave up trying to write anything that uses gigantic frameworks with hundred character identifiers, clearly you do not feel deterred

    ReplyDelete
  2. When IE receives the .application response, why does it save it in temp folder then open it immediately? Why not prompt the user and do a normal "save as/download" operation? Who tells IE the operation it should take?

    ReplyDelete
  3. Good question. I don't know. The setting is almost certainly in the registry. Having had a quick root around, my guess would be HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\Accepted Documents. On my Windows 7/IE 9 machine this location lists the following MIME types: image/gif, image/gif, image/pjpeg, application/x-ms-application, application/xaml+xml and application/x-ms-xbap. Note that the MIME type associated with a .application file, application/x-ms-application, is within that list.

    Having said that, I believe that the user can specify how particular file types should be handled via HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\*\UserChoice, which might also be relevant.

    ReplyDelete
  4. Beautiful explanation. Is it possible to get the launch http context with the System.Deployment.Application.ApplicationDeployment libraries? I would like to get the httpresponse headers that started the application. Thank you.

    ReplyDelete
    Replies
    1. My .application is called from an http: or https://server/myapp.application?parameters url and runs a myapp.exe written in vbNET. From vbNET I would like to be able to analyze the response headers that triggered the call of myapp.application.

      Delete