直接继承servicebase创建windows服务需配合sc.exe或installutil注册到scm;onstart内不可阻塞,须用线程或task;.net 6+推荐usewindowsservice()但需正确配置sdk和日志。

如何用 ServiceBase 创建可安装的 Windows 服务
直接继承 ServiceBase 是最稳妥的起点,但光写类不够——必须配合安装工具(如 sc.exe 或 InstallUtil.exe)注册到 SCM(服务控制管理器)。Visual Studio 模板生成的项目默认含 ProjectInstaller,但它在 .NET Core/.NET 5+ 中已弃用,新项目应改用 ServiceBase.Run() + 命令行安装。
关键点:
-
OnStart(string[] args)内不能阻塞,需启动后台线程或Task;否则 SCM 会判定启动超时(默认 30 秒),报错Windows could not start the service on Local Computer. Error 1053: The service did not respond to the start or control request in a timely fashion. - 服务主体逻辑建议封装进独立类,
ServiceBase仅负责生命周期桥接,便于单元测试 - 调试时别双击 exe——必须以管理员权限运行
sc start <servicename></servicename>或用 Visual Studio 的“调试 → 附加到进程”连上svchost.exe(注意筛选服务名)
sc create 安装服务时路径和权限的硬性要求
sc create 是绕过 InstallUtil 的轻量方案,但路径空格、反斜杠、权限三者极易出错。
常见失败场景:
- 可执行文件路径含空格却未用英文双引号包裹:
sc create MySvc binPath= C:My AppMySvc.exe→ 必须写成sc create MySvc binPath= "C:My AppMySvc.exe" -
binPath中的反斜杠必须是双反斜杠(\)或正斜杠(/),单反斜杠会被 sc 解析为转义符 - 服务账户默认是
LocalSystem,若需访问网络路径或数据库,得显式指定obj= "NT AUTHORITYNetworkService"或域账户,并确保该账户对程序目录有读/执行权限
.NET 6+ 中用 HostBuilder 托管 Windows 服务的兼容写法
新版 SDK 支持 Microsoft.Extensions.Hosting.WindowsServices 包,但不是简单加 UseWindowsService() 就完事——它依赖 ServiceBase 底层,且只在 Windows 平台生效。
必须检查的细节:
- 项目 SDK 必须设为
<project sdk="Microsoft.NET.Sdk"></project>(非Web或WorkerSDK),否则UseWindowsService()无效果 - 调用
UseWindowsService()前,需先调用ConfigureServices()注册所有服务,否则IHostApplicationLifetime等生命周期接口不可用 - 日志输出默认不写入 Windows 事件日志,要手动添加
EventLog提供程序:builder.Logging.AddEventLog(new EventLogSettings { SourceName = "MySvc" }),且首次运行前需用eventcreate /ID 1 /L APPLICATION /SO "MySvc" /T INFORMATION /D "Init"创建事件源
服务中访问 GUI 资源(如弹窗、剪贴板)为什么总是失败
从 Windows Vista 开始,Session 0 隔离机制强制服务运行在无交互会话中,任何尝试显示窗口、调用 Clipboard.GetText() 或操作桌面句柄的操作都会静默失败或抛 UnauthorizedAccessException。
可行替代方案:
- 需要用户交互?改用“启动时自启的普通桌面应用”,通过命名管道或本地 socket 与服务通信
- 需要读取剪贴板?让前台进程主动推送内容到服务监听的
NamedPipeServerStream - 需要触发 UI 动作?用
WTSEnumerateSessions+WTSQueryUserToken切换到当前用户会话再CreateProcessAsUser启动辅助进程(需SeAssignPrimaryTokenPrivilege权限)
这类需求往往暴露了架构问题:服务本就不该承担 UI 职责。把交互逻辑拆出去,比绕过 Session 0 更可靠。










