如何在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转换为服务名称:
- 打开目标PID的进程句柄(需要
PROCESS_QUERY_INFORMATION权限) - 提取进程令牌中的用户SID
- 判断是否为NT SERVICE类的SID(前缀为
S-1-5-80-xxxxxx) - 通过
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
相关产品推荐
相关产品推荐

