Winform自動更新程式
阿新 • • 發佈:2021-01-12
本文采用自動更新策略為“訪問Web站點獲取最新版本,下載更新檔案壓縮包,並控制解壓覆蓋舊檔案完成更新”。
注意事項:
因為是覆蓋檔案進行更新,故更新程式和主程式不能有關聯
- 不能引用相同的DLL檔案(覆蓋安裝會告知檔案佔用無法覆蓋)
- 更新過程中主程式不能在啟動狀態(理由同上)
思路:
- 部署一個Web站點,用於判斷更新資訊和下載更新檔案
- 主程式和更新程式啟動時,訪問Web站點獲取版本號並和主程式版本進行對比,不一致則提醒更新
建立專案
專案資源如下圖:
ps:主程式和更新程式不能共用更新處理類,故各放了1個。
主程式-Main程式碼:
using System; using System.Windows.Forms;View Codenamespace WinFormUpdaterDemo { public partial class Main : Form { public Main() { InitializeComponent(); } private void Main_Load(object sender, EventArgs e) { label1.Text = "程式集版本:" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString() + "\n"; //label1.Text += "檔案版本:" + Application.ProductVersion.ToString() + "\n"; } } }
主程式-更新處理類SoftUpdate.cs:
using System; using System.IO; using System.Net; using System.Reflection; using System.Xml; namespace WinFormUpdaterDemo { /// <summary> /// 更新完成觸發的事件View Code/// </summary> public delegate void UpdateState(); /// <summary> /// 程式更新 /// </summary> public class SoftUpdate { private string download; private const string updateUrl = "http://127.0.0.1:7000/update.xml";//升級配置的XML檔案地址 #region 建構函式 public SoftUpdate() { } /// <summary> /// 程式更新 /// </summary> /// <param name="file">要更新的檔案</param> public SoftUpdate(string file, string softName) { this.LoadFile = file; this.SoftName = softName; } #endregion #region 屬性 private string loadFile; private string newVerson; private string softName; private bool isUpdate; /// <summary> /// 或取是否需要更新 /// </summary> public bool IsUpdate { get { checkUpdate(); return isUpdate; } } /// <summary> /// 要檢查更新的檔案 /// </summary> public string LoadFile { get { return loadFile; } set { loadFile = value; } } /// <summary> /// 程式集新版本 /// </summary> public string NewVerson { get { return newVerson; } } /// <summary> /// 升級的名稱 /// </summary> public string SoftName { get { return softName; } set { softName = value; } } #endregion /// <summary> /// 檢查是否需要更新 /// </summary> public void checkUpdate() { try { WebClient wc = new WebClient(); Stream stream = wc.OpenRead(updateUrl); XmlDocument xmlDoc = new XmlDocument(); xmlDoc.Load(stream); XmlNode list = xmlDoc.SelectSingleNode("Update"); foreach (XmlNode node in list) { if (node.Name == "Soft" && node.Attributes["Name"].Value.ToLower() == SoftName.ToLower()) { foreach (XmlNode xml in node) { if (xml.Name == "Verson") newVerson = xml.InnerText; else download = xml.InnerText; } } } Version ver = new Version(newVerson); Version verson = Assembly.LoadFrom(loadFile).GetName().Version; int tm = verson.CompareTo(ver); if (tm >= 0) isUpdate = false; else isUpdate = true; } catch (Exception ex) { throw new Exception("更新出現錯誤,請確認網路連線無誤後重試!"); } } /// <summary> /// 獲取要更新的檔案 /// </summary> /// <returns></returns> public override string ToString() { return this.loadFile; } } }
主程式入口Program.cs:
using System; using System.Windows.Forms; namespace WinFormUpdaterDemo { static class Program { /// <summary> /// 應用程式的主入口點。 /// </summary> [STAThread] static void Main() { //檢查更新 if (checkUpdateLoad()) { Application.Exit(); return; } Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Main()); } public static bool checkUpdateLoad() { bool result = false; SoftUpdate app = new SoftUpdate(Application.ExecutablePath, "WinFormUpdaterDemo"); try { if (app.IsUpdate && MessageBox.Show("檢查到新版本,是否更新?", "版本檢查", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) { string path = Application.StartupPath; System.Diagnostics.Process process = new System.Diagnostics.Process(); process.StartInfo.FileName = "AutoUpdater.exe"; process.StartInfo.WorkingDirectory = path;//要掉用得exe路徑例如:"C:\windows"; process.StartInfo.CreateNoWindow = true; process.Start(); result = true; } else { result = false; } } catch (Exception ex) { MessageBox.Show(ex.Message); result = false; } return result; } } }View Code
PS:更新程式的生成“輸出位置”最好設定在主程式裡,這樣就省去了每次生成後都要把升級程式檔案複製到主程式目錄裡。
更新程式Updater程式碼:
using ICSharpCode.SharpZipLib.Zip; using System; using System.IO; using System.Windows.Forms; namespace AutoUpdater { public partial class Updater : Form { public Updater() { InitializeComponent(); } private void Updater_Load(object sender, EventArgs e) { richTextBox1.Text += DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " 清除歷史遺留檔案\n"; //清除之前下載來的rar檔案 if (File.Exists(Application.StartupPath + "\\Update_autoUpdate.zip")) { try { File.Delete(Application.StartupPath + "\\Update_autoUpdate.zip"); } catch (Exception) { } } if (Directory.Exists(Application.StartupPath + "\\autoupload")) { try { Directory.Delete(Application.StartupPath + "\\autoupload", true); } catch (Exception) { } } richTextBox1.Text += DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " 檢查服務端是否有新版本程式\n"; //檢查服務端是否有新版本程式 checkUpdate(); timer1.Enabled = true; } SoftUpdate app = new SoftUpdate(Application.ExecutablePath, "WinFormUpdaterDemo"); /// <summary> /// 檢查更新 /// </summary> public void checkUpdate() { app.UpdateFinish += new UpdateState(app_UpdateFinish); try { if (app.IsUpdate) { richTextBox1.Text += DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " 開始下載更新檔案\n"; app.Update(); } else { richTextBox1.Text += DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " 未檢測到新版本\n"; MessageBox.Show("未檢測到新版本!"); Application.Exit(); } } catch (Exception ex) { MessageBox.Show(ex.Message); } } /// <summary> /// 下載更新檔案完成後解壓檔案並覆蓋舊檔案完成更新 /// </summary> void app_UpdateFinish() { //解壓下載後的檔案 string path = app.FinalZipName; if (File.Exists(path)) { //後改的 先解壓濾波zip植入ini然後再重新壓縮 string dirEcgPath = Application.StartupPath + "\\" + "autoupload\\"; if (!Directory.Exists(dirEcgPath)) { Directory.CreateDirectory(dirEcgPath); } richTextBox1.Text += DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " 解壓下載後的檔案:" + path + "\n"; ; //開始解壓壓縮包 UnZIP(path, dirEcgPath); richTextBox1.Text += DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " 解壓成功\n"; ; try { //複製新檔案替換舊檔案 richTextBox1.Text += DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " 更新新檔案(替換舊檔案)\n"; DirectoryInfo TheFolder = new DirectoryInfo(dirEcgPath); foreach (FileInfo NextFile in TheFolder.GetFiles()) { File.Copy(NextFile.FullName, Application.StartupPath + "\\" + NextFile.Name, true); } richTextBox1.Text += DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " 更新成功\n"; richTextBox1.Text += DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " 刪除本次更新的殘留檔案\n"; Directory.Delete(dirEcgPath, true); File.Delete(path); richTextBox1.Text += DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " 刪除成功\n"; //覆蓋完成 重新啟動程式 richTextBox1.Text += DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " 啟動更新後的新程式\n"; path = Application.StartupPath; System.Diagnostics.Process process = new System.Diagnostics.Process(); process.StartInfo.FileName = "WinFormUpdaterDemo.exe"; process.StartInfo.WorkingDirectory = path;//要掉用得exe路徑例如:"C:\windows"; process.StartInfo.CreateNoWindow = true; process.Start(); richTextBox1.Text += DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " 退出更新軟體\n"; //Application.Exit(); } catch (Exception) { MessageBox.Show("請關閉系統在執行更新操作!"); Application.Exit(); } } } /// <summary> /// 定時器(用來更新進度條) /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void timer1_Tick(object sender, EventArgs e) { label1.Text = "下載檔案進度:" + CommonMethod.autostep.ToString() + "%"; this.progressBar1.Value = CommonMethod.autostep; if (CommonMethod.autostep == 100) { timer1.Enabled = false; } } /// <summary> /// 解壓檔案 /// </summary> /// <param name="zipFilePath">壓縮檔案路徑</param> /// <param name="saveDirectory">解壓路徑</param> public static void UnZIP(string zipFilePath, string saveDirectory) { // Perform simple parameter checking. if (!File.Exists(zipFilePath)) { return; } if (!Directory.Exists(saveDirectory)) { //解壓後存放的 資料夾路徑 Directory.CreateDirectory(saveDirectory); } using (ZipInputStream s = new ZipInputStream(File.OpenRead(zipFilePath))) { ZipEntry theEntry; while ((theEntry = s.GetNextEntry()) != null) { string directoryName = Path.GetDirectoryName(theEntry.Name); string fileName = Path.GetFileName(theEntry.Name); // create directory if (directoryName.Length > 0) { //建立目錄 string saveDir = saveDirectory + directoryName; Directory.CreateDirectory(saveDir); } if (fileName != String.Empty) { string saveFilePath = saveDirectory + theEntry.Name; using (FileStream streamWriter = File.Create(saveFilePath)) { int size = 2048; byte[] data = new byte[2048]; while (true) { size = s.Read(data, 0, data.Length); if (size > 0) { streamWriter.Write(data, 0, size); } else { break; } } } } } } } } }View Code
更新程式-更新處理類SoftUpdate.cs:
using System; using System.IO; using System.Net; using System.Reflection; using System.Xml; namespace AutoUpdater { /// <summary> /// 更新完成觸發的事件 /// </summary> public delegate void UpdateState(); /// <summary> /// 程式更新 /// </summary> public class SoftUpdate { private string download; private const string updateUrl = "http://127.0.0.1:7000/update.xml";//升級配置的XML檔案地址 #region 建構函式 public SoftUpdate() { } /// <summary> /// 程式更新 /// </summary> /// <param name="file">要更新的檔案</param> public SoftUpdate(string file, string softName) { this.LoadFile = file; this.SoftName = softName; } #endregion #region 屬性 private string loadFile; private string newVerson; private string softName; private bool isUpdate; /// <summary> /// 或取是否需要更新 /// </summary> public bool IsUpdate { get { checkUpdate(); return isUpdate; } } /// <summary> /// 要檢查更新的檔案 /// </summary> public string LoadFile { get { return loadFile; } set { loadFile = value; } } /// <summary> /// 程式集新版本 /// </summary> public string NewVerson { get { return newVerson; } } /// <summary> /// 升級的名稱 /// </summary> public string SoftName { get { return softName; } set { softName = value; } } private string _finalZipName = string.Empty; public string FinalZipName { get { return _finalZipName; } set { _finalZipName = value; } } #endregion /// <summary> /// 更新完成時觸發的事件 /// </summary> public event UpdateState UpdateFinish; private void isFinish() { if (UpdateFinish != null) UpdateFinish(); } /// <summary> /// 下載更新 /// </summary> public void Update() { try { if (!isUpdate) return; WebClient wc = new WebClient(); string filename = ""; string exten = download.Substring(download.LastIndexOf(".")); if (loadFile.IndexOf(@"\") == -1) filename = "Update_" + Path.GetFileNameWithoutExtension(loadFile) + exten; else filename = Path.GetDirectoryName(loadFile) + "\\Update_" + Path.GetFileNameWithoutExtension(loadFile) + exten; FinalZipName = filename; //wc.DownloadFile(download, filename); wc.DownloadFileAsync(new Uri(download), filename); wc.DownloadProgressChanged += new DownloadProgressChangedEventHandler(wc_DownloadProgressChanged); wc.DownloadFileCompleted += new System.ComponentModel.AsyncCompletedEventHandler(wc_DownloadFileCompleted); //wc.Dispose(); } catch { throw new Exception("更新出現錯誤,網路連線失敗!"); } } void wc_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e) { (sender as WebClient).Dispose(); if (e.Error != null) throw e.Error; else isFinish(); } void wc_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) { CommonMethod.autostep = e.ProgressPercentage; //Thread.Sleep(100); } /// <summary> /// 檢查是否需要更新 /// </summary> public void checkUpdate() { try { WebClient wc = new WebClient(); Stream stream = wc.OpenRead(updateUrl); XmlDocument xmlDoc = new XmlDocument(); xmlDoc.Load(stream); XmlNode list = xmlDoc.SelectSingleNode("Update"); foreach (XmlNode node in list) { if (node.Name == "Soft" && node.Attributes["Name"].Value.ToLower() == SoftName.ToLower()) { foreach (XmlNode xml in node) { if (xml.Name == "Verson") newVerson = xml.InnerText; else download = xml.InnerText; } } } Version ver = new Version(newVerson); Version verson = Assembly.LoadFrom(loadFile).GetName().Version; int tm = verson.CompareTo(ver); if (tm >= 0) isUpdate = false; else isUpdate = true; } catch (Exception ex) { throw new Exception("更新出現錯誤,請確認網路連線無誤後重試!"); } } /// <summary> /// 獲取要更新的檔案 /// </summary> /// <returns></returns> public override string ToString() { return this.loadFile; } } /// <summary> /// 全域性進度條變數 /// </summary> public static class CommonMethod { public static int autostep; } }View Code
Web站點:
update.xml程式碼:
<?xml version="1.0" encoding="utf-8" ?> <Update> <Soft Name="WinFormUpdaterDemo"> <Verson>1.0.0.0</Verson> <DownLoad>http://127.0.0.1:7000/Update_autoUpdate.zip</DownLoad> </Soft> </Update>View Code
每次更新程式做以下操作即可:
- 修改主程式AssemblyInfo.cs中的版本號
- 把最新的程式打包成“Update_autoUpdate.zip”,放入站點。(可進行增量更新,但是暫時無法刪除檔案,可以在XML中加入刪除節點進行刪除檔案操作,後續會優化)
- 修改站點xml檔案版本號,使之和主程式AssemblyInfo.cs中的版本號一致
好啦,這樣自動更新程式就做好啦。
效果演示:
本文參考了很多大佬的分享,主要參考:
https://blog.csdn.net/xiaodong728/article/details/81083239?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.control&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.control