Winform自动更新程序
本文采用自动更新策略为“访问Web站点获取最新版本,下载更新文件压缩包,并控制解压覆盖旧文件完成更新”。
注意事项:
因为是覆盖文件进行更新,故更新程序和主程序不能有关联
- 不能引用相同的DLL文件(覆盖安装会告知文件占用无法覆盖)
- 更新过程中主程序不能在启动状态(理由同上)
思路:
- 部署一个Web站点,用于判断更新信息和下载更新文件
- 主程序和更新程序启动时,访问Web站点获取版本号并和主程序版本进行对比,不一致则提醒更新
建立项目
项目资源如下图:
ps:主程序和更新程序不能共用更新处理类,故各放了1个。
主程序-Main代码:
using System; using System.Windows.Forms; namespace 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> /// 更新完成触发的事件 /// </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); //读取版本信息(直接使用Assembly.Load会导致资源占用,故使用如下方法读取文件,解决资源占用问题) byte[] fileData = File.ReadAllBytes(loadFile); Version verson = Assembly.Load(fileData).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; } } }
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_NewVersion.zip")) { try { File.Delete(Application.StartupPath + "\\Update_NewVersion.zip"); } catch (Exception) { } } if (Directory.Exists(Application.StartupPath + "\\autoupload")) { try { Directory.Delete(Application.StartupPath + "\\autoupload", true); } catch (Exception) { } } timer1.Interval = 200; timer1.Enabled = true; timer1.Start(); richTextBox1.Text += DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " 检查服务端是否有新版本程序\n"; //检查服务端是否有新版本程序 checkUpdate(); } SoftUpdate app = new SoftUpdate(Application.StartupPath, "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); foreach (string delfilepath in app.deleteFiles) { string delfile = Application.StartupPath + "\\" + delfilepath; if (delfilepath.Contains(".")) { //删除文件 if (File.Exists(delfile)) File.Delete(delfile); } else { //删除目录 if (Directory.Exists(delfile)) Directory.Delete(delfile,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"; 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 ex) { 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.Stop(); 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; } } } } } } } } }
更新程序-更新处理类SoftUpdate.cs:
using System; using System.Collections.Generic; 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; public List<string> deleteFiles; 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 + "\\" + softName + ".exe"; 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 = "Update_NewVersion.zip"; filename = Path.GetDirectoryName(loadFile) + "\\" + filename; 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 if (xml.Name == "DownLoad") download = xml.InnerText; else if (xml.Name == "Delete") { deleteFiles = new List<string>(); foreach (XmlNode delfile in xml) { if (delfile.Name == "file") deleteFiles.Add(delfile.InnerText); } } } } } Version ver = new Version(newVerson); //读取版本信息(直接使用Assembly.Load会导致资源占用,故使用如下方法读取文件,解决资源占用问题) byte[] fileData = File.ReadAllBytes(loadFile); Version verson = Assembly.Load(fileData).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; } }
Web站点:
update.xml代码:
<?xml version="1.0" encoding="utf-8" ?> <Update> <Soft Name="WinFormUpdaterDemo"> <Verson>1.0.0.1</Verson> <DownLoad>http://127.0.0.1:7000/Update_NewVersion.zip</DownLoad> <Delete> <file>test.txt</file> <file>test1.txt</file> <file>\test</file> </Delete> </Soft> </Update>
每次更新程序做以下操作即可:
- 修改主程序AssemblyInfo.cs中的版本号;
- 把最新的程序打包成“Update_autoUpdate.zip”,放入站点;(打包程序为覆盖安装,所以可以进行增量更新,减少更新成本)
- 修改站点xml文件版本号,使之和主程序AssemblyInfo.cs中的版本号一致。(XML中可配置要删除的文件,防止产生垃圾文件)
好啦,这样自动更新程序就做好啦。
效果演示:
源码:https://download.csdn.net/download/u012322524/14141624
懒得下源码的,照着上面自己弄一样的,说的应该很详细了。