Click here to Skip to main content
15,881,689 members
Articles / Programming Languages / XML

Simple Auto-Update: Let your application update itself using 2 lines of code

Rate me:
Please Sign up or sign in to vote.
4.80/5 (47 votes)
22 Feb 2014CPOL4 min read 212K   22.6K   146   41
Giving your application the ability to update itself from a remote source couldn't be much easier.

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

class diagram

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.

XML
<?xml version="1.0" encoding="utf-8" ?>
<!-- Increment the version for each update. -->
<Manifest version="3">
    <!-- Your application will check for updates every (seconds) -->
    <CheckInterval>900</CheckInterval>

    <!-- The URI to the remote manifest -->
    <RemoteConfigUri>https://remote.update.net/myapp/update.xml</RemoteConfigUri>

    <!-- This token must be the same at both ends to avoid tampering -->
    <SecurityToken>D68EF3A7-E787-4CC4-B020-878BA649B4CD</SecurityToken>
    <!-- All payload files are assumed to have this URI prefix. -->
    <BaseUri>https://remote.update.net/myapp/</BaseUri>

    <!-- One or more files containing updates. -->
    <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:

C#
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.

C#
using RedCell.Diagnostics.Update;

// Log activity to the console.
Log.Console = true;

// Log activity to the System.Diagnostics.Debug facilty.
Log.Debug = true;

// Prefix messages to the above.
Log.Prefix = "[Update] "; // This is the default.

// Send activity messages to the UI.
Log.Event += (sender, e) => GuiMessageBox.Show(e.Message);

Demo

If you download and run the demo application, this is what you will see:

Process_1
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().
Process_2
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Engineer Robotic Assistance Devices / AITX
Canada Canada
Yvan Rodrigues has 30 years of experience in information systems and software development for the industry. He is Senior Concept Designer at Robotic Assistance Devices

He is a Certified Technician (C.Tech.), a professional designation granted by the Institute of Engineering Technology of Ontario (IETO).

Yvan draws on experience as owner of Red Cell Innovation Inc., Mabel's Labels Inc. as Manager of Systems and Development, the University of Waterloo as Information Systems Manager, and OTTO Motors as Senior Systems Engineer and Senior Concept Designer.

Yvan is currently focused on design of embedded systems.

Comments and Discussions

 
AnswerRe: My vote of 5 Pin
Yvan Rodrigues30-Jun-16 4:10
professionalYvan Rodrigues30-Jun-16 4:10 
QuestionClickOnce Pin
Mike Meinz24-Feb-14 11:54
Mike Meinz24-Feb-14 11:54 
AnswerRe: ClickOnce Pin
Yvan Rodrigues25-Feb-14 3:17
professionalYvan Rodrigues25-Feb-14 3:17 
GeneralRe: ClickOnce Pin
Mike Meinz25-Feb-14 3:40
Mike Meinz25-Feb-14 3:40 
QuestionWindows 7/8 ? Pin
Phil Zeno24-Feb-14 10:15
Phil Zeno24-Feb-14 10:15 
AnswerRe: Windows 7/8 ? Pin
Yvan Rodrigues30-Jun-16 4:12
professionalYvan Rodrigues30-Jun-16 4:12 
Questionsecurity Pin
christophe.hermier@quickfds.com23-Feb-14 23:35
christophe.hermier@quickfds.com23-Feb-14 23:35 
GeneralGreat tip!!! Pin
Carlos190723-Feb-14 23:35
professionalCarlos190723-Feb-14 23:35 
I had this dilemma too with a namespace extension to Explorer...
I've become completely shocked when it accepted to be renamed and still working!
Thank you a lot!
www.palcomp3.com.br/ratomg

AnswerRe: Great tip!!! Pin
Yvan Rodrigues30-Jun-16 4:12
professionalYvan Rodrigues30-Jun-16 4:12 
QuestionComparison Pin
staffan_v23-Feb-14 23:15
professionalstaffan_v23-Feb-14 23:15 
AnswerRe: Comparison Pin
Yvan Rodrigues24-Feb-14 2:33
professionalYvan Rodrigues24-Feb-14 2:33 
QuestionVery nice! Pin
kmoorevs23-Feb-14 7:26
kmoorevs23-Feb-14 7:26 
AnswerRe: Very nice! Pin
Yvan Rodrigues30-Jun-16 4:13
professionalYvan Rodrigues30-Jun-16 4:13 
QuestionReferenced assemblies Pin
Benny S. Tordrup22-Feb-14 19:31
Benny S. Tordrup22-Feb-14 19:31 
AnswerRe: Referenced assemblies Pin
Yvan Rodrigues23-Feb-14 5:33
professionalYvan Rodrigues23-Feb-14 5:33 
GeneralRe: Referenced assemblies Pin
SteveTheThread12-Mar-14 7:05
SteveTheThread12-Mar-14 7:05 
GeneralMy vote of 4 Pin
fioresoft22-Feb-14 19:29
fioresoft22-Feb-14 19:29 
AnswerRe: My vote of 4 Pin
Yvan Rodrigues23-Feb-14 5:32
professionalYvan Rodrigues23-Feb-14 5:32 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.