Introduction
This is a program to check out there are updated files on the update server. The processing about how to update files is posted before, look at the post named "Simple FTP Uploader Using Zip File For Software Update". If there's one, this program executes the update process.
First, reading XML named as "UpdateList.xml" which is in the FTP server and compare the update datetime string
value with the updatetime string
s in the log file named as "SWUpdate.log". If there is no matched string
, start downloading the zip file which included all update files.
Second, unarchiving it in the executive path of your program.
Third, executing the "Updater.exe" program and sending the caller program's name and update datetime string
value which you read .
Fourth, the "Updater
" program will terminate the caller program and overwrite the files from current ones to updated ones.
Fifth, the "Updater
" program will force to restart the caller program and send the update datetime string
to the caller program.
Lastly, in starting the caller program, it is putting an update datetime string
into an updated log file. and runs itself.
Background
If you read the post named "Simple FTP Uploader Using Zip File For Software Update", first, you know updated files are in a zip file named "UpdateArchive.zip" and "UpdateList.xml" file. Each file's relative path is already mentioned in this XML file.
We will make a DLL project which is doing what I mentioned above from first to third.
Then, we will make a caller program using the function which is made in the DLL.
In the next step, we will make an updater project.
Using the Code
This code below is the source in DLL project.
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.IO.Pipes;
using System.Net;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml;
namespace UpdateLib
{
public class SWUpdateManager
{
private static SWUpdateManager instance = null;
private static object syncRoot = new Object();
public static SWUpdateManager GetInstance
{
get
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
instance = new SWUpdateManager();
}
}
return instance;
}
}
private String mLastUpdateDateTime;
public String LastUpdateDateTime
{
get
{
if (String.IsNullOrEmpty(mLastUpdateDateTime))
{
String strUpdateLogFilePath = String.Format
("{0}\\SWUpdate\\SWUpdate.log", Application.StartupPath);
try
{
String[] strLines = File.ReadAllLines(strUpdateLogFilePath);
for (int i = strLines.Length - 1; i >= 0; i--)
{
if (!String.IsNullOrEmpty(strLines[i]))
{
mLastUpdateDateTime = strLines[i];
break;
}
}
}
catch (Exception){ }
}
return mLastUpdateDateTime;
}
set
{
mLastUpdateDateTime = value;
}
}
private ManualResetEvent mEventCanReceive;
private PipeClient mPipeClient;
private Thread mThreadSWUpdate;
private SWUpdateManager()
{
mEventCanReceive = new ManualResetEvent(true);
mPipeClient = null;
mThreadSWUpdate = null;
}
public void StartCheckNewVersion()
{
StopCheckNewVersion();
ThreadStart ts = new ThreadStart(threadMain);
mThreadSWUpdate = new Thread(ts);
mThreadSWUpdate.Name = "Checking Update Version Thread";
mThreadSWUpdate.Start();
}
public void StopCheckNewVersion()
{
if (mThreadSWUpdate != null)
{
mThreadSWUpdate.Abort();
mThreadSWUpdate = null;
}
}
private int LastIndexFromBytes(List<byte> srcBuffer, byte[] patternArray)
{
int idxResult = -1;
int nLen = -1;
if (patternArray != null)
{
nLen = patternArray.Length;
if (nLen > 0 &&
srcBuffer.Count >= nLen)
{
for (int i = srcBuffer.Count - nLen; i >= 0 ; i--)
{
byte[] byteExtractFromBuffer = srcBuffer.Skip(i).Take(nLen).ToArray();
if (byteExtractFromBuffer.SequenceEqual(patternArray))
{
idxResult = i;
break;
}
}
}
}
return idxResult;
}
public void ServerUpdateFileCheck()
{
if (mEventCanReceive.WaitOne(1))
{
try
{
String strUrl = "[ftp server address]";
String strDirectory = [key directory what I mentioned in my previous post];
String strFullDirectory = "";
if (String.IsNullOrEmpty(strUrl) || String.IsNullOrEmpty(strDirectory))
return;
strUrl = strUrl.Replace("\\", "/");
if (!strUrl.EndsWith("/")) strUrl += "/";
strDirectory = strDirectory.Replace("\\", "/");
if (!strDirectory.EndsWith("/")) strDirectory += "/";
strFullDirectory = String.Format("{0}SWUpdate/{1}", strUrl, strDirectory);
byte[] byteOrderMarkUtf8 = Encoding.UTF8.GetPreamble();
String strResultData = "";
using (WebClient webClient = new WebClient())
{
webClient.Headers.Add("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2;
.NET CLR 1.0.3705;)");
Stream base_stream = webClient.OpenRead(
String.Format("{0}UpdateList.xml", strFullDirectory));
StreamReader reader = new StreamReader(base_stream, Encoding.UTF8);
strResultData = reader.ReadToEnd();
List<byte> lstBytesResultData = Encoding.UTF8.GetBytes
(strResultData).ToList();
int nStartIdx = IndexFromBytes(lstBytesResultData, byteOrderMarkUtf8);
if (nStartIdx != -1)
{
byte[] bytesResultData = lstBytesResultData.Skip
(nStartIdx + byteOrderMarkUtf8.Length).ToArray();
strResultData = Encoding.UTF8.GetString(bytesResultData);
}
}
if (!String.IsNullOrEmpty(strResultData))
{
XmlDocument xDoc = new XmlDocument();
xDoc.LoadXml(strResultData);
if (xDoc != null)
{
XmlNode xRootNode = xDoc.DocumentElement;
XmlNamespaceManager ns = new XmlNamespaceManager(xDoc.NameTable);
if (xRootNode.Attributes != null &&
xRootNode.Attributes["update"] != null)
{
String strRoot = Application.StartupPath;
if (!strRoot.EndsWith("\\")) strRoot += "\\";
String strUpdate = xRootNode.Attributes["update"].Value;
String strExtractRootPath =
String.Format("{0}\\SWUpdate\\{1}", strRoot, strUpdate);
bool shouldUpdate = true;
String strUpdateLogFilePath =
String.Format("{0}\\SWUpdate\\SWUpdate.log",
Application.StartupPath);
try
{
String strline;
using (StreamReader sr =
new StreamReader(strUpdateLogFilePath, Encoding.UTF8))
{
while ((strline = sr.ReadLine()) != null)
{
if (strline.Equals(strUpdate,
StringComparison.OrdinalIgnoreCase))
{
shouldUpdate = false;
break;
}
}
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
if (shouldUpdate)
{
if (Directory.Exists(strExtractRootPath))
{
DirectoryInfo dir = new DirectoryInfo
(strExtractRootPath);
FileInfo[] files = dir.GetFiles("*.*",
SearchOption.AllDirectories);
foreach (System.IO.FileInfo file in files)
file.Attributes = FileAttributes.Normal;
Directory.Delete(strExtractRootPath, true);
}
Directory.CreateDirectory(strExtractRootPath);
WebClient downloadWebClient = new WebClient();
downloadWebClient.Headers.Add("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0;
Windows NT 5.2; .NET CLR 1.0.3705;)");
downloadWebClient.DownloadFileCompleted +=
webClient_DownloadFileCompleted;
String strDownloadFilePath = String.Format
("{0}\\UpdateArchive.zip", strExtractRootPath);
downloadWebClient.QueryString.Add("updateTime", strUpdate);
downloadWebClient.DownloadFileAsync(
new Uri(String.Format("{0}UpdateArchive.zip",
strFullDirectory)),
strDownloadFilePath
);
}
else
{
Debug.WriteLine("There's no file to update");
}
}
}
else
{
Debug.WriteLine("Xml fail");
}
}
CheckFileDate(DateTime.Now);
Debug.WriteLine("Complete checking");
}
catch (Exception e)
{
Debug.WriteLine(e.ToString());
}
}
else
{
Debug.WriteLine("Wait until previous work's done");
}
}
private void UpdateProc(String strExtractTime)
{
try
{
String strFilePath = String.Format("{0}\\{1}.exe",
Application.StartupPath, "Updater");
Process.Start(strFilePath);
Thread.Sleep(1000);
mPipeClient = new PipeClient();
Process pc = Process.GetCurrentProcess();
String strFileName = pc.ProcessName;
int nProcId = pc.Id;
String strPipeMsg = String.Format("UPDATE||{0}||{1}||{2}",
strFileName, nProcId, strExtractTime);
mPipeClient.Send(
strPipeMsg, "Updater");
}
catch (IOException ex)
{
Debug.WriteLine(ex.ToString());
}
}
private void webClient_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
Debug.WriteLine("Completing downloading");
try
{
String strRoot = Application.StartupPath;
String strExtractTime =
((System.Net.WebClient)(sender)).QueryString["updateTime"];
String strUpdateArchivePath = String.Format(
"{0}\\SWUpdate\\{1}\\UpdateArchive.zip",
strRoot,
strExtractTime);
String strExtractRootPath = Path.GetDirectoryName(strUpdateArchivePath);
String strNewFilesPath = strExtractRootPath + "\\NEW";
if (!Directory.Exists(strNewFilesPath))
Directory.CreateDirectory(strNewFilesPath);
String strBackupFilesPath = strExtractRootPath + "\\BACKUP";
ArrayList aryArchiveList = new ArrayList();
mEventCanReceive.Reset();
using (ZipArchive archive = ZipFile.Open
(strUpdateArchivePath, ZipArchiveMode.Update))
{
archive.ExtractToDirectory(strNewFilesPath);
foreach (ZipArchiveEntry file in archive.Entries)
aryArchiveList.Add(file.FullName);
}
mEventCanReceive.Set();
foreach (String strfile in aryArchiveList)
{
string curFilePath = Path.Combine(strRoot, strfile);
if (File.Exists(curFilePath))
{
string copyToPath = String.Format("{0}\\{1}",
strBackupFilesPath, strfile);
string copyToDirPath = Path.GetDirectoryName(copyToPath);
if (!Directory.Exists(copyToDirPath))
Directory.CreateDirectory(copyToDirPath);
File.Copy(curFilePath, copyToPath);
}
}
UpdateProc(strExtractTime);
}
catch(Exception ex)
{
Debug.WriteLine(ex.ToString());
}
}
private void threadMain()
{
Thread.Sleep(5000);
Debug.WriteLine(String.Format("'{0}' Start", Thread.CurrentThread.Name));
bool bAbort = false;
while (!bAbort)
{
try
{
ServerUpdateFileCheck();
for (int i = 0; i < 30; i++) Thread.Sleep(60 * 1000);
}
catch (ThreadAbortException)
{
bAbort = true;
Thread.ResetAbort();
}
catch (System.Exception ex)
{
Debug.WriteLine(ex.ToString());
for (int i = 0; i < 60; i++) Thread.Sleep(60 * 1000);
}
}
Debug.WriteLine(String.Format("'{0}' Ended", Thread.CurrentThread.Name));
}
private void CheckFileDate(DateTime curDate)
{
String strCurDir = String.Format("{0}\\SWUpdate\\", Application.StartupPath);
if (!Directory.Exists(strCurDir)) Directory.CreateDirectory(strCurDir);
String[] dirPaths = Directory.GetDirectories(strCurDir);
if (dirPaths.Length <= 0) return;
foreach (String strDir in dirPaths)
{
DirectoryInfo dir = new DirectoryInfo(strDir);
DateTime fileDt;
if (DateTime.TryParseExact(dir.Name, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.None, out fileDt))
{
TimeSpan ts = curDate.Subtract(fileDt);
if ((int)ts.TotalDays > 1)
{
try
{
FileInfo[] files = dir.GetFiles("*.*", SearchOption.AllDirectories);
foreach (System.IO.FileInfo file in files)
file.Attributes = FileAttributes.Normal;
Directory.Delete(strDir, true);
}
catch (Exception e)
{
Debug.WriteLine(e.ToString());
}
}
}
}
}
}
public class PipeClient
{
public void Send(string SendStr, string PipeName, int TimeOut = 1000)
{
try
{
NamedPipeClientStream pipeStream = new NamedPipeClientStream(".", PipeName, PipeDirection.Out, PipeOptions.Asynchronous);
pipeStream.Connect(TimeOut);
Debug.WriteLine(String.Format("'{0}' connected", PipeName));
byte[] _buffer = Encoding.UTF8.GetBytes(SendStr);
pipeStream.BeginWrite(_buffer, 0, _buffer.Length, AsyncSend, pipeStream);
}
catch (Exception oEX)
{
Debug.WriteLine(oEX.ToString());
}
}
private void AsyncSend(IAsyncResult iar)
{
try
{
NamedPipeClientStream pipeStream = (NamedPipeClientStream)iar.AsyncState;
pipeStream.EndWrite(iar);
pipeStream.Flush();
pipeStream.Close();
pipeStream.Dispose();
}
catch (Exception oEX)
{
Debug.WriteLine(oEX.ToString());
}
}
}
}
Then, we should make a winform program and call functions when it roads and is closing.
using UpdateLib;
private void FormMain_Load(object sender, EventArgs e)
{
SWUpdateManager swManager = SWUpdateManager.GetInstance;
swManager.StartCheckNewVersion();
}
private void FormMain_FormClosing(object sender, FormClosingEventArgs e)
{
SWUpdateManager swManager = SWUpdateManager.GetInstance;
swManager.StopCheckNewVersion();
}
You know, you should "Add reference..." and add the DLL project in your winform program.
In the next step, we should make an updater program like this:
using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace UpdaterPg
{
public delegate void PipeMessageDelegate(string message);
public partial class MainWindow : Window
{
private static MainWindow instanceMain = null;
public static void ShowMsg(String strMsg, bool isClear = false)
{
if (instanceMain != null && instanceMain._contentLoaded)
{
Action action = delegate()
{
if (isClear)
instanceMain.txtMsg.Text = "";
instanceMain.txtMsg.Text +=
String.Format("{0}\t{1}\r\n", DateTime.Now.ToString(), strMsg);
};
instanceMain.Dispatcher.Invoke(DispatcherPriority.Normal, action);
}
}
private PipeServer mPipeServer;
public MainWindow()
{
InitializeComponent();
instanceMain = this;
mPipeServer = new PipeServer();
mPipeServer.PipeMessage += new DelegateMessage(PipesMessageHandler);
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
mPipeServer.Listen("Updater");
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
mPipeServer.PipeMessage -= new DelegateMessage(PipesMessageHandler);
}
private void PipesMessageHandler(string message)
{
try
{
Debug.WriteLine(String.Format("'{0}' receive", message));
String strMessege = message.Replace("\0", "");
String[] strProcessInfo = strMessege.Split(new String[] { "||" },
StringSplitOptions.RemoveEmptyEntries);
if (strProcessInfo != null &&
strProcessInfo.Length >= 4)
{
if (strProcessInfo[0].Equals
("UPDATE", StringComparison.OrdinalIgnoreCase))
StartUpdateProcess(strProcessInfo);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
}
private void StartUpdateProcess(String[] strProcessInfo)
{
String strRoot =
System.IO.Path.GetDirectoryName
(Process.GetCurrentProcess().MainModule.FileName);
String strProcName = strProcessInfo[1];
String strProcId = strProcessInfo[2];
String strExtractTime = strProcessInfo[3];
String strExtractDir = String.Format
("{0}\\SWUpdate\\{1}", strRoot, strExtractTime);
int nProcId;
if (Int32.TryParse(strProcId, out nProcId))
{
try
{
this.Dispatcher.BeginInvoke
(new Action(() => { this.WindowState = WindowState.Normal; }));
ShowMsg(
String.Format("start('{0}') update", strProcName), true);
#region kill caller program
Process findProc = Process.GetProcessById(nProcId);
if (findProc != null)
{
findProc.Kill();
Thread.Sleep(1000);
}
#endregion
String strNewPatchDir = String.Format("{0}\\NEW", strExtractDir);
String strCopyToDir = String.Format("{0}\\", strRoot);
Copy(strNewPatchDir, strCopyToDir);
Debug.WriteLine(String.Format("copy from '{0}' to '{1}'",
strNewPatchDir, strCopyToDir));
Thread.Sleep(1000);
#region restart caller program
String strFilePath = String.Format("{0}\\{1}.exe", strRoot, strProcName);
Debug.WriteLine(String.Format("restart '{0}'", strFilePath));
ProcessStartInfo startInfo = new ProcessStartInfo(strFilePath);
startInfo.Arguments = String.Format(
"MSG||{0}||Program restart due to be updated",
strExtractTime);
Process.Start(startInfo);
ShowMsg(
String.Format("Complete ('{0}') update!
Restart program.", strProcName));
Thread.Sleep(500);
this.Dispatcher.BeginInvoke(new Action(() =>
{ this.WindowState = WindowState.Minimized; }));
#endregion
}
catch (Exception e)
{
ShowMsg(e.ToString());
}
}
else
{
Debug.WriteLine(String.Format("Fail process Id : {0}", nProcId));
}
}
private void Copy(string sourceDir, string targetDir)
{
if (!Directory.Exists(targetDir))
{
Directory.CreateDirectory(targetDir);
Debug.WriteLine(String.Format("'{0}' folder created", targetDir));
}
foreach (var file in Directory.GetFiles(sourceDir))
{
File.Copy(file, System.IO.Path.Combine
(targetDir, System.IO.Path.GetFileName(file)), true);
Debug.WriteLine(String.Format("copy from '{0}' to '{1}'", file, targetDir));
}
foreach (var directory in Directory.GetDirectories(sourceDir))
Copy(directory, System.IO.Path.Combine(targetDir,
System.IO.Path.GetFileName(directory)));
}
}
}
As you've already seen, this project is made by WPF solution. It doesn't matter if you want to make a winform, you just do it.
Finally, when you follow this far, there is only one step to complete. Look at it like this:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.OleDb;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
using UpdateLib;
namespace CallerProgram
{
static class Program
{
[STAThread]
static void Main(String[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
if(args != null)
{
foreach(string strArg in args)
{
if (strArg.StartsWith("MSG||"))
{
String[] strUpdates = strArg.Split
(new String[] { "||" }, StringSplitOptions.RemoveEmptyEntries);
String strUpdateTime = "";
String strUpdateMsg = "";
if(strUpdates.Length >= 2)
{
strUpdateTime = strUpdates[1];
strUpdateMsg = strUpdates[2];
String strUpdateLogFilePath =
String.Format("{0}\\SWUpdate\\SWUpdate.log",
Application.StartupPath);
try
{
FileStream ufs = new FileStream
(strUpdateLogFilePath, FileMode.OpenOrCreate,
FileAccess.Write);
StreamWriter sw = new StreamWriter(ufs, Encoding.UTF8);
sw.BaseStream.Seek(0, SeekOrigin.End);
sw.WriteLine(strUpdateTime);
sw.Close();
sw.Dispose();
ufs.Close();
ufs.Dispose();
}
catch(Exception ex)
{
Debug.WriteLine(ex.ToString());
}
SWUpdateManager swManager = SWUpdateManager.GetInstance;
swManager.LastUpdateDateTime = strUpdateTime;
}
else
{
Debug.WriteLine(strArg);
}
}
else
{
Debug.WriteLine(strArg);
}
}
}
Application.Run(new FormMain());
}
}
}
This code must be in your main function in the caller program.
History
- 4/22/2019: First update
- 4/23/2019: Add the codes removing utf-8 bom
- 5/9/2019: Add a missing function named 'CheckFileDate'
- 5/15/2019: Add a missing class named 'PipeClient'