Click here to Skip to main content
11,417,467 members (52,466 online)
Click here to Skip to main content

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

, 22 Feb 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
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 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:

    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 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:

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.

License

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

Share

About the Author

Yvan Rodrigues
President Red Cell Innovation Inc.
Canada Canada
Yvan Rodrigues has 25 years of experience in information systems and software development for the manufacturing sector. He runs Red Cell Innovation Inc./L'innovation de Globules Rouges, a consulting company focusing on efficiency and automation of manufacturing and business processes for small businesses, healthcare, and the public sector. He is a Certified Technician (C.Tech.), a professional designation granted by the Institute of Engineering Technology of Ontario (IETO).

Yvan draws on experience at Mabel's Labels Inc. as Manager of Systems and Development, and the University of Waterloo as Information Systems Manager.

Yvan supports open-source software. He is a committer for SharpKit (C# to Javascript cross-compiler) and WebIssues (Issue/Ticket Management System), TinyMCE (JavaScript editor), and contributes to MySQL, Ghostscript, iTextSharp, Bacula, FreeBSD, and Xamarin.

Yvan's consumer-focused apps can be found in the Windows Store, Apple App Store, and Google Play marketplace.
Follow on   Google+   LinkedIn

Comments and Discussions

 
QuestionLittle question, is there a way to specify a proxy or use the default browser settings ? Pin
Member 1143559215-Mar-15 23:26
memberMember 1143559215-Mar-15 23:26 
AnswerRe: Little question, is there a way to specify a proxy or use the default browser settings ? Pin
Yvan Rodrigues16-Mar-15 4:25
professionalYvan Rodrigues16-Mar-15 4:25 
AnswerRe: Little question, is there a way to specify a proxy or use the default browser settings ? Pin
Yvan Rodrigues16-Mar-15 4:35
professionalYvan Rodrigues16-Mar-15 4:35 
GeneralRe: Little question, is there a way to specify a proxy or use the default browser settings ? Pin
Member 1143559217-Mar-15 4:11
memberMember 1143559217-Mar-15 4:11 
GeneralMy vote of 5 Pin
Renju Vinod19-Mar-14 20:26
professionalRenju Vinod19-Mar-14 20:26 
QuestionClickOnce Pin
Mike Meinz24-Feb-14 12:54
memberMike Meinz24-Feb-14 12:54 
AnswerRe: ClickOnce Pin
Yvan Rodrigues25-Feb-14 4:17
professionalYvan Rodrigues25-Feb-14 4:17 
GeneralRe: ClickOnce Pin
Mike Meinz25-Feb-14 4:40
memberMike Meinz25-Feb-14 4:40 
QuestionWindows 7/8 ? Pin
Phil Zeno24-Feb-14 11:15
memberPhil Zeno24-Feb-14 11:15 
Questionsecurity Pin
christophe.hermier@quickfds.com24-Feb-14 0:35
memberchristophe.hermier@quickfds.com24-Feb-14 0:35 
GeneralGreat tip!!! Pin
Carlos190724-Feb-14 0:35
memberCarlos190724-Feb-14 0:35 
QuestionComparison Pin
staffan_v24-Feb-14 0:15
professionalstaffan_v24-Feb-14 0:15 
AnswerRe: Comparison Pin
Yvan Rodrigues24-Feb-14 3:33
professionalYvan Rodrigues24-Feb-14 3:33 
QuestionVery nice! Pin
kmoorevs23-Feb-14 8:26
memberkmoorevs23-Feb-14 8:26 
QuestionReferenced assemblies Pin
Benny S. Tordrup22-Feb-14 20:31
memberBenny S. Tordrup22-Feb-14 20:31 
AnswerRe: Referenced assemblies Pin
Yvan Rodrigues23-Feb-14 6:33
professionalYvan Rodrigues23-Feb-14 6:33 
GeneralRe: Referenced assemblies Pin
Steve Solomon12-Mar-14 8:05
memberSteve Solomon12-Mar-14 8:05 
GeneralMy vote of 4 Pin
FatalError0x4c22-Feb-14 20:29
memberFatalError0x4c22-Feb-14 20:29 
AnswerRe: My vote of 4 Pin
Yvan Rodrigues23-Feb-14 6:32
professionalYvan Rodrigues23-Feb-14 6:32 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.150427.4 | Last Updated 22 Feb 2014
Article Copyright 2014 by Yvan Rodrigues
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid