Automatic updates for. NET client programs
When the client programs we write in daily development need to be deployed on multiple hosts, if the program needs to be upgraded, it will be very troublesome to upgrade one by one. At this time, we can use the. NET client program automatic update technology in this article.
本文所述的自动更新技术主要使用了开源的GeneralUpdate组件,可用于Winform/WPF/ConsoleApp等应用程序的自动更新。
GeneralUpdate组件是微软的一位 MVP 负责开发和维护的,仓库地址及截图如下。作者提供的使用文档和视频有些过于简单,而且不同版本还存在一定的兼容性问题,这些都没有很好地解释,所以初次接触这个组件的开发人员可能会有点懵。笔者结合自己在项目中实际的使用情况,更加详细地介绍一下该组件的使用方式。
GitHub:https://github.com/WELL-E/AutoUpdater

Gitee:https://gitee.com/Juster-zhu/GeneralUpdate

Automatic update of flow charts

In view of the fact that the explanation of the original picture is not clear enough, the author added a new explanation in red font in the above picture. The above figure looks like the interaction of three components or services, but there are four to be exact:
- Client program version verification service (optional): This service provides at least two APIs, one is used to determine whether the client program has the latest version, and the other is to obtain all updated versions of the current client. Sometimes we don't want to write and deploy a separate verification service, so we can just replace it with a database. The client program directly queries the database to determine and obtain all updated versions of the current program.
- Client program (required): For business programs that need automatic update functions, you can obtain the version number of its own assembly through reflection and compare it with the server/database to determine whether there is a new version.
- Update component (required): The update component is actually a separate executable file placed in the same directory as the client program. The main function of this component is to download all update compressed packages of the client program from the specified path and decompress them one by one to realize version-by-version upgrade of the client program. When the client obtains the path of the file to be updated from the server, it needs to start the update component through inter-process communication. After the update component is started, the client program needs to be closed to prevent some files from being occupied and causing update failure. After the update component is successfully updated, restart the client and close the component itself to complete the automatic update.
- File server (required): After the update compressed packages of the client program are uploaded to the file server, the URL of each compressed package is obtained, and the update component downloads the program based on this URL. The file server I use is HFS, and the download address is: HFS Download.
Code structure analysis

上图中以GeneralUpdate开头的工程是自动更新功能的核心代码,在 nuget 服务器上能看到各个工程的包。具体使用哪个包取决于你是想实现更新组件自更新还是更新客户端程序还是编写版本校验服务,可参考框架 README.md 中的介绍。
这里要说明的是,上述组件不是向下兼容的!3.x.x 版本的组件的很多方法都进行了更名,因此不能直接从 2.x.x 版本直接升级。
上图中以AutoUpdate开头的工程是对自动更新流程图中 3 个主要组件的简单实现:
ConsoleApp:更新组件的控制台版本 DEMO(需要和文件服务器配合使用,引入了 GeneralUpdate.Core)MauiApp-Sample:未仔细研究,不清楚MinimalService:客户端版本校验服务 DEMO(引入了 GeneralUpdate.AspNetCore)Test:更新组件自更新的 WPF 版本 DEMO(需要和 MinimalService 配合使用,引入了 GeneralUpdate.ClientCore)WpfApp:GeneralUpdate.Single包的使用 DEMO,用于构建单例版本的更新组件(引入了 GeneralUpdate.Single)WpfNet6-Sample:更新更新组件的 WPF 版本程序。
Practical practice of automatic update of Winform applications
从上节的描述可知,如果我们不想编写客户端版本校验服务,只想通过文件服务器来更新客户端程序,那么我们只需要一个控制台版本的更新组件即可,所以可参考ConsoleApp工程下的代码。
Console implementation of updated components
说明:本示例使用的是GeneralUpdate.Core的 2.1.6 版本。因为 GitHub 上的源码已升级到 3.x.x 版本,支持了.NET 6.0,但笔者电脑上的缺乏相关框架,无法编译通过,所以检出到了源码的某次提交,这样即使使用的时候出了问题也可以通过调试源码的方式来解决。如果大家充分理解了本文的意思,直接安装最新版本的 nuget 包也可以,直接参考最新版源码的相关示例。
using System;
using System.ComponentModel;
using System.Diagnostics;
using GeneralUpdate.Core;
using GeneralUpdate.Core.Strategys;
using GeneralUpdate.Core.Update;
using ProgressChangedEventArgs = GeneralUpdate.Core.Update.ProgressChangedEventArgs;
namespace AutoUpdate.ConsoleApp
{
class Program
{
static void Main(string[] args)
{
// args = new []{
// "1.0.1",
// "1.0.2",
// "",
// "http://127.0.0.1:7000/client_v1.0.2.zip",
// @"D:\Project",
// "36aad55a19f85ee6e1fbdc26510a26c1"
// };
KillProcess("你的客户端程序名,不用加exe");
GeneralUpdateBootstrap bootstrap = new GeneralUpdateBootstrap();
bootstrap.DownloadStatistics += OnDownloadStatistics;
bootstrap.ProgressChanged += OnProgressChanged;
bootstrap.Strategy<DefultStrategy>().
Option(UpdateOption.Format, "zip").
Option(UpdateOption.MainApp, "你的客户端程序名,不用加exe").
Option(UpdateOption.DownloadTimeOut, 60).
RemoteAddress(args).
Launch();
Console.ReadKey();
}
private static void OnProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (e.Type == ProgressType.Updatefile)
{
var str = $"当前更新第:{e.ProgressValue}个,更新文件总数:{e.TotalSize}";
Console.WriteLine(str);
}
if (e.Type == ProgressType.Done)
{
Console.WriteLine("更新完成");
}
if (e.Type == ProgressType.Fail)
{
Console.WriteLine(e.Message);
}
}
private static void OnDownloadStatistics(object sender, DownloadStatisticsEventArgs e)
{
Console.WriteLine($"下载速度:{e.Speed},剩余时间:{e.Remaining.Minute}:{e.Remaining.Second}");
}
private static void KillProcess(string processName)
{
foreach (var process in Process.GetProcesses())
{
if (!process.ProcessName.ToUpper().Contains(processName.ToUpper())) continue;
try
{
process.Kill();
process.WaitForExit();
}
catch (Win32Exception)
{
}
}
}
}
}
client calls
Version version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
var ver = $"{version.Major}.{version.Minor}.{version.Build}";
//从数据库获取比当前程序集版本更高的版本信息
var versionInfo = TOSBll.Instance.GetLastUpdateVersionInfo(1, ver);
if (versionInfo != null)
{
string para =
$"{ver} {versionInfo.VERSION} \"\" {versionInfo.URL} {Environment.CurrentDirectory} {versionInfo.MD5}";
ExecuteAsAdmin("AutoUpdate.ConsoleApp.exe", para);
return;
}
private static void ExecuteAsAdmin(string fileName, string args)
{
Process proc = new Process();
proc.StartInfo.FileName = fileName;
proc.StartInfo.UseShellExecute = true;
proc.StartInfo.Verb = "runas";
proc.StartInfo.Arguments = args;
proc.Start();
}
由上述代码可知,客户端使用进程间通信的方式来启动更新组件,并传入更新参数信息。这里通过管理员权限启动更新组件,以免更新失败(组件在更新时需要把文件拷贝到系统的临时目录,更新成功后删除,权限不足时会出错)。不过笔者测试中发现这种方式启动仍然失败,还是通过右键AutoUpdate.ConsoleApp.exe程序并附加管理员权限才成功的。
Several slots
- Critical versions are not labeled, and users who want to switch to the 2.1.6 version of the nuget package do not know which commit to check out.
- The new version of the component is incompatible with the old version.
- The code used in the unit test file is an old version, but the component source code is a new version, which directly confused people who have just come into contact with the component.
- 目前还存在一些小 bug,比如
FileUtil.Update32Or64Libs()就会抛出异常,因为把一个目录删除了两遍,从而导致第一次启动更新的时候更新失败,但是第二次更新的时候却能成功,因为目前已经删了。笔者已提 Issue,不知作者何时能解决。 - Documentation is too simple.
summary
虽然GeneralUpdate组件有一些不足,但相信经过本文的介绍,大家已经知道如何避坑来使用该组件。总体来说,该组件的功能还是蛮好用的。考虑到该组件只有作者一个人维护,其实已经做得蛮好了,还是要感谢作者的付出的。