You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在SERVICE_START_PENDING状态下通过进程PID获取Windows服务名称?

解决Windows服务启动Pending状态下PID与服务名称关联问题

这是个很典型的Windows服务启动阶段的信息同步问题——我来帮你拆解原因和可行的解决办法:

为什么常规方法会失败?

当服务处于SERVICE_START_PENDING状态时,服务控制管理器(SCM)还没把进程PID同步到它对外公开的状态数据结构里。你尝试的Win32_Services WMI查询、tasklist /SVC命令、EnumServicesStatusEx API,本质都是从SCM的公开状态库中读取信息,这时候SCM还没完成PID的更新,所以返回dwProcessId=0。但服务进程本身已经被系统创建,所以确实存在有效PID。

可行的解决方案

方案1:针对特定服务直接查询实时PID(已知服务名的情况)

如果你已经知道目标服务的名称,直接调用QueryServiceStatusEx API,指定SC_STATUS_PROCESS_INFO作为信息级别——哪怕服务还在启动Pending状态,只要进程已经创建,这个API就能返回真实的PID。

示例C# P/Invoke代码片段:

using System;
using System.Runtime.InteropServices;

public class ServiceUtils
{
    [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    private static extern bool QueryServiceStatusEx(IntPtr hService, int infoLevel, IntPtr lpBuffer, uint cbBufSize, out uint pcbBytesNeeded);

    [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    private static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess);

    [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    private static extern IntPtr OpenSCManager(string lpMachineName, string lpDatabaseName, uint dwDesiredAccess);

    [DllImport("advapi32.dll", SetLastError = true)]
    private static extern bool CloseServiceHandle(IntPtr hSCObject);

    private const int SC_STATUS_PROCESS_INFO = 0;
    private const uint SERVICE_QUERY_STATUS = 0x0004;
    private const uint SC_MANAGER_CONNECT = 0x0001;

    [StructLayout(LayoutKind.Sequential)]
    private struct SERVICE_STATUS_PROCESS
    {
        public uint dwServiceType;
        public uint dwCurrentState;
        public uint dwControlsAccepted;
        public uint dwWin32ExitCode;
        public uint dwServiceSpecificExitCode;
        public uint dwCheckPoint;
        public uint dwWaitHint;
        public uint dwProcessId;
        public uint dwServiceFlags;
    }

    public static uint? GetServicePid(string serviceName)
    {
        IntPtr scManager = OpenSCManager(null, null, SC_MANAGER_CONNECT);
        if (scManager == IntPtr.Zero) return null;

        IntPtr serviceHandle = OpenService(scManager, serviceName, SERVICE_QUERY_STATUS);
        if (serviceHandle == IntPtr.Zero)
        {
            CloseServiceHandle(scManager);
            return null;
        }

        try
        {
            uint bufferSize = 0;
            QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, IntPtr.Zero, 0, out bufferSize);
            IntPtr buffer = Marshal.AllocHGlobal((int)bufferSize);
            
            if (QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, buffer, bufferSize, out _))
            {
                SERVICE_STATUS_PROCESS status = Marshal.PtrToStructure<SERVICE_STATUS_PROCESS>(buffer);
                return status.dwProcessId != 0 ? status.dwProcessId : null;
            }
            
            Marshal.FreeHGlobal(buffer);
            return null;
        }
        finally
        {
            CloseServiceHandle(serviceHandle);
            CloseServiceHandle(scManager);
        }
    }
}

方案2:从PID逆向关联服务名称(仅知道PID的情况)

如果只有进程PID,不知道服务名,可以通过进程的安全令牌获取服务SID,再把SID转换为服务名称:

  1. 打开目标PID的进程句柄(需要PROCESS_QUERY_INFORMATION权限)
  2. 提取进程令牌中的用户SID
  3. 判断是否为NT SERVICE类的SID(前缀为S-1-5-80-xxxxxx)
  4. 通过LookupAccountSid将SID转换为服务名称

示例核心代码片段:

using System;
using System.Runtime.InteropServices;

public class ProcessServiceLink
{
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, int dwProcessId);

    [DllImport("advapi32.dll", SetLastError = true)]
    private static extern bool OpenProcessToken(IntPtr ProcessHandle, uint DesiredAccess, out IntPtr TokenHandle);

    [DllImport("advapi32.dll", SetLastError = true)]
    private static extern bool GetTokenInformation(IntPtr TokenHandle, uint TokenInformationClass, IntPtr TokenInformation, uint TokenInformationLength, out uint ReturnLength);

    [DllImport("advapi32.dll", SetLastError = true)]
    private static extern bool LookupAccountSid(string lpSystemName, IntPtr Sid, char[] Name, ref uint cchName, char[] ReferencedDomainName, ref uint cchReferencedDomainName, out uint peUse);

    [DllImport("advapi32.dll", SetLastError = true)]
    private static extern bool GetSidIdentifierAuthority(IntPtr pSid, out byte[] IdentifierAuthority);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool CloseHandle(IntPtr hObject);

    private const uint PROCESS_QUERY_INFORMATION = 0x0400;
    private const uint TOKEN_QUERY = 0x0008;
    private const uint TokenUser = 1;

    [StructLayout(LayoutKind.Sequential)]
    private struct TOKEN_USER
    {
        public SID_AND_ATTRIBUTES User;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct SID_AND_ATTRIBUTES
    {
        public IntPtr Sid;
        public uint Attributes;
    }

    public static string GetServiceNameFromPid(int pid)
    {
        IntPtr processHandle = OpenProcess(PROCESS_QUERY_INFORMATION, false, pid);
        if (processHandle == IntPtr.Zero) return null;

        try
        {
            if (!OpenProcessToken(processHandle, TOKEN_QUERY, out IntPtr tokenHandle)) return null;
            try
            {
                uint bufferSize = 0;
                GetTokenInformation(tokenHandle, TokenUser, IntPtr.Zero, 0, out bufferSize);
                IntPtr tokenUserPtr = Marshal.AllocHGlobal((int)bufferSize);
                
                if (GetTokenInformation(tokenHandle, TokenUser, tokenUserPtr, bufferSize, out _))
                {
                    TOKEN_USER tokenUser = Marshal.PtrToStructure<TOKEN_USER>(tokenUserPtr);
                    IntPtr sid = tokenUser.User.Sid;
                    
                    // 验证是否为NT SERVICE的SID(S-1-5-80)
                    GetSidIdentifierAuthority(sid, out byte[] authority);
                    if (authority[0] == 0 && authority[5] == 0x50)
                    {
                        char[] nameBuf = new char[256];
                        uint nameSize = (uint)nameBuf.Length;
                        char[] domainBuf = new char[256];
                        uint domainSize = (uint)domainBuf.Length;
                        
                        if (LookupAccountSid(null, sid, nameBuf, ref nameSize, domainBuf, ref domainSize, out _))
                        {
                            return new string(nameBuf, 0, (int)nameSize - 1);
                        }
                    }
                }
                Marshal.FreeHGlobal(tokenUserPtr);
                return null;
            }
            finally
            {
                CloseHandle(tokenHandle);
            }
        }
        finally
        {
            CloseHandle(processHandle);
        }
    }
}

注意事项

  • 这些操作需要管理员权限,否则可能因为权限不足无法打开进程/服务句柄
  • 对于svchost.exe这类承载多个服务的进程,一个PID会对应多个服务,需要额外枚举所有服务来匹配

内容的提问来源于stack exchange,提问作者Aaron

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 15:12:36