using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; namespace Selenium.Internal { public class ProcessExt : IDisposable { #region Standard Environemnt Variables static readonly string[] STD_ENV_VARS = { "ALLUSERSPROFILE","APPDATA","COMPUTERNAME","ComSpec","CommonProgramFiles", "CommonProgramFiles(x86)","CommonProgramW6432","HOMEDRIVE","HOMEPATH", "LOCALAPPDATA","LOGONSERVER","NUMBER_OF_PROCESSORS","OS","PATHEXT", "PROCESSOR_ARCHITECTURE","PROCESSOR_ARCHITEW6432","PROCESSOR_IDENTIFIER", "PROCESSOR_LEVEL","PROCESSOR_REVISION","PUBLIC","ProgramData","ProgramFiles", "ProgramFiles(x86)","ProgramW6432","SystemDrive","SystemRoot","TEMP","TMP", "USERDOMAIN","USERDOMAIN_ROAMINGPROFILE","USERNAME","USERPROFILE","WINDIR", "JAVA_HOME" }; #endregion /// /// Starts a process /// /// File path /// Arguments /// Working dir. Inherits the current directory if null /// Environement variables. Inherits all of them if null /// Hides the window if true /// Creates a Job if true /// public static ProcessExt Start(string filepath, IEnumerable args , string dir, Hashtable env, bool noWindow, bool createJob) { string cmd = ProcessExt.BuildCommandLine(filepath, args); StringBuilder envVars = BuildEnvironmentVars(env); var si = new Native.STARTUPINFO(); var pi = new Native.PROCESS_INFORMATION(); int createFlags = Native.CREATE_UNICODE_ENVIRONMENT; if (noWindow) createFlags |= Native.CREATE_NO_WINDOW; IntPtr hJob = IntPtr.Zero; bool success = false; if (createJob) { IntPtr curProc = Native.GetCurrentProcess(); bool isProcessInJob = false; success = Native.IsProcessInJob(curProc, IntPtr.Zero, out isProcessInJob); Native.CloseHandle(curProc); if (success) { int createFlagsJob = createFlags | Native.CREATE_SUSPENDED; if (isProcessInJob) createFlagsJob |= Native.CREATE_BREAKAWAY_FROM_JOB; success = Native.CreateProcess(null , cmd , IntPtr.Zero , IntPtr.Zero , false , createFlagsJob , envVars , dir, si, pi); if (success) { success = AssignProcessToNewJob(pi.hProcess, null, out hJob); if (success) { if (-1 == Native.ResumeThread(pi.hThread)) throw new Win32Exception(); } else { Native.TerminateProcess(pi.hProcess, -1); Native.CloseHandle(pi.hProcess); Native.CloseHandle(pi.hThread); Native.CloseHandle(hJob); hJob = IntPtr.Zero; } } } } if (!success) { success = Native.CreateProcess(null , cmd , IntPtr.Zero , IntPtr.Zero , false , createFlags , envVars , dir, si, pi); if (!success) throw new Win32Exception(); } return new ProcessExt(pi.dwProcessId, pi.hProcess, hJob); } /// /// Execute a command line. /// /// Command line /// Working dir. Inherits the current directory if null. /// Environement variables. Inherits all of them if null. /// Hides the window if true public static void Execute(string cmd, string dir , Hashtable env = null, bool noWindow = false) { var si = new Native.STARTUPINFO(); var pi = new Native.PROCESS_INFORMATION(); int createFlags = Native.CREATE_UNICODE_ENVIRONMENT; if (noWindow) createFlags |= Native.CREATE_NO_WINDOW; StringBuilder envVars = BuildEnvironmentVars(env); try { bool success = Native.CreateProcess(null , cmd , IntPtr.Zero , IntPtr.Zero , false , createFlags , envVars , dir, si, pi); if (!success) throw new Win32Exception(); } finally { Native.CloseHandle(pi.hThread); Native.CloseHandle(pi.hProcess); } } /// /// Returns all the standards environement variables. /// /// public static Hashtable GetStdEnvironmentVariables() { IDictionary dict = Environment.GetEnvironmentVariables(); Hashtable newDict = new Hashtable(StringComparer.InvariantCultureIgnoreCase); foreach (string key in STD_ENV_VARS) { object value = dict[key]; if (value != null) newDict.Add(key, (string)value); } return newDict; } [DebuggerDisplay("Pid:{Id} ParentPid:{ParentId}")] public class ProcessRelationship { public int Pid; public int ParentPid; } /// /// Returns all the process relationships. /// /// public static unsafe ProcessRelationship[] GetProcessRelationships() { var relationchips = new List(100); IntPtr snapshot = Native.CreateToolhelp32Snapshot(Native.TH32CS_SNAPPROCESS, 0); int bufferSize = sizeof(Native.WinProcessEntry); IntPtr buffer = Marshal.AllocHGlobal(bufferSize); Marshal.WriteInt32(buffer, bufferSize); var natEntryPtr = (Native.WinProcessEntry*)buffer; try { if (!Native.Process32First(snapshot, buffer)) throw new Win32Exception(); do { Native.WinProcessEntry natEntry = *natEntryPtr; ProcessRelationship relationchip = new ProcessRelationship(); relationchip.Pid = natEntry.th32ProcessID; relationchip.ParentPid = natEntry.th32ParentProcessID; relationchips.Add(relationchip); } while (Native.Process32Next(snapshot, buffer)); } finally { Marshal.FreeHGlobal(buffer); Native.CloseHandle(snapshot); } return relationchips.ToArray(); } #region Instance private int _pid; private IntPtr _hJob; private IntPtr _hProcess; // Summary: // Initializes a new instance of the System.Diagnostics.Process class. private ProcessExt(int pid, IntPtr hProcess, IntPtr hJob) { _pid = pid; _hJob = hJob; _hProcess = hProcess; } /// /// Release the resources /// public void Dispose() { if (_hJob != IntPtr.Zero) { Native.CloseHandle(_hJob); _hJob = IntPtr.Zero; } if (_hProcess != IntPtr.Zero) { Native.CloseHandle(_hProcess); _hProcess = IntPtr.Zero; } _pid = 0; } /// /// Process id /// public int Id { get { return _pid; } } /// /// Immediately stops the associated process and children processes. /// public void Kill() { if (_hProcess == IntPtr.Zero) return; if (_hJob == IntPtr.Zero || !Native.TerminateJobObject(_hJob, -1)) { ProcessRelationship[] relationships = GetProcessRelationships(); TerminateProcessTree(_pid, relationships); } } /// /// Waits the specified number of milliseconds for the associated process to exit. /// /// Amount of time, in milliseconds /// True if the associated process has exited, false otherwise public bool WaitForExit(int milliseconds = -1) { if (_hProcess == IntPtr.Zero) return true; int ret = Native.WaitForSingleObject(_hProcess, milliseconds); if (ret == unchecked((int)0xFFFFFFFF)) // == WAIT_FAILED throw new Win32Exception(); if (ret == 0x00000102L) // == WAIT_TIMEOUT return false; return true; } public int GetExitCode() { if (_hProcess == IntPtr.Zero) return 0; int code; if (!Native.GetExitCodeProcess(_hProcess, out code)) throw new Win32Exception(); return code; } /// /// Gets a value indicating whether the associated process has been terminated. /// public bool HasExited { get { return this.WaitForExit(0); } } #endregion #region Support private static string BuildCommandLine(string filepath, IEnumerable args) { var cmd = new StringBuilder(); cmd.Append('"').Append(filepath.Trim('"')).Append('"'); foreach (string arg in args) cmd.Append(' ').Append(arg); return cmd.ToString(); } /// /// Assign a process to a new Job. /// /// Process handle /// Optional - Job name /// Job handle or IntPtr.Zero if failed private static bool AssignProcessToNewJob(IntPtr hProcess, string name, out IntPtr hJob) { //Create a new Job hJob = Native.CreateJobObject(IntPtr.Zero, name); if (hJob == IntPtr.Zero) return false; var jeli = new Native.JOBOBJECT_EXTENDED_LIMIT_INFORMATION(); jeli.BasicLimitInformation.LimitFlags = Native.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; jeli.BasicLimitInformation.LimitFlags |= Native.JOB_OBJECT_LIMIT_BREAKAWAY_OK; bool success = Native.SetInformationJobObject(hJob , Native.JOB_EXTENDED_LIMIT_INFORMATION , ref jeli , Native.JOBOBJECT_EXTENDED_LIMIT_INFORMATION.SIZE); //Assign the process to the Job if (!success || !Native.AssignProcessToJobObject(hJob, hProcess)) { Native.CloseHandle(hJob); hJob = IntPtr.Zero; return false; } return true; } private static StringBuilder BuildEnvironmentVars(Hashtable dict) { if (dict == null) return null; string[] keys = new string[dict.Count]; string[] values = new string[dict.Count]; dict.Keys.CopyTo(keys, 0); dict.Values.CopyTo(values, 0); Array.Sort(keys, values, CaseInsensitiveComparer.Default); StringBuilder sb = new StringBuilder(800); for (int i = 0; i < dict.Count; ++i) { sb.Append(keys[i]); sb.Append('='); sb.Append(values[i]); sb.Append('\0'); } return sb; } private static void TerminateProcessTree(int pid, ProcessRelationship[] relationchips) { if (pid == 0) return; //terminate childs recursively for (int i = relationchips.Length; i-- > 0; ) { ProcessRelationship relationchip = relationchips[i]; if (relationchip.ParentPid == pid) { relationchip.ParentPid = 0; TerminateProcessTree(relationchip.Pid, relationchips); } } //terminate the process IntPtr procHandle = Native.OpenProcess(Native.PROCESS_ALL_ACCESS, false, pid); if (procHandle != IntPtr.Zero) { try { Native.TerminateProcess(procHandle, -1); } catch { } finally { Native.CloseHandle(procHandle); } } } class Native { const string KERNEL32 = "kernel32.dll"; const string USER32 = "user32.dll"; public const int JOB_OBJECT_LIMIT_BREAKAWAY_OK = 0x00000800; public const int JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x00002000; public const int JOB_EXTENDED_LIMIT_INFORMATION = 9; public const int CREATE_SUSPENDED = 0x00000004; public const int CREATE_NO_WINDOW = 0x08000000; public const int CREATE_UNICODE_ENVIRONMENT = 0x00000400; public const int CREATE_BREAKAWAY_FROM_JOB = 0x01000000; public const int PROCESS_ALL_ACCESS = 0x001f0fff; public const int TH32CS_SNAPPROCESS = 0x00000002; public const int STARTF_USESHOWWINDOW = 0x00000001; public const short SW_SHOWNOACTIVATE = 0x00000004; [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public unsafe struct WinProcessEntry { public int dwSize; public int cntUsage; public int th32ProcessID; public IntPtr th32DefaultHeapID; public int th32ModuleID; public int cntThreads; public int th32ParentProcessID; public int pcPriClassBase; public int dwFlags; public fixed byte fileName[260]; } [StructLayout(LayoutKind.Sequential)] public class STARTUPINFO { public int cb = Marshal.SizeOf(typeof(STARTUPINFO)); public IntPtr lpReserved = IntPtr.Zero; public IntPtr lpDesktop = IntPtr.Zero; public IntPtr lpTitle = IntPtr.Zero; public int dwX = 0; public int dwY = 0; public int dwXSize = 0; public int dwYSize = 0; public int dwXCountChars = 0; public int dwYCountChars = 0; public int dwFillAttribute = 0; public int dwFlags = 0; public short wShowWindow = 0; public short cbReserved2 = 0; public IntPtr lpReserved2 = IntPtr.Zero; public IntPtr hStdInput = IntPtr.Zero; public IntPtr hStdOutput = IntPtr.Zero; public IntPtr hStdError = IntPtr.Zero; } [StructLayout(LayoutKind.Sequential)] public class PROCESS_INFORMATION { public IntPtr hProcess = IntPtr.Zero; public IntPtr hThread = IntPtr.Zero; public int dwProcessId = 0; public int dwThreadId = 0; } [DllImport(KERNEL32, CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false)] public static extern bool CreateProcess( [MarshalAs(UnmanagedType.LPTStr)] string lpApplicationName, string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles, int dwCreationFlags, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpEnvironment, [MarshalAs(UnmanagedType.LPTStr)] string lpCurrentDirectory, STARTUPINFO lpStartupInfo, PROCESS_INFORMATION lpProcessInformation ); [StructLayout(LayoutKind.Sequential)] public struct IO_COUNTERS { public UInt64 ReadOperationCount; public UInt64 WriteOperationCount; public UInt64 OtherOperationCount; public UInt64 ReadTransferCount; public UInt64 WriteTransferCount; public UInt64 OtherTransferCount; } [StructLayout(LayoutKind.Sequential)] public struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION { public static int SIZE = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation; public IO_COUNTERS IoInfo; public UIntPtr ProcessMemoryLimit; public UIntPtr JobMemoryLimit; public UIntPtr PeakProcessMemoryUsed; public UIntPtr PeakJobMemoryUsed; } [StructLayout(LayoutKind.Sequential)] public struct JOBOBJECT_BASIC_LIMIT_INFORMATION { public Int64 PerProcessUserTimeLimit; public Int64 PerJobUserTimeLimit; public UInt32 LimitFlags; public UIntPtr MinimumWorkingSetSize; public UIntPtr MaximumWorkingSetSize; public UInt32 ActiveProcessLimit; public UIntPtr Affinity; public UInt32 PriorityClass; public UInt32 SchedulingClass; } [DllImport(KERNEL32, SetLastError = false)] public static extern IntPtr GetCurrentProcess(); [DllImport(KERNEL32, SetLastError = false)] public static extern bool IsProcessInJob(IntPtr Process, IntPtr Job, out bool Result); [DllImport(KERNEL32, SetLastError = false, CharSet = CharSet.Ansi)] public static extern IntPtr CreateJobObject(IntPtr a, string lpName); [DllImport(KERNEL32, SetLastError = false)] public static extern bool SetInformationJobObject(IntPtr hJob, int infoType , ref JOBOBJECT_EXTENDED_LIMIT_INFORMATION lpJobObjectInfo, int cbJobObjectInfoLength); [DllImport(KERNEL32, SetLastError = false)] public static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process); [DllImport(KERNEL32, SetLastError = false)] public static extern bool TerminateJobObject(IntPtr processHandle, int exitCode); [DllImport(KERNEL32)] public static extern IntPtr OpenProcess(int access, bool inherit, int processId); [DllImport(KERNEL32)] public static extern bool TerminateProcess(IntPtr processHandle, int exitCode); [DllImport(KERNEL32, SetLastError = true)] public static extern int ResumeThread(IntPtr hObject); [DllImport(KERNEL32, SetLastError = true)] public static extern bool CloseHandle(IntPtr hObject); [DllImport(KERNEL32, SetLastError = true, ExactSpelling = true)] public static extern int WaitForSingleObject(IntPtr hHandle, int dwMilliseconds); [DllImport(KERNEL32, SetLastError = true)] public static extern bool GetExitCodeProcess(IntPtr hProcess, out int lpExitCode); [DllImport(KERNEL32, SetLastError = true, CharSet = CharSet.Ansi)] public static extern IntPtr CreateToolhelp32Snapshot(int flags, int processId); [DllImport(KERNEL32, SetLastError = true, CharSet = CharSet.Ansi)] public static extern bool Process32First(IntPtr handle, IntPtr entry); [DllImport(KERNEL32, CharSet = CharSet.Ansi)] public static extern bool Process32Next(IntPtr handle, IntPtr entry); } #endregion } }