前言
接过上一章节,sc.exe
进行Windows
中,进行系统程序服务化的操作方式不难发现,直接进行基础工具服务化操作,一个是人狠话不多,出错不好说
,一个就是不太优雅,每回都需要自己手动去输入配置信息,例如:sc [Service name]
程序路径,服务方式等内容,啥也别说,改成脚本引导,交互式输入,以下是脚本的几个需求:
-
启动脚本出现输入引导信息
-
输入相关服务配置
-
完成对目标程序服务操作(注册、启动、停止和删除)
准备作业
脚本是啥?在系统中又叫批处理脚本,用于实现系统使用过程中,通过系统Dos
指令实现部分功能需求的可执行文件,相关交互指令被称之为批处理脚本,此处简称脚本,在Windows
系统中,脚本文件常见以.bat
和.cmd
作为文件后缀
要写当然啥不会肯定不行,先简单学一学指令,笔者整理了相关的基本语法,如下:
变量
变量的主要目的是为了接受在批处理文件外部接受输入值和处理文件内部对变量的设置和使用
系统变量
%CD% 获取当前路径
%PATH% 获取命令搜索路径/全局环境变量中的PATH
%DATE% 获取当前日期
%TIME% 获取当前时间
%ERRORLEVEL% 获取上一个命令的执行结果码
输出内容echo
>echo %DATE%
2021/07/16 周五
设置变量(局部)set
set 变量名称=变量值
>set test=12
>echo %test%
12
基本指令
以下介绍仅仅包含当前需要实现的批处理文件需要用到的指令,并不是完整指令细化,具体参考可以阅读对应的微软文档参考链接
注释
::注释内容
输入
参考链接:https://docs.microsoft.com/zh-cn/windows-server/administration/windows-commands/set_1
set
显示、设置或删除 cmd.exe
环境变量。 如果不使用参数,则 set 将 显示当前环境变量设置
set [<variable>=[<string>]]
set [/p] <variable>=[<promptString>]
set /a <variable>=<expression>
<variable>
指定要修改或设置的环境变量名称
<string>
实际需要给变量设置的变量值
>set test=aaa
>echo %test%
aaa
/p
将用户输入行作为对应需要设置的变量的值
创建一个setcase.bat
,CHCP 65001
目的是为了设置当前cmd
窗口的当前代码页
设置为utf-8
,同时把bat
脚本编码设置为utf-8
内容如下:
@echo off
CHCP 65001
set /p uname=请输入用户名称:
echo 用户名:%uname%
执行sctest.bat
>sctest.bat
Active code page: 65001
请输入用户名称:张珊
用户名:张珊
需要注意的是,如果执行后输出内容为中文乱码,那么极为可能是,当前bat
的文件编码和cmd
窗口编码不一致造成,可通过CHCP 编码
进行编码统一,上述案例设置的是65001
表示输出脚本当前窗口编码为utf-8
,和bat
的编码保持一致
输出
echo
参考链接:https://docs.microsoft.com/zh-cn/windows-server/administration/windows-commands/echo
显示消息或打开或关闭命令回显功能。 如果不使用参数, echo 将显示当前的回显设置,默认为on
echo [<message>]
echo [on | off]
显示信息(输出信息)
>echo Hello World
echo Hello World
控制是否显示命令提示符,在dos
环境下直接执行该指令有效,为防止在bat
文件中显示
echo [on | off]
执行echo
用于显示当前回显状态
>echo
ECHO 处于打开状态。
创建一个echo.bat
文件内容如下:
echo hellw world
pause
保存后,双击执行,输出结果如下:
bat文件路径>echo hellw world
hellw world
bat文件路径>pause
关闭回显,防止批处理文件中的所有命令 (包括 echo off
命令) 在屏幕上显示在批处理文件类型的第一行,echo.bat
修改如下:
@echo off
echo hellw world
pause
保存后,直接双击bat
执行效果如下:
hellw world
请按任意键继续. . .
暂停
pause
参考链接:https://docs.microsoft.com/zh-cn/windows-server/administration/windows-commands/pause
暂停批处理程序的执行,并显示提示, Press any key to continue . . .
,按任意键继续执行后续的执行
>pause
请按任意键继续. . .
退出
exit
参考链接:https://docs.microsoft.com/zh-cn/windows-server/administration/windows-commands/exit
退出命令解释器或当前批处理脚本
exit [/b] [<exitcode>]
/b
为退出当前批处理脚本,而不是退出cmd.exe
,如果从批处理脚本外部执行或直接执行批处理脚本,则退出 cmd.exe
<exitcode>
指定数值, 如果指定了 /b
,则 ERRORLEVEL
环境变量设置为该数字; 如果要退出命令解释器,则进程退出代码将设置为该数字
批处理脚本exit.bat
内容如下:
@echo off
echo hellw world
exit /b
直接双击运行bat
,看似无反应,实际是脚本执行结束后,退出了了
通过命令提示符切换到改exit.bat
所在目录下,执行exit.bat
,结果输出如下:
>sctest.bat
hellw world
>
命令提示符cmd.exe
并未退出
判定
if
参考链接:https://docs.microsoft.com/zh-cn/windows-server/administration/windows-commands/if
在批处理程序中执行条件处理
if [not] ERRORLEVEL <number> <command> [else <expression>]
if [not] <string1>==<string2> <command> [else <expression>]
if [not] exist <filename> <command> [else <expression>]
启用了命令扩展,语法如下:
if [/i] <string1> <compareop> <string2> <command> [else <expression>]
if cmdextversion <number> <command> [else <expression>]
if defined <variable> <command> [else <expression>]
not
当条件为false
,对应判定语句才能够执行内部的相关操作
>if not false==true echo the condition is false
the condition is false
errorlevel
表示cmd.exe
上一个执行程序的返回的退出代码对应的数字
>echo %errorlevel%
0
>if not %errorlevel%==1 echo the condition is false
the condition is false
exist
判定特定路径文件是否存在,存在则返回true
,命令提示符工作目录下存在文件sctest.bat
>if exist sctest.bat echo the condition is true
the condition is true
else <express>
不符合if
条件判定时的其他情况,执行对应的相关操作语句,执行语句需要用()
进行囊括在一个块中,否则else
将执行无效
>if exist echo.bat (echo the condition is true) else (echo the condition is false)
the condition is false
执行批处理文件
call
参考链接:https://docs.microsoft.com/zh-cn/windows-server/administration/windows-commands/call
从一个批处理程序调用另一个批处理程序,而不停止父批处理程序,需要注意的是该指令在命令提示符中执行无效
call [drive:][path]<filename> [<batchparameters>] [:<label> [<arguments>]]
[<drive>:][<path>]<filename>
需要指定的bat
的路径和名称,需要指明批处理文件的文件后缀,name.bat
或name.cmd
<batchparameters>
指定批处理程序所需的任何命令行信息
示例:
创建一个child.bat
,内容如下:
echo helloworld~
同目录下,再创建一个parent.bat
,调用child.bat
@echo off
CHCP 65001
echo 开始调用子批量处理文件
call child.bat
echo %errorlevel%
echo 完成子批量处理文件
pause
执行parent.bat
>parent.bat
Active code page: 65001
开始调用子批量处理文件
helloworld~
完成子批量处理文件
Press any key to continue . . .
:<label>
指定批处理跳转的标签名称
<arguments>
指定要传递给批处理程序的新实例(从开始)的命令行信息 :<label>
批处理参数
%~1 展开 %1 并删除周围的引号
%~f1 将 %1 扩展到完全限定的路径
示例:
创建文件callself.bat
,文件内容如下,以执行方式跳转标签并传递参数
@echo off
CHCP 65001
call :labelname "参数01"
echo 1
echo 2
if %errorlevel%==1 (
exit /b
)
:labelname
echo 3
echo 4
exit /b 1
输出结果
>callself.bat
Active code page: 65001
3
参数01
4
1
2
定向跳转
goto
将cmd.exe
定向到批处理程序中带标签的行
参考链接:https://docs.microsoft.com/zh-cn/windows-server/administration/windows-commands/goto
goto <label>
<label>
指定一个文本字符串,该字符串用作批处理程序中的标签
更改当前工作路径
获取当前bat
所在路径,切换该路径为工作目录
cd /d %~dp0
实现逻辑
将脚本文件作为一个简单的指令入口,将指令依据实际的操作标识字符串进行访问到对应的子批量处理文件,例如输入install
就开始执行安装服务的安装引导,其他操作同理,由于脚本是由多个批处理文件组成,所有批处理结构如下:
判定服务是否存在
查询服务
>sc query servicename
当服务不存在时,输出如下:
>sc query sct
[SC] EnumQueryServicesStatus:OpenService FAILED 1060:
The specified service does not exist as an installed service.
若只是需要获取错误码,则设置输出为nul
>sc query sct >nul
>
>echo %errorlevel%
1060
因此可以采用上述方式对服务是否安装进行判定sc query servicename >nul
创建isexist.bat
作为判定服务脚本
@echo off
::CHCP 65001
::判定服务是否存在
set name=%~1
::echo %name%
sc query %name% >nul
exit /b
输入判定
@echo off
::CHCP 65001
::接收外部参数
set option=%~1
:setname
::输入服务名称
set /p servicename=请输入需要%option%的服务名称:
::判定服务名称是否为空
if "%servicename%"=="" (
echo 服务名称不能为空
goto setname
)
call isexist.bat %servicename%
if %errorlevel%==1060 (
echo 服务名称不存在或服务未安装
goto setname
)
安装
创建一个install.bat
文件,具体内容如下:
@echo off
CHCP 65001
::添加跳转标签
:path
set /p startup=请输入服务目标程序路径:
if not exist %startup% (
echo %startup%路径不存在,请重新输入
goto :path
)
::通过跳转传递路径参数
call :filename %startup%
::判定执行是否合理
if %errorlevel%==1 (
exit /b
)
:filename
::默认服务名
set file_name=%~n1
::echo %file_name%
:setname
::配置服务名称
set /p servicename=请输入服务名称(默认%file_name%):
::配置服务显示名称
if "%servicename%"=="" (
set servicename=%file_name%
)
::判定服务名称是否已存在
call isexist.bat %servicename%
if %errorlevel%==1060 (
goto setname
)
::配置服务描述
set /p discription=请输入服务描述(默认%file_name%):
if "%discription%"=="" (
set discription=%file_name%
)
::配置服务启动模式
set mode=demand
::配置服务启动模式
set /p servicemode=请输入服务启动模式(默认%mode%手动):
if "%servicemode%"=="" (
set servicemode=%mode%
)
::安装服务
sc create %servicename% binPath= %startup% start= %servicemode% DisplayName= %servicename%
::修改描述
sc description %servicename% %discription%
exit /b 1
需要注意的是,if条件语句执行体如果包含多条执行语句时,需要用使用()
进行包裹,同时,与条件语句之间需要用空格进行分隔,同时必须是同行中添加(
,否则将出现异常The syntax of the command is incorrect.
这个常规异常
启动
@echo off
::CHCP 65001
set option="启动"
:::setname
::::输入服务名称
::set /p servicename=请输入需要%option%的服务名称:
::::判定服务名称是否为空
::if "%servicename%"=="" (
::echo 服务名称不能为空
::goto setname
::)
call input.bat %option%
sc start %servicename%
exit /b 0
停止
@echo off
::CHCP 65001
set option="停止"
call input.bat %option%
sc stop %servicename%
exit /b 0
卸载
@echo off
::CHCP 65001
set option="卸载"
call input.bat %option%
::执行服务删除操作
sc delete %servicename%
exit /b 0
组合
@echo off
CHCP 65001
::获取参数
set cmd=%1
::判定指令
if "%cmd%"=="" (
echo 输入%cmd%无效
goto help
)
::安装服务
if "%cmd%"=="install" (
call install.bat
goto finish
)
::卸载服务
if "%cmd%"=="uninstall" (
call uninstall.bat
goto finish
)
::启动服务
if "%cmd%"=="start" (
call start.bat
goto finish
)
::停止服务
if "%cmd%"=="stop" (
call stop.bat
goto finish
)
else (
::帮助指令
echo 输入%cmd%无效
:help
echo 输入help查看对应指令
echo scutil [command]
echo [command]如下:
echo install 安装服务
echo uninstall 卸载服务
echo start 启动服务
echo stop 停止服务
)
::退出执行
:finish
exit /b 0
测试运行
管理员启动cmd
,切换目录到scutil.bat
对应的目录,执行安装指令,当前服务测试路径为E:\Study\Servers\sctest\sctest.exe
,sctest.exe
为上一个章节的服务程序,完全符合windows服务化要求
>scutil install
Active code page: 65001
请输入服务目标程序路径:"E:\Study\Servers\sctest\sctest.exe"
请输入服务名称(默认sctest):
请输入服务描述(默认sctest):
请输入服务启动模式(默认demand手动):
[SC] CreateService SUCCESS
[SC] ChangeServiceConfig2 SUCCESS
查看本地服务sc query sctest
>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
运行程序
>scutil start
Active code page: 65001
请输入需要启动的服务名称: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 : 14244
FLAGS :
对应目录下,生成一个当前日期的内容输出文件,内容为不断追加的时间值
当前时间:Service Start
当前时间:22:03:56
当前时间:22:03:57
当前时间:22:03:58
当前时间:22:03:59
当前时间:22:04:00
当前时间:22:04:01
当前时间:22:04:02
当前时间:22:04:03
当前时间:22:04:04
当前时间:22:04:05
停止程序
>scutil stop
Active code page: 65001
请输入需要停止的服务名称: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
卸载程序
>scutil uninstall
Active code page: 65001
请输入需要卸载的服务名称:sctest
[SC] DeleteService SUCCESS
>sc query sctest
[SC] EnumQueryServicesStatus:OpenService FAILED 1060:
The specified service does not exist as an installed service
以上就是本章对于将sc.exe
实现脚本化,进而实现程序服务化的交互式操作
总结
这些脚本虽然看似鸡肋,只是将指令进行了二次封装,实际上,这是笔者第一个比较系统化的对脚本的实践和应用,能够将这些指令以类似简单编程的方式进行处理也是一种经验的积累,后续将继续讲解笔者接触到的Windows
服务化的其他工具的使用和思考
instsrv
与srvany
Winsw
Nssm
获取上述内容中的服务测试源码项目,可关注私信或直接评论回复【sc.bat
】