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

配置BackgroundService在用户会话运行的问题及解决方案咨询

Windows服务(WorkerService模板)无法获取用户会话进程的解决方案

问题概况

基于新版WorkerService模板开发Windows服务,核心监控逻辑使用Process.GetProcesses()枚举进程,筛选条件为:

item.MainWindowTitle.Length > 0 || (item.ProcessName.ToLowerInvariant() == "explorer")

在Visual Studio中调试运行正常,但通过sc create ".T" binPath="..."注册为服务并启动后,仅能检测到explorer进程。已尝试切换服务登录账户(本地系统/指定用户)、使用sc config ".T" obj= "..." password= "..."配置,均未解决问题;旧版Windows服务模板注册失败,优先使用微软推荐的WorkerService+BackgroundService方案。

核心原因

Windows服务默认运行在会话0,这是一个与用户登录会话(通常为会话1及以上)完全隔离的系统会话。即便是本地系统账户,默认也没有权限跨会话访问用户进程的交互相关信息(比如MainWindowTitle依赖进程所在的会话上下文),Process.GetProcesses()在会话0中只能枚举到该会话内的进程,以及少数跨会话的系统进程(如explorer)。

解决方法

1. 配置服务跨会话访问(仅本地系统账户可用,有安全风险)

开启服务与桌面交互权限,允许服务访问用户会话的桌面资源:

  • 命令行执行:
    sc config ".T" type= own type= interact
    
  • 或通过服务属性界面:找到目标服务→右键「属性」→「登录」选项卡→勾选「允许服务与桌面交互」

注意:该功能在Windows Vista及以后版本中被严格限制,且存在安全隐患(恶意程序可利用该权限获取用户桌面控制权),仅建议在测试环境使用。

2. 通过Windows API枚举所有会话的进程

Process.GetProcesses()无法直接获取跨会话进程,需调用Windows原生API枚举所有会话及对应进程,步骤如下:

  • 引入P/Invoke调用WTS相关API:
    using System.Runtime.InteropServices;
    using System.Diagnostics;
    using System.Collections.Generic;
    
    public class WtsHelper
    {
        [DllImport("wtsapi32.dll")]
        private static extern bool WTSEnumerateSessions(IntPtr hServer, int Reserved, int Version, out IntPtr ppSessionInfo, out 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, out IntPtr ppProcessInfo, out int pCount);
    
        [StructLayout(LayoutKind.Sequential)]
        private struct WTS_SESSION_INFO
        {
            public int SessionId;
            [MarshalAs(UnmanagedType.LPStr)]
            public string pWinStationName;
            public int State;
        }
    
        [StructLayout(LayoutKind.Sequential)]
        private struct WTS_PROCESS_INFO
        {
            public int ProcessId;
            [MarshalAs(UnmanagedType.LPStr)]
            public string pProcessName;
            [MarshalAs(UnmanagedType.LPStr)]
            public string pUserName;
            public int SessionId;
        }
    
        public static List<int> GetAllUserSessionIds()
        {
            var sessionIds = new List<int>();
            IntPtr ppSessionInfo = IntPtr.Zero;
            int count;
    
            if (WTSEnumerateSessions(IntPtr.Zero, 0, 1, out ppSessionInfo, out count))
            {
                for (int i = 0; i < count; i++)
                {
                    IntPtr currentSession = IntPtr.Add(ppSessionInfo, i * Marshal.SizeOf(typeof(WTS_SESSION_INFO)));
                    var sessionInfo = (WTS_SESSION_INFO)Marshal.PtrToStructure(currentSession, typeof(WTS_SESSION_INFO));
                    // 仅筛选活跃用户会话(State=0表示已连接)
                    if (sessionInfo.State == 0)
                    {
                        sessionIds.Add(sessionInfo.SessionId);
                    }
                }
                WTSFreeMemory(ppSessionInfo);
            }
            return sessionIds;
        }
    
        public static List<Process> GetProcessesBySessionId(int sessionId)
        {
            var processes = new List<Process>();
            IntPtr ppProcessInfo = IntPtr.Zero;
            int count;
    
            if (WTSEnumerateProcesses(IntPtr.Zero, 0, 1, out ppProcessInfo, out count))
            {
                for (int i = 0; i < count; i++)
                {
                    IntPtr currentProcess = IntPtr.Add(ppProcessInfo, i * Marshal.SizeOf(typeof(WTS_PROCESS_INFO)));
                    var processInfo = (WTS_PROCESS_INFO)Marshal.PtrToStructure(currentProcess, typeof(WTS_PROCESS_INFO));
                    if (processInfo.SessionId == sessionId)
                    {
                        try
                        {
                            var process = Process.GetProcessById(processInfo.ProcessId);
                            processes.Add(process);
                        }
                        catch
                        {
                            // 跳过无权限访问的进程
                            continue;
                        }
                    }
                }
                WTSFreeMemory(ppProcessInfo);
            }
            return processes;
        }
    }
    
  • 在WorkerService的监控逻辑中使用:
    while (/* 你的循环条件 */)
    {
        var userSessionIds = WtsHelper.GetAllUserSessionIds();
        var allUserProcesses = new List<Process>();
    
        foreach (var sessionId in userSessionIds)
        {
            allUserProcesses.AddRange(WtsHelper.GetProcessesBySessionId(sessionId));
        }
    
        foreach (var item in allUserProcesses)
        {
            // 你的筛选逻辑
            if (item.MainWindowTitle.Length > 0 || (item.ProcessName.ToLowerInvariant() == "explorer"))
            {
                // 处理逻辑
            }
        }
    
        await Task.Delay(1000);
    }
    

替代方案:无UI控制台程序+任务计划

如果不想处理Windows服务的会话隔离问题,可以改用无UI控制台程序,配合任务计划程序实现后台运行:

  • 编写控制台程序,逻辑与原WorkerService一致(无需继承BackgroundService,直接在Main方法中写循环监控逻辑)
  • 打开「任务计划程序」→创建基本任务:
    • 触发器选择「当用户登录时」
    • 操作选择「启动程序」,指向你的控制台程序
    • 勾选「隐藏」选项,实现无后台运行
  • 该程序运行在用户会话中,可正常获取所有用户进程,且能自由写入文件,无需处理会话0的隔离限制

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 16:55:26