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

如何以系统用户运行的gRPC服务在当前用户会话打开应用?

解决方案:SYSTEM服务在登录用户会话启动可见程序

可以实现,核心是借助Windows API获取当前登录用户的交互式会话令牌,以此令牌启动进程,让程序运行在用户会话中并显示界面,同时保留服务的SYSTEM身份权限。

关键原理

SYSTEM用户的服务默认运行在非交互式会话(通常是0号),而登录用户处于交互式活动会话(会话ID通常≥1)。要让程序可见,必须将进程启动到用户的活动会话中,并使用该用户的令牌创建进程。

实现步骤与代码

  1. 引入Windows API声明,用于获取会话ID、用户令牌和创建进程
  2. 编写方法获取当前活跃用户的会话ID
  3. 获取该会话对应的用户令牌
  4. 使用令牌启动可见程序

以下是完整实现代码:

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 01:25:23