Click here to Skip to main content
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

 
GeneralMy vote of 5 PinprofessionalRenju Vinod19-Mar-14 20:26 
QuestionClickOnce PinmemberMike Meinz24-Feb-14 12:54 
AnswerRe: ClickOnce PinprofessionalYvan Rodrigues25-Feb-14 4:17 
GeneralRe: ClickOnce PinmemberMike Meinz25-Feb-14 4:40 
QuestionWindows 7/8 ? PinmemberPhil Zeno24-Feb-14 11:15 
Questionsecurity Pinmemberchristophe.hermier@quickfds.com24-Feb-14 0:35 
GeneralGreat tip!!! PinmemberCarlos190724-Feb-14 0:35 
QuestionComparison Pinprofessionalstaffan_v24-Feb-14 0:15 
AnswerRe: Comparison PinprofessionalYvan Rodrigues24-Feb-14 3:33 
QuestionVery nice! Pinmemberkmoorevs23-Feb-14 8:26 
QuestionReferenced assemblies PinmemberBenny S. Tordrup22-Feb-14 20:31 
AnswerRe: Referenced assemblies PinprofessionalYvan Rodrigues23-Feb-14 6:33 
GeneralRe: Referenced assemblies PinmemberSteve Solomon12-Mar-14 8:05 
GeneralMy vote of 4 PinmemberFatalError0x4c22-Feb-14 20:29 
AnswerRe: My vote of 4 PinprofessionalYvan 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.141223.1 | Last Updated 22 Feb 2014
Article Copyright 2014 by Yvan Rodrigues
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid