• Windows程序通用自动更新模块(C#,.NET4.5以上)
  • 本通用自动更新模块适合所有Windows桌面程序的自动更新,不论语言,无论Winform还是wpf。

    一、工作流程:
    1. 主程序A调起升级程序B
    2. B从服务器获取更新程序列表,打印更新信息。
    3. B杀死A进程(此步骤可以放在步骤2~5任意位置)
    4. B根据更新信息中指示的地址,下载更新程序包(.zip文件)
    5. 解压缩.zip文件到一个新创建的文件夹
    6. 将解压后的文件拷贝到原始文件目录,做替换。
    7. 删除下载的.zip文件以及解压后创建的文件夹
    8. B打开A

    二、源码介绍:

    升级程序B的实现:

    更新信息列表用于存储版本信息,以及更新说明信息。通常为json或xml文件。本文为json文件。

    存储列表信息的类

    public class UpdateItem
    {
        public string Version { get; set; }  //版本号
        public string UpdateContent { get; set; }  //更新信息
        public string DownloadUri { get; set; }  //更新包的下载地址
        public string Time { get; set; }  //更新时间
        public string Size { get; set; }  //更新包大小
    }

     获取更新信息使用WebClient.DownloadData(Uri),其中使用Newtonsoft.Json进行json序列化及反序列化。

    WebClient client = new WebClient();
    byte[] data = client.DownloadData(uri);
    //json转为UpdateItem类对象
    UpdateInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<UpdateItem>(Encoding.UTF8.GetString(data));

     获取更新信息以后在界面上进行输出。
    下面介绍一下生成更新信息的json文件并使用FTP上传到服务器的代码。json文件也可以手动写,手动上传。不是重点,不想看可以跳到下一部分。

    UpdateItem UpdateInfo = new UpdateItem();
    ...  //赋值
    string json = JsonConvert.SerializeObject(UpdateInfo);
     //连接服务器
    FtpWebRequest request = (FtpWebRequest)WebRequest.Create("ftp://...");
    request.Method = WebRequestMethods.Ftp.UploadFile;
    request.Credentials = new NetworkCredential("账号", "密码");
    // 复制字符  
    byte[] fileContents = Encoding.UTF8.GetBytes(json);
    request.ContentLength = fileContents.Length;
    Stream requestStream = request.GetRequestStream();
    requestStream.Write(fileContents, 0, fileContents.Length);
    requestStream.Close();
    FtpWebResponse response = (FtpWebResponse)request.GetResponse();
    response.Close();

    下载更新包zip文件

    下载使用WebClient.DownloadFile(Uri, string)

    string dir = System.IO.Directory.GetCurrentDirectory();  //程序所在文件夹路径
    string zipfile = System.IO.Path.Combine(dir, "file.zip");  //下载后zip文件的完整路径
    
    WebClient client = new WebClient();
    client.DownloadFile(UpdateInfo.DownloadUri, zipfile);
    return true;

    解压缩zip文件

    解压缩需要用到System.IO.Compression.ZipFile,需要.NET4.5及以上。如果用不了(找不到ZipFile),请检查是否已添加引用System.IO.Compression.FileSystem程序集。

    代码只有一行,两个参数分别为zip文件完整路径,解压后的文件夹完整路径。

    ZipFile.ExtractToDirectory(zipfile, extractPath);

    使用第三方压缩软件生成的.zip文件有可能会解压失败,建议使用下方代码生成。两个参数分别为需要压缩的文件夹完整路径,生成的zip文件完整路径。

    ZipFile.CreateFromDirectory(startPath, zipfile);

    拷贝解压后的文件到原始目录

    foreach (string item in Directory.GetFiles(extractPath))
    {
        File.Copy(item, System.IO.Path.Combine(dir, System.IO.Path.GetFileName(item)), true);
    }

    删除临时文件

    File.Delete(zipfile);
    DirectoryInfo di = new DirectoryInfo(extractPath);
    di.Delete(true);

    B进程杀死A进程

    string appname = "file"; //A名字,不要路径,不要.exe
    Process[] processes = Process.GetProcessesByName(appname);
    foreach (var p in processes)
    p.Kill();

    B进程调起A进程

    System.Diagnostics.Process.Start(@"path");  //完整路径

    主要流程就是这些,建议用异步操作实现。
    现在上号比较少,私信很多要代码的都没回…以后都会把完整代码附上的。
    下面是完整代码。

    private Uri uri = new Uri(@"http://...");  //更新信息列表文件路径
    private UpdateItem UpdateInfo;  //UpdateItem类的定义在前面第一部分说明
    
    public MainWindow()
    {
        InitializeComponent();
        LoadingData();
    }
    
    //用户按下更新按钮
    private async void Button_Click(object sender, RoutedEventArgs e)
    {
        //下载
        Tb_State.Text = "正在下载新版本文件,请耐心等待";
        string dir = System.IO.Directory.GetCurrentDirectory();
        string zipfile = System.IO.Path.Combine(dir, "File.zip");
        bool success = await Task.Run(() =>
        {
            try
            {
                WebClient client = new WebClient();
                client.DownloadFile(UpdateInfo.DownloadUri, zipfile);
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        });
    
        if (success)
            Tb_State.Text = "文件已下载,正在复制文件";
        else
        {
            Tb_State.Text = "下载新版本文件失败,请重试";
            return;
        }
    
        //杀死主程序进程
        string appname = "File";
        Process[] processes = Process.GetProcessesByName(appname);
        foreach (var p in processes)
            p.Kill();
    
    
        //解压缩+拷贝+删除
        bool success2 = await Task.Run(() =>
        {
            try
            {
                string extractPath = System.IO.Path.Combine(dir, "NewVersion");
                ZipFile.ExtractToDirectory(zipfile, extractPath);
                foreach (string item in Directory.GetFiles(extractPath))
                    File.Copy(item, System.IO.Path.Combine(dir, System.IO.Path.GetFileName(item)), true);
                File.Delete(zipfile);
                DirectoryInfo di = new DirectoryInfo(extractPath);
                di.Delete(true);
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        });
    
        if (success2)
            Tb_State.Text = "更新完成,您可以点击下方按钮启动应用";
        else
            Tb_State.Text = "复制更新文件出错,请重试";
    }
    
    //读取更新列表文件
    private async Task<bool> LoadingData()
    {
        Tb_State.Text = "正在下载更新文件信息";
        bool success = await Task.Run(() =>
        {
            try
            {
                WebClient client = new WebClient();
                byte[] data = client.DownloadData(uri);
                UpdateInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<UpdateItem>(Encoding.UTF8.GetString(data));
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        });
    
        if (success)
            Tb_State.Text = "已获取新版本信息,可进行更新";
        else
            Tb_State.Text = "无法获取更新信息";
        return success;
    }
    
    //更新完成,更新程序B调起主程序A
    private void Btn_Open_Click(object sender, RoutedEventArgs e)
    {
        System.Diagnostics.Process.Start(@"D:...");   //A程序完整路径
    }

    再贴一个用于下载过程中,能实时显示已下载文件大小的代码
    此部分需写在下载部分之前。

    System.Windows.Threading.DispatcherTimer dt = new System.Windows.Threading.DispatcherTimer();
    dt.Interval = TimeSpan.FromMilliseconds(100);  //100毫秒
    dt.Tick += (x, y) => {
        if (File.Exists(zipfile) == false)
            return;
        string size = ((new FileInfo(zipfile).Length) / 1024.0 / 1024).ToString("f2");
        if (download == false)  //是否下载完毕
            Tb_State.Text = size + "MB / " + UpdateInfo.Size;  //输出:已下载/总大小
        else
            dt.Stop();
    };
    dt.Start();

    代码部分执行时,再次执行代码可能会报错。可能原因包括下载zip文件后未删除再次下载报错等。如要做项目还有很多地方需要进行判断,如检测路径的合法性等。为了代码便于阅读,没有加入这些错误检测部分,需自行补充。几乎所有操作前都应判断路径合法性,以及目标位置的文件是否存在(是否需要先删除)等。只能说是在输入合法的情况下,完整执行此代码是没有问题的。

    代码通过Visual Studio 2019测试,.NET4.5。

  • 相关阅读:
    Kubernetes 运维小记:node 为系统保留最低资源
    见异思迁:K8s 部署 Nginx Ingress Controller 之 kubernetes/ingressnginx
    Kubernetes 部署 Nginx Ingress Controller 之 nginxinc/kubernetesingress
    Kubernetes 与 Helm:使用同一个 Chart 部署多个应用
    终于成功部署 Kubernetes HPA 基于 QPS 进行自动伸缩
    排查 Kubernetes HPA 通过 Prometheus 获取不到 http_requests 指标的问题
    搭建 Kubernetes 高可用集群
    Kubernetes 升级记录:从 1.16.3 升级至 1.17.0
    ASP.NET Core 获取主机名时的 "Decoded string is not a valid IDN name" 错误
    ASP.NET Core 集成测试中模拟登录用户的一种姿势
  • 【推广】 阿里云小站-上云优惠聚集地(新老客户同享)更有每天限时秒杀!
    【推广】 云服务器低至0.95折 1核2G ECS云服务器8.1元/月
    【推广】 阿里云老用户升级四重礼遇享6.5折限时折扣!
  • 原文地址:https://www.cnblogs.com/tuyile006/p/12699972.html
走看看 - 开发者的网上家园