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

远程Windows Embedded设备上C#管理员程序如何以其他用户身份启动进程?

解决方案:跨会话以指定用户身份启动进程

在Windows Embedded设备上,管理员权限进程启动其他用户的exe时,核心问题是Windows会话隔离机制——默认情况下,进程会继承当前进程的会话ID,因此会在管理员会话中启动。要解决这个问题,必须明确指定目标用户的交互式会话ID,并通过CreateProcessAsUser API启动进程,而不是依赖Process.Start的默认行为。

关键步骤

1. 获取目标用户的交互式会话ID

首先需要找到目标用户登录的活跃交互式会话ID,只有在该会话中启动进程,用户才能看到界面。可以通过WTS API查询系统会话信息:

using System;
using System.Runtime.InteropServices;

public static class SessionHelper
{
    [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 WTSQuerySessionInformation(IntPtr hServer, int SessionId, WTS_INFO_CLASS WTSInfoClass, ref IntPtr ppBuffer, ref int pBytesReturned);

    private enum WTS_INFO_CLASS
    {
        WTSUserName = 5,
        WTSDomainName = 7
    }

    public static int GetUserSessionId(string targetUserName)
    {
        IntPtr serverHandle = IntPtr.Zero; // 本地设备用IntPtr.Zero
        IntPtr sessionInfoPtr = IntPtr.Zero;
        int sessionCount = 0;

        try
        {
            if (!WTSEnumerateSessions(serverHandle, 0, 1, ref sessionInfoPtr, ref sessionCount))
                return -1;

            int sessionSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
            IntPtr currentSessionPtr = sessionInfoPtr;

            for (int i = 0; i < sessionCount; i++)
            {
                WTS_SESSION_INFO sessionInfo = (WTS_SESSION_INFO)Marshal.PtrToStructure(currentSessionPtr, typeof(WTS_SESSION_INFO));
                if (sessionInfo.State == WTS_CONNECTSTATE_CLASS.WTSActive)
                {
                    IntPtr userNamePtr = IntPtr.Zero;
                    int bytesReturned = 0;
                    if (WTSQuerySessionInformation(serverHandle, sessionInfo.SessionId, WTS_INFO_CLASS.WTSUserName, ref userNamePtr, ref bytesReturned))
                    {
                        string userName = Marshal.PtrToStringAnsi(userNamePtr);
                        if (string.Equals(userName, targetUserName, StringComparison.OrdinalIgnoreCase))
                        {
                            WTSFreeMemory(userNamePtr);
                            return sessionInfo.SessionId;
                        }
                        WTSFreeMemory(userNamePtr);
                    }
                }
                currentSessionPtr += sessionSize;
            }
            return -1;
        }
        finally
        {
            if (sessionInfoPtr != IntPtr.Zero)
                WTSFreeMemory(sessionInfoPtr);
        }
    }

    private struct WTS_SESSION_INFO
    {
        public int SessionId;
        public string pWinStationName;
        public WTS_CONNECTSTATE_CLASS State;
    }

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

2. 使用CreateProcessAsUser启动进程

通过Windows API获取目标用户的令牌,设置令牌的会话ID,然后在指定会话和桌面启动进程:

using System.Diagnostics;
using System.Security;
using System.ComponentModel;
using System.Runtime.InteropServices;

public void StartProcessAsTargetUser(string exePath, string targetUserName, SecureString targetPassword, string domain = null)
{
    int sessionId = SessionHelper.GetUserSessionId(targetUserName);
    if (sessionId == -1)
        throw new InvalidOperationException("目标用户未登录或无活跃交互式会话");

    IntPtr userToken = IntPtr.Zero;
    try
    {
        // 获取目标用户的交互式登录令牌
        if (!LogonUser(targetUserName, domain ?? Environment.MachineName, targetPassword, 9, 0, ref userToken))
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        // 将令牌关联到目标用户的会话ID
        if (!SetTokenInformation(userToken, TOKEN_INFORMATION_CLASS.TokenSessionId, ref sessionId, sizeof(int)))
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        STARTUPINFO startupInfo = new STARTUPINFO
        {
            cb = Marshal.SizeOf(typeof(STARTUPINFO)),
            lpDesktop = "winsta0\\default" // 指定交互式桌面,确保用户能看到界面
        };
        PROCESS_INFORMATION processInfo = new PROCESS_INFORMATION();

        // 启动进程
        if (!CreateProcessAsUser(userToken, null, exePath, IntPtr.Zero, IntPtr.Zero, false, 0, IntPtr.Zero, null, ref startupInfo, ref processInfo))
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        // 关闭进程和线程句柄
        CloseHandle(processInfo.hProcess);
        CloseHandle(processInfo.hThread);
    }
    finally
    {
        if (userToken != IntPtr.Zero)
            CloseHandle(userToken);
    }
}

// Windows API声明
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool LogonUser(string lpszUsername, string lpszDomain, SecureString lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);

[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool SetTokenInformation(IntPtr TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass, ref int TokenInformation, int TokenInformationLength);

[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
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, ref PROCESS_INFORMATION lpProcessInformation);

[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);

private enum TOKEN_INFORMATION_CLASS
{
    TokenSessionId = 12
}

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;
}

private struct PROCESS_INFORMATION
{
    public IntPtr hProcess;
    public IntPtr hThread;
    public int dwProcessId;
    public int dwThreadId;
}

重要注意事项

  • 权限要求:你的管理员程序必须拥有SE_ASSIGNPRIMARY_TOKEN_NAME和SE_INCREASE_QUOTA_NAME权限。可以通过AdjustTokenPrivileges API调整程序令牌权限来添加。
  • 目标用户状态:目标用户必须已经登录到活跃交互式会话(状态为WTSActive),否则无法在该会话启动进程。
  • 桌面指定:必须设置lpDesktop = "winsta0\\default",否则进程会在非交互式桌面启动,用户无法看到界面。
  • 用户配置加载:如果需要加载目标用户的注册表配置,可以在CreateProcessAsUser的dwCreationFlags参数中添加CREATE_UNICODE_ENVIRONMENT,并确保目标用户的配置文件已创建。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 16:18:18