Inno Setup 安装包Windows服务

Inno Setup 安装包Windows服务

返回目录索引
示例服务程序-HelloWindowsService

在开发服务端程序时,经常需要把程序以 Windows 服务 的方式安装到目标机器。Inno Setup 本身没有直接安装服务的标记,但可以通过 sc.exe 命令或 Pascal 脚本来实现,下面分别介绍两种方案。

方案一:使用sc.exe命令

sc.exe 是 Windows 自带的服务控制命令。通过 [Run] 段在安装完成后创建服务,通过 [UninstallRun] 段在卸载时停止并删除服务。

创建服务

1
2
3
4
5
6
7
8
9
10
11
12
13

[Setup]
AppName=HelloWindowsService
AppVersion=1.0.0.0
;创建服务需要管理员权限
PrivilegesRequired=admin

[Files]
Source: "HelloWindowsService.exe"; DestDir: "{app}"

[Run]
Filename: {sys}\sc.exe; Parameters: "create HelloWindowsService start= auto DisplayName= ""HelloWindowsService"" binPath= ""{app}\HelloWindowsService.exe"""; Flags: runhidden

StackOverflow 上大家普遍反馈,上面这种方式就能正常完成服务的安装与卸载,不需要额外处理。

卸载时停止并删除服务

1
2
3
4
5

[UninstallRun]
Filename: {sys}\sc.exe; Parameters: "stop HelloWindowsService"; Flags: runhidden
Filename: {sys}\sc.exe; Parameters: "delete HelloWindowsService"; Flags: runhidden

卸载时必须先 stop 再 delete,顺序不能颠倒。

sc.exe create 常用参数说明

参考 Microsoft Learn - sc create

参数 说明
type= {own|share|kernel|filesys|rec|interact} 服务类型,默认 own(独立进程)
start= {boot|system|auto|demand|disabled|delayed-auto} 启动类型,默认 demand(手动);auto 为开机自启
error= {normal|severe|critical|ignore} 服务启动失败时的错误级别,默认 normal
binpath= <二进制路径> 服务可执行文件路径,必填
group= <加载顺序组> 服务所属的加载顺序组
depend= <依赖的服务> 服务依赖,多个依赖用 / 分隔,如 depend= mpssvc
obj= <账户名> 服务运行账户,默认 LocalSystem
displayname= <显示名称> 在服务管理器中显示的名称
password= <密码> obj 指定账户的密码

注意:每个选项的等号必须紧跟选项名(start=),等号与值之间必须有一个空格start= auto)。省略空格会导致命令失败。

设置服务描述与故障恢复

创建服务后,还可以通过 sc 子命令设置服务描述和故障恢复策略:

1
2
3
4
5
6
7

[Run]
;设置服务描述
Filename: {sys}\sc.exe; Parameters: "description HelloWindowsService ""示例服务描述"""; Flags: runhidden
;设置故障恢复:1分钟后重启服务,之后每隔1分钟继续重启
Filename: {sys}\sc.exe; Parameters: "failure HelloWindowsService reset= 86400 actions= restart/60000/restart/60000/restart/60000"; Flags: runhidden

注意 actions= 中的时间单位是毫秒reset= 的单位是分钟(如 86400 分钟即 60 天,界面上以天显示)。
个别环境下 sc failure 配合 runhidden 标志可能不生效,若发现未生效可去掉 runhidden[Run] 段默认会等待命令执行完成)。

方案二:使用Pascal脚本完整控制服务

sc.exe 方式简单直接,但无法精细判断命令执行结果;升级覆盖安装时,若旧服务还在运行,直接 sc create 会因服务已存在而失败。此时可以使用 Pascal 脚本(SCM API)完整控制服务的检测、安装、启动、停止与删除。

升级时先停止旧服务

覆盖安装时,旧服务的 exe 文件正被占用,Inno Setup 会在复制文件前检查文件占用并弹出终止进程提示。更稳妥的做法是在 PrepareToInstall 事件中(文件占用检查之前)先停止旧服务:

1
2
3
4
5
6
7
8
9
10

[Code]
procedure PrepareToInstall(var NeedsRestart: Boolean);
var
ResultCode: Integer;
begin
//忽略错误码:服务不存在时 stop 会失败,但不影响安装
Exec(ExpandConstant('{sys}\sc.exe'), 'stop HelloWindowsService', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
end;

若服务未停止(如处于停止挂起状态),可返回 NeedsRestart := True 要求系统重启后继续安装。

使用SCM API服务库

需要更精细的控制(检测服务是否存在、是否在运行、启动/停止/删除服务等),可以引入 Luigi Sandon 的服务库脚本(services_unicode.iss),在 [Code] 段开头 #include 即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

[Code]
//来源:https://stackoverflow.com/a/5416744
#include "services_unicode.iss"

const
SERVICE_NAME = 'HelloWindowsService';
SERVICE_DISPLAY_NAME = 'HelloWindowsService';
SERVICE_EXE = 'HelloWindowsService.exe';

//安装:ssInstall时若旧服务存在则先停止删除,ssPostInstall时创建并启动新服务
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssInstall then
begin
if ServiceExists(SERVICE_NAME) then
begin
if SimpleQueryService(SERVICE_NAME) = SERVICE_RUNNING then
SimpleStopService(SERVICE_NAME, True, False);
SimpleDeleteService(SERVICE_NAME);
end;
end
else if CurStep = ssPostInstall then
begin
SimpleCreateService(SERVICE_NAME, SERVICE_DISPLAY_NAME, ExpandConstant('{app}\' + SERVICE_EXE), SERVICE_AUTO_START, '', '', False, False);
SimpleStartService(SERVICE_NAME, True, False);
end;
end;

//卸载:先停止再删除服务
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
begin
if CurUninstallStep = usUninstall then
begin
if ServiceExists(SERVICE_NAME) then
begin
if SimpleQueryService(SERVICE_NAME) = SERVICE_RUNNING then
SimpleStopService(SERVICE_NAME, True, False);
SimpleDeleteService(SERVICE_NAME);
end;
end;
end;

库中常用的函数:ServiceExistsSimpleQueryServiceSimpleCreateServiceSimpleStartServiceSimpleStopServiceSimpleDeleteService。库文件建议放在脚本同目录下,与 .iss 文件一起维护。

远程服务器上的服务操作

需要操作远程机器上的服务时,sc 命令支持指定服务器名,或使用 Sysinternals 的 psservice 工具:

1
2
3
4
5
6

sc \\server stop service
sc \\server start service

psservice \\server restart service

注意事项

  • 创建/删除服务需要管理员权限,脚本中需设置 PrivilegesRequired=admin
  • binPath 中路径含空格时必须加引号,Inno Setup 参数中字面引号要用 "" 转义(如上文的 binPath= ""{app}\HelloWindowsService.exe"")。
  • 覆盖安装时,服务已存在会导致 sc create 失败(错误码 1072/1073),建议结合 PrepareToInstall 停止服务或在 [Code] 中先删除旧服务。
  • 如果服务程序本身支持自注册(如 .NET 服务通过 --install 参数调用 ManagedInstallerClass),也可在 [Run]/[UninstallRun] 中直接调用服务程序自身的安装/卸载参数,无需依赖 sc.exe

掘:奇葩史