Writing a self-updating application in C#
By Hendrik Swanepoel
Download Source Code

Introduction:

When facing a decision on the architecture of a certain application, many organizations will decide on web applications due to several reasons. Some of the main reasons are platform independence and administration. It also makes perfectly good sense to opt for a web application if you only want to serve content and information. But often the prospective application is very complex and does much more than just serve content - and on top of this, the required functionality requires a complicated (rich) user interface.

Although it would be much easier to develop this complex solution in the form of a desktop application, with a client/server model, one problem always persists. What if we have 1000 clients that are going to use this software? How would we keep their instances updated? This problem even occurs in web application environments! For instance, if you want to sell your intranet product to different organizations, these organizations will most definitely want to host these applications on their own servers. And these organizations will want to keep their installations up to date too.

So regardless of whether you work primarily in a web or desktop environment, keeping several installations up to date presents a challenge. This article will attempt in solving this problem, by implementing application blocks that Microsoft has provided us with.

A difficult aspect of working with the Updater application block, is deploying the diffrerent modules for use. This is due to the complexity of the communication between the different modules based on config files. Thus I have decided to include a whole section on the configuration of a self updating application. This said, it's real important that you have a look at the documentation provided with the application block, and see which deployment scenario suits your needs best.



Important sources:

This article is based on the Updater Application block, which can be downloaded at: Application block download
I used this application block's self-updating application as a model for my own example project, so after reading this article you will be better equipped to understand and implement this application block.

The updater application block also plays a big role in the area of smart client technology, which you can learn more about at:
Smart client download

Smart client technology is a totally different, but interesting subject, and would require a whole article on it's own.

Technology backgrounder

I would really recommend reading the documentation provided with the application block for more information on the architecture, but I will also give you a small explanation myself. This will aid in helping you understand the article's examples.

A table to better understand  the different key  projects in the Updater application block (excluding the quickstarts and demo files) can be seen below. There are other projects in the solution that is necessary for exception management and that contain interfaces.

Project name Project description Configuration required
Appstart This exe will be used to instantiate the correct classes for the updates. Yes
Microsoft.ApplicationBlocks.ApplicationUpdater The functionality for the updating is included in this assembly No, it will only be referenced
ManifestUtility Building this project results in an exe. This exe is used to create the servermanifest file, which is used to see if there were any updates published. More abput this later in the configuration section. No it will aid in the configuration of the publishing.

Have a look at figure 1.1. This tries to illustrate how a dynamic update infrastructure will be deployed. Again, I'm going to go dedicate a whole section on deployment, so don't worry too much about that the details.

All the clients that have the self-updating ability will:

  1. Have access to the Appstart exe
  2. Have a reference to the ApplicationUpdater assembly - which in turn references the ApplicationUpdater.Interfaces, ExceptionManagement and  ExceptionManagement.Interfaces assemblies. 
  3. Be configured to know where the updates are either by configuring the Appstart exe's config file or the application's own file
  4. If validation is enabled (This is a relationship between the client and server to ensure that the downloaded files are correct) must contain a public key in it's configuration or the Appstart.exe's configuration, depending on the deployment method

The server  will:

  1. Have a location where the files can be downloaded - this means that it will have an ISS public location that contains the files
  2. Have a manifest xml file in the root of the location - the manifest.xml file is created by the ManifestUtility exe included in the application block - this file lets the client know about available updates.

Figure 1.1

 

The process

The steps below are some of the key steps in the update process. Note that all the sptes will fire events to let the updating client know what has happened. For example if the files were validated correctly, it will fire an event, the event handler can then interact with the user to proceed with the downloading of the files.

Action Event fired on completion

The client downloads the server manifest file at the configured location.

 

 

ServerManifestDownloaded
It checks to see what the latest version was that it has downloaded - this version number is stored in the configuration of the Appsart exe. UpdateAvailable
The download is started

If the current installed version is older than the one available according to the server manifest, the downloading of the files can proceed.

DownloadStarted

The files are downloaded and saved to the configured location on the filesystem

DownloadCompleted
The files are validated

FilesValidated/
FilesValidationFailed

When the file download has finished, the application can switch over to the new version.

 

The configuration

The sample application is configured to update itself, there are other options for configuring application updates, which would run out of the application itself for example, but for illustration purposes I chose this model.

This article will use the BITS downloader, which copies the files from an HTTP Server. You can always write your woen downloader by deriving from the IDownloader interface.

Configuration on the client:

Our working directory for our sample application will be : d:\projects\selfupdater\SayHello\bin\debug\

The name of the application that we are going to update dynamically is SayHello.

Files necessary in the working directory of the application that will be updated:

Filename Use
Appstart.exe Will be used to instantiate the downloaded files.
AppStart.exe.config Config file for appstart.exe - will configure where the app direcotry is and the exe to instantiate - also contains the version info of the last download
Microsoft.ApplicationBlocks.ApplicationUpdater.dll The update technology lies in here
Microsoft.ApplicationBlocks.ApplicationUpdater.Interfaces.dll ApplicationUpdater references this
Microsoft.ApplicationBlocks.ExceptionManagement.dll ApplicationUpdater references this
Microsoft.ApplicationBlocks.ExceptionManagement.Interfaces.dll ApplicationUpdater references this
SayHello.exe The application that will be updated
SayHello.exe.config

The config file for the application that will be updated

This config file will contain all the config settings for the download procedure

The configuration of the appstart.exe.config file:

<appStart>

<ClientApplicationInfo>

<appFolderName>D:\projects\selfupdater\SayHello\bin\debug\2.0.0.0</appFolderName>
<appExeName>SayHello.Exe</appExeName>
<installedVersion>2.0.0.0</installedVersion>
<lastUpdated>2004-03-10T15:50:14.7737744+02:00</lastUpdated>

</ClientApplicationInfo>

</appStart>

The configuration of the app.config file:

The logfile location can be configured here, this is helpful in tracking the progress.

<logListener logPath="D:\projects\selfupdater\SayHello\bin\debug\UpdaterLog.txt" />

If validation is enabled it is important that the key that is generated with the manifest utility is stored in the <key> section. 

<key>

<RSAKeyValue>

<Modulus>nxq8YYpiEb/RFMFaEP1Q+cU62lfyct0cIk/q0xT9M+aPoBVsSEA3zC6cUoWNXSLnfbqqE
7qiWdP3u7p+JDWLy6sKZPZX6CZX0XvaVDeyrVmB0tXoDkYX
RwzcYjZiE94xDYpoi8VlPyb8OCarVC9Go
HW3KquRfm6XfpILhj/dC0s=</Modulus> <Exponent>AQAB</Exponent>

</RSAKeyValue>

</key>

The application section configures the download locations.
In the client section we will have the locations of:

  1. baseDir: The base directory / working directory
  2. xmlFile: The location of the AppStart.exe file
  3. tempDir: The location of the temp direcotry in which the files will be downloaded

In the server section we will have the locations of:

  1. xmlFile: The URL to use to get the server manifest xml file on the server
  2. xmlFileDest: The local directory to download the server manifest xml file to

<application name="SayHello" useValidation="true">

<client>

<baseDir>D:\projects\selfupdater\SayHello\bin\debug\</baseDir>
<xmlFile>D:\projects\selfupdater\SayHello\bin\debug\AppStart.exe.config</xmlFile>
<tempDir>D:\projects\selfupdater\SayHello\bin\debug\newFiles</tempDir>

</client>

<server>

<xmlFile>http://localhost/sayHelloUpdate/ServerManifest.xml</xmlFile>
<xmlFileDest>D:\projects\selfupdater\SayHello\bin\debug\ServerManifest.xml</xmlFileDest>
<maxWaitXmlFile>60000</maxWaitXmlFile>

</server>

</application>

Configuration of the server:

The server needs a publicly available location where the files will be located.
Follow these steps to create this location and to publish the correct files for the update:

  1. In IIS, create a virtual directory called sayHelloUpdate on the server - this corresponds to the value we have in the config file on the client
  2. Point this directory to a directory on the server where you want to store the files
  3. In this directory you should have a servermanifest.xml file - this can be called something else - but we have configured the client for this name
  4. Before creating the servermanifest file you have to create a directory with a name of the available version number. In our case we want to upgrade to version 2.0.0.0, so we have to create a directory in the virtual folder called 2.0.0.0.
    Into this folder we then copy the new versions of the file, any other dependencies of the files that we publish should be copied in here too.
  5. Now that the files are located in the correct download directory we can proceed in creating the servermanifest file. Open the ManifestUtility application which is provided with the application block.
    See figure 1.2 for the correct settings. Here's a rundown:
    1. Update files folder: The directory where the files are located in - not the root directory, but the directory named after the version (2.0.0.0)
    2. Update location: The url to get to the same directory as in step 1
    3. Version: The version number of the available version, in our case 2.0.0.0
    4. Validator assembly: The path to the Microsoft.ApplicationBlocks.ApplicationUpdater.dll assembly
    5. Validator class: in our case choose the RSA validator
    6. We don't have a post processot, so don't use theis section
    7. For the key you have to choose the Generate Keys option in the file menu, save the keys in a safe place. With the open file dialogue, browse to the saved location, choose the privatekey.xml file.
      You have to open the app.config file (client config) and insert the contents of the newly created publickKey.xml in the key section of the config file.
    8. Click on the "Create Manifest" button
  6. Note: Only the files that are in the directory at the moment of creation of the server manifest file will be downloaded, the other files will be ignored
  7. The IIS directory also needs some prepping:
    1. Open the internet services manager
    2. Right click on the sayHelloUpdate virtual folder
    3. Click on properties
    4. In the virtual directory tab, ensure that all these items are checked: Script source access, Read, Write and Direcotry browsing
    5. In the same tab, click the "Configuration" button.
    6. In the app mappings tab, look for a .config entry in the list of application mappings
    7. Highlight it
    8. Click on "Remove"

Figure 1.2

The fun part (Code):

Our application will have an "attempt upgrade" button. When clicking on this button, the updater process is started.

When the servermanifest file has downloaded, the updater will check to see if any updates are available, if there are the application will ask the user if a download must be done.

After the download of the files have been done and the files have been validated, the user can then choose if the downloaded files must be run.

In the button click handler, we attach the correct event handlers on the updater object. We also start the updating process by calling theStart method on it.

         private void button1_Click(object sender, System.EventArgs e)
        {
            //instantaites the object which will allow for dynamic updates
            updater = new ApplicationUpdateManager();

            //ensure that when the process exits or the app is closed - the thread in which the updater is running
            //    is stopped
            AppDomain.CurrentDomain.ProcessExit+=new EventHandler(CurrentDomain_ProcessExit);
            this.Closed+=new EventHandler(Form1_Closed);

            updater.DownloadStarted += new UpdaterActionEventHandler(updater_DownloadStarted);
            updater.FilesValidated +=new UpdaterActionEventHandler(updater_FilesValidated);
            updater.UpdateAvailable +=new UpdaterActionEventHandler(updater_UpdateAvailable);
            updater.DownloadCompleted +=new UpdaterActionEventHandler(updater_DownloadCompleted);
            updater.FilesValidationFailed+=new UpdaterActionEventHandler(updater_FilesValidationFailed);        
            updater.ServerManifestDownloaded+=new UpdaterActionEventHandler(updater_ServerManifestDownloaded);

            updaterThread = new Thread(new ThreadStart(updater.StartUpdater));        
            updaterThread.Start();
        }
				 
We have a method to open and run the downloaded versions

        private void StartNewVersion(ServerApplicationInfo server)
        {
            XmlDocument doc = new XmlDocument();

            //  load config file to get base dir
            doc.Load(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);

            //  get the base dir
            string baseDir = doc.SelectSingleNode("configuration/appUpdater/UpdaterConfiguration/application/client/baseDir").InnerText;
            string newDir = Path.Combine(baseDir, "AppStart.exe");
                
            ProcessStartInfo process = new ProcessStartInfo(newDir );
            process.WorkingDirectory = Path.Combine(newDir , server.AvailableVersion);

            string debug =Path.Combine(newDir , server.AvailableVersion);
            //  launch new version (actually, launch AppStart.exe which HAS pointer to new version )
            Process.Start(process);

            //  tell updater to stop
            CurrentDomain_ProcessExit(null, null);
            //  leave this app
            Environment.Exit(0);
        }			
					

We have event handlers that catch the events and do actions at diffrent stages of the process.
Note that only the key events are shown.
As you can see, where the events are attached to the updater object, other event handlers are specified. This is because these event handlers delegates the event to other event handlers. This is necessary due to the fact that the UI isn't thread safe.
All that the event handlers bound to the updater do is pass the arguments to the other event handlers.


         /// <summary>
        /// The files was validated - handle the event
        /// </summary>
        private void UI_FilesValidated(object sender, UpdaterActionEventArgs e)
        {
            txtStatus.Text+= "The files were validated"  + Environment.NewLine;
            DialogResult install = MessageBox.Show("Install the newly downloaded files?", "Install dialogue", MessageBoxButtons.YesNo);
            if(install == DialogResult.Yes)
            {
                StartNewVersion(e.ServerInformation);
            }
        }

        /// <summary>
        /// An update for the files was detected - handle the event
        /// </summary>
        private void UI_UpdateAvailable(object sender, UpdaterActionEventArgs e)
        {
            //gets the version on the server from the event arguments
            string newVersion = e.ServerInformation.AvailableVersion;

            //build the message for the textbox
            string statusMessage = 
                String.Format("There is an update available on the sever: {0}.", newVersion);
            txtStatus.Text+= statusMessage  + Environment.NewLine;
            
            //builds the message for the confirmation
            string popMessage = 
                String.Format("Do you want to upgrade this application to version {0}?", newVersion);
            //if the user wants to upgrade to the new version - the upgrade is done, otherwise the
            //    updating process is stopped        
            DialogResult downLoad = MessageBox.Show(popMessage, "App Update", MessageBoxButtons.YesNo);
            if(downLoad == DialogResult.No)
            {
                //stop the hupdater process
                updater.StopUpdater(e.ApplicationName);
                txtStatus.Text += "The updater process has been stopped - due to the refusal of the available upgrade" 
                    + Environment.NewLine;
            }
            else
            {
                txtStatus.Text += "Upgrade has been accepted, it is now commencing." + Environment.NewLine;
            }
        }


        /// <summary>
        /// The app domain's parent process exits - ensure the update process is topped
        /// </summary>

        private void CurrentDomain_ProcessExit(object sender, EventArgs e)
        {
            TerminateUpdate();
        }
					
Hendrik lives in South-Africa, has been developing for 4+ years and specializes in the .Net framework.  You learn more about him at http://dotnet.org.za/hendrik