简单的自动更新:让您的应用程序通过 2 行代码自行更新






4.80/5 (45投票s)
让您的应用程序能够从远程源自行更新,这再简单不过了。
引言
在一次长达 56 公里的旅程去更新我客户新收银机的软件后,以及未来还会有更多更新,我决定需要添加一个自动更新组件。 Eduardo Oliveira 于 2006 年的文章看起来很有希望,但我想要一个更简单的,甚至可以更新更新程序本身的版本。
目标
这就是我想要的
- 通过 HTTP 检查远程站点是否有新版本。
- 如果存在新版本,将其下载为 ZIP 文件。
- 在覆盖任何内容之前确保下载成功。
- 使其易于作为组件添加到任何应用程序中。
- 允许更新应用程序本身。
- 不需要引导程序或多步过程。
- 抵抗篡改。
- 支持一些简单的日志记录。
- 单一 XML 文件配置。
我只有几个小时的时间来构建一些东西。这是我最终的成果。
工作原理
Updater
类负责所有繁重的工作。它首先加载一个 XML 清单,该清单提供了它执行工作所需的所有信息。默认情况下,它会在应用程序路径中查找一个名为 update.xml
的文件。清单由 Manifest
类表示。
<?xml version="1.0" encoding="utf-8" ?>
<!-- Increment the version for each update. -->
<Manifest version="3">
<!-- Your application will check for updates every (seconds) -->
<CheckInterval>900</CheckInterval>
<!-- The URI to the remote manifest -->
<RemoteConfigUri>https://remote.update.net/myapp/update.xml</RemoteConfigUri>
<!-- This token must be the same at both ends to avoid tampering -->
<SecurityToken>D68EF3A7-E787-4CC4-B020-878BA649B4CD</SecurityToken>
<!-- All payload files are assumed to have this URI prefix. -->
<BaseUri>https://remote.update.net/myapp/</BaseUri>
<!-- One or more files containing updates. -->
<Payload>myapp.zip</Payload>
</Manifest>
本地和远程清单的格式相同。目前,有效负载必须是 ZIP 文件,并且它们的目录结构应相对于应用程序的根目录。例如,foo\bar.exe
将被放在应用程序的 foo
目录中。
更新程序创建一个 System.Threading.Timer
,该计时器按照清单中设置的间隔触发。当发生这种情况时,会创建一个新线程来执行 Check
方法。同时,应用程序在前台继续运行,不会中断。
Check
获取远程清单,检查 SecurityToken
是否被篡改,并将远程清单的版本与本地清单的版本进行比较。如果远程版本较新,则执行 Update
方法。
Update
创建一个工作目录,下载远程清单中指定的所有有效负载,并将它们解压缩。它还将远程清单复制到工作目录,因为这将成为新的本地清单。
现在来个绝招。我们如何解决先有鸡还是先有蛋的问题?其中一个有效负载可能包含对您应用程序可执行文件的替换,但它在运行时无法被覆盖。这由操作系统强制执行。但是,Windows 确实(原因不明)允许重命名正在运行的可执行文件!首先,我们将应用程序重命名为 [application].exe.bak
,然后我们将该文件复制回 [application].exe
。文件锁定已转移到备份文件,因此如果有效负载包含替换文件,它将覆盖 [application].exe
。如果它没有被替换,则不会造成损害。
要更新应用程序,我们将工作目录中的所有内容复制到应用程序目录,然后删除工作目录。
最后,应用程序以新的 Process
启动,当前进程关闭。
Using the Code
向您的项目添加对 RedCell.Diagnostics.Update.dll
的引用。
添加到您的应用程序的启动代码中
var updater = new RedCell.Diagnostics.Update.Updater();
updater.StartMonitoring();
创建一个 XML 清单,并将其同时放在您的应用程序目录和远程服务器上。
您已完成。
但是这是怎么回事?
我提供了一个简单的调试工具,或者如果您希望添加用户界面来告知用户正在发生什么。
using RedCell.Diagnostics.Update;
// Log activity to the console.
Log.Console = true;
// Log activity to the System.Diagnostics.Debug facilty.
Log.Debug = true;
// Prefix messages to the above.
Log.Prefix = "[Update] "; // This is the default.
// Send activity messages to the UI.
Log.Event += (sender, e) => GuiMessageBox.Show(e.Message);
演示
如果您下载并运行演示应用程序,您将看到以下内容
You are running version 1 of this console application.
Loaded on PID 3232.
Initializing using file 'F:\Dropbox\Red Cell Innovation\Code Project\Updater\Updater\RedCell.Diagnostics.Update.Demo\4\update.xml'.
Starting monitoring every 900s.
The main thread is going to wait for a keypress.
Check starting.
Fetching 'https://codeproject.org.cn/script/Membership/Uploads/1740717/update.xml'.
Remote config is valid.
Local version is 1.
Remote version is 2.
Remote version is newer. Updating.
Updating '1' files.
Fetching 'RedCell.UI.Controls.Demo-Version2.zip'.
Renaming running process to 'F:\Dropbox\Red Cell Innovation\Code Project\Updater\Updater\RedCell.Diagnostics.Update.Demo\4\UpdateDemo.exe.bak'.
installing file 'Program.cs'.
installing file 'update.xml'.
installing file 'update2.xml'.
installing file 'UpdateDemo.exe'.
Deleting work directory.
Spawning new process.
New process ID is 10060
Old process ID is 3232, closing.
Trying Process.CloseMainWindow().
Trying Process.Close().
Trying Environment.Exit().
You are running version 2 of this console application.
Loaded on PID 10060.
Initializing using file 'F:\Dropbox\Red Cell Innovation\Code Project\Updater\Updater\RedCell.Diagnostics.Update.Demo\4\update.xml'.
Starting monitoring every 900s.
The main thread is going to wait for a keypress.
Check starting.
Fetching 'https://codeproject.org.cn/script/Membership/Uploads/1740717/update.xml'.
Remote config is valid.
Local version is 2.
Remote version is 2.
Versions are the same.
Check ending.
结论
这对于几个小时的工作来说不算差。有一些未经项目要求的改进领域,例如
- 虽然可执行文件可以更新,但任何其他打开的文件则不能。
- 文件未进行完整性检查(例如,通过比较哈希值)。
- 文件在成功解压之前不会被覆盖,但仍然没有回滚或备份机制。
- 这并非针对安装在
Program Files
中的应用程序。这些目录是写保护的,需要 UAC 才能写入。稍后将详细介绍。
关注点
为了针对 .NET 3.5 Framework,我使用了第三方 精简版的 DotNetZip 组件,该组件在 Microsoft 公共许可证 (MS-PL) 下获得许可。ZIP 压缩直到 .NET Framework 4.5 版本才引入。
历史
- 1.0.0 – 2014 年 2 月 22 日 – 首次发布。