远程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权限。可以通过AdjustTokenPrivilegesAPI调整程序令牌权限来添加。 - 目标用户状态:目标用户必须已经登录到活跃交互式会话(状态为
WTSActive),否则无法在该会话启动进程。 - 桌面指定:必须设置
lpDesktop = "winsta0\\default",否则进程会在非交互式桌面启动,用户无法看到界面。 - 用户配置加载:如果需要加载目标用户的注册表配置,可以在
CreateProcessAsUser的dwCreationFlags参数中添加CREATE_UNICODE_ENVIRONMENT,并确保目标用户的配置文件已创建。
内容的提问来源于stack exchange,提问作者Avironman
相关产品推荐
相关产品推荐

