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

Windows服务中Process.GetProcesses()无法获取用户进程的解决咨询

解决Windows服务无法获取用户专属进程的问题

核心原因是Windows会话隔离机制:从Vista及之后的系统,服务默认运行在会话0,而用户登录后的应用进程都在独立的用户会话(比如会话1、2)里。Process.GetProcesses()默认只能获取当前会话(服务所在的会话0)的进程,所以看不到用户专属应用。即便勾选“与桌面交互”,也只是让服务能访问会话0的桌面,无法跨会话获取进程。

以下是具体解决步骤:

1. 通过Windows API跨会话枚举进程

直接用.NET的Process类做不到跨会话获取进程,必须调用Windows原生API来枚举所有会话及其对应的进程。需要用到以下几个API:

  • WTSEnumerateSessions:枚举系统中所有活跃的会话
  • WTSEnumerateProcesses:获取指定会话下的所有进程
  • WTSFreeMemory:释放API调用分配的内存

2. C#代码实现示例

首先添加P/Invoke声明:

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

public class SessionProcessHelper
{
    [DllImport("wtsapi32.dll")]
    private static extern bool WTSEnumerateSessions(IntPtr hServer, int Reserved, int Version, ref IntPtr ppSessionInfo, ref int pCount);

    [DllImport("wtsapi32.dll")]
    private static extern void WTSFreeMemory(IntPtr pMemory);

    [DllImport("wtsapi32.dll")]
    private static extern bool WTSEnumerateProcesses(IntPtr hServer, int Reserved, int Version, ref IntPtr ppProcessInfo, ref int pCount);

    private const int WTS_CURRENT_SERVER_HANDLE = 0;
    private const int WTS_SESSION_INFO_LEVEL = 1;
    private const int WTS_PROCESS_INFO_LEVEL = 1;

    public class WTSProcessInfo
    {
        public int SessionId { get; set; }
        public int ProcessId { get; set; }
        public string ProcessName { get; set; }
    }

    public static List<WTSProcessInfo> GetAllUserSessionProcesses()
    {
        var processes = new List<WTSProcessInfo>();
        IntPtr sessionInfoPtr = IntPtr.Zero;
        IntPtr processInfoPtr = IntPtr.Zero;
        int sessionCount = 0;
        int processCount = 0;

        try
        {
            // 枚举所有会话
            if (WTSEnumerateSessions(IntPtr.Zero, 0, WTS_SESSION_INFO_LEVEL, ref sessionInfoPtr, ref sessionCount))
            {
                for (int i = 0; i < sessionCount; i++)
                {
                    var sessionInfo = (WTS_SESSION_INFO)Marshal.PtrToStructure(
                        sessionInfoPtr + i * Marshal.SizeOf(typeof(WTS_SESSION_INFO)), 
                        typeof(WTS_SESSION_INFO));

                    // 跳过会话0(服务所在会话)和断开的会话
                    if (sessionInfo.SessionId == 0 || sessionInfo.State != WTS_CONNECTSTATE_CLASS.WTSActive)
                        continue;

                    // 枚举该会话下的进程
                    if (WTSEnumerateProcesses(IntPtr.Zero, 0, WTS_PROCESS_INFO_LEVEL, ref processInfoPtr, ref processCount))
                    {
                        for (int j = 0; j < processCount; j++)
                        {
                            var processInfo = (WTS_PROCESS_INFO)Marshal.PtrToStructure(
                                processInfoPtr + j * Marshal.SizeOf(typeof(WTS_PROCESS_INFO)), 
                                typeof(WTS_PROCESS_INFO));

                            processes.Add(new WTSProcessInfo
                            {
                                SessionId = processInfo.SessionId,
                                ProcessId = processInfo.ProcessId,
                                ProcessName = processInfo.ProcessName
                            });
                        }
                        WTSFreeMemory(processInfoPtr);
                        processInfoPtr = IntPtr.Zero;
                        processCount = 0;
                    }
                }
            }
        }
        finally
        {
            if (sessionInfoPtr != IntPtr.Zero)
                WTSFreeMemory(sessionInfoPtr);
            if (processInfoPtr != IntPtr.Zero)
                WTSFreeMemory(processInfoPtr);
        }

        return processes;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct WTS_SESSION_INFO
    {
        public int SessionId;
        [MarshalAs(UnmanagedType.LPStr)]
        public string WinStationName;
        public WTS_CONNECTSTATE_CLASS State;
    }

    private enum WTS_CONNECTSTATE_CLASS
    {
        WTSActive,
        WTSConnected,
        WTSConnectQuery,
        WTSShadow,
        WTSDisconnected,
        WTSIdle,
        WTSListen,
        WTSReset,
        WTSDown,
        WTSInit
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct WTS_PROCESS_INFO
    {
        public int ProcessId;
        [MarshalAs(UnmanagedType.LPStr)]
        public string ProcessName;
        public int SessionId;
        [MarshalAs(UnmanagedType.LPStr)]
        public string UserName;
        [MarshalAs(UnmanagedType.LPStr)]
        public string DomainName;
    }
}

然后在服务中调用这个方法:

var userProcesses = SessionProcessHelper.GetAllUserSessionProcesses();
foreach (var proc in userProcesses)
{
    // 处理用户进程数据,比如记录进程ID、名称、所属会话
    Console.WriteLine($"会话{proc.SessionId} - 进程:{proc.ProcessName} (PID: {proc.ProcessId})");
}

3. 权限与注意事项

  • 本地系统账户默认拥有枚举所有会话进程的权限,无需额外配置。如果使用其他服务账户,需要确保账户拥有SeSecurityPrivilege和SeTcbPrivilege权限。
  • 不需要勾选服务属性中的“允许服务与桌面交互”,这个选项是为了兼容旧系统的桌面应用,对跨会话获取进程没有帮助,且在现代系统中已被限制。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 08:12:51