
November 11, 2004
Introduction
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):
<appUpdater>
<UpdaterConfiguration>
<!-- **************** BITS DOWNLOADER **************** -->
<downloader
type=
"Microsoft.ApplicationBlocks.ApplicationUpdater.
Downloaders.BITSDownloader"
assembly=
"Microsoft.ApplicationBlocks.ApplicationUpdater,
Version=1.0.0.0,Culture=neutral,PublicKeyToken=null"/>
</UpdaterConfiguration>
</appUpdater>
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:
<postProcessor
type="Microsoft.ApplicationBlocks.
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.
Authors
 |
A commercial software developer and co-founder of SRT Solutions, Inc., 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 and Effective C#.
|
|