4

我编写了一个 Delphi 应用程序(基本上是一个用于管理服务的 GUI,它具有以下功能:允许用户设置服务使用的一些参数以及启动/停止/unintsall/安装新版本)。因此,在所有功能中,有一个“行为不正常”:在某个时候,应用程序会尝试卸载并安装新版本的 a 服务。

随着ShellExecute我运行以下命令:

C:\myPath\myService.exe /Uninstall
C:\myPath\myService.exe /Install  // this is tipically done to install a newer version of it

如果服务已经在运行,它会成功卸载(我收到“成功卸载”消息),但如果我打开 services.msc 我看到 myService 仍在服务列表中,但从其弹出菜单中禁用了启动和停止(同时我希望它根本没有列出)。

此时,如果我尝试安装该服务,则会收到以下错误:“指定的服务已标记为删除”

请注意,如果我从命令提示符运行卸载和安装命令,则卸载很好,并且该服务不在 services.msc 列表中。注意:在这种情况下,我的意思是根本不使用 Delphi(或编译的 exe)。

我尝试了很多技巧,包括在卸载后放置Sleep(10000),但它不起作用我还尝试通过保持 services.msc 关闭(因为我读到它可能是打开它的问题)。

我使用以下步骤找到了一个成功的技巧:

1)我在从 Delphi 调用 Uninstall 之后放置了一个断点

2)我去 services.msc :该服务仍在列表中,即使在“刷新”之后它仍然在列表中

3)我打破(从IDE:CTRL + F2)应用程序的执行

4)我再次进入 services.msc 我单击“刷新”按钮:myservice 已从列表中删除,因为它应该是

所以我怀疑Delphi XE2(在IDE中调试或运行exe)以某种方式“锁定服务”不允许它被完全卸载。

注意:该服务是使用另一个 delphi 项目构建的!

你能帮我理解为什么服务卸载ShellExecute确实会出现这个错误吗?

非常感谢。

重要提示: 我忘了提到我使用 IDE 和 cmd.exe 作为管理员。

4

2 回答 2

5

我有过类似的经历。在我的代码中,我使用了一个变量来保持与服务控制管理器的开放连接。如今,我将所有句柄声明为局部变量,并即时安装和卸载服务。

您可以通过调用DeleteService来卸载服务。在备注部分它写道:

DeleteService 函数将服务标记为从服务控制管理器数据库中删除。直到通过调用 CloseServiceHandle 函数关闭服务的所有打开句柄并且服务未运行时,才会删除数据库条目。通过使用 SERVICE_CONTROL_STOP 控制代码调用 ControlService 函数来停止正在运行的服务。如果无法停止服务,则在系统重新启动时删除数据库条目。

因此,它必须停止并且您应该关闭所有句柄。下面的代码应该可以解决问题:

function  UninstallService(aServiceName: String; aTimeOut: Cardinal): Boolean;
var
    ComputerName: array[0..MAX_COMPUTERNAME_LENGTH + 1] of Char;
    ComputerNameLength, StartTickCount: Cardinal;
    SCM: SC_HANDLE;
    ServiceHandle: SC_HANDLE;
    ServiceStatus: TServiceStatus;

begin
    Result:= False;

    ComputerNameLength:= MAX_COMPUTERNAME_LENGTH + 1;
    if (Windows.GetComputerName(ComputerName, ComputerNameLength)) then
    begin
        SCM:= OpenSCManager(ComputerName, nil, SC_MANAGER_ALL_ACCESS);
        if (SCM <> 0) then
        begin
            try
                ServiceHandle:= OpenService(SCM, PChar(aServiceName), SERVICE_ALL_ACCESS);
                if (ServiceHandle <> 0) then
                begin

                    // make sure service is stopped
                    QueryServiceStatus(ServiceHandle, ServiceStatus);
                    if (not (ServiceStatus.dwCurrentState in [0, SERVICE_STOPPED])) then
                    begin
                        // Stop service
                        ControlService(ServiceHandle, SERVICE_CONTROL_STOP, ServiceStatus);
                    end;

                    // wait for service to be stopped
                    StartTickCount:= GetTickCount;
                    QueryServiceStatus(ServiceHandle, ServiceStatus);
                    if (ServiceStatus.dwCurrentState <> SERVICE_STOPPED) then
                    begin
                        repeat
                            Sleep(1000);
                            QueryServiceStatus(ServiceHandle, ServiceStatus);
                        until (ServiceStatus.dwCurrentState = SERVICE_STOPPED) or ((GetTickCount - StartTickCount) > aTimeout);
                    end;

                    Result:= DeleteService(ServiceHandle);
                    CloseServiceHandle(ServiceHandle);
                end;
            finally
                CloseServiceHandle(SCM);
            end;
        end;
    end;
end;

我会将上面的代码分成几个子函数(即 QueryServiceStatus、StopService 和 UninstallService),但为了测试这段代码是否适合您,我认为最好将其编写在一个简单的解决方案中。最后一点,不要忘记该进程需要足够的权限才能成功执行此代码。

于 2013-07-01T17:41:45.550 回答
2

我认为您的命令提示符具有提升的权限,因此可以实际停止服务。Delphi 可能不是,或者至少您的项目不是,因此允许卸载服务(这只不过是从注册表中删除一些值),但它实际上不能停止服务。

该服务随后被“标记为删除”,因为它已被卸载,但仍在运行。如果您重新启动您的电脑,该服务将不会再次启动,您的工具可以安装新版本。

如果我猜对了,那么解决方案就是以管理员身份运行您的程序(本质上是一个安装程序),因此它也有权立即停止和删除该服务。

您可能会尝试的另一件事是先调用net stop <service>以停止服务,但我怀疑这是否能解决问题。

于 2013-06-28T09:37:11.870 回答