Automatically deploying your windows forms applications is one of most discussed features in .NET Development. There are many different solutions to this problem: Running applications from the Web (See http://www.theserverside.net/articles/showarticle.tss?id=RunFromWeb), the Windows form App Updater Component (See http://www.theserverside.net/articles/showarticle.tss?id=AppUpdater), and VS.NET 2005 adds support for deploying applications from the web.
In this article, I’ll discuss one other option: the Application Updater Starter Block from the Practices and Guidance Group at Microsoft. The Application Updater block provides one implementation you can use to automatically update your applications. Unlike some other solutions, the AppUpdater block was designed so that you can extend and modify it to suit your purposes. Several .NET developers have written and posted extensions or modifications to it. I would choose to base my no touch deployment implementation on the AppUpdater when two conditions are true:
First, you can’t wait for VS.NET 2005. No Touch deployment is built in to VS.NET 2005. It’s easier to use than any of the other options.
Second, you need to extend the features in the AppUpdater component. The design of the Updater Application Block provides quite a few more opportunities for extension than the AppUpdater Component.
I’ll give an overview of how to use the Updater Application Block. I’ll show up the possible extension points for creating your own implementations of some of the components. I’ll finish by discussing the reasons for picking one component over another. This article does not include its own sample, I’m relying on the samples that come with the Updater Block. To get a clear idea of the tasks that are involved in building an application that automatically updates itself, see the App Updater article. The steps are the same, even though the component is different. This article focuses on the architecture and design of the Updater Application Block.
Download and Installation
The Updater Application block installation is a two step installation. You must install the application block, then run the quick start scripts to configure both the server and the client for updates. Of course, you’d know this if you read the readme.txt file, but no one does that. The point is you don’t just load the solutions, build them, and try to run the updater. It won’t work. The quick start scripts create virtual directories on the server, install all the updates to those directories and configure the client applications to search for updates on the local host web server.
Using the samples
The samples that come with the Updater show the same basic tasks needed to auto-update an application:
Use a shim to load the application. (This facilitates updating a running application)
Code on the client machines periodically polls a known server for updates.
When a new update is available, it is downloaded and verified.
After verification, the new version is installed.
The application restarts and the new version runs.
Given these four basic tasks, it’s no surprise that most of the implementations you’ll find are reasonably similar in their design and implementation. If anything, such similarities shows that these different implementations are on the right track. The one item that separates the Updater Application Block is that its design is extensible: All the key tasks are defined by interfaces and you can create new classes to implement any of them. Before we get that far, let’s go over the solutions already provided in the Updater.
The AppStart assembly contains the shim that loads the target application. The Updater starter block includes support for running only one version of the target application. In its main routine, it creates a Mutex to ensure that the target application is not currently running:
//Check to see if AppStart is already running FOR the
// particular versioned folder of the target application
bool isOwned = false;
_appStartMutex = new Mutex( true, _config.ExePath +
_mutexGuid.ToString(), out isOwned );
if ( !isOwned )
{
MessageBox.Show(
String.Format(
CultureInfo.CurrentCulture,
"There is already a copy of the application '{0}' running. Please close that application before starting a new one.",
_config.ExePath ) );
Environment.Exit( 1 );
}
StartApp_Process();
The code snippet above uses a system mutex to determine if another copy of the AppStarter, targeting this same application, is running. The StartApp_Process method launches the target application, after setting the working directory correctly:
_exePath = Path.Combine( _config.FolderName , _config.ExePath );
//Start the app
try
{
ProcessStartInfo p = new ProcessStartInfo (_exePath);
p.WorkingDirectory = Path.GetDirectoryName(_exePath);
_process = Process.Start (p);
Debug.WriteLine("APPLICATION STARTER: Started app: " + _exePath);
}
catch (Exception e)
{
Debug.WriteLine("APPLICATION STARTER: Failed to start process at: "
+ _exePath);
HandleTerminalError(e);
}
As you’ll see a little further on, the config object returns the current version of the application to shim. When the downloader retrieves new versions, it writes new entries to determine which version should be launched.
Find and Download New versions
Finding and downloading content is accomplished by a class that implements the IDownloader interface. The IDownloader interface contains four methods, to support both synchronous and asynchronous downloading:
public enum JobStatus
{
Ready,
Downloading,
Error,
Cancelled,
Validating
}
public interface IDownloader : IDisposable
{
void Init( XmlNode config );
void Download( string sourceFile, string destFile,
TimeSpan maxTimeWait );
Guid BeginDownload( string[] sourceFile, string[] destFile );
JobStatus GetJobStatus( Guid jobId );
}
The Updater Application block contains one class that implements the IDownloader interface: The BITS Downloader. BITS is a service available on Windows 2000 and above. It is used by the Windows Update service to retrieve its updates. It is secure, handles interruption in service, and works quietly in the background. The downside is that it won’t work if you need to support older Windows versions, like Windows 98. In that case, you’ll need to create your own downloader for those aging clients. The best part about this design in the Updater Application Block is that it is configurable at runtime. The ApplicationUpdater reads a configuration file to determine which downloader to create. The config section looks like this (formatted for your screen, not for your config reader):
"Microsoft.ApplicationBlocks.ApplicationUpdater.
Downloaders.BITSDownloader"
assembly=
"Microsoft.ApplicationBlocks.ApplicationUpdater,
Version=1.0.0.0,Culture=neutral,PublicKeyToken=null"/>
You could create a different downloader for other clients. You could then write a different key in the config file when you install the system to launch a different downloader on outdated systems. As I said earlier though, while I like the architecture and design of the Update block, I would use the AppUpdater control before I would write my own custom downloader.
Validating what you download
Downloading and installing new stuff is great, but you need to guard against replacing your application with some other arbitrary executable. The Updater application block uses signatures to determine if the file has been tampered with. If there is any doubt about the file’s authenticity, the installation is aborted.
The validation contract is defined by the IValidator interface. Like the downloader, this means that you can create your own validator using your own algorithm, as long as you implement the interface:
public interface IValidator : IDisposable
{
void Init( XmlNode config );
bool Validate( string filePath, string signature );
bool Validate( XmlNode xml, string signature );
string Sign( string filePath, string key );
string Sign( XmlNode xml, string key );
}
The Init method is a hook to let you initialize your validators. The Validate and Sign methods are pairs: when you create an update for automatic download, you generate a signature for each file in the download. The signature gets generated by the Sign() method and then must be written into the server manifest file. The download client attempts to match the signatures using the Validate( ) method. The Updater Application block includes a utility application that uses the configured validator to generate the manifest (See Figure 1).
Validation is very important for any system that supports auto-updating; it’s worth going over in more detail. Remember that any assembly loaded from your computer is fully trusted. It was loaded from the “My Computer” zone. That means version 1.0.0.0 of your application is fully trusted. Your fully trusted application can go to the web, and download new assemblies without raising warnings from the system. After downloading these new assemblies, they will be loaded from the “My Computer” zone as well. That means any future versions are also fully trusted. That’s good, because that’s what you want: You automatically update your fully trusted application, and the new version should also enjoy the privileges of a fully trusted application. However, should someone sneak into your server and put a set of assemblies there, you would unwittingly deliver them to your entire install base. Worse, you’d give them that full trust by putting those assemblies on the local computer. That must be prevented. Validation prevents that.
Validation ensures that the files have come from a trusted source. Before you deploy your first version to any client computer, you generate a key that will be used to create the signature. That key is mixed in with the hash code generated from each downloadable file. That’s the important key point: The signature depends on the key, and the contents of the downloaded files. Should the server security be violated, the attackers will not have the key, and will not be able to generate the correct signature. In the same vein, if the attackers modify a file after you’ve installed it at the server, the signature will change. That will cause the validation to fail as well.
The Updater contains two possible validators you can use: a symmetric validator, and a public / private key pair (or asymmetric) validator. The asymmetric validator is more secure, because the client computers have a different key than the server. But you must guard your private key carefully. With either validator, any attacker that gains access to the key can fool you into downloading malicious content. That is a bit easier with the symmetric key because it is stored on client machines. It is encrypted, but it’s still not as safe as the asymmetric key. My recommendation is that you should use the asymmetric validator on any projects where security is a serious concern (and aren’t they all like that?)
Post Update Processing
The final addition to the Update Application block is the ability to perform post-update installation tasks. You will use this feature if you are updating a component that needs specific client-side processing to occur when an update is downloaded (like COM registration. In fact, the Updater block contains one class that implements IPostProcessor, to update the registry). To use the Post Processing facility, you create a class that implements the IPostProcessor interface, which has only one method: Run. That method should perform whatever processing you need. Include that assembly in the server manifest, with the instructions to load and execute the post-processing code:
ApplicationUpdater.Quickstarts.RegPostProcessor"
assembly="FlexPoints, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null"
name="PostProcessor\Flexpoints.exe" />
The download manager will get the specified file, and after downloading and verifying the entire package, the post – processing instructions will be executed.
Which to choose?
I started this series with the App Updater component because it is the simple, straightforward way to deploy updates to a client application simply. I’m finishing with the Updater Application Block because it shows a combination of best practices for creating an extensible solution to this problem. All the extensible components are defined by interfaces that can be implemented in a variety of ways: IDownloader, IValidator, and IPostProcessor. An application configuration file defines which assemblies, and which types in those assemblies, should be created to do each of the tasks specified by these interfaces. You can add new capabilities to the Updater facility by creating new assemblies and referencing them in the config files. It’s rare that you will need to modify the core libraries.
Putting these facts together, the Updater block provides a much better framework for you to extend over time, should you need. It will also be applicable to a much wider array of applications. If you are an experienced .NET developer, use the Updater as your starting point. When you need features not already in the updater, you’ll find ways to extend it. On the other hand, the AppUpdater component is much simpler to understand and work with. If you’ve never worked with no-touch deployment, start there. Understand that component, build one or two self-updating applications, then move on to the Updater Application Block.
No comments:
Post a Comment