Click here to Skip to main content
15,905,785 members
Articles / Programming Languages / Visual Basic
Article

Application Auto Update Revisited

Rate me:
Please Sign up or sign in to vote.
4.84/5 (23 votes)
19 Nov 2006CPOL2 min read 235.3K   6.1K   188   60
An article on updating Windows applications through the web.

Introduction

Since my first iteration of the AutoUpdate, I noticed that I don't really need the ability to change the AutoUpdate program itself, so I don't need it. The second thing is that most of the time I don't need to update all the files.

What has changed

The AutoUpdate program (AutoUpdate.exe) is no longer required. Now, everything that is needed is in the DLL (AutoUpdate.dll). No need to touch the CommandLine anymore since everything is at the same place. The AutoUpdate doesn't need to be a class, now it is just a module, so, no need to create an AutoUpdate variable.

No need to change the AutoUpdate code. The remote path, the update file name, and the error message are now properties and the last two have default values.

The update file has a new layout, as shown:

<File Name>;<Version>   [' comments    ]
<File Name>[;<Version>] [' comments    ]

<File Name>[;?]         [' comments    ]
<File Name>[;delete]    [' comments    ]
...

And this is what it means:

  • Blank lines and comments are ignored.
  • The first line should be the current program/version.
  • From the second line to the end, the second parameter is optional.
  • If the second parameter is not specified, the file is updated.
  • If the version is specified, the update checks the version.
  • If the second parameter is an interrogation mark (?), the update checks if the file already exists, and "doesn't" upgrade if it exists.
  • If the second parameter is "delete", the system tries to delete the file.
  • "'" (chr(39)) starts a line comment (like VB).

The UpdateFiles function returns True if the AutoUpdate did the update or there was an error during the update, or False if nothing was done.

The auto update web folder

Some things never change. The auto update web folder should have a folder for each system you want to upgrade. The root folder is the one that you will refer on the RemotePath variable. Each sub folder should be named as the assembly name (normally, the program name without the extension). Inside the program folder, you save the files that you want to update and the file Update.txt (or the name that you defined in the UpdateFileName property) with the layout explained above.

Using the code

You can add the module to your project, or you can add a reference to the DLL. After that, you just need to call the UpdateFiles function. You also can change the default properties before the call.

VB
Public Sub Main()
    ' You can set some parameters thru properties
    ' The remote path can be set thru the RemotePath property or 
    ' thru the RemotePath parameter in the function call
    ' UpdateFileName and ErrorMessages have default value so it's optional
    AutoUpdate.RemotePath = "http://www.url.com/directory/"

    ' the final location to the files will be RemotePath + AssembyName + "/" + FileName
    ' ex: http://www.url.com/directory/AutoUpdateTest/MyUpdate.dat

    AutoUpdate.UpdateFileName = "MyUpdate.dat"
    AutoUpdate.ErrorMessage = "Something funny is going on trying to auto update."

    ' test if an update is needed and quit the program if so.
    If AutoUpdate.UpdateFiles() Then Exit Sub

    ' here goes your regular code in the main sub
    Application.Run(New Form1)
End Sub

What else can be done

In the server side, you can build a service that automatically generates the update file, but this is up to you!

License

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


Written By
Software Developer (Senior)
Canada Canada
Eduardo Oliveira graduated in Computer Systems Analysis in Rio de Janeiro - Brazil in 1990.
He has been working as Programmer Analyst since.
In 2001 immigrated to Canada and today lives in Calgary and works with .NET and SQL server, developing desktop and web applications.

Comments and Discussions

 
GeneralVB error with this code (VS2008) Pin
n3mesis12526-Sep-08 6:46
n3mesis12526-Sep-08 6:46 
QuestionRe: VB error with this code (VS2008) Pin
n3mesis12526-Sep-08 6:49
n3mesis12526-Sep-08 6:49 
GeneralVB.NET saving and retrieving word file into sql database Pin
naresh15072-Sep-08 22:56
naresh15072-Sep-08 22:56 
GeneralVB.NET saving and retrieving word file into sql database Pin
naresh15072-Sep-08 22:55
naresh15072-Sep-08 22:55 
QuestionHow does the module handling DLL references of the software? Pin
S. Kolic4-May-08 2:22
S. Kolic4-May-08 2:22 
Questiondoes in work if your app has been initially installed via an MSI? Pin
ewart14-Mar-08 17:31
ewart14-Mar-08 17:31 
QuestionWhy not update the AutoUpdate program ? Pin
cliftonarms28-Feb-08 12:08
cliftonarms28-Feb-08 12:08 
GeneralC# v.2.1.1 (with some improvements and an example of use) [modified] PinPopular
Tiago Freitas Leal7-Feb-08 14:32
professionalTiago Freitas Leal7-Feb-08 14:32 
1. Create a project - click File -> New -> Project -> Class Library
1.1. Name it whatever you want (as long as its name is AutoUpdate Big Grin | :-D )
1.2. Rename Class1.cs to AutoUpdate.cs (accepting to rename all references)
1.2. Double click AutoUpdate.cs and paste (replace whatever is in there) the following listing



Project: AutoUpdate<br/>
File: AutoUpdate.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Diagnostics;
using System.Windows.Forms;

/// <summary>
/// Class to update .exe and .dll through HTTP connection
/// </summary>
/// <author>Eduardo Oliveira</author>
/// <Modified_by>Danny (dbembibre@virtualdesk.es)</Modified_by>
/// <history Danny>
/// Changed: C# translated and retouched
/// Changed: Added method IsUpdatable() to verify if a new update exists
/// Changed: Now you can update any file without need of exe file
/// Changed: If something went wrong, you have a collection to rollback all files to the original state
///</history Danny>
/// <Modified>Tiago Freitas Leal, Feb. 2008 - v.2.1.1</Modified>
/// <history TFL>
/// Changed: General refactoring of names
/// Changed: Refactoring of properties to constants (preparing for resources)
/// Changed: Remote URI doesn't depend on assembly name
/// Changed: Ignore heading "." and "\" on file paths
/// Changed: Ignore second instance of same file path
/// Changed: Add "=ExactVersion" option
/// Changed: ".ToDelete" filenames are prefixed with the old filename (avoid duplicate names)
/// Changed: "return" happens always after "Dispose" operations
/// Changed: Merge "IsUpdatable" into "UpdateFiles"
/// Changed: Move delete operations to new public CleanUp() method
/// Changed: Files to be deleted are made NOT ReadOnly before deletion
/// Changed: Delete operates also on sub-directories
///</history TFL>
public class AutoUpdate
{
    // <File Path>;<MinimumVersion>   [' comments    ]
    // <File Path>;=<ExactVersion>    [' comments    ]
    // <File Path>;                   [' comments    ]
    // <File Path>;?                  [' comments    ]
    // <File Path>;delete             [' comments    ]
    // ...
    // Blank lines and comments are ignored
    // First parameter - file path (eg. Dir\file.dll)
    // Second parameter:
    // If the version is specified, the file is updated if:
    //   - it doesn't exist or
    //   - the actual version number is smaller than the update version number
    // If the version is specified precedeed by a "=", the file is updated if:
    //   - it doesn't exist or
    //   - the actual version doesn't match the update version
    // If it's an interrogation mark "?" the file is updated only if it doesn't exist
    // If the second parameter is not specified, the file is updated only if it doesn't exist (just like "?")
    // If it's "delete" the system tries to delete the file
    // "'" (chr(39)) start a line (or part line) comment (like VB)

    // Method return values
    //   - True - the update was done with no errors
    //   - False - the update didn't complete successfully: either there is no update to be done
    //             or there was an error during the update

    // NB - "Version" refers to the AssemblyFileVersion as configured in file AssemblyInfo.cs

    private const string ToDeleteExtension = ".ToDelete";
    private const string UpdateFileName = "Update.txt";
    private const string ErrorMessageCheck = "There was a problem checking the update config file.";
    private const string ErrorMessageUpdate = "There was a problem runing the Auto Update.";
    private const string ErrorMessageDelete = "There was a problem deleting files.";

    #region "CleanUp"

    public static bool CleanUp()
    {
        try
        {
            string file;

            DirectoryInfo dir = new DirectoryInfo(Application.StartupPath);
            FileInfo[] infos = dir.GetFiles("*" + ToDeleteExtension, SearchOption.AllDirectories);
            foreach (FileInfo info in infos)
            {
                file = info.FullName;
                File.SetAttributes(file, FileAttributes.Normal);
                File.Delete(file);
            }
            return true;
        }
        catch (Exception ex)
        {
            MessageBox.Show(ErrorMessageDelete + "\r" + ex.Message);
            return false;
        }
    }

    #endregion

    #region "UpdateFiles"

    public static bool UpdateFiles(string remotePath, bool DoUpdate)
    {
        if (remotePath == string.Empty)
            return false;

        if (DoUpdate)
        {
            // Delete files before updating them
            CleanUp();
        }

        // execute the following line even for check runs
        System.Collections.Generic.List<AutoUpdateRollback> rollBackList =
            new System.Collections.Generic.List<AutoUpdateRollback>();

        string remoteUri = remotePath;
        WebClient myWebClient = new WebClient();

        bool retValue = false;
        try
        {
            // Get the remote file
            string contents = myWebClient.DownloadString(remoteUri + UpdateFileName);
            // Strip the "LF" from CR+LF and break it down by line
            contents = contents.Replace("\n", "");
            string[] fileList = contents.Split(Convert.ToChar("\r"));

            // Parse the file list to strip comments
            contents = string.Empty;
            foreach (string file in fileList)
            {
                string fileAux;
                if ((file.IndexOf("\'") + 1 != 0))
                    fileAux = file.Substring(0, ((file.IndexOf("\'") + 1) - 1));
                else
                    fileAux = file;
                if (fileAux.Trim() != string.Empty)
                {
                    if (!string.IsNullOrEmpty(contents))
                        contents = contents + (char) (Keys.Return);
                    contents = contents + fileAux.Trim();
                }
            }

            // Parse the file list again
            fileList = contents.Split((char) (Keys.Return));
            string[] info;
            string infoFilePath;
            String infoParam;
            List<string> fileNameList = new List<string>();
            Version Version1, Version2;
            FileVersionInfo fv;
            bool IsToDelete;
            bool IsToUpgrade;
            foreach (string file in fileList)
            {
                info = file.Split(Convert.ToChar(";"));
                infoFilePath = info[0].Trim();
                infoParam = info[1].Trim();
                while (infoFilePath[0] == '.' || infoFilePath[0] == '\\')
                {
                    infoFilePath = infoFilePath.Substring(1, infoFilePath.Length-1);
                }

                // ignore path names already on list (duplicates)
                if (fileNameList.Contains(infoFilePath.ToLowerInvariant()))
                {
                    continue;
                }

                // add path name to list
                fileNameList.Add(infoFilePath.ToLowerInvariant());
                IsToDelete = false;
                IsToUpgrade = false;
                string fileName = Application.StartupPath + @"\" + infoFilePath;
                string tempFileName = Application.StartupPath + @"\" + infoFilePath + DateTime.Now.TimeOfDay.TotalMilliseconds;
                bool FileExists = File.Exists(fileName);
                if ((infoParam == "delete"))
                {
                    IsToDelete = FileExists; // The second parameter is "delete"
                    if (DoUpdate)
                        if (IsToDelete)
                            rollBackList.Add(new AutoUpdateRollback(fileName, tempFileName + ToDeleteExtension, "Delete"));
                }
                else if ((infoParam == "?"))
                {
                    // The second parameter is "?" (check if the file exists and it TRUE, do not update
                    IsToUpgrade = !FileExists;
                }
                else if (infoParam != string.Empty && (infoParam[0] == '=' && FileExists))
                {
                    // The second parameter starts by "=" 
                    // Check the version of local and remote files
                    fv = FileVersionInfo.GetVersionInfo(fileName);
                    Version1 = new Version(infoParam.Substring(1, infoParam.Length - 1));
                    Version2 = new Version(fv.FileVersion);
                    IsToUpgrade = Version1 != Version2;
                    IsToDelete = IsToUpgrade;
                    if (DoUpdate)
                        if (IsToUpgrade)
                            rollBackList.Add(
                                new AutoUpdateRollback(fileName, tempFileName + ToDeleteExtension, "Upgrade"));
                }
                else if (FileExists)
                {
                    // Check the version of local and remote files
                    fv = FileVersionInfo.GetVersionInfo(fileName);
                    // If 2nd parameter is empty, do nothing as file already exists
                    if (infoParam != string.Empty)
                    {
                        Version1 = new Version(infoParam);
                        Version2 = new Version(fv.FileVersion);
                        IsToUpgrade = Version1 > Version2;
                        IsToDelete = IsToUpgrade;
                        if (DoUpdate)
                            if (IsToUpgrade)
                                rollBackList.Add(
                                    new AutoUpdateRollback(fileName, tempFileName + ToDeleteExtension, "Upgrade"));
                    }
                }
                else
                {
                    IsToUpgrade = true;
                }

                if (DoUpdate)
                {
                    if (IsToUpgrade)
                        myWebClient.DownloadFile(remoteUri + infoFilePath, tempFileName);
                    // Rename the file for future deletion
                    if (IsToDelete)
                        File.Move(fileName, tempFileName + ToDeleteExtension);
                    // Rename the temporary file name to the real file name
                    if (IsToUpgrade)
                        File.Move(tempFileName, fileName);
                }

                if (IsToUpgrade || IsToDelete)
                    retValue = true;
            }

            // This reruns the updated application
            //Process.Start(Application.ExecutablePath);
            // Don't use it here or you will end in an endless loop.
        }
        catch (Exception ex)
        {
            MessageBox.Show("There was an error. Trying to roll back");
            if (DoUpdate)
            {
                foreach (AutoUpdateRollback rollBack in rollBackList)
                {
                    if (rollBack.Operation == "Delete" || rollBack.Operation == "Upgrade")
                    {
                        if (File.Exists(rollBack.Renamed))
                            File.Move(rollBack.Renamed, rollBack.Original);
                    }
                }
                MessageBox.Show(ErrorMessageUpdate + "\r" + ex.Message + "\r" + "Remote URI: " + remoteUri);
            }
            else
                MessageBox.Show(ErrorMessageCheck + "\r" + ex.Message + "\r" + "Remote URI: " + remoteUri);

            retValue = false;
        }
        finally
        {
            myWebClient.Dispose();
            // execute the following line even for check runs
            rollBackList.Clear();
        }

        return retValue;
    }

    #endregion
}

public class AutoUpdateRollback
{
    #region Properties

    private string _renamed, _original, _operation;

    public string Operation
    {
        get { return _operation; }
        set { _operation = value; }
    }

    public string Original
    {
        get { return _original; }
        set { _original = value; }
    }

    public string Renamed
    {
        get { return _renamed; }
        set { _renamed = value; }
    }

    #endregion

    #region Constructor

    public AutoUpdateRollback(string Original, string Renamed, string Operation)
    {
        _original = Original;
        _renamed = Renamed;
        _operation = Operation;
    }

    #endregion
}

2. Add a new project of type Windows Application named "Updated"
2.1. Rename Form1.cs to ShowVersionForm.cs
2.2. Click View Code and paste (replace whatever is in there) the following listing
;


Project: Updated<br/>
File: ShowVersionForm.cs

using System;
using System.Diagnostics;
using System.Reflection;
using System.Windows.Forms;

namespace Updated
{
    public partial class ShowVersionForm : Form
    {
        public ShowVersionForm()
        {
            InitializeComponent();
            label2.Text = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Close();
        }
    }
}

3. Add a new project of type Windows Application named "AutoUpdateTest"
3.1. Add a reference to the AutoUpdate and to Updated projects
3.2. Rename Form1.cs to Startup.cs
3.3. Click View Code and paste (replace whatever is in there) the following listing



Project: AutoUpdateTest<br/>
File: Startup.cs

using System;
using System.Diagnostics;
using System.Windows.Forms;

namespace AutoUpdateTest
{
    public partial class Startup : Form
    {
        public Startup()
        {
            InitializeComponent();
        }

        private void Startup_Load(object sender, EventArgs e)
        {
            Show();

            //Change to reflect your RemotePath
            string RemotePath = "http://localhost/AutoUpdateTest/";
            label1.Text = RemotePath;
            Form helper = new Updated.ShowVersionForm();
            helper.Show();
            MessageBox.Show("Checking if update is needed...");
            if (AutoUpdate.UpdateFiles(RemotePath, false))
            {
                MessageBox.Show("Update is needed.");
                if (AutoUpdate.UpdateFiles(RemotePath, true))
                {
                    MessageBox.Show("Auto Update succedeed!");
                    Dispose();
                    Process.Start(Application.ExecutablePath); 
                    Application.Exit();
                }
            }
            else
            {
                MessageBox.Show("No update is available.");
                AutoUpdate.CleanUp();
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Close();
        }
    }
}

4. On IIS create a virtual directory named "AutoUpdateTest"
4.1. Put your sample files in there.
4.2. Put there an Update.txt like this one


\Updated2.exe ; 'Get Updated2.exe if it doesn't exist (ignore "\")
 .\Updated3.exe ; ? 'Get Updated3.exe if it doesn't exist (ignore ".\")
 .\Updated3.exe ; delete 'This line is ignored as it's the second instance of the same file path
 ..\Updated4.exe ; =1.0.0.3 'If actual Updated4.exe isn't this version, get it (ignore "..\")
 Updated5.exe ; delete 'Delete Updated5.exe
 Updated.exe ; 1.0.0.3 'If actual Updated.exe version is smaller than 1.0.0.3, get it 
 'Update2.exe ; delete 'ignore this line
'the lines below just test the same features on sub-directories
 TST\TSTUpdated.dll ; 1.0.0.1 
 TST\SubSub\SUBUpdated.dll ; 1.0.0.1 
 \TST\SubSub\Slash.dll ; 1.0.0.1 
 .\TST\SubSub\DotSlash.dll ; 1.0.0.1 
 .\TST\SubSub\Trash.dll ; delete 


5. Build all projects in the solution and execute AutoUpdateTest.exe

NB 1 - If you have questions, just reply to this message. I'll receive a notification and will answer.
NB 2 - Part 2 was added as an answer to a message from "Member 6038196"

modified on Thursday, November 12, 2009 4:10 PM

GeneralRe: C# v.2.1.1 (with some improvements and an example of use) Pin
mayaolong1-Mar-08 17:46
mayaolong1-Mar-08 17:46 
GeneralRe: C# v.2.1.1 (with some improvements and an example of use) Pin
Nate Liu20-Nov-08 22:11
Nate Liu20-Nov-08 22:11 
GeneralRe: C# v.2.1.1 (with some improvements and an example of use) Pin
Nate Liu21-Nov-08 20:59
Nate Liu21-Nov-08 20:59 
QuestionRe: C# v.2.1.1 (with some improvements and an example of use) Pin
gwenny3-Jun-09 19:36
gwenny3-Jun-09 19:36 
AnswerRe: C# v.2.1.1 (with some improvements and an example of use) Pin
Tiago Freitas Leal5-Jun-09 12:43
professionalTiago Freitas Leal5-Jun-09 12:43 
GeneralRe: C# v.2.1.1 (with some improvements and an example of use) Pin
c95se5m14-Aug-09 8:21
c95se5m14-Aug-09 8:21 
GeneralRe: C# v.2.1.1 (with some improvements and an example of use) Pin
Jasmine Pomelo11-Nov-09 18:14
Jasmine Pomelo11-Nov-09 18:14 
GeneralRe: C# v.2.1.1 (with some improvements and an example of use) Pin
Tiago Freitas Leal12-Nov-09 10:16
professionalTiago Freitas Leal12-Nov-09 10:16 
GeneralRe: C# v.2.1.1 (with some improvements and an example of use) Pin
tdata16-Nov-09 0:24
tdata16-Nov-09 0:24 
GeneralRe: C# v.2.1.1 (with some improvements and an example of use) Pin
omzig26-Mar-10 9:59
omzig26-Mar-10 9:59 
GeneralRe: C# v.2.1.1 (with some improvements and an example of use) Pin
KyferEz9-Nov-10 11:56
KyferEz9-Nov-10 11:56 
GeneralRe: C# v.2.1.1 (with some improvements and an example of use) Pin
KyferEz8-Nov-10 18:35
KyferEz8-Nov-10 18:35 
QuestionRe: Need urgent reply (C# v.2.1.1 (with some improvements and an example of use)) Pin
pranpara26-Nov-10 2:38
pranpara26-Nov-10 2:38 
GeneralRe: C# v.2.1.1 (with some improvements and an example of use) [modified] Pin
hitbraga25-Jan-19 0:06
hitbraga25-Jan-19 0:06 
GeneralDownload not accessible :( [modified] Pin
twisterPB656-Dec-07 18:59
twisterPB656-Dec-07 18:59 
GeneralRe: Download not accessible :( [modified] Pin
twisterPB659-Dec-07 21:09
twisterPB659-Dec-07 21:09 
GeneralGreat Code Pin
Aust Paul24-Nov-07 23:18
Aust Paul24-Nov-07 23:18 

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.