推荐直接使用 Microsoft.Extensions.Hosting.WindowsServices,因其为微软官方推荐方案,兼容 .NET 6+ 宿主模型、原生集成 DI 与配置,无需额外依赖;Topshelf 已归档,不支持现代 .NET,易出现服务未真正运行、日志失败及安装报错等问题。
To
ps
helf 是个已被官方归档的库(2023 年停止维护),现在直接用
更稳、更轻、无额外依赖。如果你手头已有 Topshelf 项目,迁移成本不高;但新项目别再引入它了。
为什么 Topshelf 现在不推荐用
Topshelf 本质是封装了
的调用逻辑,并提供命令行交互能力。但它长期没适配 .NET 6+ 的宿主模型,也不支持现代 DI 和配置系统原生集成。官方 GitHub 已标记为
,NuGet 上最新版(4.2.1)仍基于 .NET Framework 4.6.1,无法真正跑在 .NET 6+ 的 Windows 服务模式下。
常见现象:
安装后服务状态显示“已启动”,但
根本没执行——其实是 Topshelf 启动了控制台宿主,却没正确切换到 Windows Service 上下文
报错 “Could not load file or assembly 'System.ServiceProcess'”——.NET Core/5+ 默认不带这个程序集
日志写不到事件查看器,
抛
,因为 Topshelf 没帮你提权注册事件源
.NET 6+ 替代方案:用 Worker Service + WindowsServices 包
这是微软当前唯一推荐路径,零学习成本,且天然兼容所有
生态(如
、Quartz.NET、Hangfire.Client)。
操作步骤:
新建项目选
模板(不是 “Windows Service (.NET Framework)”)
NuGet 安装
修改
:在
后加
,再调用
确保发布时选
或
,不要用
(尤其当你引用了 x86 原生 DLL)
关键代码片段:
sc.exe 安装服务时最常踩的坑
不用
,也不用 Topshelf 的
命令。直接用系统自带的
,干净、可控、权限明确。
稿定在线PS
PS软件网页版
下载
典型命令:
注意细节:
后面**必须有空格**,且路径不能带引号(除非含空格,才用英文双引号包裹整个路径)
指定运行账户:
权限最高,但访问网络资源受限;若需连 SQL 或 SMB 共享,请用
并提前赋权
安装前务必以管理员身份运行 CMD/PowerShell,否则
卸载用
,别漏掉已存在的同名服务残留
OnStart 超时失败?你可能根本没在用 Windows Service 模式
很多人以为只要项目类型叫“Windows Service”就自动进服务上下文,其实不是。.NET 6+ 必须显式调用
,否则即使你用
注册了,进程仍是普通控制台应用,只是被 SCM(服务控制管理器)拉起来而已。
验证方式:
任务管理器 → 详细信息 → 找到你的服务进程 → 右键“转到服务”,如果没关联,说明没走 Windows Service 流程
在
开头加一行
,输出
就是没生效
服务启动超时(30 秒)不是因为你代码慢,而是 SCM 根本没收到“服务已就绪”信号——因为宿主没注册服务通知回调
真正容易被忽略的一点:发布后的 EXE 文件名,必须和
里写的
完全一致,包括大小写。Windows 文件系统不区分大小写,但 SCM 解析
时会校验签名和路径有效性,名字对不上会导致静默失败。
Microsoft.Extensions.Hosting.WindowsServicesServiceBaseArchivedExecuteAsynctopshelf installEventLog.WriteEntrySecurityExceptionIHostedServiceBackgroundServiceWorker ServiceMicrosoft.Extensions.Hosting.WindowsServicesProgram.csHost.CreateApplicationBuilderbuilder.Services.AddHostedService() builder.UseWindowsService()win-x64win-x86AnyCPUvar builder = Host.CreateApplicationBuilder(args);
builder.UseWindowsService(); // ← 这一行必须有,且要在 Build() 前
builder.Services.AddHostedService();
var host = builder.Build();
host.Run(); InstallUtil.exeinstallsc.exesc create MyService binPath= "C:\MyService\MyService.exe" start= auto obj= "LocalSystem"binPath=obj=LocalSystemDOMAIN\userAccess Deniedsc delete MyServiceUseWindowsService()sc createWorker.ExecuteAsync_logger.LogInformation("Running as Windows Service: {IsService}", Environment.IsService())falsesc createbinPathbinPath