internal static extern bool CreateProcessAsUser(
     IntPtr hToken,
     string lpApplicationName,
     string lpCommandLine,
     SecurityAttributes lpProcessAttributes,
     SecurityAttributes lpThreadAttributes,
     bool bInheritHandles,
     uint dwCreationFlags,
     IntPtr lpEnvironment,
     string lpCurrentDirectory,
     StartupInfo lpStartupInfo,
     out ProcessInformation lpProcessInformation);
        private void RedirectStandardIoHandles(ref StartupInfo startupInfo, int bufferSize)
        {
            // Some of this code is based on System.Diagnostics.Process.StartWithCreateProcess method implementation
            SafeFileHandle standardInputWritePipeHandle;
            SafeFileHandle standardOutputReadPipeHandle;
            SafeFileHandle standardErrorReadPipeHandle;

            // http://support.microsoft.com/kb/190351 (How to spawn console processes with redirected standard handles)
            // If the dwFlags member is set to STARTF_USESTDHANDLES, then the following STARTUPINFO members specify the standard handles of the child console based process:
            // HANDLE hStdInput - Standard input handle of the child process.
            // HANDLE hStdOutput - Standard output handle of the child process.
            // HANDLE hStdError - Standard error handle of the child process.
            startupInfo.Flags = (int)StartupInfoFlags.STARTF_USESTDHANDLES;
            this.CreatePipe(out standardInputWritePipeHandle, out startupInfo.StandardInputHandle, true, bufferSize);
            this.CreatePipe(out standardOutputReadPipeHandle, out startupInfo.StandardOutputHandle, false, bufferSize);
            this.CreatePipe(out standardErrorReadPipeHandle, out startupInfo.StandardErrorHandle, false, 4096);

            this.StandardInput = new StreamWriter(new FileStream(standardInputWritePipeHandle, FileAccess.Write, bufferSize, false), Encoding.Default, bufferSize)
                                     {
                                         AutoFlush = true
                                     };
            this.StandardOutput = new StreamReader(new FileStream(standardOutputReadPipeHandle, FileAccess.Read, bufferSize, false), Encoding.Default, true, bufferSize);
            this.StandardError = new StreamReader(new FileStream(standardErrorReadPipeHandle, FileAccess.Read, 4096, false), Encoding.Default, true, 4096);

            /*
             * Child processes that use such C run-time functions as printf() and fprintf() can behave poorly when redirected.
             * The C run-time functions maintain separate IO buffers. When redirected, these buffers might not be flushed immediately after each IO call.
             * As a result, the output to the redirection pipe of a printf() call or the input from a getch() call is not flushed immediately and delays, sometimes-infinite delays occur.
             * This problem is avoided if the child process flushes the IO buffers after each call to a C run-time IO function.
             * Only the child process can flush its C run-time IO buffers. A process can flush its C run-time IO buffers by calling the fflush() function.
             */
        }
        public RestrictedProcess(string fileName, string workingDirectory, IEnumerable<string> arguments = null, int bufferSize = 4096)
        {
            // Initialize fields
            this.fileName = fileName;
            this.IsDisposed = false;

            // Prepare startup info and redirect standard IO handles
            var startupInfo = new StartupInfo();
            this.RedirectStandardIoHandles(ref startupInfo, bufferSize);

            // Create restricted token
            var restrictedToken = this.CreateRestrictedToken();

            // Set mandatory label
            this.SetTokenMandatoryLabel(restrictedToken, SecurityMandatoryLabel.Low);

            var processSecurityAttributes = new SecurityAttributes();
            var threadSecurityAttributes = new SecurityAttributes();
            this.processInformation = new ProcessInformation();

            const uint CreationFlags = (uint)(
                CreateProcessFlags.CREATE_SUSPENDED |
                CreateProcessFlags.CREATE_BREAKAWAY_FROM_JOB |
                CreateProcessFlags.CREATE_UNICODE_ENVIRONMENT |
                CreateProcessFlags.CREATE_NEW_PROCESS_GROUP |
                CreateProcessFlags.DETACHED_PROCESS | // http://stackoverflow.com/questions/6371149/what-is-the-difference-between-detach-process-and-create-no-window-process-creat
                CreateProcessFlags.CREATE_NO_WINDOW) |
                (uint)ProcessPriorityClass.High;

            string commandLine;
            if (arguments != null)
            {
                var commandLineBuilder = new StringBuilder();
                commandLineBuilder.AppendFormat("\"{0}\"", fileName);
                foreach (var argument in arguments)
                {
                    commandLineBuilder.Append(' ');
                    commandLineBuilder.Append(argument);
                }

                commandLine = commandLineBuilder.ToString();
            }
            else
            {
                commandLine = fileName;
            }

            if (!NativeMethods.CreateProcessAsUser(
                    restrictedToken,
                    null,
                    commandLine,
                    processSecurityAttributes,
                    threadSecurityAttributes,
                    true, // In order to standard input, output and error redirection work, the handles must be inheritable and the CreateProcess() API must specify that inheritable handles are to be inherited by the child process by specifying TRUE in the bInheritHandles parameter.
                    CreationFlags,
                    IntPtr.Zero,
                    workingDirectory,
                    startupInfo,
                    out this.processInformation))
            {
                throw new Win32Exception();
            }

            this.safeProcessHandle = new SafeProcessHandle(this.processInformation.Process);

            // This is a very important line! Without disposing the startupInfo handles, reading the standard output (or error) will hang forever.
            // Same problem described here: http://social.msdn.microsoft.com/Forums/vstudio/en-US/3c25a2e8-b1ea-4fc4-927b-cb865d435147/how-does-processstart-work-in-getting-output
            startupInfo.Dispose();

            NativeMethods.CloseHandle(restrictedToken);
        }
Example #4
0
        public RestrictedProcess(string fileName, string workingDirectory, IEnumerable <string> arguments = null, int bufferSize = 4096)
        {
            // Initialize fields
            this.fileName   = fileName;
            this.IsDisposed = false;

            // Prepare startup info and redirect standard IO handles
            var startupInfo = new StartupInfo();

            this.RedirectStandardIoHandles(ref startupInfo, bufferSize);

            // Create restricted token
            var restrictedToken = this.CreateRestrictedToken();

            // Set mandatory label
            this.SetTokenMandatoryLabel(restrictedToken, SecurityMandatoryLabel.Low);

            var processSecurityAttributes = new SecurityAttributes();
            var threadSecurityAttributes  = new SecurityAttributes();

            this.processInformation = new ProcessInformation();

            const uint CreationFlags = (uint)(
                CreateProcessFlags.CREATE_SUSPENDED |
                CreateProcessFlags.CREATE_BREAKAWAY_FROM_JOB |
                CreateProcessFlags.CREATE_UNICODE_ENVIRONMENT |
                CreateProcessFlags.CREATE_NEW_PROCESS_GROUP |
                CreateProcessFlags.DETACHED_PROCESS | // http://stackoverflow.com/questions/6371149/what-is-the-difference-between-detach-process-and-create-no-window-process-creat
                CreateProcessFlags.CREATE_NO_WINDOW) |
                                       (uint)ProcessPriorityClass.High;

            string commandLine;

            if (arguments != null)
            {
                var commandLineBuilder = new StringBuilder();
                commandLineBuilder.AppendFormat("\"{0}\"", fileName);
                foreach (var argument in arguments)
                {
                    commandLineBuilder.Append(' ');
                    commandLineBuilder.Append(argument);
                }

                commandLine = commandLineBuilder.ToString();
            }
            else
            {
                commandLine = fileName;
            }

            if (!NativeMethods.CreateProcessAsUser(
                    restrictedToken,
                    null,
                    commandLine,
                    processSecurityAttributes,
                    threadSecurityAttributes,
                    true, // In order to standard input, output and error redirection work, the handles must be inheritable and the CreateProcess() API must specify that inheritable handles are to be inherited by the child process by specifying TRUE in the bInheritHandles parameter.
                    CreationFlags,
                    IntPtr.Zero,
                    workingDirectory,
                    startupInfo,
                    out this.processInformation))
            {
                throw new Win32Exception();
            }

            this.safeProcessHandle = new SafeProcessHandle(this.processInformation.Process);

            // This is a very important line! Without disposing the startupInfo handles, reading the standard output (or error) will hang forever.
            // Same problem described here: http://social.msdn.microsoft.com/Forums/vstudio/en-US/3c25a2e8-b1ea-4fc4-927b-cb865d435147/how-does-processstart-work-in-getting-output
            startupInfo.Dispose();

            NativeMethods.CloseHandle(restrictedToken);
        }