验证服务是否被标记为删除

4
有时候我卸载使用WIX制作的安装程序后,服务仍会被标记为删除,用户必须重新启动计算机才能再次安装。如何验证服务是否被标记为删除,并告知用户在进行其他安装之前重启计算机呢?

不确定您是否可以从“ServiceController.GetServices()”中找到它,但值得一试。 - Jaroslav Jandek
6个回答

5
通常情况下,当某个东西仍然附着在该服务上时,会发生这种情况,从而阻止Windows在注册表中删除其配置。(在大多数情况下,只是意外地在后台打开了服务应用程序——services.msc。)
对于检测,我建议您阅读CreateService和其他Service API。例如,如果服务已标记为删除,则在调用CreateService时将收到ERROR_SERVICE_MARKED_FOR_DELETE。
关于您提出的重新启动解决方案... Windows已经足够先进,几乎不需要为任何原因重新启动。除非您正在安装专门的内核驱动程序,否则您不需要重新启动。不要偷懒!牢记用户!我建议修改您的安装程序逻辑,以检测潜在的冲突运行程序,如服务应用程序,并建议关闭。

2
你如何检测服务小程序是否加载到mmc中? - txt

2
这里有一个SO(Stack Overflow)的帖子可能会对你有所帮助。虽然原问题是关于服务安装的,但答案也涵盖了卸载和状态。 如何在C#中以编程方式安装Windows服务? 这里还有一篇文章,解释了为什么你可能会收到“已标记为删除”消息以及如何避免它。

http://weblogs.asp.net/avnerk/archive/2007/09/05/windows-services-services-msc-and-the-quot-this-service-is-marked-for-deletion-quot-error.aspx

编辑:
根据Christopher Painter的评论,我更新了这个答案以促进最佳实践。尽管在我的经验中,收到“标记为删除”的消息更多是由于拥有services.msc控制台而不是未发布的资源,但编写自定义操作以重新启动并不是最好的方法。
要在WiX处理后安排重启,请使用WiX XML(在Wix# here中解释如何使用)如下:
<?xml version='1.0' encoding='windows-1252'?>
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
    ...
    <InstallExecuteSequence>
        <ScheduleReboot After="InstallFinalize"/>
    <InstallExecuteSequence>
</Wix>

如果能解释一下-1就好了。 - bitxwise
WiX使用Windows Installer来支持安装/卸载服务。任何尝试在安装程序中编写自定义操作都被认为是一种不良实践。请参见:http://robmensching.com/blog/posts/2007/8/17/Zataoca-Custom-actions-are-generally-an-admission-of-failure - Christopher Painter
感谢您的解释。我同意您在链接的博客文章中提出的许多观点,但是我并不完全同意您对该文章的解释。我不想引发争端,但是该文章的作者强调,通常不建议使用自定义操作,原因是设置开发人员编写自定义操作。话虽如此,OP应该通过WiX XML在<InstallExecuteSequence>标记中使用<ScheduleReboot After="InstallFinalize"/>而不是编写自定义操作以获得更好的实践。我将更新答案以反映这一点。 - bitxwise
我认为在大多数商店里,这归结于1.开发人员的努力/时间/技能量和2.优先事项。可以有不同的论据。我曾经写过一些可能已经存在的东西,但是我用改进的方法写了出来。仅仅因为一个界面已经存在并不意味着创新就应该因此而消亡。我并不是说OP试图创新。我只是想说我并不完全同意你的解释中的硬性规定。再次感谢您的见解。 - bitxwise
1
因此,与其在可用时间内解决问题,您认为应该花费时间尝试学习新技术并未能交付吗?开发往往是理想主义与实用主义的对立。 - John Nicholas
显示剩余3条评论

2

我找不到一种API方法来实现它(这不涉及调用CreateServiceDeleteService,因为两者都会产生不良的副作用),但是HKLM\SYSTEM\CurrentControlSet\Services\ServiceName包含一个DeleteFlag=1(REG_DWORD)值似乎相当明显地表示了这种不幸的状态。


1

我还搜索了解决方案,以确定服务是否存在或已标记为删除,即使 OpenService 成功。我还发现 ChangeServiceConfig 返回的错误代码是 ERROR_SERVICE_MARKED_FOR_DELETE。因此,以下是通过 C/C++ 检查服务是否已标记为删除的方法:

BOOL SetDisplayName(SC_HANDLE schService, LPCTSTR lpDisplayName)
{
    return ChangeServiceConfig(schService, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, lpDisplayName);
}

//
// You need to obtain the hSCManager through OpenSCManager first
//
SC_HANDLE schService = OpenService(hSCManager, pszServiceName, SERVICE_CHANGE_CONFIG);
if (schService != nullptr)
{
    //
    // obtain the display name of a service
    //
    TCHAR szDisplayName[0x1000] = { 0 };
    DWORD cchDisplayName = ARRAYSIZE(szDisplayName);
    bResult = GetServiceDisplayName(schService, pszServiceName, szDisplayName, &cchDisplayName);
    if (bResult)
    {
        //
        // change the display name to the original display name
        //
        bResult = SetDisplayName(schService, szDisplayName);
        if (!bResult)
        {
            DWORD dwError = GetLastError();
            switch (dwError) {
            case ERROR_ACCESS_DENIED:
            case ERROR_CIRCULAR_DEPENDENCY:
            case ERROR_DUPLICATE_SERVICE_NAME:
            case ERROR_INVALID_SERVICE_ACCOUNT:
                break;
            case ERROR_INVALID_HANDLE:
                break;
            case ERROR_INVALID_PARAMETER:
                break;
            //
            // the service is marked for deletion
            //
            case ERROR_SERVICE_MARKED_FOR_DELETE:
                break;
            default:
                break;
            }
        }
    }
}
else
{
    DWORD dwError = GetLastError();
    switch (dwError) {
    case ERROR_ACCESS_DENIED:
        break;
    case ERROR_INVALID_HANDLE:
        break;
    case ERROR_INVALID_NAME:
        break;
    case ERROR_SERVICE_DOES_NOT_EXIST:
        break;
    default:
        break;
    }
}

听起来像一个很好的解决方案,没有副作用(不像调用创建/删除服务以获取错误)。 - Tomas Pruzina

0

您是否在使用ServiceControl元素/表来在卸载期间停止服务?如果是,您的服务是否成功停止?如果没有,请查看服务内部发生了什么,以确保它在请求时释放所有资源并正常关闭。

您不需要编写任何自定义操作来编程调用SCM API。Windows Installer应该能够为您处理此操作。


这并没有回答问题。有些服务是无法停止的,例如当服务属于一个不能安全卸载的设备驱动程序时。 - Ilya
你忽略了一个重要的点,这不是一个安装程序的问题,而是需要检查你的服务代码。如果一个服务无法停止,就不要让安装程序去停止它。 - Christopher Painter
您的卸载程序的目标是删除该服务。卸载程序应首先停止它,但如果无法停止,则仍应将其删除(这将在系统关闭时将其标记为删除)。如果用户选择在您的卸载程序结束时不重新启动,然后再重新安装,他们可能会在安装程序尝试再次创建该服务时遇到问题。 - Ilya

0
在我的情况下,由于我没有正确处理一个对象(在我的情况下是rabbitmq-connection),卸载服务后该服务被标记为删除。
这不是对问题的直接回答,但可能有助于解决其根本问题。

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