如何在C#中更新并重启正在运行的Windows服务?
可靠的Windows服务自重启更新方案(.NET 6独立部署)
针对你遇到的.NET 6独立部署Windows服务自更新重启需求,以下是几个经过验证的可靠方案:
方案1:使用独立辅助进程(推荐)
核心思路是让当前服务启动一个脱离自身进程树的轻量级辅助程序,由它负责完成「停止服务→替换二进制文件→启动服务」的完整流程,避免服务自身停止后无法继续执行后续步骤。
实现步骤:
- 编写一个独立的控制台应用(例如命名为
ServiceUpdater.exe),接收服务名称、新版本文件路径等参数,执行更新逻辑:using System.Diagnostics; using System.IO; using System.Threading; if (args.Length < 2) return; var serviceName = args[0]; var newBinaryDir = args[1]; var currentServiceDir = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName); // 等待3秒确保服务完全停止 Thread.Sleep(3000); // 停止目标服务 using var stopProcess = Process.Start(new ProcessStartInfo { FileName = "sc.exe", Arguments = $"stop {serviceName}", CreateNoWindow = true, UseShellExecute = false }); stopProcess?.WaitForExit(); // 替换服务文件(覆盖现有文件) foreach (var file in Directory.GetFiles(newBinaryDir)) { var destPath = Path.Combine(currentServiceDir, Path.GetFileName(file)); File.Copy(file, destPath, overwrite: true); } // 启动目标服务 using var startProcess = Process.Start(new ProcessStartInfo { FileName = "sc.exe", Arguments = $"start {serviceName}", CreateNoWindow = true, UseShellExecute = false }); startProcess?.WaitForExit(); - 在主服务中,检测到新版本后,先将新版本文件下载到临时目录,启动辅助程序并传递参数,随后主动退出服务:
// 检测到新版本后的执行逻辑 var updaterPath = Path.Combine(AppContext.BaseDirectory, "ServiceUpdater.exe"); var tempNewDir = @"C:\Temp\ServiceNewVersion"; // 新版本文件存放路径 var serviceName = "YourTargetServiceName"; var startInfo = new ProcessStartInfo(updaterPath) { Arguments = $"\"{serviceName}\" \"{tempNewDir}\"", CreateNoWindow = true, UseShellExecute = false, WindowStyle = ProcessWindowStyle.Hidden, WorkingDirectory = AppContext.BaseDirectory }; Process.Start(startInfo); // 主动退出服务,让辅助程序完成后续操作 Environment.Exit(0);
方案2:利用Windows任务计划程序
通过创建一次性定时任务,让系统任务计划服务托管更新流程,不受原服务进程生命周期影响,确保服务停止后仍能执行更新和重启操作。
实现步骤:
在主服务中检测到新版本后,生成更新脚本并创建一次性任务:
using System; using System.Diagnostics; using System.IO; var serviceName = "YourTargetServiceName"; var currentServiceDir = AppContext.BaseDirectory; var tempNewDir = @"C:\Temp\ServiceNewVersion"; var updateScriptPath = Path.Combine(currentServiceDir, "update_task.bat"); // 生成批处理脚本 File.WriteAllText(updateScriptPath, $@" @echo off sc stop {serviceName} timeout /t 3 /nobreak >nul xcopy ""{tempNewDir}\*"" ""{currentServiceDir}"" /E /Y /Q sc start {serviceName} del ""{updateScriptPath}"" "); // 创建一次性任务,延迟2秒执行 var taskName = $"ServiceUpdate_{Guid.NewGuid()}"; using var taskProcess = Process.Start(new ProcessStartInfo { FileName = "schtasks.exe", Arguments = $"/create /tn \"{taskName}\" /tr \"{updateScriptPath}\" /sc once /st {DateTime.Now.AddSeconds(2).ToString("HH:mm:ss")} /f /rl highest", CreateNoWindow = true, UseShellExecute = false }); taskProcess?.WaitForExit(); // 主动退出服务 Environment.Exit(0);
方案3:复用服务故障恢复机制
通过让服务以特定退出码主动终止,利用Windows服务的故障恢复配置自动重启服务,同时在服务重启时完成文件替换。
实现步骤:
- 提前配置服务故障恢复策略:
- 打开「服务」控制台,找到目标服务→右键属性→「恢复」标签页
- 设置「第一次失败」为「重启服务」,重启延迟设为3秒
- 主服务检测到新版本后的逻辑:
using System.Diagnostics; using System.IO; var tempNewDir = @"C:\Temp\ServiceNewVersion"; var flagPath = Path.Combine(tempNewDir, "update_flag.txt"); // 下载新版本文件到临时目录 // ...(省略下载逻辑) // 标记需要更新,写入当前服务目录路径 File.WriteAllText(flagPath, AppContext.BaseDirectory); // 写入自定义事件日志,说明是主动更新操作 EventLog.WriteEntry("YourServiceName", "触发版本更新,主动终止服务以执行重启", EventLogEntryType.Information); // 以非0码退出,触发故障恢复重启 Environment.Exit(1); - 在服务
OnStart方法开头添加更新检测逻辑:protected override void OnStart(string[] args) { var tempNewDir = @"C:\Temp\ServiceNewVersion"; var flagPath = Path.Combine(tempNewDir, "update_flag.txt"); if (File.Exists(flagPath)) { var targetDir = File.ReadAllText(flagPath); // 替换服务文件 foreach (var file in Directory.GetFiles(tempNewDir)) { var destPath = Path.Combine(targetDir, Path.GetFileName(file)); File.Copy(file, destPath, overwrite: true); } // 清理临时文件 Directory.Delete(tempNewDir, recursive: true); } // 正常启动服务的业务逻辑 // ... }
内容的提问来源于stack exchange,提问作者Fabian
相关产品推荐
相关产品推荐

