winform自动更新程序实现

一、问题背景

  本地程序在实际项目使用过程中,因为可以操作电脑本地的一些信息,并且对于串口、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等互联网相关(最近被互联网架构搞的死去活来- -)

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,如有问题, 可站内信告知.

  

 

  

原文地址:https://www.cnblogs.com/OMango/p/8509436.html

时间: 2024-10-05 04:24:21

winform自动更新程序实现的相关文章

C#之tcp自动更新程序

.NETTCP自动更新程序有如下几步骤: 第一步:服务端开启监听 ServiceHost host; private void button1_Click(object sender, EventArgs e) { host = new ServiceHost(typeof(WCFService.Service)); host.Open(); if (host.State == CommunicationState.Opened) { this.button1.Enabled = false;

完美世界自动更新程序

附件:http://url.cn/J04mZB 来自为知笔记(Wiz) 完美世界自动更新程序

分享一个客户端程序(winform)自动升级程序,思路+说明+源码

做winform的程序,不管用没用过自动更新,至少都想过自动更新是怎么实现的. 我这里共享一个自动更新的一套版本,给还没下手开始写的人一些帮助,也希望有大神来到,给指点优化意见. 本初我是通过socket来传输文件的,后来因为传大文件出现异常,前期又没有定义比较好的协议,后来就改用webservices了. 自动更新分三个部分,服务端(webservice服务,配置与更新),配置客户端(或叫发布客户端),客户端(主程序) 服务端:         ConfigServer.asmx 因为代码共享

C# 编写自动更新程序 (转)

感觉用的到,存下来,转自:http://blog.csdn.net/gisfarmer/article/details/4437994 现在但凡是一个程序都有相应的升级程序,如果你的程序没有相应的升级程序,那么你就需要留意了.你的用户很可能丢失!!!网上关于自动升级的例子也有很多,前几天一个朋友很苦恼的跟我说它的客户在逐渐减少(据他所说,他都客户因为他的程序升级很麻烦,所以很多人放弃了使用它的软件),问我说怎么办?其实他也知道该怎么办?所以...朋友嘛!就给他做了一个自动升级程序.恰好今天CSD

Winfrom强大的自动更新程序

.Net 小型软件自动更新库(SimpAutoUpdater) http://www.fishlee.net/soft/simple_autoupdater/usage.html 下载地址:https://github.com/iccfish/FSLib.App.SimpleUpdater 比较吸引的就是提供了更新包生成工具

winform自动更新并实现文件的批量异步下载

public partial class update : Form    {        private WebClient client;        int downfilenum = 0; //已下载文件数        int downlistnum = 0;//总下载文件数        List<string> list;        private string URl;        private string fileName;        private con

写自动更新程序出现&quot;远程服务器返回错误: (404) 未找到&quot;

在win2003配置后,在客户端运行时能够下载exe和dll文件,但是在更新lib文件时总是报“远程服务器返回错误: (404) 未找到”错误,不明白咋会出现这个问题,去网上一查,发现以下解决办法: 发现如下方法可以解决该问题: 给网站添加MIME的类型,如下: .*application/octet-stream 按照说法做,在服务器上打开iis,找到自动升级的文件夹,查看属性,选择”HTTP头“选项卡,发现有个MIME映射选项,单击”文件类型“,再单击”新类型”,在关联扩展名里添加“.*”,

WinForm应用程序中实现自动更新功能

WinForm应用程序中实现自动更新功能 编写人:左丘文 2015-4-20 近来在给一客户实施ECM系统,但他们使用功能并不是我们ECM制造版提供的标准功能,他们要求对系统作一些定制功能,为了避免因程序的bug而带来频繁让用户更新程序的不良影响,就想给ECM增加一个winform自动更新功能,今天在这里,我想与大家一起分享代码,在此做个小结,以供参考.有兴趣的同学,可以一同探讨与学习一下,否则就略过吧.   1. 首先我们在这里先分析一下其它程序猿的一些基本情况: 相信有许多程序猿都喜欢用Wi

SNF开发平台WinForm之八-自动升级程序部署使用说明-SNF快速开发平台3.3-Spring.Net.Framework

9.1运行效果: 9.2开发实现: 1.首先配置服务器端,把“SNFAutoUpdate2.0\服务器端部署“目录按网站程序进行发布到IIS服务器上. 2.粘贴语句,生成程序 需要调用的应用程序的Load事件或者Program入口的Main方法第一行代码加上如下代码: 注意:是主程序的 Load事件要加上调整自动更新程序的代码.要以模式打开窗口.如果没有差异会自动关闭升级窗口显示主窗口. 3.把下面目录里的文件拷贝到 应用程序的同级目录下: 4.配置WINFORMS应用程序目录下Updateli