前言

日常开发工作中,对于已经包含基础功能的系统,需要为前端同事提供测试环境时,常常需要在搭建好测试环境后,保证测试环境的正常运行,如果程序本身是控制台程序,那控制台就需要保证一直开启,关闭控制台程序将影响到测试系统的正常运行,相比于Linux系的相关系统,例如CentosUbuntu,服务化便捷,同时支持也很多,例如SupervisorWindows系统中,就显得比较尴尬,服务化手段较少,而且集中化管理较弱,测试系统演进如下:

  • 人工通过控制台启动

    服务重新部署需要退出控制台,部署后重新打开控制台

  • .bat.cmd脚本启动

    双击脚本(管理员或普通用户),测试系统就启动了,单个系统,单个程序启动忍一忍就过了,辅助的系统和程序一多,维护成本也会逐渐增加

  • 系统服务化启动

    通过注册服务、启动服务、停止服务实现对系统启动细节的忽略,只关心系统的最终执行状态 太阳当空照-Windows服务化方式sc.exe_技术分享

sc.exe简述

sc.exe为服务控制管理service control manager,用于对计算机的系统服务进行管理,实现注册,运行,暂停,自启等基础操作,用户能够将符合Windows系统服务化条件的程序进行本地系统后台服务化运行,注册到服务注册表目录下的程序,通过sc的指令实现程序执行状态的切换和管理。

指令清单

通过cmd注册服务,需要cmd执行管理员权限sc.exe 创建 | Microsoft Docs,执行指令sc,可以看到如下输出:

sc
描述:
        SC 是用来与服务控制管理器和服务进行通信
        的命令行程序。
用法:
        sc <server> [command] [service name] <option1> <option2>...


        <server> 指定服务所在的远程服务器的名称。选项的格式为 "\\ServerName"
        可通过键入以下命令获取有关命令的更多帮助: "sc [command]"
        命令:
          query-----------查询服务的状态,
                          或枚举服务类型的状态。
          queryex---------查询服务的扩展状态,
                          或枚举服务类型的状态。
          start-----------启动服务。
          pause-----------向服务发送 PAUSE 控制请求。
          interrogate-----向服务发送 INTERROGATE 控制请求。
          continue--------向服务发送 CONTINUE 控制请求。
          stop------------向服务发送 STOP 请求。
          config----------更改服务的配置(永久)。
          description-----更改服务的描述。
          failure---------更改失败时服务执行的操作。
          failureflag-----更改服务的失败操作标志。
          sidtype---------更改服务的服务 SID 类型。
          privs-----------更改服务的所需特权。
          managedaccount--更改服务以将服务帐户密码
                          标记为由 LSA 管理。
          qc--------------查询服务的配置信息。
          qdescription----查询服务的描述。
          qfailure--------查询失败时服务执行的操作。
          qfailureflag----查询服务的失败操作标志。
          qsidtype--------查询服务的服务 SID 类型。
          qprivs----------查询服务的所需特权。
          qtriggerinfo----查询服务的触发器参数。
          qpreferrednode--查询服务的首选 NUMA 节点。
          qmanagedaccount-查询服务是否将帐户
                          与 LSA 管理的密码结合使用。
          qprotection-----查询服务的进程保护级别。
          quserservice----查询用户服务模板的本地实例。
          delete----------(从注册表中)删除服务。
          create----------创建服务(并将其添加到注册表中)。
          control---------向服务发送控制。
          sdshow----------显示服务的安全描述符。
          sdset-----------设置服务的安全描述符。
          showsid---------显示与任意名称对应的服务 SID 字符串。
          triggerinfo-----配置服务的触发器参数。
          preferrednode---设置服务的首选 NUMA 节点。
          GetDisplayName--获取服务的 DisplayName。
          GetKeyName------获取服务的 ServiceKeyName。
          EnumDepend------枚举服务依赖关系。

        以下命令不需要服务名称:
        sc <server> <command> <option>
          boot------------(ok | bad)指示是否应将上一次启动另存为
                          最近一次已知的正确启动配置
          Lock------------锁定服务数据库
          QueryLock-------查询 SCManager 数据库的 LockStatus
示例:
        sc start MyService


QUERY 和 QUERYEX 选项:
        如果查询命令带服务名称,将返回
        该服务的状态。其他选项不适合这种
        情况。如果查询命令不带参数或
        带下列选项之一,将枚举此服务。
    type=    要枚举的服务的类型(driver, service, userservice, all)
             (默认 = service)
    state=   要枚举的服务的状态 (inactive, all)
             (默认 = active)
    bufsize= 枚举缓冲区的大小(以字节计)
             (默认 = 4096)
    ri=      开始枚举的恢复索引号
             (默认 = 0)
    group=   要枚举的服务组
             (默认 = all groups)

语法示例
sc query                - 枚举活动服务和驱动程序的状态
sc query eventlog       - 显示 eventlog 服务的状态
sc queryex eventlog     - 显示 eventlog 服务的扩展状态
sc query type= driver   - 仅枚举活动驱动程序
sc query type= service  - 仅枚举 Win32 服务
sc query state= all     - 枚举所有服务和驱动程序
sc query bufsize= 50    - 枚举缓冲区为 50 字节
sc query ri= 14         - 枚举时恢复索引 = 14
sc queryex group= ""    - 枚举不在组内的活动服务
sc query type= interact - 枚举所有不活动服务
sc query type= driver group= NDIS     - 枚举所有 NDIS 驱动程序

这么长,我们需要细细看一看????也可以直接cmd输出sc [commandname]进行用途查看,其中需要注意的是<server>并不是服务名称,而是针对远程服务器的对应服务器名称,本地运行sc.exe不需要此参数,笔者在这儿,抽取常用项,如下:

sc create

描述:
        在注册表和服务数据库中创建服务项。
用法:
        sc <server> create [service name] [binPath= ] <option1> <option2>...
选项:
注意: 选项名称包括等号。
      等号和值之间需要一个空格。
 type= <own|share|interact|kernel|filesys|rec|userown|usershare>
       (默认 = own)
 start= <boot|system|auto|demand|disabled|delayed-auto>
       (默认 = demand)
 error= <normal|severe|critical|ignore>
       (默认 = normal)
 binPath= <.exe 文件的 BinaryPathName>
 group= <LoadOrderGroup>
 tag= <yes|no>
 depend= <依存关系(以 / (斜杠)分隔)>
 obj= <AccountName|ObjectName>
       (默认= LocalSystem)
 DisplayName= <显示名称>
 password= <密码>

sc config

描述:
        在注册表和服务数据库中修改服务项。
用法:
        sc <server> config [服务名称] <option1> <option2>...

选项:
注意: 选项名称包括等号。
      等号和值之间需要一个空格。
      要删除依赖关系,请使用单个“/”表示依赖关系值。
 type= <own|share|interact|kernel|filesys|rec|adapt|userown|usershare>
 start= <boot|system|auto|demand|disabled|delayed-auto>
 error= <normal|severe|critical|ignore>
 binPath= <.exe 文件的 BinaryPathName>
 group= <LoadOrderGroup>
 tag= <yes|no>
 depend= <依赖关系(以 / (正斜杠)分隔)>
 obj= <AccountName|ObjectName>
 DisplayName= <显示名称>
 password= <密码>

sc description

描述:
        设置服务的描述字符串。
用法:
        sc <server> description [service name] [description]

sc start

描述:
        启动服务运行。
用法:
        sc <server> start [service name] <arg1> <arg2> ...

sc query

cmd中直接sc query执行,默认查询出当前系统所有注册的服务信息

描述:
		查询服务信息。
用法:
		sc <server> query [service name] [type= {driver | service | all}] [type= {own | share | interact | kernel | filesys | rec | adapt}] [state= {active | inactive | all}] [bufsize= <Buffersize>] [ri= <Resumeindex>] [group= <groupname>]
选项:
注意:	选项名称包括等号。
type={driver | service | all }
	(默认 = service)
	指定要枚举的内容。 选项包括:
	驱动程序 -指定仅枚举驱动程序。
	服务 -仅指定枚举服务。 这是默认值。
	all -指定同时枚举驱动程序和服务。
type={ own | share | interact | kernel | filesys | rec | adapt }
	
	指定要枚举的服务或驱动程序类型的类型。 选项包括:
	拥有 -指定在其自己的进程中运行的服务。 它不与其他服务共享可执行文件。 这是默认值。
	共享 -指定作为共享进程运行的服务。 它与其他服务共享可执行文件。
	内核 -指定驱动程序。
	filesys -指定文件系统驱动程序。
	rec -指定文件系统识别的驱动程序,该驱动程序标识计算机上使用的文件系统。
	交互 -指定可与桌面交互的服务,接收来自用户的输入。 交互式服务必须在 LocalSystem 帐户下运行。 此类型必须结合使	用 type=自有或type=shared (例如, type=交互****类型 =) 。 使用type=自行交互会生成错误。
state= {active | inactive | all}
	(默认 = active)
	指定要枚举的服务的已启动状态。 选项包括:
	活动 -指定所有活动的服务。 这是默认值。
	非活动 -指定所有暂停或停止的服务。
	全部 -指定所有服务。
bufsize= <Buffersize>
	(默认 = 4096)
	指定枚举缓冲区) 大小 ((以字节为单位)。 默认缓冲区大小为1024个字节。 当查询生成的显示超过1024个字节时,应增加缓冲区的大小。
ri= <Resumeindex>
	(默认 = 0)
	指定枚举开始或恢复的索引号。 默认值为 0(零)。 如果返回的信息与默认缓冲区可显示的信息不同,请将此参数与 bufsize= 参数一起使用。
group= <Groupname>
	(默认 = all groups)
	指定要枚举的服务组。 默认情况下,会枚举所有组。 默认情况下,将 ( * * group = * * ) 枚举所有组。
/?	在命令提示符下显示帮助。

sc pause

描述:
        发送 PAUSE 控制请求到服务。
用法:
        sc <server> pause [service name]

sc continue

描述:
        发送 CONTINUE 控制请求到服务。
用法:
        sc <server> continue [service name]

sc stop

描述:
        发送 STOP 控制请求到服务。
用法:
        sc <server> stop [service name] <reason> <comment>
        <reason> = 服务停止的可选原因代码编号

sc delete

描述:
        从注册表删除服务项。
        如果服务正在运行,或另一进程已经打开
        到此服务的句柄,服务将简单地标记为
        删除。
用法:
        sc <server> delete [service name]

示例

服务案例

案例为笔者自己基于C#,在基于Core3.1创建的控制台程序,用于每隔一秒,项当前目录下的日志文件中换行写入小时:分钟:秒,由于是使用符合Windows服务化标准的相关依赖库,此处略过对类库Microsoft.Extensions.Hosting.WindowsServices的介绍和使用,项目源码可通过公众号进行领取,此处程序目前未为x86,也可以依据需求生成x64

打包生成的文件结构如下:

太阳当空照-Windows服务化方式sc.exe_服务化_02

创建服务

管理员权限运行cmd,执行创建指令,binPath=<空格>尾部添加对应sctest.exe的绝对路径,避免注册服务过程中,路径中的特殊字符无法被正常识别,可对路径外部添加双引号

sc create sctest binPath= "\"E:\Study\Servers\sctest\sctest.exe\""
[SC] CreateService 成功

查看任务管理器中的服务

太阳当空照-Windows服务化方式sc.exe_服务化_03

打开服务,找到sctest服务,查看属性,会发现,binPath实际就是可执行文件的路径

太阳当空照-Windows服务化方式sc.exe_启动服务_04

或者执行query指令

>sc query sctest

SERVICE_NAME: sctest
        TYPE               : 10  WIN32_OWN_PROCESS
        STATE              : 1  STOPPED
        WIN32_EXIT_CODE    : 1077  (0x435)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x0

添加描述

创建服务时,如果为通过指令sc create [service name] binPath= 路径 displayname= "描述内容",进行描述设置,可以通过,sc description进行设置

>sc description sctest "this is sctestservice"
[SC] ChangeServiceConfig2 成功

打开服务查看sctest的描述内容如下:
太阳当空照-Windows服务化方式sc.exe_服务化_05

启动服务

方式一

直接使用sc start [service name]进行服务启动,启动成功输出结果如下:

>sc start sctest

SERVICE_NAME: sctest
        TYPE               : 10  WIN32_OWN_PROCESS
        STATE              : 2  START_PENDING
                                (NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x7d0
        PID                : 32296
        FLAGS              :

方式二

也可以使用net start [service name]进行服务启动

>net start sctest
sctest 服务正在启动 .
sctest 服务已经启动成功。

服务启动成功后,可以看到在sctest.exe同一级别目录下,生成一个[年-月-日].log文件,内容大体如下:

当前时间:Service Start
当前时间:00:29:50
当前时间:00:29:51
.........

停止服务

停止服务可以使用sc,也可以使用net,服务停止后,对应的[年-月-日].log文件内容,也将不再增加

方式一

使用sc stop,在使用sc query查看服务当前状态,或直接去到任务管理器,查看任务页签中服务sctest的状态

>sc stop sctest

SERVICE_NAME: sctest
        TYPE               : 10  WIN32_OWN_PROCESS
        STATE              : 3  STOP_PENDING
                                (STOPPABLE, NOT_PAUSABLE, ACCEPTS_SHUTDOWN)
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x0
        
>sc query sctest

SERVICE_NAME: sctest
        TYPE               : 10  WIN32_OWN_PROCESS
        STATE              : 1  STOPPED
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x0

方式二

使用net stop进行服务停止

>net stop sctest
sctest 服务正在停止.
sctest 服务已成功停止。

删除服务

使用指令sc delete将对应服务进行删除

>sc delete sctest
[SC] DeleteService 成功

关闭任务管理器再次打开,其中将不再能够看到服务sctest,表明服务删除成功

常见问题

服务注册

binPath服务程序执行路径

注册服务时,binPath为必须项,需要指定路径,若是执行文件夹路径包含特殊字符时,需要为路径添加双引号如下:

sc create [service name] binPath= “服务程序绝对路径”

服务启动

启动失败,返回1503

服务注册成功后,启动服务时,服务等待一段时间后,返回程序无响应

[SC] StartService 失败 1053:

服务没有及时响应启动或控制请求。

查看Windows自带的事件查看器,在Windows日志系统可以看到来源Service Control Manager的相关错误信息

太阳当空照-Windows服务化方式sc.exe_启动服务_06

日志内容如下:

太阳当空照-Windows服务化方式sc.exe_服务化_07

造成此类问题的情况很多,目前笔者摸索到的有以下几类:

  • 目标程序本身,在编码级别,并没有通过编码方式,实现程序自身的Windows服务化,简单来说就是,程序不支持sc方式启动

  • 程序必要的程序启动路径,配置文件加载路径,内部数据输出路径等错误,造成程序执行异常

    服务本身在启动时,默认为独立进程启动,实际上依据sc的管理启动,程序的工作目录并不是在当前服务exe所在目录,所以服务程序相关路径最好是以绝对路径方式配置,或者启动服务时参数传入,同时服务程序如果有对应的日志输出,也可以依据本地日志输出查看对应问题细节

服务删除

无法删除服务,提示服务已暂停

使用sc delete删除对应服务时,提示该服务已暂停,此时需要关闭services.msc,重新启动就可以发现,服务已经不存在

获取上述内容中的服务测试源码项目,可关注微信笔者的公众号,回复【sc.exe

太阳当空照-Windows服务化方式sc.exe_驱动程序_08