自动更新必须分四步:检查版本、下载文件、替换旧程序、重启进程;直接覆盖运行中.exe会失败,需用独立Updater.exe等待旧进程退出后复制并启动新程序。

更新逻辑必须拆成「检查 + 下载 + 替换 + 重启」四步走
直接在主程序里覆盖自己.exe文件会失败,Windows会锁住正在运行的进程文件。所以不能“边下边替”,必须让旧进程主动退出、由新进程完成替换——这是所有C#自动更新绕不开的前提。
常见错误现象:System.UnauthorizedAccessException(提示“进程正被另一个进程使用”)、File is being used by another process、更新后界面卡死但没重启。
- 检查版本:用HTTP GET请求比对远程
version.json或API接口,别硬编码URL,配置进appsettings.json - 下载文件:用
HttpClient而非WebClient(后者已标记为过时),启用Progress监听进度,避免UI假死 - 替换文件:把新exe下载到临时目录(如
Path.Combine(Path.GetTempPath(), "MyAppUpdate")),再用独立启动器(.exe或PowerShell脚本)执行复制+删除+重启 - 重启触发:主程序调用
Process.Start("Updater.exe", "--restart")后立即Environment.Exit(0),不要Application.Exit()(WinForms)或MainWindow.Close()(WPF),它们不保证进程退出
用独立Updater.exe比in-process更新更可靠
很多人试图用Assembly.LoadFrom或反射加载新DLL来热更新,这在桌面应用中基本不可行——资源句柄、窗体实例、静态字段状态全会错乱,且.NET Core/.NET 6+默认禁用LoadFrom。
真正稳定的做法是写一个极简的控制台程序Updater.exe,只做三件事:等主程序退出、复制新文件、启动新主程序。
- 它不需要引用你的主程序任何DLL,编译目标设为
net6.0或net8.0即可,体积可压到100KB以内 - 启动参数传入原程序路径(
args[0])、新exe路径(args[1])、是否静默(--silent) - 关键点:用
Task.Delay(1000)等主程序完全释放文件句柄后再执行File.Copy(..., true),否则仍可能报错IOException - 复制完成后调用
Process.Start(args[1]),然后Environment.Exit(0),不留残留进程
重启后丢失命令行参数或工作目录怎么办
主程序关闭前,必须把当前启动参数、工作目录、甚至窗口状态序列化存到临时文件(如update_state.json),否则Updater.exe重启时只能拿到裸路径,用户打开的文档、调试模式、管理员权限等全丢了。
常见错误现象:更新后程序回到初始状态、上次打开的文件没恢复、以普通用户身份重启导致需要提权的功能失效。
- 保存内容至少包括:
Environment.GetCommandLineArgs()、Environment.CurrentDirectory、AppDomain.CurrentDomain.BaseDirectory - 用
JsonSerializer.Serialize写入临时目录,文件名加时间戳防冲突(如update_state_20241105_1423.json) -
Updater.exe启动主程序时,把临时文件路径作为最后一个参数传入;主程序启动时检查该参数是否存在,存在则读取并还原上下文 - 注意清理:主程序成功启动后,立刻
File.Delete该临时文件,避免下次误读
HTTPS证书校验和更新包签名验证不能省
跳过SSL验证(如设置HttpClientHandler.ServerCertificateCustomValidationCallback = (a,b,c,d) => true)等于给中间人攻击开绿灯;不验签名则恶意服务器可下发任意代码——这两项在企业环境或上架商店时是硬性要求。
容易被忽略的点:很多开发者只校验HTTP响应状态码200,却没校验证书链有效性或签名哈希值。
- 证书校验:保持
HttpClientHandler默认行为(即严格校验),开发测试时若用自签证书,改用dotnet dev-certs https --trust而非关校验 - 签名验证:发布时用
signtool sign /fd SHA256 /f cert.pfx /p password MyApp.exe,更新流程中用AuthenticodeVerifier.Verify(需NuGet引用Microsoft.CodeAnalysis)或调用PowerShell -Command "Get-AuthenticodeSignature"检查 - 哈希校验:服务端返回的
version.json里必须带sha256字段,客户端下载完新exe后用SHA256.HashData(File.ReadAllBytes(path))比对,不一致就删掉重下
最难调的其实是时机:主程序何时算“彻底退出”,Updater.exe何时算“可以安全覆盖”。多试几次Process.WaitForExit(3000)加Thread.Sleep(500)组合,比纯靠运气强。











