| Microsoft | Articles | Forums | Groups |
| C# .NET |  |  |  |
| VB.NET |  |  |  |
| Visual Studio .NET |  |  |  |
| ADO.NET |  |  |  |
| Xml/Xslt |  |  |  |
| VB 6.0 |  |  |  |
| .NET CF |  |  |  |
| GDI+ |  |  |  |
| LINQ |  |  |  |
| Deployment |  |  |  |
| Security |  |  |  |
| FoxPro |  |  |  |
| Silverlight / WPF |  |  | |
| Entity Framework |  |  | |
| RIA Services |  |  | |
|
| Web Programming | Articles | Forums | Groups |
| JavaScript |  |  | |
| ASP |  |  | |
| ASP.NET |  |  |  |
| Web Services |  |  |  |
|
| Non-Microsoft | Articles | Forums | Groups |
| NHibernate |  |  | |
| Perl |  |  | |
| PHP |  |  | |
| Ruby |  |  | |
| Java |  |  | |
| Linux / Unix |  |  | |
| Apple |  |  | |
| Open Source |  |  | |
|
| Databases | Articles | Forums | Groups |
| SQL Server |  |  |  |
| Access |  |  |  |
| Oracle |  |  | |
| MySQL |  |  | |
| Other Databases |  |  | |
|
| Office | Articles | Forums | Groups |
| Microsoft Excel |  |  |  |
| Microsoft Word |  |  |  |
| Microsoft Powerpoint |  |  |  |
| Publisher |  |  |  |
| Money |  |  |  |
|
| Operating Systems | Articles | Forums | Groups |
| Windows 7 |  |  | |
| Windows Server |  |  |  |
| Windows Vista |  |  |  |
| Windows XP |  |  |  |
| Windows Update |  |  |  |
| MAC |  |  | |
| Linux / UNIX |  |  | |
|
| Server Platforms | Articles | Forums | Groups |
| Share Point |  |  |  |
| BizTalk |  |  |  |
| Site Server |  |  |  |
| Exhange Server |  |  |  |
| IIS |  |  |  |
| Transaction Server | | |  |
|
| Graphic Design | Articles | Forums | Groups |
| Macromedia Flash |  |  | |
| Adobe PhotoShop |  |  | |
| Microsoft Expression |  |  |  |
|
| Other | Articles | Forums | Groups |
| Subversion / CVS |  |  | |
| Ask Dr. Dotnetsky |  |  | |
| Active Directory |  |  |  |
| Networking |  |  | |
| Uninstall Virus |  |  | |
| Job Openings |  |  | |
| Reviews |  |  | |
| Search Engines |  |  | |
| Resumes |  |  | |
|
|
|
|
| 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:
-
Have access to the Appstart exe
-
Have a reference to the ApplicationUpdater assembly - which in turn references
the ApplicationUpdater.Interfaces, ExceptionManagement
and ExceptionManagement.Interfaces assemblies.
-
Be configured to know where the updates are either by configuring the Appstart
exe's config file or the application's own file
-
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:
-
Have a location where the files can be downloaded - this means that it will
have an ISS public location that contains the files
-
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:
-
baseDir: The base directory / working directory
-
xmlFile: The location of the AppStart.exe file
-
tempDir: The location of the temp direcotry in which the
files will be downloaded
In the server section we will have the locations of:
-
xmlFile: The URL to use to get the server manifest xml
file on the server
-
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:
-
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
-
Point this directory to a directory on the server where
you want to store the files
-
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
-
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.
-
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:
-
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)
-
Update location: The url to get to the same directory as
in step 1
-
Version: The version number of the available version, in
our case 2.0.0.0
-
Validator assembly: The path to the
Microsoft.ApplicationBlocks.ApplicationUpdater.dll assembly
-
Validator class: in our case choose the RSA validator
-
We don't have a post processot, so don't use theis section
-
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.
-
Click on the "Create Manifest" button
-
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
-
The IIS directory also needs some prepping:
-
Open the internet services manager
-
Right click on the sayHelloUpdate virtual folder
-
Click on properties
-
In the virtual directory tab, ensure that all these items
are checked: Script source access, Read, Write and Direcotry browsing
-
In the same tab, click the "Configuration" button.
-
In the app mappings tab, look for a .config entry in the
list of application mappings
-
Highlight it
-
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 |
|
|
|