본문 바로가기
Coding/C#, .Net Framework

Making Auto Updater with C#

by 생각하는대로살자 2009. 8. 9.

2009.07.03 뇌이뇬 블로깅

-----------------------------------------

원래 코드프로젝트에서 VB로 된 것을 누군가가 댓글에 C# 코드로...

샘플형식으로 변환을 해 놨다 ㅋㅋ

자동 업데이트 프로그램을 만들고자 하는 사람들은 참조해도 좋을듯...

 

1. Create a project - click File -> New -> Project -> Class Library
1.1. Name it whatever you want (as long as its name is AutoUpdate )
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

[-] Collapse
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 "AutoUpdateTest"
2.1. Add a reference to the AutoUpdate project
2.2. Rename Form1.cs to Startup.cs
2.3. Click View Code and paste (replace whatever is in there) the following


[-] Collapse
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();
       }
   }
}


3. On IIS create a virtual directory named "AutoUpdateTest"
3.1. Put your sample files in there.
3.2. Put there an Update.txt like this one


 [-] Collapse 
\Updated2.exe ; 'G't Updated2.exe if it doesn't exist (ignore "\")
.\Updated3.exe ; ? 'G't Updated3.exe if it doesn't exist (ignore ".\")
.\Updated3.exe ; delete 'T'is line is ignored as it's the second instance of the same file path
..\Updated4.exe ; =1.0.0.3 'I' actual Updated4.exe isn't this version, get it (ignore "..\")
Updated5.exe ; delete 'D'lete Updated5.exe
Updated.exe ; 1.0.0.3 'I' actual Updated.exe version is smaller than 1.0.0.3, get it
'U'date2.exe ; delete 'i'nore this line
't'e 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

4. Build and execute AutoUpdateTest.exe