It's late Friday afternoon, and you just found a security vulnerability in one of your enterprise desktop applications....
You make the fix, and your work is done.
Or is it? Your application is installed on several hundred PCs around the organization. You're not done until you've deployed this fix on all those machines. You can take the easy way: copy your installation to a common server and send an email. But you know that only a small percentage of your users will read the email and actually update their machines. The alternative is even worse: you or your team will need to visit every single machine and manually install the update. There goes your evening. There goes the weekend, there goes fun.
This is why Click Once, and other related features are so important. Your users want a Windows interface. You and your team do not want to spend time running installers on hundreds of machines. You want to place your updates on one central location, and have each desktop machine grab the updates and install them. Software does the grunt work for you. ClickOnce, in Visual Studio 2005, gives you this feature, but the free AppUpdater will work as well. (You'll need the component to work through the samples in this article.)
Using the AppUpdater component is quite simple. You add it to your application's main form. When your application runs, the AppUpdater queries a configured Web site for updated versions of any of the assemblies that are part of your application. The AppUpdater can be configured to search only at startup, or to periodically poll the website for updates. When the AppUpdater finds an update, it downloads the new assemblies, installs them, and restarts the application.
The rest of this article will show you how to modify an application to take advantage of the AppUpdater component. You'll build the AppUpdater and the application launch shim. Then, you'll create an application that updates itself, using two different update options. Finally, we'll look at how the AppUpdater guards against malicious code being installed on your update server.
As we go through these details, you'll see how to configure the server, how to create the desktop application, and how to control update schedules.
The AppUpdater component performs three important tasks to ensure that your application is up to date:
- Your application finds out if updates are available. This is accomplished by looking at the Web server to see if newer versions of your application's assemblies are installed on the server. There are three ways to do that using the AppUpdater component. You can simply check timestamps of files stored at the server. You can create some form of a manifest file that lists the files on the server. Or, you can create a Web service that communicates with your application to notify it when updates are available. I'll cover the first two in this article.
- If updates are available, your application needs to download the files. Once you know you have an update, you simply download the files. That's the easy part. Next comes:
- Validate the files. You need to have ample checks that the assemblies being downloaded are from a trusted source. These checks almost always boil down to some form of examination of the public keys for a signed assembly.
- You need to install the files on the desktop machine. This is harder than it sounds, because the application must update itself. Your application is running, so all its assemblies are locked: you can't update them in place. The documentation for the AppUpdater mentions several possibilities; I'll just discuss the solution actually implemented. It's modeled after the .NET side-by-side application configuration. Instead of updating the application, simply create a new install directory that contains the existing version. After that, change all the shortcuts to load the new version.
That last item hides a few details. The user launches a shim executable that launches your application. The shim reads a config file to determine which application and which version to launch. The AppLauncher is also part of the AppUpdater component download. You just have to install it and use it.
Getting Started with AppUpdater
You download the AppUpdater component as a zip file that contains the component, an Application Launcher app, a strong name keys assembly, and three samples. After you unzip the AppUpdater, build the AppUpdater component project. Once you build the AppUpdater, you should add the component to your Visual Studio .NET toolbox. Right-click on the ''My User Controls'' tab in the toolbox and select ''Add/Remove items''. Browse to the release/bin directory and add the AppUpdater.dll. The AppUpdater component can be quickly added to any desktop application you create. Just drag and drop from the toolbox.
Next, you need to build the AppStart project. This application is responsible for launching and restarting your application. You can use it as is, it does have all the features you need. In most cases, I change the icon to match the target application, rather than the default .NET application icon.
Now let's make an application that can update itself. The AppUpdater will work with any application. This article is not about the sample application, it's about the update process. The sample application does nothing but display a Windows form that contains the version of the application. That's all you need to see when updates have been installed.
Using the AppUpdater
The first step to using the AppUpdater component is to add the component to the main form of the application. AppUpdater derives from System.ComponentModel.Component. All you need to do on the destktop application is set some properties to drive how the AppUpdater looks for and installs updates There is one property that you must change for each application: The update URL. This is the address that the AppUpdater uses to poll for updates. The first sample uses the direct file access check to determine if updates are available. I set the Web address to http://localhost/FileCheck/. The closing slash is important: The AppUpdater component will browse the directory for each assembly used in your application. The other change I almost always make is to use the Default UI in AppUpdater. If the default UI is shown, the updater restarts the application when a new version has been downloaded. If the default UI is not shown, the new version would be loaded when the user restarts the application. You get users updated more quickly by configuring the AppUpdater to restart the application.
You're done on the client. The rest of the steps are modifications to the desktop installer and setting up the server website.
You need to install your application so that the AppUpdater can create new versions for you. The AppUpdater component installs updated versions following the side-by-side installation strategy used by the .NET Framework. You need to create the initial directory structure for version 220.127.116.11 of your application.
Your users will launch the AppStart.Exe application, which will read a config file and launch your app. For example, my sample application is called SampleApp.exe. I would normally install that into ''c:\Program Files\The Server Side\SampleApp\SampleApp.exe. The start menu shortcuts would point there. When you're working with the AppUpdater, you install AppStart.Exe into c:\Program Files\The Server Side\SampleApp, along with a config file, AppStart.Config. AppStart.config contains information about the target application:
<Config> <AppFolderName>18.104.22.168</AppFolderName> <AppExeName>SampleApp.exe</AppExeName> </Config>
The AppFolderName key tells the AppStart app where to look for the current version of the application. The AppExeName gives the name of the executable to download. In this example, the current version is stored in ./22.214.171.124/Sample.Exe. You need to update your installer to create the extra directory structure and create the initial version of the AppStart.Config file. For those of you familiar with .NET config files, notice that the AppStart.Config file does not follow that normal convention. It can't because your application will update that file when a new version is installed. The .NET FCL does not contain APIs to modify the config files, only to read them.
The DirectFileCheck uses the HTTP HEAD method to see if the timestamp of the file on the server is newer than the timestamp on the desktop machine. You must enable directory browsing on the server's virtual directory for the AppUpdater to work properly.
The last step is to create the web site and populate it. The Direct File check is the simplest method. It compares timestamps of the files on the Web server to the timestamps on the client machine. It does not look at version numbers, strong names or anything else. All it checks are time stamps. For ease of use, it can't be beat. You just put updated assemblies in the directory that maps to the virtual directory you specified for the AppUpdater component. You don't even have to change version numbers.
As soon as the AppUpdater senses a newer assembly file on the server, it downloads it and prompts the user to restart the application. The AppUpdater modifes the AppStart.config file to point to the new version. Notice that the folder name above matches the product version number given on the main assembly. If you update an assembly without modifying the version number, the AppUpdater component creates a new folder for you by appending â ˜_1' to the version number. In practice, you should modify the version number whenever you create new software.
To test, I updated my sample application, changing the background color of the form, and installed it on the server. The AppUpdater saw the update, downloaded it and prompted me to restart the application.
Manifest File Updates
The Direct File check is the simplest strategy, but it has a serious limitation for multi-assembly applications. You run the risk of downloading some of the files necessary for an update and restarting the application before you have placed all the files on the server. The fix for this is to change to the ServerManifestCheck Change Detection mode on the AppUpdater component.
This mode directs the AppUpdater component to request a manifest file from the server. That file is an XML file that contains two keys that tell the AppUpdater component the current product version, and where on the server to get the current version. The AvailableVersion element gives the version number. The ApplicationURL element gives the virtual directory on the server where the current version can be found. This level on indirection means that you can upload all the application assemblies to a new directory on the server, and then modify the manifest file to point to the new version. Let's build version 2.0 of this little sample and update it using a server manifest.
You change the AppUpdater's ChangeDetection mode property to ServerManifestCheck. Then, modify the UpdateURL property to point to the manifest file. I chose http://localhost/FileCheck/manifest.xml. Update the version number in the assemblyInfo.cs file and build a new version.
One great feature of the AppUpdater component is that you can use it to update and switch from one update strategy to the next. Simply copy version 2.0 into the virtual directory you used for the direct file check, and the AppUpdater will install version 2.0, which now makes use of the ServerManifestCheck. The other great feature of the AppUpdater component is that it silently ignores errors when the server resources are not available. As soon as you have version 2.0 ready, you can place it on the server and let the clients update themselves, even before you have place the manifest file on the server.
Once you've installed version 2.0 on the server, you need to update the server so that you're ready to update clients using the manifest.
First, create a new directory on the server for version 2.1. I chose http://localhost/FileCheck/126.96.36.199. Place your updates for version 2.1 in this directory. Nothing happens yet. The client application is looking for the manifest file that points to the updated version. When you're ready to roll out the update, add the following manifest file to the server:
<VersionConfig> <AvailableVersion>188.8.131.52</AvailableVersion> <ApplicationUrl>http://localhost/Filecheck/184.108.40.206/</ApplicationUrl> </VersionConfig>
The client application reads this file, detects the new version and installs as before. The only difference is that the client does not start getting updates until you've updated the manifest. You have the opportunity to update the entire package of assemblies before client machines start grabbing files. Use the Manifest check method whenever you have multi-assembly applications.
Strong names and keys
We're almost done. You can update the application easily by placing new files on the server. That's exactly what you wanted, right? Or is it? If you can place files on the server and every desktop in your enterprise pulls them down, what's to stop a hacker from doing the same thing? If a hacker compromises your update server and places a malicious assembly that has the same name as your application, the AppUpdater would download the malicious code and execute it. The results could be disastrous. You've downloaded and installed assemblies that now have full trust on your local machine.
The AppUpdater component has safeguards to avoid that problem: It can examine and verify the key of each assembly it downloads. You must strongly name all the assemblies in your application. If the key stored in the assembly does not match the key found in the currently running application assembly, the download aborts. The net result is that the AppUpdater will not download an assembly signed by any key other than your application's private key.
Guard your private key carefully, and the AppUpdater component will not download an imposter. This feature is turned off by default. To turn it on, use the Property page on the AppUpdater component, and set the ''ValidateAssemblies'' property to true.
In practice, there is one caveat to this strategy: it won't work for third-party components from some outside vendor. A little indirection fixes this problem as well. The AppUpdater component uses a special assembly name ''AppUpdaterKeys.dll'' to store a list of allowed public keys. This assembly must be signed using the same key as your main application assembly. It contains a list of public keys defined in all the 3rd party components. The AppUpdater component uses this list to validate any other assemblies on your server. A sample AppUpdaterKeys project is included with the download. You can modify this assembly to include the keys for your components, sign it with your application's key. The AppUpdater will download only those assemblies with keys from the AppUpdaterKeys assembly. Since AppUpdaterKeys is signed with your application's key, it's safe.
The AppUpdater component provides one design to automatically configure client applications to update themselves. It will save you valuable time when you need to next update your applications in the field. Once the AppUpdater is in place, you can deploy your desktop applications to a central server, and let each desktop pull the new version. Your updates will take place on your schedule, without dispatching support personnel to every PC in your enterprise. Now, when you fix that bug on a Friday afternoon, you really can be done: Update the server with the new version, and every desktop in your enterprise installs the update automatically. You can go home and enjoy the weekend.
About the author
A commercial software developer and co-founder of SRT Solutions, Bill Wagner facilitates adoption of .NET in clients' product and enterprise development. His principal strengths include the core framework, the C# language, Smart Clients, and service oriented architecture and design. In 2003 Microsoft recognized his .NET expertise appointing him Regional Director for Michigan. A frequent writer and speaker, Bill's work is published in ASP.NET Pro and Visual Studio Magazine. He is the author of C# Core Language Little Black Book (O'Reilly Media) and Effective C# (Addison-Wesley).