在System身份运行的.NET程序中以指定的用户身份启动可交互式进程

news/2024/9/28 12:19:19

今天在技术群里,石头哥向大家提了个问题:"如何在一个以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(){// 获取当前活动的控制台会话IDuint 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~11Windows Server 2016~2022 操作系统,测试通过。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hjln.cn/news/47328.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

CKEditor5 自定义构建富文本编辑器!

前言 CKEditor5的编辑是一个非常好的编辑器,但其英文文档比较绕眼睛,所以特地记录一下,如何使用自定义构建。 1、Online Bulider、Source Building 此为官方提供的,不适合我等现代构建方式。 自定义构建文档 2、自定义构建,在项目直接创建一个全新的0开始的编辑器。 此次,…

验证码识别

import ddddocrocr = ddddocr.DdddOcr()with open(img/验证码3.png, rb) as f:img_bytes = f.read()result = ocr.classification(img_bytes) print(result) 运行结果:

VIP视频解析

效果图 新建窗口import tkinter as tk# 创建一个窗口 root = tk.Tk()# 设置窗口大小 root.geometry(700x250+200+200)# 设置标题 root.title(在线观看电影软件)# 让窗口持续展现 root.mainloop() 设置背景图片# 设置读取一张图片 img = tk.PhotoImage(file=img\\封面.png)# 布局…

源代码安全漏洞扫描

构建一个应用程序,并始终确保应用程序其安全性的话,事实上构建应用程序的时候需要花大量的工作,一个步骤没有检查就可能导致整个系统或者产品都处于受黑客攻击的危险之中,谁不希望在产品发布初期就发现安全漏洞并且修复漏洞,那何乐而不为呢! 源代码安全漏洞扫描工具 可以…

首期openGauss训练营结营,48个FAQ和全部PPT通通给你

首期openGauss训练营结营,48个FAQ和全部PPT通通给你,随附62人结营学员名单转载mob604756fa96d72021-06-04 14:37:20 文章标签Java文章分类Java后端开发阅读数118 玩转openGauss的 数据和云2021年3月27-28日,由openGauss社区技术委员会主席、openGauss首席架构师、华为公司数…

Vue项目打包部署(Nginx)

Vue项目打包控制台输入打包命令:npm run build  成功后会在本地项目路径下生成一份 dist 文件。进行压缩 为 .zip包就可以上传Linux了。

你了解base么?1 解题

CTF 你了解base么?1 解题 题目:CTF 你了解base么?1 题目内容:在数据的深海里,我探寻Base的奥秘, 如星辰般闪烁,是信息的集结地。 代码编织的网,捕捉着数据的踪迹, Base,你是数据的港湾,是智慧的基石。字符串的舞蹈,在Base中跃动, 二进制、十六进制,变幻着节奏…

NVIDIA Broadcast+普通麦克风+ai

关于 NVIDIA Broadcast NVIDIA Broadcast 通过 AI 的强大功能将标准网络摄像头和麦克风升级为高级智能设备,将任何房间转变为家庭工作室。通过麦克风噪声和房间回声消除、虚拟背景、网络摄像头自动取景和视频噪声消除等 AI 功能提高直播的视频和音频质量。借助 NVIDIA RTX GPU…