Introduction
After one 56km trip to update the software on my client's new kiosk, and many more updates to come, I decided I needed to add an auto-update component. Eduardo Oliveira's article from 2006 looked promising, but I wanted something even simpler that could even update the updater itself.
Objective
This is what I wanted:
- Check a remote site via HTTP for a newer version.
- If a newer version is available, download it as a ZIP.
- Ensure successful download before overwriting anything.
- Make it easy to add to any application as a component.
- Allow updating of the application itself.
- Do not require a bootstrapper, or multi-step process.
- Resist tampering.
- Accommodate some simple logging.
- Single XML file configuration.
I only had a few hours to put something together. This is what I came up with.
How it Works
The Updater
class does all the heavy lifting. It starts by loading an XML manifest that supplies all of the information it needs to do its work. By default it will look for a file called update.xml
in the application's path. The manifests are represented by the Manifest
class.
="1.0"="utf-8"
<Manifest version="3">
<CheckInterval>900</CheckInterval>
<RemoteConfigUri>https://remote.update.net/myapp/update.xml</RemoteConfigUri>
<SecurityToken>D68EF3A7-E787-4CC4-B020-878BA649B4CD</SecurityToken>
<BaseUri>https://remote.update.net/myapp/</BaseUri>
<Payload>myapp.zip</Payload>
</Manifest>
The format for the local and remote manifests is the same. At present, payloads must be ZIP files and their directory structure should be relative to the application's root. i.e. foo\bar.exe
will be put in the application's foo
directory.
The updater creates a System.Threading.Timer
that ticks at the interval set by the manifest. When this occurs, a new thread is created that executes the Check
method. Meanwhile, the application continues to run without interruption in the foreground.
Check
fetches the remote manifest, checks the SecurityToken
for tampering, and compares the version of the remote manifest to that of the local one. If the remote version is newer, the Update
method is executed.
Update
creates a work directory, downloads each of the payloads specified in the remote manifest, and unzips each of them. It also copies the remote manifest to the working directory, because it will become the new local manifest.
Now for a cool trick. How do we solve the chicken and egg problem? One of the payloads might contain a replacement for your application's executable itself, but it can't be overwritten while it is running. This is enforced by the operating system. However, Windows does (reason unknown) allow a running executable to be renamed! First we rename the application to [application].exe.bak
and then we copy that file back to [application].exe
. The file lock has been moved to the backup file, so if a payload contains a replacement, it will overwrite [application].exe
. If it is not being replaced, no harm done.
To update the application we copy everything in the work directory to the application directory, and then delete the work directory.
Finally, the application is spawned as a new Process
, and the current process is closed.
Using the Code
Add a reference to RedCell.Diagnostics.Update.dll
to your project.
Add to your application's startup code:
var updater = new RedCell.Diagnostics.Update.Updater();
updater.StartMonitoring();
Create an XML manifest and place it both in your application's directory and on the remote server.
You're done.
But what's going on?
I have included a simple facilty for debugging, or if you wish to add a user interface to let the user know what is happening.
using RedCell.Diagnostics.Update;
Log.Console = true;
Log.Debug = true;
Log.Prefix = "[Update] ";
Log.Event += (sender, e) => GuiMessageBox.Show(e.Message);
Demo
If you download and run the demo application, this is what you will see:
You are running version 1 of this console application.
Loaded on PID 3232.
Initializing using file 'F:\Dropbox\Red Cell Innovation\Code Project\Updater\Updater\RedCell.Diagnostics.Update.Demo\4\update.xml'.
Starting monitoring every 900s.
The main thread is going to wait for a keypress.
Check starting.
Fetching 'http://www.codeproject.com/script/Membership/Uploads/1740717/update.xml'.
Remote config is valid.
Local version is 1.
Remote version is 2.
Remote version is newer. Updating.
Updating '1' files.
Fetching 'RedCell.UI.Controls.Demo-Version2.zip'.
Renaming running process to 'F:\Dropbox\Red Cell Innovation\Code Project\Updater\Updater\RedCell.Diagnostics.Update.Demo\4\UpdateDemo.exe.bak'.
installing file 'Program.cs'.
installing file 'update.xml'.
installing file 'update2.xml'.
installing file 'UpdateDemo.exe'.
Deleting work directory.
Spawning new process.
New process ID is 10060
Old process ID is 3232, closing.
Trying Process.CloseMainWindow().
Trying Process.Close().
Trying Environment.Exit().
You are running version 2 of this console application.
Loaded on PID 10060.
Initializing using file 'F:\Dropbox\Red Cell Innovation\Code Project\Updater\Updater\RedCell.Diagnostics.Update.Demo\4\update.xml'.
Starting monitoring every 900s.
The main thread is going to wait for a keypress.
Check starting.
Fetching 'http://www.codeproject.com/script/Membership/Uploads/1740717/update.xml'.
Remote config is valid.
Local version is 2.
Remote version is 2.
Versions are the same.
Check ending.
Conclusion
It's not bad for a few hours work. There are areas for improvement that weren't required for this project, such as:
- Although the executable can be updated, any other open files can't.
- Files aren't checked for integrity i.e. by comparing hashes.
- Files aren't overwritten until they are successfully unpacked, but there still isn't a rollback or backup mechanism.
- This isn't intended for applications that reside in
Program Files
. These directories are write-protected and writing requires UAC. More on that topic later.
Points of Interest
In order to target the .NET 3.5 Framework, I used the third-party reduced DotNetZip component, licensed under the Microsoft Public License (MS-PL). ZIP compression was not introduced into the .NET Framework until version 4.5.
History
- 1.0.0 – February 22, 2014 – First release.