转自原文C#做的在线升级小程序
日前收到一个小任务,要做一个通用的在线升级程序。更新的内容包括一些dll或exe或、配置文件。升级的大致流程是这样的,从服务器获取一个更新的配置文件,经过核对后如有新的更新,则会从服务器下载相应的文件更新到被升级的程序目录下。如果被升级的程序在升级之前已经启动,程序则会强制关闭它,待到升级完成之后重新启动相应的程序。在升级之前程序会自动备份一次,以防升级失败造成程序不能运行。
首先来的是数据实体
public class FileENT { public string FileFullName { get; set; } public string Src { get; set; } public string Version { get; set; } public int Size { get; set; } public UpdateOption Option { get; set; } }
下面这个类时程序的一些参数,包括了系统的配置参数,为了程序能通用一点,就加了配置上去。
public class AppParameter { /// <summary> /// 备份路径 /// </summary> public static string BackupPath = ConfigurationManager.AppSettings["backupPath"]; /// <summary> /// 更新的URL /// </summary> public static string ServerURL = ConfigurationManager.AppSettings["serverURL"]; /// <summary> /// 本地更新文件全名 /// </summary> public static string LocalUPdateConfig = ConfigurationManager.AppSettings["localUPdateConfig"]; /// <summary> /// 版本号 /// </summary> public static string Version = ConfigurationManager.AppSettings["version"]; /// <summary> /// 更新程序路径 /// </summary> public static string LocalPath = AppDomain.CurrentDomain.BaseDirectory; /// <summary> /// 主程序路径 /// </summary> public static string MainPath = ConfigurationManager.AppSettings["mainPath"]; /// <summary> /// 有否启动主程序 /// </summary> public static bool IsRunning = false; /// <summary> /// 主程序名 /// </summary> public static List<string> AppNames = ConfigurationManager.AppSettings["appName"].Split(';').ToList(); }
接着就介绍程序的代码
程序是用窗体来实现的,下面三个是窗体新添加的三个字段
private bool isDelete = true; //是否要删除升级配置
private bool runningLock = false;//是否正在升级
private Thread thread; //升级的线程
载入窗体时需要检查更新,如果没有更新就提示”暂时无更新”;如果有更新的则先进行备份,备份失败的话提示错误退出更新。
if (CheckUpdate()) { if (!Backup()) { MessageBox.Show("备份失败!"); btnStart.Enabled = false; isDelete = true; return; } } else { MessageBox.Show("暂时无更新"); this.btnFinish.Enabled = true; this.btnStart.Enabled = false; isDelete = false; this.Close(); }
在这些操作之前还要检测被更新程序有否启动,有则将其关闭。
List<string> processNames = new List<string>(); string mainPro = string.Empty; processNames.AddRange(AppParameter.AppNames); for (int i = 0; i < processNames.Count; i++) { processNames[i] = processNames[i].Substring(processNames[i].LastIndexOf('\\')).Trim('\\').Replace(".exe", ""); } mainPro = processNames.FirstOrDefault(); AppParameter.IsRunning = ProcessHelper.IsRunningProcess(mainPro); if (AppParameter.IsRunning) { MessageBox.Show("此操作需要关闭要更新的程序,请保存相关数据按确定继续", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); foreach (string item in processNames) ProcessHelper.CloseProcess(item); }
另外上面用到的CheckUpdate( )和Backup( )方法如下
/// <summary> /// 检查更新 有则提示用户 确认后下载新的更新配置 /// </summary> /// <returns>用户确认信息</returns> public static bool CheckUpdate() { bool result = false; HttpHelper.DownLoadFile(AppParameter.ServerURL, AppParameter.LocalPath + "temp_config.xml"); if (!File.Exists(AppParameter.LocalUPdateConfig)) result = true; else { long localSize = new FileInfo(AppParameter.LocalUPdateConfig).Length; long tempSize = new FileInfo(AppParameter.LocalPath + "temp_config.xml").Length; if (localSize >= tempSize) result = false; else result = true; } if (result) { if (File.Exists(AppParameter.LocalUPdateConfig)) File.Delete(AppParameter.LocalUPdateConfig); File.Copy(AppParameter.LocalPath + "temp_config.xml", AppParameter.LocalUPdateConfig); } else result = false; File.Delete(AppParameter.LocalPath + "temp_config.xml"); return result; } /// <summary> /// 备份 /// </summary> public static bool Backup() { string sourcePath = Path.Combine(AppParameter.BackupPath, DateTime.Now.ToString("yyyy-MM-dd HH_mm_ss")+"_v_"+AppParameter.Version + ".rar"); return ZipHelper.Zip(AppParameter.MainPath.Trim() , sourcePath); }
下面则是更新部分的代码,使用了多线程。出于两方面的考虑,一是进度条需要;二是如果用单线程,万一更新文件下载时间过长或者更新内容过多,界面会卡死。
/// <summary> /// 更新 /// </summary> public void UpdateApp() { int successCount = 0; int failCount = 0; int itemIndex = 0; List<FileENT> list = ConfigHelper.GetUpdateList(); if (list.Count == 0) { MessageBox.Show("版本已是最新", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); this.btnFinish.Enabled = true; this.btnStart.Enabled = false; isDelete = false; this.Close(); return; } thread = new Thread(new ThreadStart(delegate { #region thread method FileENT ent = null; while (true) { lock (this) { if (itemIndex >= list.Count) break; ent = list[itemIndex]; string msg = string.Empty; if (ExecUpdateItem(ent)) { msg = ent.FileFullName + "更新成功"; successCount++; } else { msg = ent.FileFullName + "更新失败"; failCount++; } if (this.InvokeRequired) { this.Invoke((Action)delegate() { listBox1.Items.Add(msg); int val = (int)Math.Ceiling(1f / list.Count * 100); progressBar1.Value = progressBar1.Value + val > 100 ? 100 : progressBar1.Value + val; }); } itemIndex++; if (successCount + failCount == list.Count && this.InvokeRequired) { string finishMessage = string.Empty; if (this.InvokeRequired) { this.Invoke((Action)delegate() { btnFinish.Enabled = true; }); } isDelete = failCount != 0; if (!isDelete) { AppParameter.Version = list.Last().Version; ConfigHelper.UpdateAppConfig("version", AppParameter.Version); finishMessage = "升级完成,程序已成功升级到" + AppParameter.Version; } else finishMessage = "升级完成,但不成功"; MessageBox.Show(finishMessage, "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); runningLock = false; } } } #endregion })); runningLock = true; thread.Start(); } /// <summary> /// 执行单个更新 /// </summary> /// <param name="ent"></param> /// <returns></returns> public bool ExecUpdateItem(FileENT ent) { bool result = true; try { if (ent.Option == UpdateOption.del) File.Delete(ent.FileFullName); else HttpHelper.DownLoadFile(ent.Src, Path.Combine(AppParameter.MainPath, ent.FileFullName)); } catch { result = false; } return result; }
只开了一个子线程,原本是开了5个子线程的,但是考虑到多线程会导致下载文件的顺序不确定,还是用回单线程会比较安全。线程是用了窗体实例里的thread字段,在开启线程时还用到runningLock标识字段,表示当前正在更新。当正在更新程序时关闭窗口,则要提问用户是否结束更新,若用户选择了是则要结束那个更新进程thread了,下面则是窗口关闭的时间FormClosing事件的方法。
1 if (runningLock )
2 {
3 if (MessageBox.Show("升级还在进行中,中断升级会导致程序不可用,是否中断",
4 "提示", MessageBoxButtons.YesNo, MessageBoxIcon.Asterisk) == DialogResult.Yes)
5 {
6 if (thread != null) thread.Abort();
7 isDelete = true;
8 AppParameter.IsRunning = false;
9 }
10 else
11 {
12 e.Cancel = true;
13 return;
14 }
15 }
16 if (isDelete) File.Delete(AppParameter.LocalUPdateConfig);
在这里还要做另一件事,就是把之前关了的程序重新启动。
1 try
2 {
3 if (AppParameter.IsRunning) ProcessHelper.StartProcess(AppParameter.AppNames.First());
4 }
5 catch (Exception ex)
6 {
7
8 MessageBox.Show("程序无法启动!" + ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
9 }
在这里展示一下更新的界面。挺丑的,别笑哈。
更新程序的配置信息如下
1 <appSettings>
2 <add key="backupPath" value="C:\Users\Administrator\Desktop\temp\backup"/>
3 <add key="serverURL" value="http://localhost:8015/updateconfig.xml"/>
4 <add key="localUPdateConfig" value="E:\HopeGi\Code\MyUpdate\MyUpdate\bin\Debug\updateconfig.xml"/>
5 <add key="version" value="2"/>
6 <add key="mainPath" value="C:\Users\Administrator\Desktop\temp\main"/>
7 <add key="appName" value="D:\test.exe"/>
8 </appSettings>
完整文档下载:http://pan.baidu.com/share/link?shareid=443378&uk=85241834