|
今天在技术群里,石头哥向大家提了个问题:"如何在一个以System身份运行的.NET程序(Windows Services)中,以其它活动的用户身份启动可交互式进程(桌面应用程序、控制台程序、等带有UI和交互式体验的程序)"?
我以前有过类似的需求,是在GitLab流水线中运行带有UI的自动化测试程序。
其中流水线是GitLab Runner执行的,而GitLab Runner则被注册为Windows服务,以System身份启动的。
然后我在流水线里,巴拉巴拉写了一大串PowerShell脚本代码,通过调用任务计划程序实现了这个需求。
但我没试过在C#里实现这个功能。
对此,我很感兴趣,于是着手研究,最终捣鼓出来了。
二话不多说,上代码:- using System;
- using System.ComponentModel;
- using System.Diagnostics;
- using System.Runtime.InteropServices;
- using System.Runtime.Versioning;
-
- namespace AllenCai.Windows
- {
- /// <summary>
- /// 进程工具类
- /// </summary>
- [SupportedOSPlatform("windows")]
- public static class ProcessUtils
- {
- /// <summary>
- /// 在当前活动的用户会话中启动进程
- /// </summary>
- /// <param name="fileName">程序名称或程序路径</param>
- /// <param name="commandLine">命令行参数</param>
- /// <param name="workDir">工作目录</param>
- /// <param name="noWindow">是否无窗口</param>
- /// <param name="minimize">是否最小化</param>
- /// <returns></returns>
- /// <exception cref="ArgumentNullException"></exception>
- /// <exception cref="ApplicationException"></exception>
- /// <exception cref="Win32Exception"></exception>
- public static int StartProcessAsActiveUser(string fileName, string commandLine = null, string workDir = null, bool noWindow = false, bool minimize = false)
- {
- if (string.IsNullOrWhiteSpace(fileName)) throw new ArgumentNullException(nameof(fileName));
-
- // 获取当前活动的控制台会话ID和安全的用户访问令牌
- IntPtr userToken = GetSessionUserToken();
- if (userToken == IntPtr.Zero)
- throw new ApplicationException("Failed to get user token for the active session.");
-
- IntPtr duplicateToken = IntPtr.Zero;
- IntPtr environmentBlock = IntPtr.Zero;
- try
- {
- String file = fileName;
- bool shell = string.IsNullOrEmpty(workDir) && (!fileName.Contains('/') && !fileName.Contains('\\'));
- if (shell)
- {
- if (string.IsNullOrWhiteSpace(workDir)) workDir = Environment.CurrentDirectory;
- }
- else
- {
- if (!Path.IsPathRooted(fileName))
- {
- file = !string.IsNullOrEmpty(workDir) ? Path.Combine(workDir, fileName).GetFullPath() : fileName.GetFullPath();
- }
- if (string.IsNullOrWhiteSpace(workDir)) workDir = Path.GetDirectoryName(file);
- }
-
- if (string.IsNullOrWhiteSpace(commandLine)) commandLine = "";
-
- // 复制令牌
- SecurityAttributes sa = new SecurityAttributes();
- sa.Length = Marshal.SizeOf(sa);
- if (!DuplicateTokenEx(userToken, MAXIMUM_ALLOWED, ref sa, SecurityImpersonationLevel.SecurityIdentification, TokenType.TokenPrimary, out duplicateToken))
- throw new ApplicationException("Could not duplicate token.");
-
- // 创建环境块(检索该用户的环境变量)
- if (!CreateEnvironmentBlock(out environmentBlock, duplicateToken, false))
- throw new ApplicationException("Could not create environment block.");
-
- // 启动信息
- ProcessStartInfo psi = new ProcessStartInfo
- {
- UseShellExecute = shell,
- FileName = $"{file} {commandLine}", //解决带参数的进程起不来或者起来的进程没有参数的问题
- Arguments = commandLine,
- WorkingDirectory = workDir,
- RedirectStandardError = false,
- RedirectStandardOutput = false,
- RedirectStandardInput = false,
- CreateNoWindow = noWindow,
- WindowStyle = minimize ? ProcessWindowStyle.Minimized : ProcessWindowStyle.Normal
- };
-
- // 在指定的用户会话中创建进程
- SecurityAttributes saProcessAttributes = new SecurityAttributes();
- SecurityAttributes saThreadAttributes = new SecurityAttributes();
- CreateProcessFlags createProcessFlags = (noWindow ? CreateProcessFlags.CREATE_NO_WINDOW : CreateProcessFlags.CREATE_NEW_CONSOLE) | CreateProcessFlags.CREATE_UNICODE_ENVIRONMENT;
- bool success = CreateProcessAsUser(duplicateToken, null, $"{file} {commandLine}", ref saProcessAttributes, ref saThreadAttributes, false, createProcessFlags, environmentBlock, null, ref psi, out ProcessInformation pi);
- if (!success)
- {
- throw new Win32Exception(Marshal.GetLastWin32Error());
- //throw new ApplicationException("Could not create process as user.");
- }
-
- return pi.dwProcessId;
- }
- finally
- {
- // 清理资源
- if (userToken != IntPtr.Zero) CloseHandle(userToken);
- if (duplicateToken != IntPtr.Zero) CloseHandle(duplicateToken);
- if (environmentBlock != IntPtr.Zero) DestroyEnvironmentBlock(environmentBlock);
- }
- }
-
- /// <summary>
- /// 获取活动会话的用户访问令牌
- /// </summary>
- /// <exception cref="Win32Exception"></exception>
- private static IntPtr GetSessionUserToken()
- {
- // 获取当前活动的控制台会话ID
- uint sessionId = WTSGetActiveConsoleSessionId();
-
- // 获取活动会话的用户访问令牌
- bool success = WTSQueryUserToken(sessionId, out IntPtr hToken);
- // 如果失败,则从会话列表中获取第一个活动的会话ID,并再次尝试获取用户访问令牌
- if (!success)
- {
- sessionId = GetFirstActiveSessionOfEnumerateSessions();
- success = WTSQueryUserToken(sessionId, out hToken);
- if (!success)
- throw new Win32Exception(Marshal.GetLastWin32Error());
- }
-
- return hToken;
- }
-
- /// <summary>
- /// 枚举所有用户会话,获取第一个活动的会话ID
- /// </summary>
- private static uint GetFirstActiveSessionOfEnumerateSessions()
- {
- IntPtr pSessionInfo = IntPtr.Zero;
- try
- {
- Int32 sessionCount = 0;
-
- // 枚举所有用户会话
- if (WTSEnumerateSessions(IntPtr.Zero, 0, 1, ref pSessionInfo, ref sessionCount) != 0)
- {
- Int32 arrayElementSize = Marshal.SizeOf(typeof(WtsSessionInfo));
- IntPtr current = pSessionInfo;
-
- for (Int32 i = 0; i < sessionCount; i++)
- {
- WtsSessionInfo si = (WtsSessionInfo)Marshal.PtrToStructure(current, typeof(WtsSessionInfo));
- current += arrayElementSize;
-
- if (si.State == WtsConnectStateClass.WTSActive)
- {
- return si.SessionID;
- }
- }
- }
-
- return uint.MaxValue;
- }
- finally
- {
- WTSFreeMemory(pSessionInfo);
- CloseHandle(pSessionInfo);
- }
- }
-
- /// <summary>
- /// 以指定用户的身份启动进程
- /// </summary>
- [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
- private static extern bool CreateProcessAsUser(
- IntPtr hToken,
- string lpApplicationName,
- string lpCommandLine,
- ref SecurityAttributes lpProcessAttributes,
- ref SecurityAttributes lpThreadAttributes,
- bool bInheritHandles,
- CreateProcessFlags dwCreationFlags,
- IntPtr lpEnvironment,
- string lpCurrentDirectory,
- ref ProcessStartInfo lpStartupInfo,
- out ProcessInformation lpProcessInformation
- );
-
- /// <summary>
- /// 获取当前活动的控制台会话ID
- /// </summary>
- [DllImport("kernel32.dll", SetLastError = true)]
- private static extern uint WTSGetActiveConsoleSessionId();
-
- /// <summary>
- /// 枚举所有用户会话
- /// </summary>
- [DllImport("wtsapi32.dll", SetLastError = true)]
- private static extern int WTSEnumerateSessions(IntPtr hServer, int reserved, int version, ref IntPtr ppSessionInfo, ref int pCount);
-
- /// <summary>
- /// 获取活动会话的用户访问令牌
- /// </summary>
- [DllImport("wtsapi32.dll", SetLastError = true)]
- private static extern bool WTSQueryUserToken(uint sessionId, out IntPtr phToken);
-
- /// <summary>
- /// 复制访问令牌
- /// </summary>
- [DllImport("advapi32.dll", SetLastError = true)]
- private static extern bool DuplicateTokenEx(IntPtr hExistingToken, uint dwDesiredAccess, ref SecurityAttributes lpTokenAttributes, SecurityImpersonationLevel impersonationLevel, TokenType tokenType, out IntPtr phNewToken);
-
- /// <summary>
- /// 创建环境块(检索指定用户的环境)
- /// </summary>
- [DllImport("userenv.dll", SetLastError = true)]
- private static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, bool bInherit);
-
- /// <summary>
- /// 释放环境块
- /// </summary>
- [DllImport("userenv.dll", SetLastError = true)]
- private static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment);
-
- [DllImport("wtsapi32.dll", SetLastError = false)]
- private static extern void WTSFreeMemory(IntPtr memory);
-
- [DllImport("kernel32.dll", SetLastError = true)]
- private static extern bool CloseHandle(IntPtr hObject);
-
- [StructLayout(LayoutKind.Sequential)]
- private struct WtsSessionInfo
- {
- public readonly uint SessionID;
-
- [MarshalAs(UnmanagedType.LPStr)]
- public readonly string pWinStationName;
-
- public readonly WtsConnectStateClass State;
- }
-
- [StructLayout(LayoutKind.Sequential)]
- private struct SecurityAttributes
- {
- public int Length;
- public IntPtr SecurityDescriptor;
- public bool InheritHandle;
- }
-
- [StructLayout(LayoutKind.Sequential)]
- private struct ProcessInformation
- {
- public IntPtr hProcess;
- public IntPtr hThread;
- public int dwProcessId;
- public int dwThreadId;
- }
-
- private const uint TOKEN_DUPLICATE = 0x0002;
- private const uint MAXIMUM_ALLOWED = 0x2000000;
- private const uint STARTF_USESHOWWINDOW = 0x00000001;
-
- /// <summary>
- /// Process Creation Flags。<br/>
- /// More:https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags
- /// </summary>
- [Flags]
- private enum CreateProcessFlags : uint
- {
- DEBUG_PROCESS = 0x00000001,
- DEBUG_ONLY_THIS_PROCESS = 0x00000002,
- CREATE_SUSPENDED = 0x00000004,
- DETACHED_PROCESS = 0x00000008,
- /// <summary>
- /// The new process has a new console, instead of inheriting its parent's console (the default). For more information, see Creation of a Console. <br />
- /// This flag cannot be used with <see cref="DETACHED_PROCESS"/>.
- /// </summary>
- CREATE_NEW_CONSOLE = 0x00000010,
- NORMAL_PRIORITY_CLASS = 0x00000020,
- IDLE_PRIORITY_CLASS = 0x00000040,
- HIGH_PRIORITY_CLASS = 0x00000080,
- REALTIME_PRIORITY_CLASS = 0x00000100,
- CREATE_NEW_PROCESS_GROUP = 0x00000200,
- /// <summary>
- /// If this flag is set, the environment block pointed to by lpEnvironment uses Unicode characters. Otherwise, the environment block uses ANSI characters.
- /// </summary>
- CREATE_UNICODE_ENVIRONMENT = 0x00000400,
- CREATE_SEPARATE_WOW_VDM = 0x00000800,
- CREATE_SHARED_WOW_VDM = 0x00001000,
- CREATE_FORCEDOS = 0x00002000,
- BELOW_NORMAL_PRIORITY_CLASS = 0x00004000,
- ABOVE_NORMAL_PRIORITY_CLASS = 0x00008000,
- INHERIT_PARENT_AFFINITY = 0x00010000,
- INHERIT_CALLER_PRIORITY = 0x00020000,
- CREATE_PROTECTED_PROCESS = 0x00040000,
- EXTENDED_STARTUPINFO_PRESENT = 0x00080000,
- PROCESS_MODE_BACKGROUND_BEGIN = 0x00100000,
- PROCESS_MODE_BACKGROUND_END = 0x00200000,
- CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
- CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000,
- CREATE_DEFAULT_ERROR_MODE = 0x04000000,
- /// <summary>
- /// The process is a console application that is being run without a console window. Therefore, the console handle for the application is not set. <br />
- /// This flag is ignored if the application is not a console application, or if it is used with either <see cref="CREATE_NEW_CONSOLE"/> or <see cref="DETACHED_PROCESS"/>.
- /// </summary>
- CREATE_NO_WINDOW = 0x08000000,
- PROFILE_USER = 0x10000000,
- PROFILE_KERNEL = 0x20000000,
- PROFILE_SERVER = 0x40000000,
- CREATE_IGNORE_SYSTEM_DEFAULT = 0x80000000,
- }
-
- private enum WtsConnectStateClass
- {
- WTSActive,
- WTSConnected,
- WTSConnectQuery,
- WTSShadow,
- WTSDisconnected,
- WTSIdle,
- WTSListen,
- WTSReset,
- WTSDown,
- WTSInit
- }
-
- private enum SecurityImpersonationLevel
- {
- SecurityAnonymous,
- SecurityIdentification,
- SecurityImpersonation,
- SecurityDelegation
- }
-
- private enum TokenType
- {
- TokenPrimary = 1,
- TokenImpersonation
- }
- }
- }
复制代码 用法:- ProcessUtils.StartProcessAsActiveUser("ping.exe", "www.baidu.com -t");
- ProcessUtils.StartProcessAsActiveUser("notepad.exe");
- ProcessUtils.StartProcessAsActiveUser("C:\\Windows\\System32\\notepad.exe");
复制代码 在 Windows 7~11、Windows Server 2016~2022 操作系统,测试通过。
来源:https://www.cnblogs.com/VAllen/p/18257879/in-dotnet-program-run-as-system-to-start-an-interactive-process-as-the-specified-user
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作! |
|