如何以系统用户运行的gRPC服务在当前用户会话打开应用?
解决方案:SYSTEM服务在登录用户会话启动可见程序
可以实现,核心是借助Windows API获取当前登录用户的交互式会话令牌,以此令牌启动进程,让程序运行在用户会话中并显示界面,同时保留服务的SYSTEM身份权限。
关键原理
SYSTEM用户的服务默认运行在非交互式会话(通常是0号),而登录用户处于交互式活动会话(会话ID通常≥1)。要让程序可见,必须将进程启动到用户的活动会话中,并使用该用户的令牌创建进程。
实现步骤与代码
- 引入Windows API声明,用于获取会话ID、用户令牌和创建进程
- 编写方法获取当前活跃用户的会话ID
- 获取该会话对应的用户令牌
- 使用令牌启动可见程序
以下是完整实现代码:
using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Security.Principal; public class SessionProcessStarter { // Windows API 声明 [DllImport("wtsapi32.dll")] private static extern bool WTSQueryUserToken(uint sessionId, out IntPtr token); [DllImport("kernel32.dll")] private static extern uint WTSGetActiveConsoleSessionId(); [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)] private static extern bool CreateProcessAsUser( IntPtr hToken, string lpApplicationName, string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation); [StructLayout(LayoutKind.Sequential)] private struct STARTUPINFO { public int cb; public string lpReserved; public string lpDesktop; public string lpTitle; public int dwX; public int dwY; public int dwXSize; public int dwYSize; public int dwXCountChars; public int dwYCountChars; public int dwFillAttribute; public int dwFlags; public short wShowWindow; public short cbReserved2; public IntPtr lpReserved2; public IntPtr hStdInput; public IntPtr hStdOutput; public IntPtr hStdError; } [StructLayout(LayoutKind.Sequential)] private struct PROCESS_INFORMATION { public IntPtr hProcess; public IntPtr hThread; public uint dwProcessId; public uint dwThreadId; } private const uint CREATE_NEW_CONSOLE = 0x00000010; private const int SW_SHOW = 5; public static void StartProcessInUserSession(string filePath, string arguments = "") { // 获取当前活跃控制台会话ID(登录用户的会话) uint sessionId = WTSGetActiveConsoleSessionId(); if (sessionId == 0xFFFFFFFF) { throw new InvalidOperationException("未找到活跃的用户会话"); } // 获取会话对应的用户令牌 if (!WTSQueryUserToken(sessionId, out IntPtr userToken)) { throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); } try { // 设置启动信息,指定桌面和窗口显示方式 STARTUPINFO startupInfo = new STARTUPINFO(); startupInfo.cb = Marshal.SizeOf(startupInfo); startupInfo.lpDesktop = "winsta0\\default"; // 指定用户桌面 startupInfo.wShowWindow = SW_SHOW; // 显示窗口 startupInfo.dwFlags = 0x00000001; // STARTF_USESHOWWINDOW PROCESS_INFORMATION processInfo = new PROCESS_INFORMATION(); string commandLine = string.IsNullOrEmpty(arguments) ? filePath : $"{filePath} {arguments}"; // 以用户令牌创建进程 bool success = CreateProcessAsUser( userToken, null, // 用commandLine指定程序路径 commandLine, IntPtr.Zero, IntPtr.Zero, false, CREATE_NEW_CONSOLE, IntPtr.Zero, null, ref startupInfo, out processInfo); if (!success) { throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); } // 关闭句柄释放资源 CloseHandle(processInfo.hProcess); CloseHandle(processInfo.hThread); } finally { CloseHandle(userToken); } } [DllImport("kernel32.dll")] private static extern bool CloseHandle(IntPtr hObject); } // 调用示例 public class Program { public static void Main() { try { // 启动记事本,显示在登录用户会话中 SessionProcessStarter.StartProcessInUserSession("notepad.exe"); } catch (Exception ex) { Console.WriteLine($"启动失败: {ex.Message}"); } } }
注意事项
- 原代码中
WindowStyle = ProcessWindowStyle.Hidden和CreateNoWindow = true会导致窗口隐藏,必须移除或改为对应可见配置(上述API实现中已通过SW_SHOW确保窗口可见) - SYSTEM权限足够调用
WTSQueryUserToken和CreateProcessAsUser,无需修改服务运行身份 - 若存在多个登录用户,
WTSGetActiveConsoleSessionId会返回当前在控制台(物理或远程桌面)活跃的用户会话ID
内容的提问来源于stack exchange,提问作者Álvaro García
相关产品推荐
相关产品推荐

