/// <summary>
        /// Gets the native flag for the given priority class, as passed
        /// </summary>
        /// <param name="Priority">The priority value</param>
        /// <returns>The native priority class flag</returns>
        private static int GetNativePriorityClassFlag(ManagedProcessPriority Priority)
        {
            // Values below are taken from documentation on MSDN
            switch (Priority)
            {
            case ManagedProcessPriority.BelowNormal:
                // BELOW_NORMAL_PRIORITY_CLASS
                return(0x4000);

            case ManagedProcessPriority.Normal:
                // NORMAL_PRIORITY_CLASS
                return(0x0020);

            case ManagedProcessPriority.AboveNormal:
                // ABOVE_NORMAL_PRIORITY_CLASS
                return(0x8000);
            }
            throw new Exception(String.Format("Invalid priority value: {0}", Priority));
        }
        /// <summary>
        /// Spawns a new managed process.
        /// </summary>
        /// <param name="Group">The managed process group to add to</param>
        /// <param name="FileName">Path to the executable to be run</param>
        /// <param name="CommandLine">Command line arguments for the process</param>
        /// <param name="WorkingDirectory">Working directory for the new process. May be null to use the current working directory.</param>
        /// <param name="Environment">Environment variables for the new process. May be null, in which case the current process' environment is inherited</param>
        /// <param name="Input">Text to be passed via stdin to the new process. May be null.</param>
        public ManagedProcess(ManagedProcessGroup Group, string FileName, string CommandLine, string WorkingDirectory, IReadOnlyDictionary <string, string> Environment, string Input, ManagedProcessPriority Priority)
        {
            // Create the child process
            IntPtr EnvironmentBlock = IntPtr.Zero;

            try
            {
                // Create the environment block for the child process, if necessary.
                if (Environment != null)
                {
                    // The native format for the environment block is a sequence of null terminated strings with a final null terminator.
                    List <byte> EnvironmentBytes = new List <byte>();
                    foreach (KeyValuePair <string, string> Pair in Environment)
                    {
                        EnvironmentBytes.AddRange(Encoding.UTF8.GetBytes(Pair.Key));
                        EnvironmentBytes.Add((byte)'=');
                        EnvironmentBytes.AddRange(Encoding.UTF8.GetBytes(Pair.Value));
                        EnvironmentBytes.Add((byte)0);
                    }
                    EnvironmentBytes.Add((byte)0);

                    // Allocate an unmanaged block of memory to store it.
                    EnvironmentBlock = Marshal.AllocHGlobal(EnvironmentBytes.Count);
                    Marshal.Copy(EnvironmentBytes.ToArray(), 0, EnvironmentBlock, EnvironmentBytes.Count);
                }

                PROCESS_INFORMATION ProcessInfo = new PROCESS_INFORMATION();
                try
                {
                    // Get the flags to create the new process
                    ProcessCreationFlags Flags = ProcessCreationFlags.CREATE_NO_WINDOW | ProcessCreationFlags.CREATE_SUSPENDED;
                    switch (Priority)
                    {
                    case ManagedProcessPriority.BelowNormal:
                        Flags |= ProcessCreationFlags.BELOW_NORMAL_PRIORITY_CLASS;
                        break;

                    case ManagedProcessPriority.Normal:
                        Flags |= ProcessCreationFlags.NORMAL_PRIORITY_CLASS;
                        break;

                    case ManagedProcessPriority.AboveNormal:
                        Flags |= ProcessCreationFlags.ABOVE_NORMAL_PRIORITY_CLASS;
                        break;
                    }

                    // Acquire a global lock before creating inheritable handles. If multiple threads create inheritable handles at the same time, and child processes will inherit them all.
                    // Since we need to wait for output pipes to be closed (in order to consume all output), this can result in output reads not returning until all processes with the same
                    // inherited handles are closed.
                    lock (LockObject)
                    {
                        SafeFileHandle StdInRead   = null;
                        SafeFileHandle StdOutWrite = null;
                        SafeWaitHandle StdErrWrite = null;
                        try
                        {
                            // Create stdin and stdout pipes for the child process. We'll close the handles for the child process' ends after it's been created.
                            SECURITY_ATTRIBUTES SecurityAttributes = new SECURITY_ATTRIBUTES();
                            SecurityAttributes.nLength        = Marshal.SizeOf(SecurityAttributes);
                            SecurityAttributes.bInheritHandle = 1;

                            if (CreatePipe(out StdInRead, out StdInWrite, SecurityAttributes, 0) == 0 || SetHandleInformation(StdInWrite, HANDLE_FLAG_INHERIT, 0) == 0)
                            {
                                throw new Win32Exception();
                            }
                            if (CreatePipe(out StdOutRead, out StdOutWrite, SecurityAttributes, 0) == 0 || SetHandleInformation(StdOutRead, HANDLE_FLAG_INHERIT, 0) == 0)
                            {
                                throw new Win32Exception();
                            }
                            if (DuplicateHandle(GetCurrentProcess(), StdOutWrite, GetCurrentProcess(), out StdErrWrite, 0, true, DUPLICATE_SAME_ACCESS) == 0)
                            {
                                throw new Win32Exception();
                            }

                            // Create the new process as suspended, so we can modify it before it starts executing (and potentially preempting us)
                            STARTUPINFO StartupInfo = new STARTUPINFO();
                            StartupInfo.cb         = Marshal.SizeOf(StartupInfo);
                            StartupInfo.hStdInput  = StdInRead;
                            StartupInfo.hStdOutput = StdOutWrite;
                            StartupInfo.hStdError  = StdErrWrite;
                            StartupInfo.dwFlags    = STARTF_USESTDHANDLES;

                            if (CreateProcess(null, new StringBuilder("\"" + FileName + "\" " + CommandLine), IntPtr.Zero, IntPtr.Zero, true, Flags, EnvironmentBlock, WorkingDirectory, StartupInfo, ProcessInfo) == 0)
                            {
                                throw new Win32Exception();
                            }
                        }
                        finally
                        {
                            // Close the write ends of the handle. We don't want any other process to be able to inherit these.
                            if (StdInRead != null)
                            {
                                StdInRead.Dispose();
                                StdInRead = null;
                            }
                            if (StdOutWrite != null)
                            {
                                StdOutWrite.Dispose();
                                StdOutWrite = null;
                            }
                            if (StdErrWrite != null)
                            {
                                StdErrWrite.Dispose();
                                StdErrWrite = null;
                            }
                        }
                    }

                    // Add it to our job object
                    if (AssignProcessToJobObject(Group.JobHandle, ProcessInfo.hProcess) == 0)
                    {
                        // Support for nested job objects was only addeed in Windows 8; prior to that, assigning processes to job objects would fail. Figure out if we're already in a job, and ignore the error if we are.
                        int OriginalError = Marshal.GetLastWin32Error();

                        bool bProcessInJob;
                        IsProcessInJob(GetCurrentProcess(), IntPtr.Zero, out bProcessInJob);

                        if (!bProcessInJob)
                        {
                            throw new Win32Exception(OriginalError);
                        }
                    }

                    // Allow the thread to start running
                    if (ResumeThread(ProcessInfo.hThread) == -1)
                    {
                        throw new Win32Exception();
                    }

                    // If we have any input text, write it to stdin now
                    using (StreamWriter StdInWriter = new StreamWriter(new FileStream(StdInWrite, FileAccess.Write, 4096, false), Console.InputEncoding, 4096))
                    {
                        if (!String.IsNullOrEmpty(Input))
                        {
                            StdInWriter.WriteLine(Input);
                            StdInWriter.Flush();
                        }
                    }

                    // Create the stream objects for reading the process output
                    InnerStream = new FileStream(StdOutRead, FileAccess.Read, 4096, false);
                    ReadStream  = new StreamReader(InnerStream, Console.OutputEncoding);

                    // Wrap the process handle in a SafeFileHandle
                    ProcessHandle = new SafeFileHandle(ProcessInfo.hProcess, true);
                }
                finally
                {
                    if (ProcessInfo.hProcess != IntPtr.Zero && ProcessHandle == null)
                    {
                        CloseHandle(ProcessInfo.hProcess);
                    }
                    if (ProcessInfo.hThread != IntPtr.Zero)
                    {
                        CloseHandle(ProcessInfo.hThread);
                    }
                }
            }
            finally
            {
                if (EnvironmentBlock != IntPtr.Zero)
                {
                    Marshal.FreeHGlobal(EnvironmentBlock);
                    EnvironmentBlock = IntPtr.Zero;
                }
            }
        }
		/// <summary>
		/// Gets the native flag for the given priority class, as passed 
		/// </summary>
		/// <param name="Priority">The priority value</param>
		/// <returns>The native priority class flag</returns>
		private static int GetNativePriorityClassFlag(ManagedProcessPriority Priority)
		{
			// Values below are taken from documentation on MSDN
			switch(Priority)
			{
				case ManagedProcessPriority.BelowNormal:
					// BELOW_NORMAL_PRIORITY_CLASS
					return 0x4000;
				case ManagedProcessPriority.Normal:
					// NORMAL_PRIORITY_CLASS
					return 0x0020;
				case ManagedProcessPriority.AboveNormal:
					// ABOVE_NORMAL_PRIORITY_CLASS
					return 0x8000;
			}
			throw new Exception(String.Format("Invalid priority value: {0}", Priority));
		}
Example #4
0
        /// <summary>
        /// Spawns a new managed process.
        /// </summary>
        /// <param name="FileName">Path to the executable to be run</param>
        /// <param name="CommandLine">Command line arguments for the process</param>
        /// <param name="WorkingDirectory">Working directory for the new process. May be null to use the current working directory.</param>
        /// <param name="Environment">Environment variables for the new process. May be null, in which case the current process' environment is inherited</param>
        /// <param name="Input">Text to be passed via stdin to the new process. May be null.</param>
        public ManagedProcess(string FileName, string CommandLine, string WorkingDirectory, IReadOnlyDictionary <string, string> Environment, string Input, ManagedProcessPriority Priority, ulong MemoryLimit)
        {
            // Create the job object that the child process will be added to
            JobHandle = CreateJobObject(IntPtr.Zero, IntPtr.Zero);
            if (JobHandle == null)
            {
                throw new Win32Exception();
            }

            // Configure the job object to terminate the processes added to it when the handle is closed
            JOBOBJECT_EXTENDED_LIMIT_INFORMATION LimitInformation = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();

            LimitInformation.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_PROCESS_MEMORY;
            LimitInformation.ProcessMemoryLimit = new UIntPtr(MemoryLimit);

            int    Length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
            IntPtr LimitInformationPtr = Marshal.AllocHGlobal(Length);

            Marshal.StructureToPtr(LimitInformation, LimitInformationPtr, false);

            if (SetInformationJobObject(JobHandle, JobObjectExtendedLimitInformation, LimitInformationPtr, Length) == 0)
            {
                throw new Win32Exception();
            }

            // Create the child process
            IntPtr         EnvironmentBlock = IntPtr.Zero;
            SafeFileHandle StdInRead        = null;
            SafeFileHandle StdOutWrite      = null;
            SafeWaitHandle StdErrWrite      = null;

            try
            {
                // Create stdin and stdout pipes for the child process. We'll close the handles for the child process' ends after it's been created.
                SECURITY_ATTRIBUTES SecurityAttributes = new SECURITY_ATTRIBUTES();
                SecurityAttributes.bInheritHandle = 1;

                if (CreatePipe(out StdInRead, out StdInWrite, SecurityAttributes, 0) == 0 || SetHandleInformation(StdInWrite, HANDLE_FLAG_INHERIT, 0) == 0)
                {
                    throw new Win32Exception();
                }
                if (CreatePipe(out StdOutRead, out StdOutWrite, SecurityAttributes, 0) == 0 || SetHandleInformation(StdOutRead, HANDLE_FLAG_INHERIT, 0) == 0)
                {
                    throw new Win32Exception();
                }
                if (DuplicateHandle(GetCurrentProcess(), StdOutWrite, GetCurrentProcess(), out StdErrWrite, DUPLICATE_SAME_ACCESS, true, 0) == 0)
                {
                    throw new Win32Exception();
                }

                // Create the environment block for the child process, if necessary.
                if (Environment != null)
                {
                    // The native format for the environment block is a sequence of null terminated strings with a final null terminator.
                    List <byte> EnvironmentBytes = new List <byte>();
                    foreach (KeyValuePair <string, string> Pair in Environment)
                    {
                        EnvironmentBytes.AddRange(Encoding.UTF8.GetBytes(Pair.Key));
                        EnvironmentBytes.Add((byte)'=');
                        EnvironmentBytes.AddRange(Encoding.UTF8.GetBytes(Pair.Value));
                        EnvironmentBytes.Add((byte)0);
                    }
                    EnvironmentBytes.Add((byte)0);

                    // Allocate an unmanaged block of memory to store it.
                    EnvironmentBlock = Marshal.AllocHGlobal(EnvironmentBytes.Count);
                    Marshal.Copy(EnvironmentBytes.ToArray(), 0, EnvironmentBlock, EnvironmentBytes.Count);
                }

                // Set the startup parameters for the new process
                STARTUPINFO StartupInfo = new STARTUPINFO();
                StartupInfo.cb         = Marshal.SizeOf(StartupInfo);
                StartupInfo.hStdInput  = StdInRead;
                StartupInfo.hStdOutput = StdOutWrite;
                StartupInfo.hStdError  = StdErrWrite;
                StartupInfo.dwFlags    = STARTF_USESTDHANDLES;

                PROCESS_INFORMATION ProcessInfo = new PROCESS_INFORMATION();
                try
                {
                    // Get the flags to create the new process
                    ProcessCreationFlags Flags = ProcessCreationFlags.CREATE_NO_WINDOW | ProcessCreationFlags.CREATE_SUSPENDED | ProcessCreationFlags.CREATE_BREAKAWAY_FROM_JOB;
                    switch (Priority)
                    {
                    case ManagedProcessPriority.BelowNormal:
                        Flags |= ProcessCreationFlags.BELOW_NORMAL_PRIORITY_CLASS;
                        break;

                    case ManagedProcessPriority.Normal:
                        Flags |= ProcessCreationFlags.NORMAL_PRIORITY_CLASS;
                        break;

                    case ManagedProcessPriority.AboveNormal:
                        Flags |= ProcessCreationFlags.ABOVE_NORMAL_PRIORITY_CLASS;
                        break;
                    }

                    // Create the new process as suspended, so we can modify it before it starts executing (and potentially preempting us)
                    if (CreateProcess(null, new StringBuilder("\"" + FileName + "\" " + CommandLine), IntPtr.Zero, IntPtr.Zero, true, Flags, EnvironmentBlock, WorkingDirectory, StartupInfo, ProcessInfo) == 0)
                    {
                        throw new Win32Exception();
                    }

                    // Add it to our job object
                    if (AssignProcessToJobObject(JobHandle, ProcessInfo.hProcess) == 0)
                    {
                        throw new Win32Exception();
                    }

                    // Allow the thread to start running
                    if (ResumeThread(ProcessInfo.hThread) == -1)
                    {
                        throw new Win32Exception();
                    }

                    // If we have any input text, write it to stdin now
                    using (StreamWriter StdInWriter = new StreamWriter(new FileStream(StdInWrite, FileAccess.Write, 4096, false), Console.InputEncoding, 4096))
                    {
                        if (!String.IsNullOrEmpty(Input))
                        {
                            StdInWriter.WriteLine(Input);
                            StdInWriter.Flush();
                        }
                    }

                    // Create the stream objects for reading the process output
                    InnerStream = new FileStream(StdOutRead, FileAccess.Read, 4096, false);
                    ReadStream  = new StreamReader(InnerStream, Console.OutputEncoding);

                    // Wrap the process handle in a SafeFileHandle
                    ProcessHandle = new SafeFileHandle(ProcessInfo.hProcess, true);
                }
                finally
                {
                    if (ProcessInfo.hProcess != IntPtr.Zero && ProcessHandle == null)
                    {
                        CloseHandle(ProcessInfo.hProcess);
                    }
                    if (ProcessInfo.hThread != IntPtr.Zero)
                    {
                        CloseHandle(ProcessInfo.hThread);
                    }
                }
            }
            finally
            {
                if (EnvironmentBlock != IntPtr.Zero)
                {
                    Marshal.FreeHGlobal(EnvironmentBlock);
                    EnvironmentBlock = IntPtr.Zero;
                }
                if (StdInRead != null)
                {
                    StdInRead.Dispose();
                    StdInRead = null;
                }
                if (StdOutWrite != null)
                {
                    StdOutWrite.Dispose();
                    StdOutWrite = null;
                }
                if (StdErrWrite != null)
                {
                    StdErrWrite.Dispose();
                    StdErrWrite = null;
                }
            }
        }
		/// <summary>
		/// Spawns a new managed process.
		/// </summary>
		/// <param name="FileName">Path to the executable to be run</param>
		/// <param name="CommandLine">Command line arguments for the process</param>
		/// <param name="WorkingDirectory">Working directory for the new process. May be null to use the current working directory.</param>
		/// <param name="Environment">Environment variables for the new process. May be null, in which case the current process' environment is inherited</param>
		/// <param name="Input">Text to be passed via stdin to the new process. May be null.</param>
		public ManagedProcess(string FileName, string CommandLine, string WorkingDirectory, IReadOnlyDictionary<string, string> Environment, string Input, ManagedProcessPriority Priority)
		{
			// Create the job object that the child process will be added to
			JobHandle = CreateJobObject(IntPtr.Zero, IntPtr.Zero);
			if(JobHandle == null)
			{
				throw new Win32Exception();
			}

			// Configure the job object to terminate the processes added to it when the handle is closed
			JOBOBJECT_EXTENDED_LIMIT_INFORMATION LimitInformation = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
			LimitInformation.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;

			int Length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
			IntPtr LimitInformationPtr = Marshal.AllocHGlobal(Length);
			Marshal.StructureToPtr(LimitInformation, LimitInformationPtr, false);

			if(SetInformationJobObject(JobHandle, JobObjectExtendedLimitInformation, LimitInformationPtr, Length) == 0)
			{
				throw new Win32Exception();
			}

			// Create the child process
			IntPtr EnvironmentBlock = IntPtr.Zero;
			SafeFileHandle StdInRead = null;
			SafeFileHandle StdOutWrite = null;
			SafeWaitHandle StdErrWrite = null;
			try
			{
				// Create stdin and stdout pipes for the child process. We'll close the handles for the child process' ends after it's been created.
				SECURITY_ATTRIBUTES SecurityAttributes = new SECURITY_ATTRIBUTES();
				SecurityAttributes.bInheritHandle = 1;

				if(CreatePipe(out StdInRead, out StdInWrite, SecurityAttributes, 0) == 0 || SetHandleInformation(StdInWrite, HANDLE_FLAG_INHERIT, 0) == 0)
				{
					throw new Win32Exception();
				}
				if(CreatePipe(out StdOutRead, out StdOutWrite, SecurityAttributes, 0) == 0 || SetHandleInformation(StdOutRead, HANDLE_FLAG_INHERIT, 0) == 0)
				{
					throw new Win32Exception();
				}
				if(DuplicateHandle(GetCurrentProcess(), StdOutWrite, GetCurrentProcess(), out StdErrWrite, DUPLICATE_SAME_ACCESS, true, 0) == 0)
				{
					throw new Win32Exception();
				}

				// Create the environment block for the child process, if necessary.
				if(Environment != null)
				{
					// The native format for the environment block is a sequence of null terminated strings with a final null terminator.
					List<byte> EnvironmentBytes = new List<byte>();
					foreach(KeyValuePair<string, string> Pair in Environment)
					{
						EnvironmentBytes.AddRange(Encoding.UTF8.GetBytes(Pair.Key));
						EnvironmentBytes.Add((byte)'=');
						EnvironmentBytes.AddRange(Encoding.UTF8.GetBytes(Pair.Value));
						EnvironmentBytes.Add((byte)0);
					}
					EnvironmentBytes.Add((byte)0);

					// Allocate an unmanaged block of memory to store it.
					EnvironmentBlock = Marshal.AllocHGlobal(EnvironmentBytes.Count);
					Marshal.Copy(EnvironmentBytes.ToArray(), 0, EnvironmentBlock, EnvironmentBytes.Count);
				}

				// Set the startup parameters for the new process
				STARTUPINFO StartupInfo = new STARTUPINFO();
                StartupInfo.cb = Marshal.SizeOf(StartupInfo);
				StartupInfo.hStdInput = StdInRead;
				StartupInfo.hStdOutput = StdOutWrite;
				StartupInfo.hStdError = StdErrWrite;
				StartupInfo.dwFlags = STARTF_USESTDHANDLES;

				PROCESS_INFORMATION ProcessInfo = new PROCESS_INFORMATION();
				try
				{
					// Get the flags to create the new process
					ProcessCreationFlags Flags = ProcessCreationFlags.CREATE_NO_WINDOW | ProcessCreationFlags.CREATE_SUSPENDED | ProcessCreationFlags.CREATE_BREAKAWAY_FROM_JOB;
					switch(Priority)
					{
						case ManagedProcessPriority.BelowNormal:
							Flags |= ProcessCreationFlags.BELOW_NORMAL_PRIORITY_CLASS;
							break;
						case ManagedProcessPriority.Normal:
							Flags |= ProcessCreationFlags.NORMAL_PRIORITY_CLASS;
							break;
						case ManagedProcessPriority.AboveNormal:
							Flags |= ProcessCreationFlags.ABOVE_NORMAL_PRIORITY_CLASS;
							break;
					}

					// Create the new process as suspended, so we can modify it before it starts executing (and potentially preempting us)
					if(CreateProcess(null, new StringBuilder("\"" + FileName + "\" " + CommandLine), IntPtr.Zero, IntPtr.Zero, true, Flags, EnvironmentBlock, WorkingDirectory, StartupInfo, ProcessInfo) == 0)
					{
						throw new Win32Exception();
					}

					// Add it to our job object
					if(AssignProcessToJobObject(JobHandle, ProcessInfo.hProcess) == 0)
					{
						throw new Win32Exception();
					}

					// Allow the thread to start running
					if(ResumeThread(ProcessInfo.hThread) == -1)
					{
						throw new Win32Exception();
					}

					// If we have any input text, write it to stdin now
					using(StreamWriter StdInWriter = new StreamWriter(new FileStream(StdInWrite, FileAccess.Write, 4096, false), Console.InputEncoding, 4096))
					{
						if(!String.IsNullOrEmpty(Input))
						{
							StdInWriter.WriteLine(Input);
							StdInWriter.Flush();
						}
					}

					// Create the stream objects for reading the process output
					InnerStream = new FileStream(StdOutRead, FileAccess.Read, 4096, false);
					ReadStream = new StreamReader(InnerStream, Console.OutputEncoding);

					// Wrap the process handle in a SafeFileHandle
					ProcessHandle = new SafeFileHandle(ProcessInfo.hProcess, true);
				}
				finally
				{
					if(ProcessInfo.hProcess != IntPtr.Zero && ProcessHandle == null)
					{
						CloseHandle(ProcessInfo.hProcess);
					}
					if(ProcessInfo.hThread != IntPtr.Zero)
					{
						CloseHandle(ProcessInfo.hThread);
					}
				}
			}
			finally
			{
				if(EnvironmentBlock != IntPtr.Zero)
				{
					Marshal.FreeHGlobal(EnvironmentBlock);
					EnvironmentBlock = IntPtr.Zero;
				}
				if(StdInRead != null)
				{
					StdInRead.Dispose();
					StdInRead = null;
				}
				if(StdOutWrite != null)
				{
					StdOutWrite.Dispose();
					StdOutWrite = null;
				}
				if(StdErrWrite != null)
				{
					StdErrWrite.Dispose();
					StdErrWrite = null;
				}
			}
		}