ServiceController构造时服务名必须为注册表中的真实名称(如wuauserv而非“Windows Update”),且需管理员权限、处理异步状态更新、及时Dispose释放句柄。

ServiceController 构造时服务名写错就直接报异常
Windows 服务的“服务名”不是控制面板里看到的显示名称,而是注册表 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\ 下的子项名。比如“Windows Update”服务,真实服务名是 wuauserv,用 DisplayName(如 "Windows Update")去构造 ServiceController 会抛 System.InvalidOperationException:“无法找到服务”。
- 查服务名最稳的方式是命令行:
sc query或Get-Service | Select Name, DisplayName -
new ServiceController("wuauserv")✅;new ServiceController("Windows Update")❌ - 服务名区分大小写?不区分,但建议严格按注册表原样写,避免空格或不可见字符
Start/Stop 调用后状态没立刻变,得轮询或等事件
ServiceController.Start() 和 Stop() 是异步发起请求,调用完立刻查 Status 很可能还是旧值——因为 Windows 服务控制管理器(SCM)需要时间通知服务进程响应。直接判断 Status == ServiceControllerStatus.Running 容易误判。
- 必须用
Refresh()刷新状态,再检查;推荐加简单轮询(最多 10 秒):while (sc.Status != ServiceControllerStatus.Running) { sc.Refresh(); Thread.Sleep(500); } - 更稳妥的做法是监听
ServiceController.ServiceHandle的状态变化,但需 P/InvokeQueryServiceStatusEx,普通场景没必要 - 如果服务依赖其他服务未启动,
Start()会直接抛Win32Exception错误码 1053(服务未及时响应),不是超时
权限不足时所有操作都失败,连查询都会被拒绝
即使只是读取服务状态,.NET 的 ServiceController 默认需要 SeServiceLogonRight 权限——普通用户账户(包括管理员组成员但未提权)默认没有。表现是构造 ServiceController 或调用 Refresh() 时抛 System.UnauthorizedAccessException。
- 最简解法:程序以管理员身份运行(右键 → “以管理员身份运行”)
- 开发调试时别依赖 IDE 默认权限,VS 启动的进程不一定带提升 token;可用
Process.GetCurrentProcess().StartInfo.Verb = "runas"自动提权(需重启自身) - 想免提权?只能改服务的 DACL,给当前用户加
SERVICE_QUERY_STATUS权限,但涉及系统安全策略,生产环境慎用
Dispose 不调用会导致句柄泄漏,尤其在循环中反复创建
ServiceController 包装了 Windows 的服务句柄(SC_HANDLE),不显式释放会在长时间运行的服务管理工具中积累句柄泄漏。GC 最终会回收,但不保证及时,且 Finalize 仅释放句柄,不保证服务状态同步。
- 务必用
using语句包裹:using (var sc = new ServiceController("wuauserv")) { ... } - 若需跨方法持有,记得在业务结束处调用
sc.Dispose();不要依赖析构函数 - 注意:调用
Dispose()后再访问Status会抛ObjectDisposedException,别在 Dispose 后还读属性


















