使用 Inno Setup 升级 Windows 服务

Posted

技术标签:

【中文标题】使用 Inno Setup 升级 Windows 服务【英文标题】:Upgrading Windows service using Inno Setup 【发布时间】:2011-01-28 06:10:42 【问题描述】:

我已经使用 Inno Setup 创建了一个基本的 Windows 服务安装。安装和卸载都可以正常工作。

但是,我在升级过程中遇到了问题。

为了升级服务可执行文件需要停止服务,并且只有在服务完全停止后才能将更新的可执行文件放置在目标文件夹中。

如何执行服务停止命令并等待服务完全停止,然后再启动文件部署步骤?

【问题讨论】:

【参考方案1】:

以下代码来自以下页面:http://www.vincenzo.net/isxkb/index.php?title=Service_-_Functions_to_Start%2C_Stop%2C_Install%2C_Remove_a_Service

但是,我必须应用小修复才能使其正常工作。

请注意,我最初在 2010 年发布了此答案。代码位于 上面的页面是 2011 年更新的,所以可能值得一看。

我在我的安装程序中使用此代码作为#include。它在 Inno Setup ANSI 中编译。 通过在所有 external 声明中将 A@ 替换为 W@,它可能适用于 Inno Setup 的 Unicode 版本(感谢 JeroenWiertPluimers 指出这一点)。

还要注意StartServiceStopService只是发送一个启动/停止信号,而不是等待服务处于运行停止状态。您可以使用 using IsServiceRunning 和 Pascal 脚本 Sleep() 函数来构建等待服务运行的代码。或者只是 Sleep() 预定义的秒数。

代码实现了这些功能:

function IsServiceInstalled(ServiceName: string) : boolean;
function IsServiceRunning(ServiceName: string) : boolean;
function InstallService(FileName, ServiceName, DisplayName, Description : string;ServiceType,StartType :cardinal) : boolean;
function RemoveService(ServiceName: string) : boolean;
function StartService(ServiceName: string) : boolean;
function StopService(ServiceName: string) : boolean;
function SetupService(service, port, comment: string) : boolean;

实际代码:

type
    SERVICE_STATUS = record
        dwServiceType               : cardinal;
        dwCurrentState              : cardinal;
        dwControlsAccepted          : cardinal;
        dwWin32ExitCode             : cardinal;
        dwServiceSpecificExitCode   : cardinal;
        dwCheckPoint                : cardinal;
        dwWaitHint                  : cardinal;
    end;
    HANDLE = cardinal;

const
    SERVICE_QUERY_CONFIG        = $1;
    SERVICE_CHANGE_CONFIG       = $2;
    SERVICE_QUERY_STATUS        = $4;
    SERVICE_START               = $10;
    SERVICE_STOP                = $20;
    SERVICE_ALL_ACCESS          = $f01ff;
    SC_MANAGER_ALL_ACCESS       = $f003f;
    SERVICE_WIN32_OWN_PROCESS   = $10;
    SERVICE_WIN32_SHARE_PROCESS = $20;
    SERVICE_WIN32               = $30;
    SERVICE_INTERACTIVE_PROCESS = $100;
    SERVICE_BOOT_START          = $0;
    SERVICE_SYSTEM_START        = $1;
    SERVICE_AUTO_START          = $2;
    SERVICE_DEMAND_START        = $3;
    SERVICE_DISABLED            = $4;
    SERVICE_DELETE              = $10000;
    SERVICE_CONTROL_STOP        = $1;
    SERVICE_CONTROL_PAUSE       = $2;
    SERVICE_CONTROL_CONTINUE    = $3;
    SERVICE_CONTROL_INTERROGATE = $4;
    SERVICE_STOPPED             = $1;
    SERVICE_START_PENDING       = $2;
    SERVICE_STOP_PENDING        = $3;
    SERVICE_RUNNING             = $4;
    SERVICE_CONTINUE_PENDING    = $5;
    SERVICE_PAUSE_PENDING       = $6;
    SERVICE_PAUSED              = $7;

 nt based service utilities 
function OpenSCManager(lpMachineName, lpDatabaseName: string; dwDesiredAccess :cardinal): HANDLE;
external 'OpenSCManagerA@advapi32.dll stdcall';

function OpenService(hSCManager :HANDLE;lpServiceName: string; dwDesiredAccess :cardinal): HANDLE;
external 'OpenServiceA@advapi32.dll stdcall';

function CloseServiceHandle(hSCObject :HANDLE): boolean;
external 'CloseServiceHandle@advapi32.dll stdcall';

function CreateService(hSCManager :HANDLE;lpServiceName, lpDisplayName: string;dwDesiredAccess,dwServiceType,dwStartType,dwErrorControl: cardinal;lpBinaryPathName,lpLoadOrderGroup: String; lpdwTagId : cardinal;lpDependencies,lpServiceStartName,lpPassword :string): cardinal;
external 'CreateServiceA@advapi32.dll stdcall';

function DeleteService(hService :HANDLE): boolean;
external 'DeleteService@advapi32.dll stdcall';

function StartNTService(hService :HANDLE;dwNumServiceArgs : cardinal;lpServiceArgVectors : cardinal) : boolean;
external 'StartServiceA@advapi32.dll stdcall';

function ControlService(hService :HANDLE; dwControl :cardinal;var ServiceStatus :SERVICE_STATUS) : boolean;
external 'ControlService@advapi32.dll stdcall';

function QueryServiceStatus(hService :HANDLE;var ServiceStatus :SERVICE_STATUS) : boolean;
external 'QueryServiceStatus@advapi32.dll stdcall';

function QueryServiceStatusEx(hService :HANDLE;ServiceStatus :SERVICE_STATUS) : boolean;
external 'QueryServiceStatus@advapi32.dll stdcall';

function GetLastError() : cardinal;
external 'GetLastError@kernel32.dll stdcall';

function OpenServiceManager() : HANDLE;
begin
    if UsingWinNT() = true then begin
        Result := OpenSCManager('','',SC_MANAGER_ALL_ACCESS);
        if Result = 0 then
            MsgBox('the servicemanager is not available', mbError, MB_OK)
    end
    else begin
            MsgBox('only nt based systems support services', mbError, MB_OK)
            Result := 0;
    end
end;

function IsServiceInstalled(ServiceName: string) : boolean;
var
    hSCM    : HANDLE;
    hService: HANDLE;
begin
    hSCM := OpenServiceManager();
    Result := false;
    if hSCM <> 0 then begin
        hService := OpenService(hSCM,ServiceName,SERVICE_QUERY_CONFIG);
        if hService <> 0 then begin
            Result := true;
            CloseServiceHandle(hService)
        end;
        CloseServiceHandle(hSCM)
    end
end;

function InstallService(FileName, ServiceName, DisplayName, Description : string;ServiceType,StartType :cardinal) : boolean;
var
    hSCM    : HANDLE;
    hService: HANDLE;
begin
    hSCM := OpenServiceManager();
    Result := false;
    if hSCM <> 0 then begin
        hService := CreateService(hSCM,ServiceName,DisplayName,SERVICE_ALL_ACCESS,ServiceType,StartType,0,FileName,'',0,'','','');
        if hService <> 0 then begin
            Result := true;
             Win2K & WinXP supports additional description text for services 
            if Description<> '' then
                RegWriteStringValue(HKLM,'System\CurrentControlSet\Services\' + ServiceName,'Description',Description);
            CloseServiceHandle(hService)
        end;
        CloseServiceHandle(hSCM)
    end
end;

function RemoveService(ServiceName: string) : boolean;
var
    hSCM    : HANDLE;
    hService: HANDLE;
begin
    hSCM := OpenServiceManager();
    Result := false;
    if hSCM <> 0 then begin
        hService := OpenService(hSCM,ServiceName,SERVICE_DELETE);
        if hService <> 0 then begin
            Result := DeleteService(hService);
            CloseServiceHandle(hService)
        end;
        CloseServiceHandle(hSCM)
    end
end;

function StartService(ServiceName: string) : boolean;
var
    hSCM    : HANDLE;
    hService: HANDLE;
begin
    hSCM := OpenServiceManager();
    Result := false;
    if hSCM <> 0 then begin
        hService := OpenService(hSCM,ServiceName,SERVICE_START);
        if hService <> 0 then begin
            Result := StartNTService(hService,0,0);
            CloseServiceHandle(hService)
        end;
        CloseServiceHandle(hSCM)
    end;
end;

function StopService(ServiceName: string) : boolean;
var
    hSCM    : HANDLE;
    hService: HANDLE;
    Status  : SERVICE_STATUS;
begin
    hSCM := OpenServiceManager();
    Result := false;
    if hSCM <> 0 then begin
        hService := OpenService(hSCM,ServiceName,SERVICE_STOP);
        if hService <> 0 then begin
            Result := ControlService(hService,SERVICE_CONTROL_STOP,Status);
            CloseServiceHandle(hService)
        end;
        CloseServiceHandle(hSCM)
    end;
end;

function IsServiceRunning(ServiceName: string) : boolean;
var
    hSCM    : HANDLE;
    hService: HANDLE;
    Status  : SERVICE_STATUS;
begin
    hSCM := OpenServiceManager();
    Result := false;
    if hSCM <> 0 then begin
        hService := OpenService(hSCM,ServiceName,SERVICE_QUERY_STATUS);
        if hService <> 0 then begin
            if QueryServiceStatus(hService,Status) then begin
                Result :=(Status.dwCurrentState = SERVICE_RUNNING)
            end;
            CloseServiceHandle(hService)
            end;
        CloseServiceHandle(hSCM)
    end
end;

【讨论】:

我继承了一些看起来就像你的答案的代码。他们没有提到来源也没有提到 ANSI 所以我花了一段时间才意识到 OpenSCManager 引发的 ERROR_INVALID_NAME aka 123 (0x7B) 错误可以通过将 A@ 替换为 @987654336 来解决@ 在所有 external 声明中,因为我正在运行 Inno Setup Unicode 我使用了这个解决方案,但在最新版本的 Windows 10 中出现了这个错误:“服务管理器不可用”【参考方案2】:

从 Inno Setup 5.5.0 开始,CloseApplications 和 RestartApplications 指令现在可用。这些选项将检测正在使用的文件并关闭使用它们的应用程序。

【讨论】:

CloseApplications 和 RestartApplications 使用Windows Restart Manager,它支持 GUI、Console 和 service 应用程序。 我无法让 CloseApplications 为服务工作 - 不知道为什么【参考方案3】:

这有两个部分:

    使用 Inno Setup 创建的安装程序如何启动和停止服务,如何创建和删除它们,如何更改其启动模式?

    通过使用此collection of routines 中提供的帮助函数,正是为此目的而编写的。它是为 Inno Setup 的 Ansi 版本编写的,因此需要对 API 函数导入和 PChar 参数类型进行更改,但它应该可以帮助您入门。

    如何在复制新文件版本之前停止现有服务?

    您基本上有两种方法可以做到这一点。您将使用 Pascal 脚本来执行上面链接的函数以停止服务,您只需要决定是在其中一个事件函数中执行此操作,还是在将通过 @987654323 调用的自定义函数中执行此操作@ 服务可执行文件的文件条目的参数。我肯定会在前者,所以你可以检查服务是否成功停止,并在失败时禁止安装实际启动。

    您应该查看CurStepChanged()NextButtonClick() 事件函数,具体取决于您是否可能需要阻止下一步。 Inno Setup 示例展示了这两种功能的使用。

【讨论】:

您的答案仍然相关,您介意在 11 年后编辑死链接吗:P【参考方案4】:

我正在使用停止、卸载、安装和启动给定服务的批处理文件,我只是在复制所有文件后使用 innosetup 调用批处理文件。

[Run]
Filename: "app\Scripts\installwindowsService.bat"; Parameters: "app"; Flags: runhidden

我将以下命令放在我的批处理文件中

net stop MyService

%SYSTEMROOT%\Microsoft.NET\Framework\v4.0.30319\installutil.exe /u MyService.exe 

%SYSTEMROOT%\Microsoft.NET\Framework\v4.0.30319\installutil.exe  MyService.exe /unattended

net start MyService

它就像一个魅力,非常简单。可用于首次安装或更新。希望对你有帮助。

【讨论】:

现在发表评论为时已晚,但如果有人在 2021 年或之后访问,这是值得的 - 如果服务已经安装并运行,并且当您重新运行设置时,这将不起作用。服务文件将保持锁定状态,不会被新文件替换。

以上是关于使用 Inno Setup 升级 Windows 服务的主要内容,如果未能解决你的问题,请参考以下文章

想做一个支持升级模式的安装程序,用inno setup,请问怎么做吗?

Inno Setup安装程序单例运行

Windows 7 上错误的桌面快捷方式图标(Inno Setup)

C/S打包 客户端/windows程序 Inno Setup

在关闭Inno Setup到Windows之前,发送关于重新检查PATH环境变量的消息[重复]。

Inno Setup 检测Windows系统版本