1. 程式人生 > >winform自動更新程序實現

winform自動更新程序實現

覆蓋 win7 流程 win mar 現在 OS mes pre

一、問題背景

  本地程序在實際項目使用過程中,因為可以操作電腦本地的一些信息,並且對於串口、OPC、並口等數據可以方便的進行收發,雖然現在軟件行業看著動不動都是互聯網啊啥的,大有Web服務就是高大上的感覺,但是作為本地的應用還是有著非常重要的位置,特別是在制造業工廠裏,車間裏相關的程序。

  拋開一切業務上的功能不談,本地程序一直比較詬病的地方就是在於軟件的更新上,由於程序都在客戶端電腦上運行,當需要更新的時候,就不得不由專門的實施人員過去,部署更新,無形中增加項目成本,SO,對於c/s程序的自動更新也是比較苦惱的問題,下面我就來稍微解析下,一個自動更新程序應該要怎麽實現(PS:思路可能比較傳統,歡迎大家拍磚提供更好的思路

二、自動更新的關註點

  技術分享圖片

  如圖所示,對於一個自動更新程序,關註點應該都是以上幾個點

  • 管理員權限,在win7以後,如果應用位置在C盤的話,每次操作目錄都會申請管理員權限,emmmm,所以這個必須要考慮
  • 對於要實現一個較為通用的自動更新,應該要安裝了.NetFrameWork的都要可以使用,並且方便使用
  • 更新程序同時要只能啟動一個,不然肯定出事兒,雖然很少有會有人去點2次,但是還是要考慮
  • 界面要求上,更新說明以及進度條要顯示
  • 很多時候可能我們也是需要一個靜默更新的操作
  • 運行更新的時候,記得要關閉運行的程序,不然肯定更新失敗
  • 對於更新失敗,得有完善的回滾以及備份機制
  • 更新成功後,得可以啟動對應的主程序
  • 有些 時候程序部分信息是記錄在註冊表裏,如果註冊表要修改咋辦呢,so,對於註冊表也得要支持
  • 有些時候程序更新到後面,會出現一些多余的DLL,這些DLL那也是要幹掉滴(雖然覺得有點雞肋)

  大概就是以上的一些點,這些是我自己思考的時候羅列出來的,可能比較亂,大家明白就好

三、設計說明

  技術分享圖片

  更新程序主要流程如圖所示,大的流程方向上是比較簡單的,但是如果深入後,還是有部分會比較復雜

技術分享圖片

  程序類的一些簡單說明

  config.update:註冊表的增刪規則以及文件的刪除規則,如下規則所示

[regedit_del] //刪除註冊表
SOFTWARE\\XXX\\XXX,name
[regedit_add]//新增註冊表
SOFTWARE\\XXX\\XXXX,name=John Doe 
[file_del] //刪除文件
hello.dll

  Server.xml&RemoteInfo.cs:服務端的版本配置文件信息

  Local.xml&LocalInfo.cs:客戶端的版本配置文件信息

  UpdateWork.cs:核心的更新方法,為了方便後續有界面定制化的需求,將更新相關的全部放在UpdateWork中,使用UpdateWork.Do方法就可以進行更新

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

  一些細節說明

  1、如何讓程序盡量的方便集成?

  由於自動更新程序必須是要與主程序分開的,所以我們要讓主程序啟動更新程序的時候,將主程序自己的信息帶進去,這樣才可以盡可能的做到通用

        /// <summary>
        /// 應用程序的主入口點。
     /// <param name="args">[0]程序名稱,[1]靜默更新 0:否 1:是</param> 
/// </summary> [STAThread] static void Main(string[] args) { if (f) { try { if (String.IsNullOrEmpty(args[0]) == false) { UpdateWork updateWork = new UpdateWork(args[0]); if (updateWork.UpdateVerList.Count > 0) { /* 當前用戶是管理員的時候,直接啟動應用程序 * 如果不是管理員,則使用啟動對象啟動程序,以確保使用管理員身份運行 */ //獲得當前登錄的Windows用戶標示 System.Security.Principal.WindowsIdentity identity = System.Security.Principal.WindowsIdentity.GetCurrent(); //創建Windows用戶主題 Application.EnableVisualStyles(); System.Security.Principal.WindowsPrincipal principal = new System.Security.Principal.WindowsPrincipal(identity); //判斷當前登錄用戶是否為管理員 if (principal.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator)) { if (args[1] == "1") { updateWork.Do(); } else { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new MainForm(updateWork)); } } else { String result = Environment.GetEnvironmentVariable("systemdrive"); if (AppDomain.CurrentDomain.BaseDirectory.Contains(result)) { //創建啟動對象 ProcessStartInfo startInfo = new ProcessStartInfo { //設置運行文件 FileName = System.Windows.Forms.Application.ExecutablePath, //設置啟動動作,確保以管理員身份運行 Verb = "runas", Arguments = " " + args[0] + " " + args[1] }; //如果不是管理員,則啟動UAC System.Diagnostics.Process.Start(startInfo); } else { if (args[1] == "1") { updateWork.Do(); } else { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new MainForm(updateWork)); } } } } } } catch (Exception ex) { MessageBox.Show(ex.Message); } } }

  winform在啟動的main方法裏,有一個參數是args,這就是我們可以接收來自外界的參數,從代碼中可以看到,總共傳遞進來的參數有2個,args[0]程序名稱 args[1] 靜默安裝的配置信息,通過這2個參數,我們就可以將自動更新與主程序分開

  2、更新前備份

        /// <summary>
        /// 備份當前的程序目錄信息
        /// </summary>
        private UpdateWork Bak()
        {
            try
            {

                LogTool.AddLog("更新程序:準備執行備份操作");

                DirectoryInfo di = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory);
                foreach (var item in di.GetFiles())
                {
                    if (item.Name != mainName)//當前文件不需要備份
                    {
                        if (item.Name == "DotNetZip.dll")
                        {
                        }
                        else
                        {
                            File.Copy(item.FullName, bakPath + item.Name, true);
                        }
                    }
                }
                //文件夾復制
                foreach (var item in di.GetDirectories())
                {
                    if (item.Name != "bak" && item.Name != "temp")
                    {
                        CopyDirectory(item.FullName, bakPath);
                    }
                }
                LogTool.AddLog("更新程序:備份操作執行完成,開始關閉應用程序");
                OnUpdateProgess?.Invoke(20);
                return this;
            }
            catch (Exception EX)
            {
                throw EX;
            }
        }

  對於自動更新來說,如果更新失敗的話,我們需要保證,程序是可回滾的,那就需要在更新前要對程序進行一個備份,如代碼所示,由於我這邊用到了DotNetZip.dll,所以這個dll是不能備份的,不然恢復的時候由於自動更新程序還在跑,覆蓋的時候會報錯,上述代碼邏輯還是很簡單的,拷貝當前運行程序下的所有文件以及文件夾到備份目錄(ps:由於windows的安全限制,c盤目錄普通用戶只有對temp文件夾有操作權限,so需要將bakPath設置到temp目錄下,如下代碼所示

String bakPath = Path.Combine(Environment.GetEnvironmentVariable("TEMP"), @"MAutoUpdate\bak\");//獲取temp文件夾下的bak目錄,如果不存在記得通過程序去創建

  3、更新文件的下載

 /// <summary>
        /// 下載方法
        /// </summary>
        private UpdateWork DownLoad()
        {
            using (WebClient web = new WebClient())
            {
                foreach (var item in UpdateVerList)
                {
                    try
                    {
                        LogTool.AddLog("更新程序:下載更新包文件" + item.ReleaseVersion);
                        web.DownloadFile(item.ReleaseUrl, tempPath + item.ReleaseVersion + ".zip");
                        OnUpdateProgess?.Invoke(60 / UpdateVerList.Count);
                    }
                    catch (Exception ex)
                    {
                        LogTool.AddLog("更新程序:更新包文件" + item.ReleaseVersion + "下載失敗,本次停止更新,異常信息:" + ex.Message);
                        throw ex;
                    }
                }
                return this;
            }
        }

  要更新嘛,那當然得有下載,索性.Net給我們提供了一個非常簡單的下載玩意兒,WebClient,給url就下載,絕對不二話,WebClient本身也有不少的事件可以使用,這個大家自己摸索,由於更新可能會存在好幾個包一起更新的情況,所以這邊使用循環先將所有要更新的下載下來,這部分下載代碼還是比較簡單的

  4、更新方法

  技術分享圖片

  流程如上圖所示,文字表達捉急,只能靠圖了,阿門

        private UpdateWork Update()
        {
            foreach (var item in UpdateVerList)
            {
                try
                {
                    //如果是覆蓋安裝的話,先刪除原先的所有程序
                    if (item.UpdateMode == "Cover")
                    {
                        DelLocal();
                    }
                    string path = tempPath + item.ReleaseVersion + ".zip";
                    using (ZipFile zip = new ZipFile(path))
                    {
                        LogTool.AddLog("更新程序:解壓" + item.ReleaseVersion + ".zip");
                        zip.ExtractAll(AppDomain.CurrentDomain.BaseDirectory, ExtractExistingFileAction.OverwriteSilently);
                        LogTool.AddLog("更新程序:" + item.ReleaseVersion + ".zip" + "解壓完成");
                        ExecuteINI();//執行註冊表等更新以及刪除文件
                    }
                    localInfo.LastUdpate = item.ReleaseDate;
                    localInfo.LocalVersion = item.ReleaseVersion;
                    localInfo.SaveXml();
                }
                catch (Exception ex)
                {
                    LogTool.AddLog("更新程序出現異常:異常信息:" + ex.Message);
                    LogTool.AddLog("更新程序:更新失敗,進行回滾操作");
                    Restore();
                    break;
                }
                finally
                {
                    //刪除下載的臨時文件
                    LogTool.AddLog("更新程序:刪除臨時文件" + item.ReleaseVersion);
                    DelTempFile(item.ReleaseVersion + ".zip");//刪除更新包
                    LogTool.AddLog("更新程序:臨時文件刪除完成" + item.ReleaseVersion);
                }
            }
            OnUpdateProgess?.Invoke(98);
            return this;
        }

四、如何在程序中使用

  新建winform項目後,在Main裏加入以下代碼

        /// <summary>
        /// 應用程序的主入口點。
        /// </summary>
        [STAThread]
        static void Main()
        {
            String path = AppDomain.CurrentDomain.BaseDirectory + "MAutoUpdate.exe";
            //同時啟動自動更新程序
            if (File.Exists(path))
            {
                ProcessStartInfo processStartInfo = new ProcessStartInfo()
                {
                    FileName = "MAutoUpdate.exe",
                    Arguments = " MAutoUpdate.Test 1"//1表示靜默更新 0表示彈窗提示更新
                };
                Process proc = Process.Start(processStartInfo);
                if (proc != null)
                {
                    proc.WaitForExit();
                }
            }
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }

  更新界面圖(可以自己改,這個反正很簡單),細心的小夥伴們可能發現了,我這是有點魔方微信的圖(ps:也是看了微信的升級後,突然想到我們項目裏目前為止比較糾結的自動更新,所以趁著這幾個晚上搗鼓出來)

  技術分享圖片

  技術分享圖片

五、總結

  花了大概三個晚上的時間搗鼓這個,裏面其實考慮的因素還是很多,以前思考的時候不會深入思考,想著自動更新麽 就是判斷、更新啟動結束,但是做下來,細節點真的是很多,後續可能會在公司內部項目裏進行一個小的推廣,看看符不符合真實的需求,由於個人原因,在測試上可能會有所不夠,這個也是一個拋磚引玉,如果有小夥伴有更好更方便的升級方式,也請告知(ps:文字表達弱雞,大家多多包涵)

  項目地址:https://github.com/Hello-Mango/MAutoUpdate,代碼比較亂,見諒

作者: Mango

出處: http://www.cnblogs.com/OMango/

關於自己:專註.Net桌面開發以及Web後臺開發,開始接觸微服務、docker等互聯網相關(最近被互聯網架構搞的死去活來- -)

本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,如有問題, 可站內信告知.

  

 

  

winform自動更新程序實現