程序自动升级

升级整体流程

  • 双击主程序(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)。主程序检测到更新后,根据下载链接下载压缩包到临时目录,解压后拷贝到指定目录。

posted @ 2019-12-31 21:00  wesson2019  阅读(804)  评论(0编辑  收藏  举报