Tuesday, April 08, 2008

How to add automatic updater to a null soft installer and the pros and cons of automatic updates

I really think that when it comes to windows installer the most convenient and easy to use installer suite available on the market is nullsoft installer.
To build an installer you only need to write a UTF-8 script using your favorites editor. When it comes to automatic update i really like a software written by a smart guy in Netherland. The updater can be purchase from http://www.catenalogic.com/

Once you get the updater you need to hook the updater into your installer.
To do so you need to provide a way to check for update from the start menu
using a shortcut towards the updater.exe. Your application should also execute the updater as well once in a while (periodic perdiod) or during each startup.

Here is what you need to add to your null soft script to provide a complete end to end update solution to you application.

First you need to compress the updater files and custom graphics such as top banner and left bitmap. Be carefull, the size of the graphics element are very important. One pixel off and you will see the default graphics instead.

!define CLIENT_VERSION "1.0"
!define CLIENT_NAME "MyHelloWorld Sample Application"
!define PRODUCT_NAME "${CLIENT_NAME}"
!define PRODUCT_VERSION "1.0.0.0"
!define SETUP_FILE "setup_1_0.exe"

!define APP_FILE1 "..\files\HelloWorld.exe"
!define UPDATER_FILE1 "..\files\updater.exe"
!define UPDATER_FILE2 "..\files\updaterbanner.bmp"
!define UPDATER_FILE3 "..\files\updaterleft.bmp"


Inside the Installer section of your script
you need to add script code to uncompress and copy the updater files
inside the target program file folder for your application. The one choosen
by the user or set by default under Program Files.

;script start

SetOverwrite ifnewer

CreateDirectory "$PROGRAMFILES\MyApp"

SetOutPath "$PROGRAMFILES\MyApp"

;HelloWorld.exe application file
File "${APP_FILE1}"

;UPDATER FILES TO EXTRACT
File "${UPDATER_FILE1}"
File "${UPDATER_FILE2}"
File "${UPDATER_FILE3}"

;you need to generate and update the settings.ini file
;used by the updater.exe so that the updater will know where to download
;the XML file on the update server. The updater will compare your
;application version set in the ini with the one available on the remote
;server. Because this call is self contain in the installer once you will
;uninstall and install a new version it will automatically update the up to
;date settings.ini

;Update Updater INI file with installation application path and product version
FileOpen $1 "$INSTDIR\settings.ini" w
;FileSeek $1 0 END $INI_SIZE
FileWrite $1 "[UPDATER]$\r$\n"
FileWrite $1 "runmode=full$\r$\n"
FileWrite $1 "logolarge=updaterleft.bmp$\r$\n"
FileWrite $1 "logosmall=updaterbanner.bmp$\r$\n"
FileWrite $1 "customnotification=false$\r$\n"
FileWrite $1 "[UPDATEINFO]$\r$\n"
FileWrite $1 "URL=http://www.yourwebserverforupdate.com/download/HelloWorld/full/update.xml$\r$\n"

;as you see the update.xml will provide a way for updater to get http path
;for update along with command (action) to execute in a chronological order.
;I will post a sample XML file in an other article.

FileWrite $1 "servertimeout=2000$\r$\n"
FileWrite $1 "[APPLICATION]$\r$\n"

StrCpy $KEY "name="
FileWrite $1 $KEY
StrCpy $VALUE "${PRODUCT_NAME}"
FileWrite $1 $VALUE
FileWrite $1 "$\r$\n"

StrCpy $KEY "version="
FileWrite $1 $KEY
StrCpy $VALUE ${CLIENT_VERSION}
FileWrite $1 $VALUE
FileWrite $1 "$\r$\n"

StrCpy $KEY "location="
FileWrite $1 $KEY
StrCpy $VALUE $INSTDIR\HelloWorld.exe
FileWrite $1 $VALUE
FileWrite $1 "$\r$\n"

FileWrite $1 "[PROXY]$\r$\n"
FileWrite $1 "type=autodetect$\r$\n"

FileClose $1

;last things you need to do is to create a shortcut.
;Te cool things with nullsoft when you create a shortcut is that
;it will use the last SetOutPath call as the working directory for your
;shortcut. Alternatively you can specify the path of the settings.ini

StrCpy $STARTMENU_FOLDER "MyHelloWorldApplication"

SetOutPath "$PROGRAMFILES\HelloWorld"
CreateDirectory "$SMPROGRAMS\$STARTMENU_FOLDER"
CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\Check for update.lnk" "$INSTDIR\updater.exe"

In your application code you will launch updater.exe from your application folder.
You can also read a registry key to get a custom path. This registry key will be set by the installer itself.



Finally what i like with semi automatic update is not to seat in the tray icon and use my CPU but just be invoked by the application who need the service of the updater one time, ask the user.



Hope you will find this Null Soft Updater Script for catenalogic useful !

4 comments:

Anyoka said...

Thanks alot..

Khepry Quixote said...

Could you please provide an example bit of code covering the invocation of the updater.exe program from within a C#.NET application?

Where would you put it? Within the program.cs file? Within the main form?

Khepry Quixote said...

Could you please provide some example code for invoking the updater.exe program from within a C#.NET application?

Would you invoke it from within the program.cs file? The main form? On a timer? What is the best practice?

Thomas Younsi said...

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Data;

namespace CheckForUpdatesExample
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);

// Should we automatically check for updates?
if (Properties.Settings.Default.CheckForUpdates == true)
{
// Get path
string updaterPath = String.Format("{0}\\updater\\", Application.StartupPath);
string updaterFile = String.Format("{0}\\updater.exe", updaterPath);

// Check if path exists
if (System.IO.File.Exists(updaterFile))
{
// Run Updater
System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();
startInfo.FileName = updaterFile;
startInfo.WorkingDirectory = updaterPath;
startInfo.Arguments = "-checkforupdates";

// Execute the file with the parameters
System.Diagnostics.Process process = new System.Diagnostics.Process();
process.StartInfo = startInfo;
process.Start();

// Wait until updater is finished, shouldn't take longer than 120 seconds to check for a new version
process.WaitForExit(120000);

// Is there a new version available?
if (process.ExitCode.Equals(1))
{
// Show message box
if (MessageBox.Show("A new update is available! Do you want to update now?", "New update available!",
MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
{
// Launch updater again in full mode (using settings file)
startInfo.Arguments = "";

// Run again
process.StartInfo = startInfo;
process.Start();
}
}
}
}

Application.Run(new MainForm());
}
}
}