程序自动升级
升级整体流程
- 双击主程序(MainWindow.exe),先确定是否升级(升级程序、主程序的版本检查),再定期检查(比如3分钟);
- 升级程序(AutoUpdate.exe)(与主程序在同级目录下)若更新,直接下载最新并覆盖;
- 主程序若更新,下载xml升级信息,打开升级程序并关闭主程序,进入升级流程;
- 升级主流程:查看Cache里的dll、zip文件,若存在需要的文件就不下载;否则根据传递的参数信息下载相应文件;若需要就解压zip,并更新主程序文件;
- 升级完毕,重新打开主程序。
主程序
private void Window_Loaded(object sender, RoutedEventArgs e)
{
if (!File.Exists(UPGRADE_NAME))
{
MessageBox.Show("缺少自动升级程序");
Environment.Exit(0);
}
// 先确定是否升级
UpgradeProcess();
// 定期检查(3分钟)
_timer = new System.Timers.Timer(1000 * 60 * 3);
_timer.Elapsed += _timer_Elapsed;
_timer.Start();
}
private static int _nTimer = 0;
private void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (Interlocked.Exchange(ref _nTimer, 1) == 0)
{
Task.Run(() =>
{
UpgradeProcess();
});
Interlocked.Exchange(ref _nTimer, 0);
}
}
private void UpgradeProcess()
{
CheckUpgraderInfo();
CheckMainerInfo();
}
/// <summary>
/// 检查升级程序是否需要更新
/// </summary>
private void CheckUpgraderInfo()
{
// 获取服务器上升级程序的md5与下载地址
string newMd5 = "";
string newUrl = "";
string curMd5 = GetMD5(UPGRADE_NAME);
if (curMd5 != newMd5)
{
using (WebClient client = new WebClient())
{
client.DownloadFileAsync(new Uri(newUrl), UPGRADE_NAME);
Console.WriteLine("Download new \"{0}\"......{1}", UPGRADE_NAME, DateTime.Now);
}
}
}
/// <summary>
/// 主程序版本升级检查
/// </summary>
private void CheckMainerInfo()
{
// 比较版本信息,确定是否升级
// 若升级,获取xml流
string newUrl = "";
using (WebClient client = new WebClient())
{
client.DownloadStringCompleted += XmlDownCompleted;
client.DownloadStringAsync(new Uri(newUrl));
}
}
private void XmlDownCompleted(object sender, DownloadStringCompletedEventArgs e)
{
string xml = e.Result;
// 启动路径
string callbackExe = AppDomain.CurrentDomain.SetupInformation.ApplicationBase + AppDomain.CurrentDomain.SetupInformation.ApplicationName;
// 关闭主程序,用升级程序下载
try
{
// 若升级程序已经打开,不进行本次升级
string progressName = Path.GetFileNameWithoutExtension(AppDomain.CurrentDomain.BaseDirectory + UPGRADE_NAME);
if (Process.GetProcessesByName(progressName).Any())
{
return;
}
Process p = new Process();
p.StartInfo.FileName = UPGRADE_NAME;
p.StartInfo.Arguments = string.Join(" ", new string[] { xml, callbackExe });
p.Start();
}
catch (Exception ex)
{
throw ex;
}
System.Environment.Exit(0);
}
升级程序
public UpgradeWindow(string remote, List<VerFileInfo> obj)
{
InitializeComponent();
try
{
_callBackExeName = Application.Current.Properties["exe"].ToString();
_callBackDir = Path.GetDirectoryName(_callBackExeName);
_callBackDir = _callBackDir.EndsWith(@"\") ? _callBackDir : _callBackDir + @"\";
_fileSum = obj.Count;
_fileCnt = 0;
_timer = new System.Timers.Timer();
_timer.Interval = 1000;
_timer.Elapsed += _timer_Elapsed;
_timer.Start();
Task.Run(() =>
{
DownloadAsync();
});
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString(), "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private static int _nTimer = 0;
private void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (Interlocked.Exchange(ref _nTimer, 1) == 0)
{
updateProgressBar();
Interlocked.Exchange(ref _nTimer, 0);
}
}
ConcurrentQueue<VerFileInfo> _queue = new ConcurrentQueue<VerFileInfo>();
private void DownloadAsync()
{
CheckCacheInfo();
// 若不需要下载,直接用Cache里的文件升级主程序
if (obj.Count == 0)
{
UpdateConfigInfo();
return;
}
foreach (var item in obj)
{
_queue.Enqueue(item);
}
this.Dispatcher.Invoke(() =>
{
lbInfo.Text = $"{_prefixTitle} 正在更新第{_fileSum}/{_fileCnt + 1}{Properties.Resources.Updateing_string3}";
});
Download();
}
/// <summary>
/// 查看Cache里的信息,若存在就不下载
/// </summary>
private void CheckCacheInfo()
{
if (!Directory.Exists(_callBackDir + CACHE_DIR))
{
Directory.CreateDirectory(_callBackDir + CACHE_DIR);
return;
}
foreach (var item in Directory.GetFiles(_callBackDir + CACHE_DIR))
{
string fileName = Path.GetFileName(item);
var info = obj.Find(x => x.FileName == fileName);
if (info == null)
{
// 删除无用文件
File.Delete(item);
}
else
{
var md5 = WSCommFunc.GetMD5(item);
if (info.MD5 == md5)
{
// 已下载,不用重复下载
obj.Remove(info);
}
else
{
// 删除已损坏或无用文件
File.Delete(item);
}
}
}
}
private void Download()
{
VerFileInfo item;
if (_queue.TryDequeue(out item))
{
this.Dispatcher.Invoke(() =>
{
lbFileInfo.Text = item.FileName;
});
try
{
using (WebClient client = new WebClient())
{
client.DownloadProgressChanged += client_DownloadProgressChanged;
client.DownloadFileCompleted += client_DownloadFileCompleted;
string url = item.URL;
client.DownloadFileAsync(new Uri(url), $"{_callBackDir}{CACHE_DIR}{item.FileName}");
}
}
catch (Exception ex)
when (ex.GetType().Name == "WebException")
{
WebException we = (WebException)ex;
using (HttpWebResponse hr = (HttpWebResponse)we.Response)
{
int statusCode = (int)hr.StatusCode;
StringBuilder sb = new StringBuilder();
StreamReader sr = new StreamReader(hr.GetResponseStream(), Encoding.UTF8);
sb.Append(sr.ReadToEnd());
WSCommFunc.WriteLog(_logFile, string.Format("StatusCode:{0},Content: {1}", statusCode, sb));
lbFileInfo.Text = "下载出现异常!请重新下载或联系管理员";
}
}
}
}
private void client_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
if (e.Cancelled)
{
return;
}
this.Dispatcher.Invoke(() =>
{
Interlocked.Increment(ref _fileCnt);
if (_fileCnt == _fileSum)
{
lbInfo.Text = "下载完成!";
lbFileInfo.Text = string.Empty;
lbSizeInfo.Visibility = Visibility.Hidden;
}
else
{
lbInfo.Text = $"{Properties.Resources.Updateing_string1}{_fileSum}{Properties.Resources.Updateing_string2}{_fileCnt + 1}{Properties.Resources.Updateing_string3}";
Download();
}
});
}
private void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
long iTotalSize = e.TotalBytesToReceive;
long iSize = e.BytesReceived;
this.Dispatcher.Invoke(() =>
{
lbSizeInfo.Visibility = Visibility.Visible;
lbSizeInfo.Text = string.Format("文件大小总共 {1} KB, 当前已接收 {0} KB", (iSize / 1024), (iTotalSize / 1024));
/*单个文件进度
probar.Visibility = Visibility.Visible;
probar.Value = Convert.ToDouble(iSize) / Convert.ToDouble(iTotalSize) * 100;
if (probar.Value >= 100)
{
_timer.Close();
UpdateConfigInfo();
}*/
});
}
private void updateProgressBar()
{
this.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
(ThreadStart)delegate ()
{
probar.Visibility = Visibility.Visible;
probar.Value = Convert.ToDouble(_fileCnt) / Convert.ToDouble(_fileSum) * 100;
if (probar.Value >= 100)
{
_timer.Close();
UpdateConfigInfo();
}
});
}
private void UpdateConfigInfo()
{
// 检测主程序是否执行
string progressName = Path.GetFileNameWithoutExtension(_callBackExeName);
var p = Process.GetProcessesByName(progressName);
if (p.Any())
{
if (MessageBox.Show("请关闭主程序进行自动升级!", "软件升级", MessageBoxButton.OK, MessageBoxImage.Warning) == MessageBoxResult.OK)
{
foreach (var item in p)
{
item.Kill();
}
}
}
UpdateMainer();
}
/// <summary>
/// 更新主程序文件
/// </summary>
private void UpdateMainer()
{
foreach (var item in Directory.GetFiles(_callBackDir + CACHE_DIR))
{
if (item.EndsWith(".zip"))
{
string tempStr = DateTime.Now.ToString("yyyyMMddHHmmssfff");
string tempDir = Path.GetTempPath() + tempStr;
Console.WriteLine();
WSCommFunc.WriteLog(_logFile, "TempDir: " + tempDir);
ZipArchiveHelper.UnZip(item, tempDir);
ZipArchiveHelper.CopyDirectory(tempDir, _callBackDir);
// 更新后删除zip与临时文件夹数据
Task.Run(() =>
{
File.Delete(item);
if (tempDir.EndsWith(tempStr))
{
ZipArchiveHelper.DeleteFolder(tempDir);
}
});
}
}
Process process = new Process();
process.StartInfo.FileName = _callBackExeName;
process.Start();
System.Environment.Exit(0);
}
生成升级信息
// 文件与目录同级
private const string Server_VERSION_FILE = "server_version.xml";
private const string UpdateFile1 = "Release";
static void Main(string[] args)
{
bool bConfigure = false;
DirectoryInfo dir = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory);
foreach (var item in dir.GetDirectories())
{
if (item.Name.Contains(UpdateFile1))
{
bConfigure = true;
UpdateVersionInfo(item);
}
}
if (true == bConfigure)
{
Console.WriteLine("\n\n更新文件生成成功,5s后退出!");
}
else
{
Console.WriteLine("未找到需要配置的目录!");
}
Thread.Sleep(5000);
}
private static void UpdateVersionInfo(DirectoryInfo item)
{
var file = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Server_VERSION_FILE);
if (File.Exists(file))
{
File.Delete(file);
}
var info = CreateVersionInfo(item);
VersionInfo version = new VersionInfo()
{
AppVersion = DateTime.Now.ToString("yyyyMMddHHmmssfff"),
AppInfo = info,
};
string xml = XmlHelper.Serialize(version);
XmlHelper.WriteXmlString(file, xml);
}
/// <summary>
/// 同级下更新指定目录内的文件信息
/// </summary>
private static List<VerFileInfo> CreateVersionInfo(DirectoryInfo dir)
{
List<VerFileInfo> rslt = new List<VerFileInfo>();
foreach (var item in dir.GetFiles())
{
rslt.Add(new VerFileInfo()
{
FileDir = item.DirectoryName,
FileName = item.Name,
MD5 = WSCommFunc.GetMD5(item.FullName),
URL = "",
});
Console.WriteLine("正在创建\"{0}\"的更新配置", item.FullName);
}
foreach (var subDir in dir.GetDirectories())
{
if (subDir.FullName.EndsWith("Logs"))
{
continue;
}
rslt.AddRange(CreateVersionInfo(subDir));
}
return rslt;
}
一些细节
-
xml升级信息 XmlHelper
服务端的升级信息,可由单独的程序(比如:UpdateListBuilder.exe)自动生成xml。
-
压缩、解压缩zip文件 ZipArchive
using System.IO.Compression;
操作zip存档及其文件条目的方法分布于三个类: ZipFile、 ZipArchive和ZipArchiveEntry。 -
管理员权限
在win7以后,如果应用位置在C盘的话,每次操作目录都会申请管理员权限。 -
注册表修改
有些时候程序部分信息是记录在注册表里,也要支持对注册表的操作。
升级方案 2
如果待升级的模块与主模块相互独立,可以简化流程,不使用升级程序(AutoUpdate.exe)。主程序检测到更新后,根据下载链接下载压缩包到临时目录,解压后拷贝到指定目录。