您的当前位置:首页>全部文章>文章详情

【C#】C# Winform自动更新

CrazyPanda发表于:2024-02-01 23:23:51浏览:338次TAG:

目录

一、需求

二、更新文件列表生成器

三、软件启动器

1.判断是否需要更新

2.文件下载

3.执行 下载,覆盖,删除任务

4.执行结果

四、搭建更新服务器

1.启动服务器

2.新建项目本体

3.给启动软件加密

4.修改版本号

五、整体测试

1.生成更新文件

2.软件更新

3.下载最新的版本

4.打开软件本体

5.总结

结束



当前项目已停止维护,推荐使用 FTP 版自动更新

C# 自动更新(基于FTP)_c# 程序自动升级-CSDN博客


一、需求

在Unity里面,有XLua,ILRuntime 这样的热更新框架,有Unity经验的人都知道,在Unity里面发布的各个平台,哪怕是Windows平台,根本不必关闭程序才能进行更新,但是 Winform 项目必须关闭程序才进行下载替换,我也在网上找了一下,目前 Winform 还没看到什么比较好的开源框架,于是我自己动手写了一个

效果如下:

01174706_63da351a96fff24847.gif

在 Windows 平台,客户端自动更新方法一般如下面几个步骤,最少需要做三个软件

1.更新文件列表生成器

2.软件启动器

3.软件本体(只能由软件启动器 打开)

软件启动入口可以加密码,防止用户随意打开


二、更新文件列表生成器

我们知道,在原有基础上更新文件必要的三个基础:新增,覆盖,删除,那么问题来了,新增和删除好理解,在覆盖文件这块,假如:最新文件和旧版文件有一个同名的文件都叫 Test.dll,这时候要怎么判断是否进行下载和替换?

这个问题,可以用文件的哈希值去解决,如果客户端匹配不上,那么就需要从服务器下载和替换了,代码如下:

using System;
using System.IO;
using System.Security.Cryptography;
 
namespace Test
{
    internal class Program
    {
        static void Main(string[] args)
        {
            string path1 = "D:\\单据备份\\53.png";
            string saveHash = "97-9F-FC-54-BF-C0-95-B8-15-64-C5-AC-81-03-DE-90-13-CD-03-D5";
            using (HashAlgorithm hash = HashAlgorithm.Create())
            {
                using (FileStream file1 = new FileStream(path1, FileMode.Open))
                {
                    byte[] hashByte1 = hash.ComputeHash(file1);//哈希算法根据文本得到哈希码的字节数组
                    string str1 = BitConverter.ToString(hashByte1);//将字节数组装换为字符串
                    Console.WriteLine(str1);
 
                    if (saveHash.Equals(str1))
                        Console.WriteLine("两个文件相同");
                    else
                        Console.WriteLine("两个文件不相同");
                }
            }
 
            Console.ReadKey();
        }
    }
}

更新文件生成器界面如下,将最新的软件文件放到服务器一个固定的文件夹,然后用更新文件生成器就可以生成文件列表等数据了

1.png


新建一个类 DES加密类,这个是给json加密用的,不过源码中,我将这功能注释了

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
 
namespace Utils
{
 
    public class DESKey
    {
        /// <summary>
        /// Des默认密钥向量
        /// </summary>
        public static byte[] IV = { 0x13, 0x35, 0x16, 0x78, 0x90, 0xAB, 0xCD, 0xEF };
        /// <summary>
        /// Des加解密钥必须8位
        /// </summary>
        public const string Key = "4hghhgg";
    }
 
    public class DES
    {
        /// <summary>
        /// Des加密
        /// </summary>
        /// <param name="source">源字符串</param>
        /// <param name="key">des密钥,长度必须8位</param>
        /// <param name="iv">密钥向量</param>
        /// <returns>加密后的字符串</returns>
        public static string EncryptDes(string source, string key, byte[] iv)
        {
            using (DESCryptoServiceProvider desProvider = new DESCryptoServiceProvider())
            {
                byte[] rgbKeys = GetDesKey(key),
                    rgbIvs = iv,
                    inputByteArray = Encoding.UTF8.GetBytes(source);
                using (MemoryStream memoryStream = new MemoryStream())
                {
                    using (CryptoStream cryptoStream = new CryptoStream(memoryStream, desProvider.CreateEncryptor(rgbKeys, rgbIvs), CryptoStreamMode.Write))
                    {
                        cryptoStream.Write(inputByteArray, 0, inputByteArray.Length);
                        cryptoStream.FlushFinalBlock();
                        return Convert.ToBase64String(memoryStream.ToArray());
                    }
                }
            }
        }
 
        /// <summary>
        /// Des解密
        /// </summary>
        /// <param name="source">源字符串</param>
        /// <param name="key">des密钥,长度必须8位</param>
        /// <param name="iv">密钥向量</param>
        /// <returns>解密后的字符串</returns>
        public static string DecryptDes(string source, string key, byte[] iv)
        {
            using (DESCryptoServiceProvider desProvider = new DESCryptoServiceProvider())
            {
                byte[] rgbKeys = GetDesKey(key), rgbIvs = iv, inputByteArray = Convert.FromBase64String(source);
                using (MemoryStream memoryStream = new MemoryStream())
                {
                    using (CryptoStream cryptoStream = new CryptoStream(memoryStream, desProvider.CreateDecryptor(rgbKeys, rgbIvs), CryptoStreamMode.Write))
                    {
                        cryptoStream.Write(inputByteArray, 0, inputByteArray.Length);
                        cryptoStream.FlushFinalBlock();
                        return Encoding.UTF8.GetString(memoryStream.ToArray());
                    }
                }
            }
        }
 
        /// <summary>
        /// 获取Des8位密钥
        /// </summary>
        /// <param name="key">Des密钥字符串</param>
        /// <returns>Des8位密钥</returns>
        private static byte[] GetDesKey(string key)
        {
            if (string.IsNullOrEmpty(key))
            {
                throw new ArgumentNullException("key", "Des密钥不能为空");
            }
            if (key.Length > 8)
            {
                key = key.Substring(0, 8);
            }
            if (key.Length < 8)
            {
                // 不足8补全
                key = key.PadRight(8, '0');
            }
            return Encoding.UTF8.GetBytes(key);
        }
    }
}

Form1.cs

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
 
namespace UpdateFileCreater
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
 
        private class FileType
        {
            /// <summary>
            /// 路径
            /// </summary>
            public string Path { get; set; }
            /// <summary>
            /// 哈希值
            /// </summary>
            public string Hashs { get; set; }
        }
 
        private class UpdateFile
        {
            /// <summary>
            /// 文件夹列表
            /// </summary>
            public List<string> DirectoryList { get; set; } = new List<string>();
            /// <summary>
            /// 文件列表
            /// </summary>
            public List<FileType> FilesinfoList { get; set; } = new List<FileType>();
            /// <summary>
            /// 服务器版本号
            /// </summary>
            public string Version { get; set; }
        }
 
 
        //需要生成列表的目录
        private string Path = "D:\\H\\本地服务器工具\\Software\\News\\";
        //存放json的位置
        private string FileConfigJsonPath = "D:\\H\\本地服务器工具\\Software\\FileList.json";
 
        //本地文件的黑名单(不参与到更新)
        private static List<string> LocalFileBlacklist = new List<string>();
 
        //文件夹列表
        private static List<string> DirectorysList = new List<string>();
        //文件列表
        private static List<FileType> FilesinfoList = new List<FileType>();
 
 
        private void Form1_Load(object sender, EventArgs e)
        {
            this.TextBox_Path.Text = Path;
 
            //添加黑名单
            LocalFileBlacklist.Add("log4net.dll");
            LocalFileBlacklist.Add("log4net.xml");
            LocalFileBlacklist.Add("Newtonsoft.Json.dll");
            LocalFileBlacklist.Add("Newtonsoft.Json.xml");
 
 
            string localVersionPath = FileVersionInfo.GetVersionInfo(Path + "\\Test1.exe").FileVersion;
            Console.WriteLine("软件的版本号" + localVersionPath);
        }
 
        /// <summary>
        /// 选择路径
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Button_SelectionPath_Click(object sender, EventArgs e)
        {
            FolderBrowserDialog dialog = new FolderBrowserDialog();
            dialog.Description = "请选择文件夹";
            dialog.SelectedPath = Path;
            if (dialog.ShowDialog() == DialogResult.OK)
            {
                if (string.IsNullOrEmpty(dialog.SelectedPath))
                {
                    MessageBox.Show(this, "文件夹路径不能为空", "提示");
                    return;
                }
                Path = dialog.SelectedPath;
                this.TextBox_Path.Text = Path;
            }
        }
 
        /// <summary>
        /// 生成文件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Button_CreaterFile_Click(object sender, EventArgs e)
        {
            DirectorysList.Clear();
            FilesinfoList.Clear();
 
            if (System.IO.Directory.Exists(Path))
            {
                Task.Run(() =>
                {
                    GetDirectoryFileList(Path);
 
                    if (FilesinfoList.Count > 0)
                    {
                        UpdateFile updateFile = new UpdateFile();
                        updateFile.DirectoryList = DirectorysList;
                        updateFile.FilesinfoList = FilesinfoList;
                        updateFile.Version = FileVersionInfo.GetVersionInfo(Path + "\\Test1.exe").FileVersion;
 
                        //生成json文件
                        string json = JsonConvert.SerializeObject(updateFile);
                        //加密json文件
                        //json = Utils.DES.EncryptDes(json, Utils.DESKey.Key, Utils.DESKey.IV);
                        WriteJsonFile(FileConfigJsonPath, json);
                        MessageBox.Show("生成文件成功!");
                    }
                });
            }
        }
 
 
        /// <summary>
        /// 获取一个文件夹下的所有文件和文件夹集合
        /// </summary>
        /// <param name="path"></param>
        private void GetDirectoryFileList(string path)
        {
            DirectoryInfo directory = new DirectoryInfo(path);
            FileSystemInfo[] filesArray = directory.GetFileSystemInfos();
            foreach (var item in filesArray)
            {
                if (item.Attributes == FileAttributes.Directory)
                {
                    //添加文件夹
                    string dir = item.FullName.Replace(Path, "");
                    DirectorysList.Add(dir);
                    GetDirectoryFileList(item.FullName);
                }
                else
                {
                    string fileName = item.FullName.Replace(Path, "");
                    fileName = fileName.Replace(@"\", @"/");
 
                    //是否在黑名单中
                    bool isBlackList = false;
                    if (LocalFileBlacklist.Count > 0)
                    {
                        for (int i = 0; i < LocalFileBlacklist.Count; i++)
                        {
                            if (LocalFileBlacklist[i].Equals(fileName))
                            {
                                isBlackList = true;
                                break;
                            }
                        }
                    }
                    if (!isBlackList)
                    {
                        //添加文件
                        FileType fileType = new FileType();
                        fileType.Path = fileName;
                        fileType.Hashs = GetHashs(item.FullName);
                        FilesinfoList.Add(fileType);
                    }
                }
            }
        }
 
        /// <summary>
        /// 获取文件的哈希值
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        private string GetHashs(string path)
        {
            //创建一个哈希算法对象
            using (HashAlgorithm hash = HashAlgorithm.Create())
            {
                using (FileStream file1 = new FileStream(path, FileMode.Open))
                {
                    //哈希算法根据文本得到哈希码的字节数组
                    byte[] hashByte1 = hash.ComputeHash(file1);
                    //将字节数组装换为字符串
                    return BitConverter.ToString(hashByte1);
                }
            }
        }
 
        /// <summary>
        /// 将json字符串内容写入Json文件并保存(若json文件不存在则创建)
        /// </summary>
        /// <param name="path">路径</param>
        /// <param name="jsonConents">Json内容</param>
        private void WriteJsonFile(string path, string jsonConents)
        {
            using (FileStream fs = new FileStream(path, FileMode.OpenOrCreate, System.IO.FileAccess.ReadWrite, FileShare.ReadWrite))
            {
                fs.Seek(0, SeekOrigin.Begin);
                fs.SetLength(0);
                using (StreamWriter sw = new StreamWriter(fs, Encoding.UTF8))
                {
                    sw.WriteLine(jsonConents);
                }
            }
        }
    }
}

这样 更新文件列表生成器 就基本完成了。


三、软件启动器

在我们平时用到的很多Windows软件打开时都会有软件启动器,大部分的作用都是基于软件的更新和文件的效验,这也形成了软件体系一个固定的框架,那么为什么要用软件启动器呢,那是因为,Windows软件打开以后,软件的文件就会被占用,无法进行删除,替换等操作,所以,想要更新软件,软件启动器是必须的。

软件启动器需要给项目添加管理员权限,软件启动器是启动软件之前,做的一些安全判断,比如文件是否完整,是否需要更新到最新版本,然后执行文件的新增,删除,覆盖 等任务。


1.判断是否需要更新

判断版本是否更新的方法:

Version v1 = new Version(txt1.Text);
Version v2 = new Version(txt2.Text);
if (v1 > v2)
{
    MessageBox.Show("版本1高于版本2");
}
if (v1 < v2)
{
    MessageBox.Show("版本1低于版本2");
}


2.文件下载

1.如果需要更新,将本地文件的哈希值和服务器返回的列表进行判断,如果服务器文件哈希值和本地文件哈希值不一致,那么就加入到下载列表

2.文件操作,更新软件本体通常的操作是:

① 新增文件直接下载

② 相同文件直接覆盖

③ 删除文件直接删除


3.执行 下载,覆盖,删除任务

需要操作的文件加入到任务列表(下载,覆盖,删除),需要专门写一个任务系统来执行此次操作,将每个子任务的结果显示到UI界面,这里可以用一个进度条和文本来显示执行的进度,

但也需要注意一点,自动更新系统都有一个毛病,就是不能确保文件的完整性,如果在下载的中途结束软件启动器进程,会导致软件的文件不全,从而打不开软件,解决这个问题,可以用一个文件夹先下载好文件,下完之后,直接覆盖现有的文件。


4.执行结果

如果执行完成,打开软件本体

如果执行失败,弹框显示错误信息,退出


1.png


软件启动器的界面就这样,没有过于复杂的部分

代码

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Utils;
 
namespace FileBootUp
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
 
        private void Form1_Load(object sender, EventArgs e)
        {
            Label_DownLog.Visible = false;
            ProgressBar_DownProgress.Visible = false;
 
            Task.Run(() =>
            {
                //获取服务器文件列表
                GetConfig.StartGetConfig();
                //对比本地文件
                ComparisonVersion.Instance.StartComparison(StartDown);
            });
        }
 
        private void StartDown(List<DownInfo> addedList, List<DownInfo> coverList, List<string> deleteList)
        {
            if(addedList.Count == 0 && coverList.Count == 0 && deleteList.Count == 0)
            {
                MessageBox.Show("没有需要更新的文件");
                return;
            }
 
            FormControlExtensions.InvokeIfRequired(this, () =>
            {
                Label_DownLog.Visible = true;
                ProgressBar_DownProgress.Visible = true;
            });
 
            //删除
            if (deleteList.Count > 0)
            {
                for (int i = 0; i < deleteList.Count; i++)
                {
                    string paths = deleteList[i];
                    File.Delete(paths);
                    Console.WriteLine(string.Format("删除文件完成,路径:{0}", paths));
                }
            }
            //新增
            if (addedList.Count > 0)
            {
                for (int i = 0; i < addedList.Count; i++)
                {
                    DownInfo downInfo = addedList[i];
                    HTTPDownManager.DownloadFileData(downInfo.DownURL, downInfo.SavePath, DownProgress, DownEnd);
                }
            }
            //覆盖
            if(coverList.Count > 0)
            {
                for (int i = 0; i < coverList.Count; i++)
                {
                    DownInfo downInfo = coverList[i];
                    HTTPDownManager.DownloadFileData(downInfo.DownURL, downInfo.SavePath, DownProgress, DownEnd);
                }
            }
 
            AllTaskEnd();
        }
 
        private void DownProgress(string fileName, string size, int percent)
        {
            FormControlExtensions.InvokeIfRequired(this, () =>
            {
                this.Label_DownLog.Text = string.Format("正在下载:{0}  大小:{1}", fileName, size);
                this.Label_DownProgress.Text = string.Format("{0}%", percent);
                this.ProgressBar_DownProgress.Value = percent;
            });
        }
 
        private void DownEnd(string fileName)
        {
            Console.WriteLine(string.Format("{0} 下载完成", fileName));
        }
 
 
        private void AllTaskEnd()
        {
            FormControlExtensions.InvokeIfRequired(this, () =>
            {
                this.Label_DownLog.Text = string.Empty;
                this.Label_DownProgress.Text = string.Empty;
                this.ProgressBar_DownProgress.Value = 0;
 
                this.Label_DownLog.Visible = false;
                this.ProgressBar_DownProgress.Visible = false;
 
                MessageBox.Show("所有任务完成");
            });
        }
 
 
    }
}


上面代码中用到的一部分工具类,我这里就不展示了,代码太多了。

1.png


其中的配置文件 App.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
    </startup>
	<appSettings>
		<!--服务器版本URL-->
		<add key="ServerFileList" value="http://localhost/Software/FileList.json"/>
		<!--服务器文件库-->
		<add key="ServerFileVersion" value="http://localhost/Software/News"/>
	</appSettings>
</configuration>



四、搭建更新服务器

服务器可以使用软件来实现,不用的时候就关掉,在测试的时候,我们不必使用 IIS 这样的服务器,配置起来太麻烦了,软件名如下图

1.png

1.启动服务器

首先是上面图片中的 index.html 文件,里面没有html 代码,就是一句话

1.png


在双击 NetBox2.exe 后,就会自动打开一个网站

1.png

并且有任务栏中,可以看到图标

1.png

这样服务器就运行起来了


2.新建项目本体

项目本体,就用来作为更新的软件,那就新建一个 winform 项目吧,取名 Test1,界面不重要,也用不上,随便弄一下

1.png


目前作为测试,最主要的是,让软件的生存目录的文件多一些,比如选择这个插件

Swashbuckle.AspNetCore.Swagger.6.4.0

1.png

安装完成后,我们来修改一下,Program.cs 的代码


3.给启动软件加密

代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
 
namespace Test1
{
    internal static class Program
    {
        /// <summary>
        /// 应用程序的主入口点。
        /// </summary>
        [STAThread]
        static void Main(string[] args)
        {
            if (args.Length == 0)
                return;
            if (args[0] != "密码")
                return;
 
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}

这里的 string[] arrgs 原本是没有的,我这里加上后,另外加上密码,直接双击exe 文件是打不开的,这里代码的主要作用也是如此。

代码添加完成后,点击生成

1.png

由于刚刚添加的插件,生成的文件特别多,先别管他了 


4.修改版本号

另外,就是版本号的修改了,打开 AssemblyInfo.cs

1.png

一般修改版本号改  AssemblyFileVersion

1.png

将版本改为 1.0.0.1 吧

1.png

现在准备工作也做的差不多了,下面开始整体测试。 


五、整体测试

1.生成更新文件

将Test1项目生成的文件复制到服务器固定目录

1.png


打开更新文件列表生成器

1.png


点击生成

1.png


 这时候,就看到了生成的文件

1.png


由于是加密的,直接看是不知道里面有什么的

1.png

在源码中,我将加密部分注释了,那么效果就是这样的

{
	"DirectoryList": [],
	"FilesinfoList": [{
		"Path": "Microsoft.AspNetCore.Http.Abstractions.dll",
		"Hashs": "4F-30-77-3D-BA-F6-84-0E-F7-57-E5-EF-64-7A-96-B2-54-F9-BA-3B"
	}, {
		"Path": "Microsoft.AspNetCore.Http.Abstractions.xml",
		"Hashs": "25-2C-B5-23-37-0F-B2-2F-BF-F9-DA-B1-7E-EE-CA-63-42-7A-06-65"
	}, {
		"Path": "Microsoft.AspNetCore.Http.Extensions.dll",
		"Hashs": "74-56-B5-F3-E1-37-45-D6-A2-99-2F-F9-17-E1-20-E1-BA-7E-37-CE"
	}, {
		"Path": "Microsoft.AspNetCore.Http.Extensions.xml",
		"Hashs": "A8-B0-E3-46-56-25-8A-19-FE-33-91-27-67-3C-EC-62-F5-E0-8F-AE"
	}, {
		"Path": "Microsoft.AspNetCore.Http.Features.dll",
		"Hashs": "1E-CD-4E-A7-C0-11-CE-91-A9-E2-54-25-ED-E4-B0-C1-A6-4D-88-FD"
	}, {
		"Path": "Microsoft.AspNetCore.Http.Features.xml",
		"Hashs": "5F-5D-0D-BB-A3-42-5B-E8-CA-F2-99-5C-26-BC-2F-F4-35-AC-EE-0F"
	}, {
		"Path": "Microsoft.AspNetCore.Routing.Abstractions.dll",
		"Hashs": "60-64-E5-E5-8B-00-A0-EF-6F-0A-0B-82-83-9C-FA-84-91-CD-10-BC"
	}, {
		"Path": "Microsoft.AspNetCore.Routing.Abstractions.xml",
		"Hashs": "40-98-34-26-61-84-70-B0-4E-F4-67-7A-FF-5E-B0-85-25-20-DB-29"
	}, {
		"Path": "Microsoft.AspNetCore.Routing.dll",
		"Hashs": "4A-32-C9-0C-F5-3D-3C-29-E3-B9-81-8C-3C-25-02-E3-31-F7-70-72"
	}, {
		"Path": "Microsoft.AspNetCore.Routing.xml",
		"Hashs": "05-B5-91-BF-D1-2B-97-99-31-9B-4F-7A-88-40-68-A5-75-57-E2-1E"
	}, {
		"Path": "Microsoft.Extensions.DependencyInjection.Abstractions.dll",
		"Hashs": "5E-46-B9-38-63-9A-28-65-1B-D4-DE-8E-DA-43-8C-CC-5A-21-2E-1C"
	}, {
		"Path": "Microsoft.Extensions.DependencyInjection.Abstractions.xml",
		"Hashs": "05-3C-29-79-BE-6B-34-77-69-4D-25-FD-CE-0E-89-C6-FD-71-6D-E1"
	}, {
		"Path": "Microsoft.Extensions.FileProviders.Abstractions.dll",
		"Hashs": "F1-D2-1C-08-B2-AC-28-18-17-37-6C-DB-2D-F2-E5-FA-A2-1E-C8-45"
	}, {
		"Path": "Microsoft.Extensions.FileProviders.Abstractions.xml",
		"Hashs": "D2-BD-BA-26-10-70-52-BE-8D-BE-02-88-63-49-7E-0D-63-BB-D8-50"
	}, {
		"Path": "Microsoft.Extensions.Logging.Abstractions.dll",
		"Hashs": "65-5B-52-5B-F7-33-AF-9F-1F-F7-A7-89-8E-98-11-63-47-E6-7C-F8"
	}, {
		"Path": "Microsoft.Extensions.Logging.Abstractions.xml",
		"Hashs": "E1-82-DC-EA-78-DF-37-4B-5E-16-1D-93-D2-D9-6D-09-B0-78-4F-88"
	}, {
		"Path": "Microsoft.Extensions.ObjectPool.dll",
		"Hashs": "4E-FD-D1-C7-9B-20-F9-D4-A8-18-2E-30-2D-1E-2C-86-98-BB-23-06"
	}, {
		"Path": "Microsoft.Extensions.ObjectPool.xml",
		"Hashs": "4C-0C-2F-42-F1-B0-70-E7-2F-B5-6F-AD-9E-93-40-D8-F3-E7-65-DE"
	}, {
		"Path": "Microsoft.Extensions.Options.dll",
		"Hashs": "C2-F4-79-71-79-93-EA-E1-61-42-A2-07-F1-75-48-21-69-D8-BC-CB"
	}, {
		"Path": "Microsoft.Extensions.Options.xml",
		"Hashs": "CB-37-C1-6A-43-5C-57-BE-7D-4C-C7-1D-29-AD-17-39-11-3C-A6-25"
	}, {
		"Path": "Microsoft.Extensions.Primitives.dll",
		"Hashs": "91-4D-D1-1A-94-E9-FB-1C-A7-9D-BF-F6-BE-C6-61-D4-E2-9C-D0-B0"
	}, {
		"Path": "Microsoft.Extensions.Primitives.xml",
		"Hashs": "DE-7E-1A-8D-C9-27-3F-C0-B6-3A-3D-4D-1B-88-EB-A2-F5-E1-84-9B"
	}, {
		"Path": "Microsoft.Net.Http.Headers.dll",
		"Hashs": "98-D4-88-0C-05-CD-B1-32-BB-D7-F7-3B-EF-A0-CF-A7-B2-2A-57-78"
	}, {
		"Path": "Microsoft.Net.Http.Headers.xml",
		"Hashs": "63-68-C4-F0-5D-24-8D-94-9D-1A-F5-29-C9-BF-05-DB-E0-81-A0-38"
	}, {
		"Path": "Microsoft.OpenApi.dll",
		"Hashs": "45-FB-EB-24-39-BE-3F-E9-15-A4-62-3C-01-30-DC-B1-08-AC-E8-08"
	}, {
		"Path": "Microsoft.OpenApi.pdb",
		"Hashs": "FA-6C-06-BA-24-9B-80-50-BF-A0-FB-17-57-BE-82-EE-D1-CE-B0-DE"
	}, {
		"Path": "Microsoft.OpenApi.xml",
		"Hashs": "1E-1D-79-43-25-67-4C-52-5C-A3-02-E9-30-2A-17-A4-A9-4E-38-B2"
	}, {
		"Path": "Swashbuckle.AspNetCore.Swagger.dll",
		"Hashs": "A8-52-E6-35-A3-A4-9B-B5-A8-B3-F5-44-C6-C3-89-75-87-BB-7D-A8"
	}, {
		"Path": "Swashbuckle.AspNetCore.Swagger.pdb",
		"Hashs": "48-3B-7C-A7-EA-B8-E2-61-67-F9-FE-00-A3-9F-AD-A2-20-D4-73-D7"
	}, {
		"Path": "Swashbuckle.AspNetCore.Swagger.xml",
		"Hashs": "AA-2F-DD-84-B8-9E-2C-68-04-92-4A-8E-0B-12-D3-81-9E-A9-B2-78"
	}, {
		"Path": "System.Buffers.dll",
		"Hashs": "BA-C3-4C-8A-68-C1-20-51-C6-F5-39-5C-5A-75-9D-7A-B5-19-A8-BA"
	}, {
		"Path": "System.Buffers.xml",
		"Hashs": "7F-40-69-34-1C-6B-62-EC-FC-99-9A-6C-2D-8A-2D-5F-B5-9D-44-F6"
	}, {
		"Path": "System.Memory.dll",
		"Hashs": "A8-8D-AB-84-A7-D3-13-BF-49-EE-01-C7-00-04-37-E5-7D-BB-A6-97"
	}, {
		"Path": "System.Memory.xml",
		"Hashs": "CF-44-E6-55-7F-DE-93-28-8F-F2-56-7A-00-2A-69-27-99-65-CA-BA"
	}, {
		"Path": "System.Numerics.Vectors.dll",
		"Hashs": "35-9F-9F-8A-3E-0E-E6-3F-9E-B6-BC-56-E3-BA-C3-00-C7-31-C0-80"
	}, {
		"Path": "System.Numerics.Vectors.xml",
		"Hashs": "E2-A3-B3-AC-B3-80-A4-EB-62-6B-44-FF-6E-E0-4A-37-11-0A-33-89"
	}, {
		"Path": "System.Runtime.CompilerServices.Unsafe.dll",
		"Hashs": "FD-55-A3-BC-6B-09-28-A0-29-B2-9D-D0-55-9F-ED-4C-E3-0B-79-D4"
	}, {
		"Path": "System.Runtime.CompilerServices.Unsafe.xml",
		"Hashs": "E7-05-41-4B-E7-2B-48-66-BC-3A-D0-2B-95-29-65-60-14-C6-3C-B1"
	}, {
		"Path": "System.Text.Encodings.Web.dll",
		"Hashs": "D5-9E-68-86-E0-1F-31-F5-6F-84-8A-5A-A7-28-19-0F-0C-27-AA-49"
	}, {
		"Path": "System.Text.Encodings.Web.xml",
		"Hashs": "84-8E-B4-3B-4C-C5-37-51-FC-DC-27-B3-6B-A6-4B-AF-4B-E2-28-80"
	}, {
		"Path": "Test1.exe",
		"Hashs": "92-04-71-3C-1B-B0-7B-05-EA-3F-46-71-4A-55-1D-E4-17-39-C1-23"
	}, {
		"Path": "Test1.exe.config",
		"Hashs": "61-F0-51-D8-F3-00-F7-BA-4C-AA-36-CE-89-11-D0-EC-0F-17-5C-C4"
	}, {
		"Path": "Test1.pdb",
		"Hashs": "E3-D6-DD-6B-AF-A4-FB-C8-4F-2C-65-96-D4-E4-B0-BA-8C-25-CD-3D"
	}],
	"Version": "1.0.0.0"
}



2.软件更新

打开 软件启动器

如果是第一次运行的话,会直接下载所有的文件

1.png

然后就可以看到这个Test1 所有项目文件

1.png

这时候,就完成了基本的更新功能了,当然,我们要做的功能不是下载文件就完事了,最主要的是更新的功能,现在我们更新服务器的版本看看。

在更新服务器文件之前,最好先把刚刚下载好的文件删除一些,不然软件启动器只会下载服务器端和本地端文件不一致的文件,删除后如下图

1.png



Test1.exe 相关的文件不要删除了,不然软件启动器会认为当前是第一次下载,将把所有的文件下载一遍。


3.下载最新的版本

打开 Test1的项目,将 Test1 的版本号改为 1.0.0.2

1.png

然后生成项目,将这三个文件复制到 D:\H\本地服务器工具\Software\News 目录下,就是刚刚搭建的服务器那个目录,这个根据个人的配置,目录不一定非得和我的一样。

1.png

然后用 更新文件列表生成器 重新生成 json 文件,可以打开json文件,看看版本是否和你设置的一致。

1.png

这时候,打开 软件启动器,就会发现弹出了是否更新的提示框

1.png

点击确定,由于文件比较小,所以下载的有点快

01174706_63da351a96fff24847.gif

 下载完成

1.png


在控制台同样有下载的日志

1.png

这样更新的工作就算完成了


4.打开软件本体

在这里需要更新一下 软件启动器的 Form1 的代码,如下

        private void AllTaskEnd()
        {
            FormControlExtensions.InvokeIfRequired(this, () =>
            {
                this.Label_DownLog.Text = string.Empty;
                this.Label_DownProgress.Text = string.Empty;
                this.ProgressBar_DownProgress.Value = 0;
 
                this.Label_DownLog.Visible = false;
                this.ProgressBar_DownProgress.Visible = false;
 
                //MessageBox.Show("所有任务完成");
                Console.WriteLine("所有任务完成");
 
                string path = Application.StartupPath + "\\Test1.exe";
                string pwd = "密码";
                string[] parameter = { pwd };
                bool startResult = StartProcess(path, parameter);
                if (startResult)
                    System.Environment.Exit(0);//退出软件启动器
            });
        }
 
        /// <summary>
        /// 启动一个软件,并传入参数
        /// </summary>
        /// <param name="runFilePath"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public bool StartProcess(string runFilePath, params string[] args)
        {
            string s = "";
            foreach (string arg in args)
            {
                s = s + arg + " ";
            }
            s = s.Trim();
            Process process = new Process();//创建进程对象    
            ProcessStartInfo startInfo = new ProcessStartInfo(runFilePath, s); // 括号里是(程序名,参数)
            process.StartInfo = startInfo;
            process.Start();
            return true;
        }

 再次重复和上节重复的操作,就会实现下面的效果

01174706_63da351a96fff24847.gif


更新完成后,会自动打开软件本体,这样,我们所有的效果都实现了。


5.总结

虽然说效果基本是实现了,但是项目依然有不足的地方,需要好好打磨,比如,假如更新过程中突然断网了,就会出现更新了一部分,还有一部分没有更新完成,这样可能会导致软件本体打不开,或者打开后异常,我其实也想好好完善这些功能,但是,自己的工作中,各种烦人的事实在太多了,每次下班基本什么都不想弄了,这个帖子也一直拖了很久才更新完,如果你发现代码有那些bug ,或者需要完善的地方,欢迎评论告诉我,谢谢。

项目源码:点击下载

源码有错误,或者疑问的,可以随时私信给我,我看到了都会回复的,谢谢


结束

如果这个帖子对你有用,欢迎 关注 + 点赞 + 留言,谢谢

end


原文链接C# Winform自动更新_winform自动更新程序-CSDN博客

猜你喜欢

【C#】C# Winfrom 常用功能整合-2
目录Winfrom&nbsp;启动一个外部exe文件,并传入参数Winform ListBox用法HTTP下载文件(推荐)Winform HTTP下载并显示进度Winform&nbsp;HTTP下载文件Winform&nbsp;跨线程访问UI组件Winform 改变文字的颜色和大小Winfrom&nbsp;启动一个外部exe文件,并传入参数在我们常用的一些软件中,经常有些软件,双击之后根本打不开,这是因为启动时做了限制,我们需要传入一些参数才能打开,在工作中,这个需求也可以用在软件的自动更新上,
发表于:2024-02-02 浏览:333 TAG:
【C#】Winform NanUI 0.88版本 JS和C#相互调用
目录一、需求版本二、实例JS调用C#注册的只读属性JS调用C#注册的字段JS调用C#注册的同步方法JS调用C#注册的异步方法C#注册一个方法,JS调用并传递参数C#注册一个方法,JS调用并接收C#返回值C#注册一个方法,接收JS的数组参数C#注册一个方法,接收JS的一个函数,执行这个JS函数,并将C#的值传递过去三、结束一、需求在软件的界面和软件逻辑分离后,最重要的就是要处理参数的传递,和函数的调用,因此存在JS中和C#相互调用的需求。版本NanUI 版本:0.8.80.191二、实例using
发表于:2024-02-06 浏览:370 TAG:
【C#】C#超急速举例入门-适用有C/C++语言基础
前提编程环境:vs2022电脑系统:win10学习目的:能看懂c#,不纠结各种细节,快速适应开发。入门篇程序结构变量类型类似c语言,不掌握细节,int,float,double都有。输入输出Console.WriteLine(&quot;变量0:{0}&quot;,&nbsp;para0); var&nbsp;a=Console.ReadLine();&nbsp;运算符几乎相同。sizeof();typeof();取地址,取值:&amp;,*;三元运算符: ? :判断类型:is强制转换:as。注
发表于:2024-01-30 浏览:340 TAG:
【C#】C# Winform Label 控件
目录一、概述二、基本用法1.控件内容显示2.控件的外观3.自定义控件的大小4.控件的内边距&nbsp;5.设置文本的固定位置6.控件的事件结束一、概述Label 控件是 winform 开发中最常用的一个控件,一般用做显示文本,也有时用做打开链接等操作。二、基本用法新建一个 winform 项目,点击 form1 界面,找到工具箱,在工具箱里找到 Label ,拖入到界面即可。1.控件内容显示label 拖入界面中,如下,单击在属性界面就能看到具体的控件属性在这里有两个重要的属性:1.Name在
发表于:2024-02-04 浏览:291 TAG:
【C#】C#调用win10系统自带软键盘的方法
上次做了个笔记是关于调用windows系统自带的触摸键盘的方法:C#调用Windows系统自带触摸键盘的方法_c# 虚拟键盘-CSDN博客除了调用触摸键盘,我们也可以通过调用win10的自带软键盘作为输入途径。方法很简单。1、添加using System.Diagnostics引用。2、创建进程Process Winvirkey = Process.Start(&quot;osk.exe&quot;);3、打开键盘:Winvirkey = Process.Start(&quot;osk.exe&amp;
发表于:2024-01-29 浏览:357 TAG:
【C#】C# 自动更新(基于FTP)
目录一、前言二、功能的实现1.本地黑名单2.读取配置文件3.读取 FTP 文件列表4.读取本地文件5.匹配更新6.版本的切换三、环境搭建四、常见问题2023.12.30 更新结束效果启动软件后,会自动读取所有的 FTP 服务器文件,然后读取本地需要更新的目录,进行匹配,将 FTP 服务器的文件同步到本地Winform 界面一、前言在去年,我写了一个 C# 版本的自动更新,这个是根据配置文件 + 网站文件等组成的框架,以实现本地文件的新增、替换和删除,虽然实现了自动更新的功能,但用起来过于复杂,代
发表于:2024-02-03 浏览:342 TAG:
【C#】C# Winform 日志系统
目录一、效果1.刷新日志效果2.单独日志的分类3.保存日志的样式二、概述三、日志系统API1.字段Debug.IsScrollingDebug.VersionDebug.LogMaxLenDebug.LogTitleDebug.IsConsoleShowLog2.方法Debug.Log(string)Debug.Log(string, params object[])Debug.Logs(string)Debug.Logs(string, params object[])Debug.LogSav
发表于:2024-02-18 浏览:361 TAG:
【C#】C# Winfrom 右键菜单
目录一、概述二、新建&nbsp;winform 项目三、给图片控件添加右键菜单四、给菜单添加点击事件五、测试结束一、概述ContextMenuStrip 是 Windows 窗体应用程序中的一个控件,它提供了一个弹出菜单,用于在用户右键单击控件或其他界面元素时显示上下文相关的选项。它通常用于在图形用户界面中提供快捷操作和功能。ContextMenuStrip 控件可以通过在 Visual Studio 的设计器中拖放方式添加到窗体上,或者通过编程方式创建和配置。它可以与其他控件(如按钮、文本框等
发表于:2024-02-02 浏览:354 TAG:
【C#】C# Winform 文本面板带滚动条
在PC软件开发中经常有这样的需求,需要在一个固定大小的面板中显示一些内容,并且面板能上下拖动,将所有的内容完整的展示,有点类似网页上看新闻,如果要在 winfrom 中要如何实现的呢,下面就演示如何实现的吧效果:1.新建一个winform 项目,在界面中拖入一个Panel&nbsp;将 panel1 的&nbsp;AutoScroll 设置为 True2.再次拖入一个&nbsp;Panel ,将高度拉长,这时就自动出现了滚动条,只是此时里面还没有任何内容,下面就在 panel2 中加入一点内容。
发表于:2024-02-03 浏览:323 TAG:
【C#】C# Winfrom 常用功能整合-1
目录Winform 最大化遮挡任务栏和全屏显示问题Winfrom 给图片画 矩形,椭圆形,文字Winfrom TabControl选项卡 动态添加,删除,修改Winform ErrorProvider控件Winform 读取Resources图片Winfrom 读取内存条占用大小,硬盘占用大小Winform 全局捕获异常Winform 用线程写入TXT文件,并更新UI和进度Winform 摄像头识别二维码,保存图片Winform 判断窗体是否已打开Winform 动态添加菜单列表,点击切换对应面
发表于:2024-02-02 浏览:397 TAG: