指定的服务在Delphi应用程序中已被标记为删除

5
我是一位有用的助手,可以为您翻译文本。
我写了一个Delphi应用程序(基本上是一个GUI,用于管理服务,它具有以下功能:允许用户设置一些由服务使用的参数以及启动/停止/卸载/安装新版本)。 因此,在所有功能中,有一个“无法正常工作”的功能:在某个时刻,应用程序尝试卸载并安装新版本的服务。
使用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)在某种程度上“锁定了服务”,不允许完全卸载。请帮助我理解为什么通过ShellExecute进行的服务卸载会出现此错误?
谢谢。

2
在卸载服务之前停止它(net stop "yourservicename")。 - whosrdaddy
2
为什么你要使用shell execute,当你可以直接调用service manager的API呢?使用SvCom可以使服务exe具有通过service.exe /uninstall(卸载)进行(卸载)安装的能力,并调用适当的API来完成此操作。不过,要注意的是,我们从IDE运行的服务之一尝试(卸)安装过。 - Marjan Venema
你是在等它完全停止后再卸载吗? - Jerry Dodge
1
如果仍有其他进程使用当前服务的句柄,则该服务将被标记为删除。仔细检查您的代码并关闭从服务管理器获取的所有句柄。我知道我曾经在 MSDN 文档中读到过这个问题,但现在我在手机上。稍后我会尽力回来给出一个合适的答案。 - SpaghettiCook
1
@user193655,我写了更多。这回答你的问题了吗? - SpaghettiCook
显示剩余3条评论
2个回答

5
我有类似的经验。在我的代码中,我使用变量来保持与服务控制管理器的开放连接。现在,我将所有句柄声明为本地变量,并在安装和卸载服务时进行操作。
您可以通过调用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),但是为了测试这段代码是否适用于您,我认为最好写成一个简单的解决方案。最后,请不要忘记该进程需要足够的权限才能成功执行此代码。

太完美了,这个函数很好用!非常感谢! - UnDiUdin
1
欢迎。感谢您的300分。我现在感觉很有男子气概 :-) 更重要的是,我刚刚注意到代码片段中有一个相当重要的错误。我已经纠正了,请复制。重复直到块中的超时比较方式是错误的。如果您的服务及时停止,您可能不会注意到这一点。否则,您将永远陷入循环中,这需要很长时间。 - SpaghettiCook
谢谢更新,我会检查的。你应该得到这300分,这就是StackOverflow的运作方式! - UnDiUdin

2
我认为你的命令提示符具有提升的权限,因此被允许实际停止服务。但 Delphi 可能没有这个权限,或者至少你的项目没有,所以它可以卸载服务(只需从注册表中删除一些值),但不能真正停止服务。
服务随后会被“标记为删除”,因为它已经被卸载,但仍在运行。如果您重新启动计算机,服务就不会重新启动,您的工具就可以安装新版本。
如果我的猜测是正确的,那么解决方案就是以管理员身份运行您的程序 - 它基本上是一个安装程序 - 这样它也有立即停止和删除服务的权限。
另外一个你可以尝试的方法是先调用net stop <service>来停止服务,但我怀疑这是否会解决问题。

哦,我忘了提到我正在使用IDE和命令提示符“作为管理员”。我会更新问题。 - UnDiUdin
是的,我也这么想。但我认为你的项目没有继承这些管理员权限,所以即使你的IDE以管理员身份运行,你自己的程序,即使从IDE启动,也不会有管理员权限。不过我对此并不完全确定。 - GolezTrol
2
程序由提升的 IDE 启动,继承了提升权限。 - David Heffernan
@DavidHeffernan 感谢您澄清这一点。那么,ShellExecute的行为与从提示符运行命令不同真是非常奇怪。 - GolezTrol
我也会尝试使用"net stop",并告诉你结果。 - UnDiUdin
同时也是通过“Net stop”命令来实现相同的行为!如同在问题描述中所述,关闭IDE会使卸载成功,这就是问题所在。 - UnDiUdin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接