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 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);
}
/// 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);
}
#if (NET35 || NET40 || NET45)
/// Gets the child processes.
/// The process.
/// if set to true include descendants of child processes as well.
/// A reference for enumerating child processes.
public static IEnumerable GetChildProcesses(this Process p, bool includeDescendants = false)
{
if (p == null) throw new ArgumentNullException(nameof(p));
var l = new List>();
var scope = p.MachineName == "." ? new ManagementScope() : new ManagementScope($"\\\\{p.MachineName}\\root\\cimv2");
using (var mos = new ManagementObjectSearcher(scope, new ObjectQuery("select ProcessId, ParentProcessId from win32_process")))
{
foreach (var obj in mos.Get().Cast())
l.Add(new Tuple(Convert.ToInt32(obj["ProcessId"]), Convert.ToInt32(obj["ParentProcessId"])));
}
return GetChildProcesses(p.Id, l.GroupBy(i => i.Item2).ToDictionary(g => g.Key, g => g.ToList()), p.MachineName, includeDescendants);
}
#endif
///
/// The function gets the integrity level of the current process. Integrity level is only available on Windows Vista and newer operating systems, thus
/// GetProcessIntegrityLevel throws an exception if it is called on systems prior to Windows Vista.
///
/// Returns the integrity level of the current process.
///
/// When any native Windows API call fails, the function throws a Win32Exception with the last error code.
///
/// must be a valid .
public static ProcessIntegrityLevel GetIntegrityLevel(this Process p)
{
if (p == null)
throw new ArgumentNullException(nameof(p));
// Open the access token of the current process with TOKEN_QUERY.
var hObject = SafeHTOKEN.FromProcess(p, TokenAccess.TOKEN_QUERY | TokenAccess.TOKEN_DUPLICATE);
// Marshal the TOKEN_MANDATORY_LABEL struct from native to .NET object.
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.
return (GetSidSubAuthority(tokenIL.Label.Sid, 0)) switch
{
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)
///
/// Gets the parent process.
///
/// A object for the process that called the specified process. null if no parent can be established.
/// must be a valid .
public static Process GetParentProcess(this Process p)
{
if (p == null)
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);
}
catch { }
return null;
}
#endif
/// Gets the privileges for this process.
/// The process.
///
/// An enumeration of instances that include the process privileges and their associated attributes (enabled,
/// disabled, removed, etc.).
///
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));
}
/// Determines whether the specified privilege is had by the process.
/// The process.
/// The privilege.
/// true if the process has the specified privilege; otherwise, false.
public static bool HasPrivilege(this Process process, SystemPrivilege priv) => HasPrivileges(process, true, priv);
/// Determines whether the specified privileges are had by the process.
/// The process.
/// if set to true require all privileges to be enabled in order to return true.
/// The privileges to check.
/// 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);
}
///
/// The function gets the elevation information of the current process. It dictates whether the process is elevated or not. Token elevation is only
/// available on Windows Vista and newer operating systems, thus IsProcessElevated throws a C++ exception if it is called on systems prior to Windows
/// Vista. It is not appropriate to use this function to determine whether a process is run as administrator.
///
/// Returns true if the process is elevated. Returns false if it is not.
///
/// When any native Windows API call fails, the function throws a Win32Exception with the last error code.
///
/// must be a valid .
///
/// TOKEN_INFORMATION_CLASS provides TokenElevationType to check the elevation type (TokenElevationTypeDefault / TokenElevationTypeLimited /
/// TokenElevationTypeFull) of the process. It is different from TokenElevation in that, when UAC is turned off, elevation type always returns
/// TokenElevationTypeDefault even though the process is elevated (Integrity Level == High). In other words, it is not safe to say if the process is
/// elevated based on elevation type. Instead, we should use TokenElevation.
///
public static bool IsElevated(this Process p)
{
if (p == null)
throw new ArgumentNullException(nameof(p));
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;
}
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.
///
/// The process to check.
///
/// Returns true if the primary access token of the process belongs to user account that is a member of the local Administrators group. Returns false
/// if the token does not.
///
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);
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)
{
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 = 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();
}
}
#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)
{
if (allProcs == null) throw new ArgumentNullException(nameof(allProcs));
foreach (var val in allProcs[pid])
{
var cpid = val.Item1;
if (allChildren && allProcs.ContainsKey(cpid))
foreach (var cval in GetChildProcesses(cpid, allProcs, machineName))
yield return cval;
Process retProc = null;
try { retProc = Process.GetProcessById(cpid, machineName); } catch { }
if (retProc != null) yield return retProc;
}
}
}
}