diff --git a/System/Extensions/ProcessExtension.cs b/System/Extensions/ProcessExtension.cs index 45f4830e..c0dcb3d1 100644 --- a/System/Extensions/ProcessExtension.cs +++ b/System/Extensions/ProcessExtension.cs @@ -1,45 +1,66 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; #if (NET20 || NET35 || NET40 || NET45) using System.Management; #endif using System.Runtime.InteropServices; +using System.Text; +using Vanara.Extensions.Reflection; +using Vanara.InteropServices; +using Vanara.IO; +using Vanara.PInvoke; using Vanara.Security; using Vanara.Security.AccessControl; using static Vanara.PInvoke.AdvApi32; +using static Vanara.PInvoke.Kernel32; namespace Vanara.Extensions { /// Values which define a processes integrity level. public enum ProcessIntegrityLevel { + /// Untrusted. Untrusted, + + /// Undefined. Undefined, + + /// Low. Low, + + /// Medium. Medium, + + /// High. High, + + /// System. System } - /// Extension methods for for privilegs, status, elevation and relationships. + /// Extension methods for for privileges, status, elevation and relationships. public static partial class ProcessExtension { + /// Disables a specified system privilege on a process. + /// The process on which to disable the privilege. + /// The privilege to disable. public static void DisablePrivilege(this Process process, SystemPrivilege privilege) { - using (var hObj = SafeHTOKEN.FromProcess(process, TokenAccess.TOKEN_ADJUST_PRIVILEGES | TokenAccess.TOKEN_QUERY)) - { - hObj.AdjustPrivilege(privilege, PrivilegeAttributes.SE_PRIVILEGE_DISABLED); - } + using var hObj = SafeHTOKEN.FromProcess(process, TokenAccess.TOKEN_ADJUST_PRIVILEGES | TokenAccess.TOKEN_QUERY); + hObj.AdjustPrivilege(privilege, PrivilegeAttributes.SE_PRIVILEGE_DISABLED); } + /// Enables a specified system privilege on a process. + /// The process on which to enable the privilege. + /// The privilege to enable. public static void EnablePrivilege(this Process process, SystemPrivilege privilege) { - using (var hObj = SafeHTOKEN.FromProcess(process, TokenAccess.TOKEN_ADJUST_PRIVILEGES | TokenAccess.TOKEN_QUERY)) - { - hObj.AdjustPrivilege(privilege, PrivilegeAttributes.SE_PRIVILEGE_ENABLED); - } + using var hObj = SafeHTOKEN.FromProcess(process, TokenAccess.TOKEN_ADJUST_PRIVILEGES | TokenAccess.TOKEN_QUERY); + hObj.AdjustPrivilege(privilege, PrivilegeAttributes.SE_PRIVILEGE_ENABLED); } #if (NET35 || NET40 || NET45) @@ -83,21 +104,15 @@ namespace Vanara.Extensions var tokenIL = hObject.GetInfo(TOKEN_INFORMATION_CLASS.TokenIntegrityLevel); // Integrity Level SIDs are in the form of S-1-16-0xXXXX. (e.g. S-1-16-0x1000 stands for low integrity level SID). There is one and only one subauthority. - switch (GetSidSubAuthority(tokenIL.Label.Sid, 0)) + return (GetSidSubAuthority(tokenIL.Label.Sid, 0)) switch { - case 0: - return ProcessIntegrityLevel.Untrusted; - case 0x1000: - return ProcessIntegrityLevel.Low; - case var iVal when iVal >= 0x2000 && iVal < 0x3000: - return ProcessIntegrityLevel.Medium; - case var iVal when iVal >= 0x4000: - return ProcessIntegrityLevel.System; - case var iVal when iVal >= 0x3000: - return ProcessIntegrityLevel.High; - default: - return ProcessIntegrityLevel.Undefined; - } + 0 => ProcessIntegrityLevel.Untrusted, + 0x1000 => ProcessIntegrityLevel.Low, + var iVal when iVal >= 0x2000 && iVal < 0x3000 => ProcessIntegrityLevel.Medium, + var iVal when iVal >= 0x4000 => ProcessIntegrityLevel.System, + var iVal when iVal >= 0x3000 => ProcessIntegrityLevel.High, + _ => ProcessIntegrityLevel.Undefined, + }; } #if (NET20 || NET35 || NET40 || NET45) @@ -112,11 +127,9 @@ namespace Vanara.Extensions throw new ArgumentNullException(nameof(p)); try { - using (var mo = new ManagementObject($"win32_process.handle='{p.Id}'")) - { - mo.Get(); - return Process.GetProcessById(Convert.ToInt32(mo["ParentProcessId"]), p.MachineName); - } + using var mo = new ManagementObject($"win32_process.handle='{p.Id}'"); + mo.Get(); + return Process.GetProcessById(Convert.ToInt32(mo["ParentProcessId"]), p.MachineName); } catch { } return null; @@ -131,10 +144,8 @@ namespace Vanara.Extensions /// public static IEnumerable GetPrivileges(this Process process) { - using (var hObj = SafeHTOKEN.FromProcess(process, TokenAccess.TOKEN_QUERY | TokenAccess.TOKEN_DUPLICATE)) - { - return hObj.GetPrivileges().Select(la => new PrivilegeAndAttributes(la.Luid.GetPrivilege(process.MachineName), la.Attributes)); - } + using var hObj = SafeHTOKEN.FromProcess(process, TokenAccess.TOKEN_QUERY | TokenAccess.TOKEN_DUPLICATE); + return hObj.GetPrivileges().Select(la => new PrivilegeAndAttributes(la.Luid.GetPrivilege(process.MachineName), la.Attributes)); } /// Determines whether the specified privilege is had by the process. @@ -150,8 +161,8 @@ namespace Vanara.Extensions /// true if the process has the specified privilege; otherwise, false. public static bool HasPrivileges(this Process process, bool requireAll, params SystemPrivilege[] privs) { - using (var hObj = SafeHTOKEN.FromProcess(process, TokenAccess.TOKEN_QUERY | TokenAccess.TOKEN_DUPLICATE)) - return hObj.HasPrivileges(requireAll, privs); + using var hObj = SafeHTOKEN.FromProcess(process, TokenAccess.TOKEN_QUERY | TokenAccess.TOKEN_DUPLICATE); + return hObj.HasPrivileges(requireAll, privs); } /// @@ -178,13 +189,23 @@ namespace Vanara.Extensions try { // Open the access token of the current process with TOKEN_QUERY. - using (var hObject = SafeHTOKEN.FromProcess(p, TokenAccess.TOKEN_QUERY | TokenAccess.TOKEN_DUPLICATE)) - return hObject.IsElevated; + using var hObject = SafeHTOKEN.FromProcess(p, TokenAccess.TOKEN_QUERY | TokenAccess.TOKEN_DUPLICATE); + return hObject.IsElevated; } catch { } return false; } + /// Determines whether the process is running within a job object. + /// + /// + /// The process to be tested. The handle must have the PROCESS_QUERY_INFORMATION or PROCESS_QUERY_LIMITED_INFORMATION access right. + /// + /// Windows Server 2003 and Windows XP: The handle must have the PROCESS_QUERY_INFORMATION access right. + /// + /// if the process is running in a job, and otherwise. + public static bool IsInJob(this Process p) => IsProcessInJob(p, HJOB.NULL, out var res) ? res : throw Win32Error.GetLastError().GetException(); + /// /// The function checks whether the primary access token of the process belongs to user account that is a member of the local Administrators group, /// even if it currently is not elevated. @@ -196,12 +217,197 @@ namespace Vanara.Extensions /// public static bool IsRunningAsAdmin(this Process proc) => UAC.IsRunningAsAdmin(proc); + /// Removes a specified system privilege from a process. + /// The process from which to remove the privilege. + /// The privilege to remove. public static void RemovePrivilege(this Process process, SystemPrivilege privilege) { - using (var hObj = SafeHTOKEN.FromProcess(process, TokenAccess.TOKEN_ADJUST_PRIVILEGES | TokenAccess.TOKEN_QUERY)) + using var hObj = SafeHTOKEN.FromProcess(process, TokenAccess.TOKEN_ADJUST_PRIVILEGES | TokenAccess.TOKEN_QUERY); + hObj.AdjustPrivilege(privilege, PrivilegeAttributes.SE_PRIVILEGE_REMOVED); + } + + /// Resumes the primary thread on the process. + /// The running process. + public static void ResumePrimaryThread(this Process process) + { + using var hTh = OpenThread((uint)ThreadAccess.THREAD_RESUME, true, (uint)process.Threads[0].Id); + ResumeThread(hTh); + } + + /// Extension method to start a process with extra flags. + /// The process to start. + /// The process flags. + /// if successful. + /// + /// Process.StartEx cannot be used when StartInfo.UseShellExecute is true. or File name is missing. or StandardOutputEncoding not + /// allowed or StandardErrorEncoding not allowed + /// + /// Can't set duplicate password + public static bool StartEx(this Process process, CREATE_PROCESS flags) + { + var startInfo = process.StartInfo; + if (startInfo.UseShellExecute) + throw new InvalidOperationException("Process.StartEx cannot be used when StartInfo.UseShellExecute is true."); + if (startInfo.FileName.Length == 0) + throw new InvalidOperationException("File name is missing."); + + process.Close(); + + if (startInfo.StandardOutputEncoding != null && !startInfo.RedirectStandardOutput) + throw new InvalidOperationException("StandardOutputEncoding not allowed"); + + if (startInfo.StandardErrorEncoding != null && !startInfo.RedirectStandardError) + throw new InvalidOperationException("StandardErrorEncoding not allowed"); + + // TODO: Cannot start a new process and store its handle if the object has been disposed, since finalization has been suppressed. + // if (process.disposed) throw new ObjectDisposedException(GetType().Name); + + var commandLine = new StringBuilder(string.Concat(PathEx.QuoteIfHasSpaces(startInfo.FileName), string.IsNullOrEmpty(startInfo.Arguments) ? "" : " " + startInfo.Arguments)); + + var startupInfo = STARTUPINFO.Default; + bool retVal; + Win32Error errorCode = 0; + // handles used in parent process + SafeHPIPE standardInputWritePipeHandle = null, standardOutputReadPipeHandle = null, standardErrorReadPipeHandle = null, hStdInput = null, hStdOutput = null, hStdError = null; + + // set up the streams + if (startInfo.RedirectStandardInput || startInfo.RedirectStandardOutput || startInfo.RedirectStandardError) { - hObj.AdjustPrivilege(privilege, PrivilegeAttributes.SE_PRIVILEGE_REMOVED); + if (startInfo.RedirectStandardInput) + { + CreatePipe(out standardInputWritePipeHandle, out hStdInput, new SECURITY_ATTRIBUTES(), 0); + startupInfo.hStdInput = hStdInput; + } + else + { + startupInfo.hStdInput = (IntPtr)GetStdHandle(StdHandleType.STD_INPUT_HANDLE); + } + + if (startInfo.RedirectStandardOutput) + { + CreatePipe(out standardOutputReadPipeHandle, out hStdOutput, new SECURITY_ATTRIBUTES(), 0); + startupInfo.hStdOutput = hStdOutput; + } + else + { + startupInfo.hStdOutput = (IntPtr)GetStdHandle(StdHandleType.STD_OUTPUT_HANDLE); + } + + if (startInfo.RedirectStandardError) + { + CreatePipe(out standardErrorReadPipeHandle, out hStdError, new SECURITY_ATTRIBUTES(), 0); + startupInfo.hStdError = hStdError; + } + else + { + startupInfo.hStdError = (IntPtr)GetStdHandle(StdHandleType.STD_ERROR_HANDLE); + } + + startupInfo.dwFlags = STARTF.STARTF_USESTDHANDLES; } + + // set up the creation flags paramater + var creationFlags = flags; + if (startInfo.CreateNoWindow) creationFlags |= CREATE_PROCESS.CREATE_NO_WINDOW; + + var workingDirectory = startInfo.WorkingDirectory; + if (workingDirectory == string.Empty) + workingDirectory = Environment.CurrentDirectory; + + SafePROCESS_INFORMATION processInfo; + if (startInfo.UserName.Length != 0) + { + if (startInfo.Password != null) + throw new ArgumentException("Can't set duplicate password"); + + ProcessLogonFlags logonFlags = 0; + if (startInfo.LoadUserProfile) + logonFlags = ProcessLogonFlags.LOGON_WITH_PROFILE; + + using var password = startInfo.Password != null ? new SafeCoTaskMemString(startInfo.Password) : new SafeCoTaskMemString(string.Empty); + System.Runtime.CompilerServices.RuntimeHelpers.PrepareConstrainedRegions(); + try { } + finally + { + retVal = CreateProcessWithLogonW( + startInfo.UserName, + startInfo.Domain, + password, + logonFlags, + null, // we don't need this since all the info is in commandLine + commandLine, + creationFlags, + startInfo.EnvironmentVariables?.Cast().Select(e => $"{e.Key}={e.Value}").ToArray(), + workingDirectory, + startupInfo, // pointer to STARTUPINFO + out processInfo); // pointer to PROCESS_INFORMATION + if (!retVal) + errorCode = Win32Error.GetLastError(); + } + if (!retVal) + { + if (errorCode == Win32Error.ERROR_BAD_EXE_FORMAT || errorCode == Win32Error.ERROR_EXE_MACHINE_TYPE_MISMATCH) + throw errorCode.GetException("Invalid application"); + throw errorCode.GetException(); + } + } + else + { + System.Runtime.CompilerServices.RuntimeHelpers.PrepareConstrainedRegions(); + try { } + finally + { + retVal = CreateProcess( + null, // we don't need this since all the info is in commandLine + commandLine, // pointer to the command line string + null, // pointer to process security attributes, we don't need to inheriat the handle + null, // pointer to thread security attributes + true, // handle inheritance flag + creationFlags, // creation flags + startInfo.EnvironmentVariables?.Cast().Select(e => $"{e.Key}={e.Value}").ToArray(), + workingDirectory, // pointer to current directory name + startupInfo, // pointer to STARTUPINFO + out processInfo); // pointer to PROCESS_INFORMATION + if (!retVal) + errorCode = Marshal.GetLastWin32Error(); + } + if (!retVal) + { + if (errorCode == Win32Error.ERROR_BAD_EXE_FORMAT || errorCode == Win32Error.ERROR_EXE_MACHINE_TYPE_MISMATCH) + throw errorCode.GetException("Invalid application"); + throw errorCode.GetException(); + } + } + +#pragma warning disable CS0618 // Type or member is obsolete + if (startInfo.RedirectStandardInput) + { + var stdIn = new StreamWriter(new FileStream(standardInputWritePipeHandle.DangerousGetHandle(), System.IO.FileAccess.Write, false, 4096), Console.InputEncoding, 4096); + stdIn.AutoFlush = true; + process.SetFieldValue("standardInput", stdIn); + } + if (startInfo.RedirectStandardOutput) + { + var enc = (startInfo.StandardOutputEncoding != null) ? startInfo.StandardOutputEncoding : Console.OutputEncoding; + var stdOut = new StreamReader(new FileStream(standardOutputReadPipeHandle.DangerousGetHandle(), System.IO.FileAccess.Read, false, 4096), enc, true, 4096); + process.SetFieldValue("standardOutput", stdOut); + } + if (startInfo.RedirectStandardError) + { + var enc = (startInfo.StandardErrorEncoding != null) ? startInfo.StandardErrorEncoding : Console.OutputEncoding; + var stdErr = new StreamReader(new FileStream(standardErrorReadPipeHandle.DangerousGetHandle(), System.IO.FileAccess.Read, false, 4096), enc, true, 4096); + process.SetFieldValue("standardError", stdErr); + } +#pragma warning restore CS0618 // Type or member is obsolete + + if (!processInfo.hProcess.IsInvalid) + { + //process.InvokeMethod("SetProcessHandle", new Microsoft.Win32.SafeHandles.SafeProcessHandle(processInfo.hProcess.Duplicate(), true)); + process.InvokeMethod("SetProcessId", (int)processInfo.dwProcessId); + var h = process.Handle; + return true; + } + return false; } private static IEnumerable GetChildProcesses(int pid, Dictionary>> allProcs, string machineName, bool allChildren = true)