Helper.Windows.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;

namespace GeeLaw
{
    namespace PSUseRawPipeline
    {
        #region Interfaces

        public interface ITeedProcess
        {
            IntPtr ReleaseStandardOutputReadHandle();
            bool HasExited { get; }
            void Terminate();
        }

        public interface ITeedProcessStartInfo
        {
            bool CanInvoke { get; }
            ITeedProcess Invoke();
        }

        #endregion Interfaces

        #region Win32

        public static class Win32
        {
            public enum Bool : int
            {
                False = 0,
                True = 1
            }

            [StructLayout(LayoutKind.Sequential)]
            public struct SecurityAttributes
            {
                public int StructSize;
                public IntPtr SecurityDescriptor;
                public Bool CanInheritHandle;

                public void Initialize()
                {
                    StructSize = Marshal.SizeOf(typeof(SecurityAttributes));
                    SecurityDescriptor = IntPtr.Zero;
                    CanInheritHandle = Bool.False;
                }
            }

            [StructLayout(LayoutKind.Sequential)]
            public struct ProcessInformation
            {
                public IntPtr HandleToProcess;
                public IntPtr HandleToThread;
                public int ProcessId;
                public int ThreadId;
            }

            [Flags]
            public enum StartProcessFlags : uint
            {
                None = 0,
                UseStdHandles = 256
            }

            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
            public struct StartupInfo
            {
                public int StructSize;
                public string Reserved;
                public string Desktop;
                public string Title;
                public int X;
                public int Y;
                public int Width;
                public int Height;
                public int CharWidth;
                public int CharHeight;
                public int FillAttribute;
                public StartProcessFlags Flags;
                public short ShowWindow;
                public short Reserved2;
                public IntPtr ReservedPtr2;
                public IntPtr StandardInput;
                public IntPtr StandardOutput;
                public IntPtr StandardError;

                public void Initialize()
                {
                    StructSize = Marshal.SizeOf(typeof(StartupInfo));
                    Reserved = null;
                    Desktop = null;
                    Title = null;
                    X = 0; Y = 0;
                    Width = 0; Height = 0;
                    CharWidth = 0; CharHeight = 0;
                    FillAttribute = 0;
                    Flags = StartProcessFlags.None;
                    ShowWindow = 0;
                    Reserved2 = 0; ReservedPtr2 = IntPtr.Zero;
                    StandardInput = IntPtr.Zero;
                    StandardOutput = IntPtr.Zero;
                    StandardError = IntPtr.Zero;
                }
            }

            public enum StandardHandleId : int
            {
                StandardInput = -10,
                StandardOutput = -11,
                StandardError = -12
            }

            public static IntPtr InvalidHandle { get { return new IntPtr(-1); } }

            [Flags]
            public enum FileAccess : uint
            {
                GenericRead = 0x80000000,
                GenericWrite = 0x40000000,
                AppendData = 0x4
            }

            [Flags]
            public enum FileShare : uint
            {
                Read = 1,
                Write = 2
            }

            [Flags]
            public enum FileAttributes : uint
            {
                Normal = 0x80000000
            }

            public enum FileCreateDisposition : uint
            {
                CreateNew = 1,
                CreateAlways = 2,
                OpenExisting = 3,
                OpenAlways = 4,
                TruncateExisting = 5
            }

            public enum MoveFileMethod : int
            {
                FromBegin = 0,
                FromCurrent = 1,
                FromEnd = 2
            }

            [Flags]
            public enum DuplicateHandleOptions : uint
            {
                None = 0,
                CloseSource = 1,
                SameAccess = 2
            }

            [Flags]
            public enum HandleFlags : uint
            {
                None = 0,
                Inherit = 1,
                ProtectFromClose = 2
            }

            [Flags]
            public enum ProcessCreationFlags : uint
            {
                None = 0,
                CreateSuspended = 4
            }

            [DllImport("kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
            public static extern IntPtr CreateFile(
                string fileName,
                FileAccess desiredAccess,
                FileShare shareMode,
                ref SecurityAttributes securityAttributes,
                FileCreateDisposition creationDisposition,
                FileAttributes flagsAndAttributes,
                IntPtr templateFile
            );

            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern uint SetFilePointer(IntPtr fileHandle, long distanceToMove, IntPtr distanceToMoveHigh, MoveFileMethod moveMethod);

            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern Bool CreatePipe(
                out IntPtr readHandle,
                out IntPtr writeHandle,
                ref SecurityAttributes pipeAttributes,
                uint bufferSize);

            [DllImport("kernel32.dll", SetLastError =true)]
            public static extern Bool ReadFile(IntPtr handle,
                [MarshalAs(UnmanagedType.LPArray)]byte[] buffer,
                uint capacity, out uint bytesRead, IntPtr overlapped);

            [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
            public static extern Bool CreateProcess(
                string applicationName,
                StringBuilder commandLine,
                IntPtr processAttributes,
                IntPtr threadAttributes,
                Bool canInheritHandles,
                ProcessCreationFlags creationFlags,
                IntPtr environmentHandle,
                string currentDirectory,
                ref StartupInfo startupInfo,
                out ProcessInformation processInformation);

            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern IntPtr GetStdHandle(StandardHandleId handleId);

            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern Bool CloseHandle(IntPtr handle);

            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern Bool DuplicateHandle(IntPtr sourceProcess, IntPtr sourceHandle,
                IntPtr targetProcess, out IntPtr targetHandle,
                uint desiredAccess, Bool canInherit, DuplicateHandleOptions options);

            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern Bool SetHandleInformation(IntPtr handle, HandleFlags change, HandleFlags value);

            public const int ExitCodeStillActive = 259;

            [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            public static extern Bool GetExitCodeProcess(IntPtr processHandle, out int exitCode);

            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern uint ResumeThread(IntPtr hThread);

            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern Bool TerminateProcess(IntPtr processHandle, uint exitCode);

            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern IntPtr GetCurrentProcess();

            public const uint WaitForInfiniteTime = 0xFFFFFFFFu;

            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern uint WaitForSingleObject(IntPtr handle, uint milliseconds);
        }

        #endregion Win32

        #region Helper: Win32 helper methods and consumers

        public static class Helper
        {
            const string CouldNotOpenFileErrorFormat = "Could not open file \"{0}\". Win32 GetLastError() returns {1}.";
            const string CouldNotDuplicateHandleErrorFormat = "Could not duplicate handle \"{0}\". Win32 GetLastError() returns {1}.";
            const string CouldNotSetHandleInformationErrorFormat = "Could not set the inheritance of handle \"{0}\". Win32 GetLastError() returns {1}.";
            const string CouldNotRetrieveStandardOutputHandleErrorMessage = "Could not retrieve the standard output handle of the specified ITeedProcess. It might have been taken by others.";

            public static IntPtr OpenReadFile(string fileName)
            {
                if (fileName == null)
                    throw new ArgumentNullException("fileName");
                Win32.SecurityAttributes securityAttributes = new Win32.SecurityAttributes();
                securityAttributes.Initialize();
                IntPtr handle = Win32.CreateFile(fileName,
                    Win32.FileAccess.GenericRead,
                    Win32.FileShare.Read,
                    ref securityAttributes,
                    Win32.FileCreateDisposition.OpenExisting,
                    Win32.FileAttributes.Normal, IntPtr.Zero);
                if (handle == IntPtr.Zero)
                {
                    int lastError = Marshal.GetLastWin32Error();
                    throw new Win32Exception(lastError,
                        string.Format(CouldNotOpenFileErrorFormat, fileName, lastError));
                }
                return handle;
            }

            public static IntPtr OpenTruncatedFile(string fileName)
            {
                if (fileName == null)
                    throw new ArgumentNullException("fileName");
                Win32.SecurityAttributes securityAttributes = new Win32.SecurityAttributes();
                securityAttributes.Initialize();
                IntPtr handle = Win32.CreateFile(fileName,
                    Win32.FileAccess.GenericWrite,
                    Win32.FileShare.Read,
                    ref securityAttributes,
                    Win32.FileCreateDisposition.CreateAlways,
                    Win32.FileAttributes.Normal, IntPtr.Zero);
                if (handle == IntPtr.Zero)
                {
                    int lastError = Marshal.GetLastWin32Error();
                    throw new Win32Exception(lastError,
                        string.Format(CouldNotOpenFileErrorFormat, fileName, lastError));
                }
                return handle;
            }

            public static IntPtr OpenAppendFile(string fileName)
            {
                if (fileName == null)
                    throw new ArgumentNullException("fileName");
                Win32.SecurityAttributes securityAttributes = new Win32.SecurityAttributes();
                securityAttributes.Initialize();
                IntPtr handle = Win32.CreateFile(fileName,
                    Win32.FileAccess.AppendData,
                    Win32.FileShare.Read,
                    ref securityAttributes,
                    Win32.FileCreateDisposition.OpenAlways,
                    Win32.FileAttributes.Normal, IntPtr.Zero);
                if (handle == IntPtr.Zero)
                {
                    int lastError = Marshal.GetLastWin32Error();
                    throw new Win32Exception(lastError,
                        string.Format(CouldNotOpenFileErrorFormat, fileName, lastError));
                }
                return handle;
            }

            public static IntPtr DuplicateHandleForInheritance(IntPtr handle)
            {
                IntPtr resultHandle;
                if (Win32.DuplicateHandle(Win32.GetCurrentProcess(), handle,
                    Win32.GetCurrentProcess(), out resultHandle,
                    0, Win32.Bool.True, Win32.DuplicateHandleOptions.SameAccess) == Win32.Bool.False)
                {
                    int lastError = Marshal.GetLastWin32Error();
                    throw new Win32Exception(lastError,
                        string.Format(CouldNotDuplicateHandleErrorFormat, handle, lastError));
                }
                return resultHandle;
            }

            public static void EnsureHandleInheritable(IntPtr handle)
            {
                if (Win32.SetHandleInformation(handle, Win32.HandleFlags.Inherit, Win32.HandleFlags.Inherit) == Win32.Bool.False)
                {
                    int lastError = Marshal.GetLastWin32Error();
                    throw new Win32Exception(lastError,
                        string.Format(CouldNotSetHandleInformationErrorFormat, handle, lastError));
                }
            }

            public static IEnumerable<byte> EnumerateBytesInStandardOutput(ITeedProcess process)
            {
                IntPtr stdoutReadHandle = process.ReleaseStandardOutputReadHandle();
                if (stdoutReadHandle == IntPtr.Zero)
                {
                    Exception ex = new InvalidOperationException(CouldNotRetrieveStandardOutputHandleErrorMessage);
                    ex.Data.Add("ITeedProcess", process);
                    throw ex;
                }
                byte[] buffer = new byte[4096];
                try
                {
                    while (true)
                    {
                        /* Be careful!
                        *
                        * Retrieve the status of process before
                        * trying to read. If 0 bytes were read,
                        * it could be the process flushing its
                        * stdout without putting anything into
                        * it, or it could be the process has
                        * exited.
                        *
                        * However, if one checks the exit status
                        * after reading 0 bytes, it could be the
                        * case that:
                        * 1. The process flushes stdout;
                        * 2. The reading completes;
                        * 3. The process outputs something and quits;
                        * 4. The reader found the process has exited;
                        * 5. The several bytes vomitted by the process
                        * before it dies are never read.
                        */
                        bool hasExited = process.HasExited;
                        uint count;
                        Win32.ReadFile(stdoutReadHandle, buffer, 4096, out count, IntPtr.Zero);
                        if (count == 0 && hasExited)
                            break;
                        for (int i = 0; i != count; ++i)
                            yield return buffer[i];
                    }
                }
                finally
                {
                    Win32.CloseHandle(stdoutReadHandle);
                }
            }

            static char ProcessCharacters(StringBuilder sb, StreamReader sr, char[] charBuf, char lastSeen, List<string> collect)
            {
                int charRead = sr.Read(charBuf, 0, charBuf.Length);
                int lastBegin = 0;
                for (int i = 0; i != charRead; lastSeen = charBuf[i++])
                    if (charBuf[i] == '\n' || charBuf[i] == '\r')
                    {
                        /* Consecutive \r\n is one line. */
                        if (charBuf[i] != '\n' || lastSeen != '\r')
                        {
                            sb.Append(charBuf, lastBegin, i - lastBegin);
                            collect.Add(sb.ToString());
                            sb.Length = 0;
                        }
                        lastBegin = i + 1;
                    }
                sb.Append(charBuf, lastBegin, charRead - lastBegin);
                return lastSeen;
            }

            static void CleanStreamIfAppropriate(MemoryStream ms, int leastSize, byte[] gapBuf)
            {
                if (ms.Length < leastSize || ms.Length - ms.Position > gapBuf.Length)
                    return;
                int gap = (int)(ms.Length - ms.Position);
                ms.Read(gapBuf, 0, gap);
                ms.SetLength(0);
                ms.Write(gapBuf, 0, gap);
                ms.Position = 0;
            }

            public static IEnumerable<string> EnumerateLinesInStandardOutput(ITeedProcess process, Encoding encoding)
            {
                IntPtr stdoutReadHandle = process.ReleaseStandardOutputReadHandle();
                if (stdoutReadHandle == IntPtr.Zero)
                {
                    Exception ex = new InvalidOperationException(CouldNotRetrieveStandardOutputHandleErrorMessage);
                    ex.Data.Add("ITeedProcess", process.ToString());
                    throw ex;
                }
                byte[] buffer = new byte[4096];
                char[] charBuf = new char[4096];
                char lastSeen = '\0';
                StringBuilder sb = new StringBuilder();
                List<string> collect = new List<string>();
                MemoryStream ms = new MemoryStream();
                try
                {
                    StreamReader sr;
                    if (encoding != null)
                        sr = new StreamReader(ms, encoding);
                    else
                        sr = new StreamReader(ms, true);
                    using (sr)
                    {
                        while (true)
                        {
                            bool hasExited = process.HasExited;
                            uint count;
                            Win32.ReadFile(stdoutReadHandle, buffer, 4096, out count, IntPtr.Zero);
                            if (count == 0 && hasExited)
                                break;
                            /* Attention: Recover the Position after Write. */
                            {
                                long oldPosition = ms.Position;
                                ms.Position = ms.Length;
                                ms.Write(buffer, 0, (int)count);
                                ms.Position = oldPosition;
                            }
                            lastSeen = ProcessCharacters(sb, sr, charBuf, lastSeen, collect);
                            foreach (string s in collect)
                                yield return s;
                            collect.Clear();
                            /* If the stream is at least 1 MB and the gap is
                            * at most the size of buffer, remove the useless
                            * part of the stream.
                            */
                            CleanStreamIfAppropriate(ms, 1048576, buffer);
                        }
                        while (!sr.EndOfStream)
                        {
                            lastSeen = ProcessCharacters(sb, sr, charBuf, lastSeen, collect);
                            foreach (string s in collect)
                                yield return s;
                            collect.Clear();
                        }
                        if (sb.Length != 0)
                            yield return sb.ToString();
                    }
                }
                finally
                {
                    Win32.CloseHandle(stdoutReadHandle);
                    ms.Dispose();
                }
            }

            public static void CopyStandardOutput(ITeedProcess process, Stream target)
            {
                IntPtr stdoutReadHandle = process.ReleaseStandardOutputReadHandle();
                if (stdoutReadHandle == IntPtr.Zero)
                {
                    Exception ex = new InvalidOperationException(CouldNotRetrieveStandardOutputHandleErrorMessage);
                    ex.Data.Add("ITeedProcess", process);
                    throw ex;
                }
                try
                {
                    byte[] buffer = new byte[4096];
                    while (true)
                    {
                        bool hasExited = process.HasExited;
                        uint count;
                        Win32.ReadFile(stdoutReadHandle, buffer, 4096, out count, IntPtr.Zero);
                        if (count == 0 && hasExited)
                            break;
                        target.Write(buffer, 0, (int)count);
                        target.Flush();
                    }
                }
                finally
                {
                    Win32.CloseHandle(stdoutReadHandle);
                }
            }

            public static string StringFromStandardOutput(ITeedProcess process, Encoding encoding)
            {
                MemoryStream ms = new MemoryStream();
                string returnValue = null;
                try
                {
                    CopyStandardOutput(process, ms);
                }
                finally
                {
                    try
                    {
                        ms.Position = 0;
                        StreamReader sr;
                        if (encoding != null)
                            sr = new StreamReader(ms, encoding);
                        else
                            sr = new StreamReader(ms, true);
                        using (sr)
                            returnValue = sr.ReadToEnd();
                    }
                    catch
                    {
                        ms.Dispose();
                    }
                }
                return returnValue;
            }
        }

        #endregion Helper: Win32 helper methods and consumers

        #region Piped process implementation

        public sealed class PipedProcessStartInfo : ITeedProcessStartInfo
        {
            const string CannotInvokeMessage = "Could not invoke the object. The object might have been invoked.";

            public bool CanInvoke { get; private set; }

            List<string> arguments;

            public string FilePath { get; private set; }
            public string BaseDirectory { get; private set; }
            public string WorkingDirectory { get; private set; }
            public IEnumerable<string> Arguments { get { return arguments.AsReadOnly(); } }
            public ITeedProcessStartInfo RedirectedStandardInput { get; private set; }
            public string RedirectedStandardError { get; private set; }
            bool standardErrorAppend;
            public bool? StandardErrorAppend { get { return RedirectedStandardError != null ? (bool?)standardErrorAppend : null; } }

            /// <summary>
            /// Creates a new PipedProcessStartInfo object.
            /// </summary>
            /// <param name="filePath">The path to the executable file. Must not be null.</param>
            /// <param name="baseDirectory">The current searching directory of the executable file.</param>
            /// <param name="workingDirectory">The initial working directory of the process.</param>
            /// <param name="arguments">The arguments to supply to the process. Can be null, equivalent to empty collection.</param>
            /// <param name="redirectedStandardInput">The source of standard input. Can be null (suppress redirection of standard input).</param>
            public PipedProcessStartInfo(string filePath, string baseDirectory, string workingDirectory, IEnumerable<string> arguments, ITeedProcessStartInfo redirectedStandardInput)
                : this(filePath, baseDirectory, workingDirectory, arguments, redirectedStandardInput, null, false)
            { }

            /// <summary>
            /// Creates a new PipedProcessStartInfo object.
            /// </summary>
            /// <param name="filePath">The path to the executable file. Must not be null.</param>
            /// <param name="baseDirectory">The current searching directory of the executable file.</param>
            /// <param name="workingDirectory">The initial working directory of the process.</param>
            /// <param name="arguments">The arguments to supply to the process. Can be null, equivalent to empty collection.</param>
            /// <param name="redirectedStandardInput">The source of standard input. Can be null (suppress redirection of standard input).</param>
            /// <param name="redirectedStandardError">The file name of redirected standard error. Default to null (suppress redirection of standard error).</param>
            /// <param name="append">Indicates whether the standard error will be appended to the file (true) or overwrite the file (false, default).</param>
            public PipedProcessStartInfo(string filePath, string baseDirectory, string workingDirectory, IEnumerable<string> arguments, ITeedProcessStartInfo redirectedStandardInput, string redirectedStandardError, bool append)
            {
                CanInvoke = false;
                if (filePath == null)
                    throw new ArgumentNullException("filePath");
                FilePath = filePath;
                if (baseDirectory == null)
                    baseDirectory = Directory.GetCurrentDirectory();
                BaseDirectory = baseDirectory;
                if (workingDirectory == null)
                    workingDirectory = baseDirectory;
                WorkingDirectory = workingDirectory;
                this.arguments = new List<string>();
                if (arguments != null)
                    this.arguments.AddRange(arguments);
                RedirectedStandardInput = redirectedStandardInput;
                RedirectedStandardError = redirectedStandardError;
                standardErrorAppend = append;
                CanInvoke = true;
            }

            public ITeedProcess Invoke()
            {
                if (!CanInvoke)
                    throw new InvalidOperationException(CannotInvokeMessage);
                CanInvoke = false;
                ITeedProcess stdin = null;
                if (RedirectedStandardInput != null)
                    stdin = RedirectedStandardInput.Invoke();
                try
                {
                    return new PipedProcess(FilePath,
                        BaseDirectory, WorkingDirectory,
                        Arguments, stdin,
                        RedirectedStandardError, standardErrorAppend);
                }
                catch
                {
                    if (stdin != null)
                        stdin.Terminate();
                    throw;
                }
            }

            public override string ToString()
            {
                StringBuilder sb = new StringBuilder();
                if (RedirectedStandardInput != null)
                {
                    sb.AppendLine(RedirectedStandardInput.ToString())
                    .AppendLine("WILL BE REDIRECTED TO");
                }
                sb.AppendLine("{")
                .Append(" FilePath = ")
                .Append(FilePath == null ? "(null)" : FilePath)
                .AppendLine(",")
                .Append(" BaseDirectory = ")
                .Append(BaseDirectory == null ? "(null)" : BaseDirectory)
                .AppendLine(",")
                .Append(" WorkingDirectory = ")
                .Append(WorkingDirectory == null ? "(null)" : WorkingDirectory)
                .AppendLine(",")
                .Append(" RedirectedStandardError = ")
                .Append(RedirectedStandardError == null ? "(null)" : RedirectedStandardError)
                .AppendLine(",")
                .Append(" StandardErrorAppend = ")
                .Append(StandardErrorAppend == null ? "(null)" : StandardErrorAppend.ToString())
                .AppendLine(",")
                .Append(" CanInvoke = ")
                .AppendLine(CanInvoke.ToString())
                .AppendLine("}")
                .Append("Arguments:");
                if (arguments.Count == 0)
                    sb.AppendLine().Append(" (empty)");
                else
                    foreach (string s in arguments)
                        sb.AppendLine().Append(" ").Append(s);
                return sb.ToString();
            }
        }

        public sealed class PipedProcess : ITeedProcess
        {
            const string HandleReleasedMessage = "No process is associated with this object.";
            const string CouldNotGetExitCodeProcessErrorFormat = "Call to GetExitCodeProcess failed with error code {0}.";
            const string CouldNotCreatePipeErrorFormat = "Could not create anonymous pipe for redirection. Win32 GetLastError() returned {0}.";
            const string CouldNotCreateProcessErrorFormat = "Call to CreateProcess failed with error code {0}.";

            static void WaitForProcessExitWorker(object parameter)
            {
                PipedProcess that = (PipedProcess)parameter;
                IntPtr targetHandle = IntPtr.Zero;
                lock (that)
                {
                    targetHandle = that.processHandle;
                }
                if (targetHandle == IntPtr.Zero)
                    return;
                Win32.WaitForSingleObject(targetHandle, Win32.WaitForInfiniteTime);
                lock (that)
                {
                    that.processHandle = IntPtr.Zero;
                    that.hasExited = true;
                    that.ProcessId = null;
                }
                Win32.CloseHandle(targetHandle);
            }

            IntPtr processHandle;
            bool hasExited;
            IntPtr stdoutReadHandle;
            List<string> arguments;

            public string FilePath { get; private set; }
            public string BaseDirectory { get; private set; }
            public string InitialWorkingDirectory { get; private set; }
            public IEnumerable<string> Arguments { get { return arguments.AsReadOnly(); } }
            public ITeedProcess RedirectedStandardInput { get; private set; }
            public string RedirectedStandardError { get; private set; }
            bool standardErrorAppend;
            public bool? StandardErrorAppend { get { return RedirectedStandardError != null ? (bool?)standardErrorAppend : null; } }

            public PipedProcess(string filePath, string baseDirectory, string workingDirectory, IEnumerable<string> arguments, ITeedProcess redirectedStandardInput, string redirectedStandardError, bool append)
            {
                hasExited = true;
                if (filePath == null)
                    throw new ArgumentNullException("filePath");
                FilePath = filePath;
                if (baseDirectory == null)
                    baseDirectory = Directory.GetCurrentDirectory();
                BaseDirectory = baseDirectory;
                if (workingDirectory == null)
                    workingDirectory = baseDirectory;
                InitialWorkingDirectory = workingDirectory;
                this.arguments = new List<string>();
                if (arguments != null)
                    this.arguments.AddRange(arguments);
                RedirectedStandardInput = redirectedStandardInput;
                RedirectedStandardError = redirectedStandardError;
                standardErrorAppend = append;
                string currentWorkingDirectory = Directory.GetCurrentDirectory();
                try
                {
                    Directory.SetCurrentDirectory(baseDirectory);
                    StartProcess();
                }
                finally
                {
                    try { Directory.SetCurrentDirectory(currentWorkingDirectory); }
                    catch { }
                }
            }

            StringBuilder BuildCommandLine()
            {
                StringBuilder sb = new StringBuilder();
                sb.Append('"').Append(FilePath).Append('"');
                foreach (string arg in arguments)
                {
                    sb.Append(' ');
                    bool requiresEscaping = false;
                    foreach (char ch in arg)
                        if (Char.IsWhiteSpace(ch) || ch == '"')
                        {
                            requiresEscaping = true;
                            break;
                        }
                    if (!requiresEscaping)
                    {
                        sb.Append(arg);
                        continue;
                    }
                    int numberOfBackslashes = 0;
                    sb.Append('"');
                    foreach (char ch in arg)
                    {
                        if (ch == '\\')
                        {
                            ++numberOfBackslashes;
                        }
                        else if (ch == '"')
                        {
                            sb.Append('\\', numberOfBackslashes * 2 + 1).Append('"');
                            numberOfBackslashes = 0;
                        }
                        else
                        {
                            sb.Append('\\', numberOfBackslashes).Append(ch);
                            numberOfBackslashes = 0;
                        }
                    }
                    sb.Append('\\', numberOfBackslashes * 2).Append('"');
                }
                return sb;
            }

            void StartProcess()
            {
                IntPtr stdin = IntPtr.Zero;
                IntPtr stdoutRead = IntPtr.Zero;
                IntPtr stdoutWrite = IntPtr.Zero;
                IntPtr stderr = IntPtr.Zero;
                try
                {
                    Win32.StartupInfo startupInfo = new Win32.StartupInfo();
                    startupInfo.Initialize();
                    startupInfo.Flags = Win32.StartProcessFlags.UseStdHandles;
                    #region Set stdin
                    {
                        if (RedirectedStandardInput != null)
                        {
                            stdin = RedirectedStandardInput.ReleaseStandardOutputReadHandle();
                            Helper.EnsureHandleInheritable(stdin);
                        }
                        else
                        {
                            /* Duplicate the handle so that
                            * the cleaning logic is unified.
                            */
                            stdin = Helper.DuplicateHandleForInheritance(
                                Win32.GetStdHandle(Win32.StandardHandleId.StandardInput));
                        }
                        startupInfo.StandardInput = stdin;
                    }
                    #endregion Set stdin
                    #region Set stdout
                    {
                        Win32.SecurityAttributes pipeAttributes = new Win32.SecurityAttributes();
                        pipeAttributes.Initialize();
                        if (Win32.CreatePipe(out stdoutRead, out stdoutWrite,
                            ref pipeAttributes, 4096) == Win32.Bool.False)
                        {
                            stdoutRead = IntPtr.Zero;
                            stdoutWrite = IntPtr.Zero;
                            int lastError = Marshal.GetLastWin32Error();
                            throw new Win32Exception(lastError,
                                string.Format(CouldNotCreatePipeErrorFormat, lastError));
                        }
                        Helper.EnsureHandleInheritable(stdoutWrite);
                        startupInfo.StandardOutput = stdoutWrite;
                    }
                    #endregion Set stdout
                    #region Set stderr
                    {
                        if (RedirectedStandardError != null)
                        {
                            stderr = standardErrorAppend
                                ? Helper.OpenAppendFile(RedirectedStandardError)
                                : Helper.OpenTruncatedFile(RedirectedStandardError);
                            Helper.EnsureHandleInheritable(stderr);
                        }
                        else
                        {
                            stderr = Helper.DuplicateHandleForInheritance(
                                Win32.GetStdHandle(Win32.StandardHandleId.StandardError));
                        }
                        startupInfo.StandardError = stderr;
                    }
                    #endregion Set stderr
                    Win32.ProcessInformation processInformation;
                    /* Create the process suspended,
                     * and resume it only after we
                     * have started waiting for it.
                     */
                    if (Win32.CreateProcess(null,
                        BuildCommandLine(),
                        IntPtr.Zero, IntPtr.Zero,
                        Win32.Bool.True, Win32.ProcessCreationFlags.CreateSuspended, IntPtr.Zero,
                        InitialWorkingDirectory,
                        ref startupInfo, out processInformation)
                        == Win32.Bool.False)
                    {
                        int lastError = Marshal.GetLastWin32Error();
                        throw new Win32Exception(lastError,
                            string.Format(CouldNotCreateProcessErrorFormat, lastError));
                    }
                    hasExited = false;
                    processHandle = processInformation.HandleToProcess;
                    ProcessId = processInformation.ProcessId;
                    new Thread(WaitForProcessExitWorker).Start(this);
                    Win32.ResumeThread(processInformation.HandleToThread);
                    Win32.CloseHandle(processInformation.HandleToThread);
                    stdoutReadHandle = stdoutRead;
                    /* Prevent this very handle from being
                    * closed. Other handles are useless now
                    * and will be closed in "finally".
                    */
                    stdoutRead = IntPtr.Zero;
                }
                finally
                {
                    if (stdin != IntPtr.Zero)
                        Win32.CloseHandle(stdin);
                    if (stdoutRead != IntPtr.Zero)
                        Win32.CloseHandle(stdoutRead);
                    if (stdoutWrite != IntPtr.Zero)
                        Win32.CloseHandle(stdoutWrite);
                    if (stderr != IntPtr.Zero)
                        Win32.CloseHandle(stderr);
                }
            }

            public bool HasExited
            {
                get
                {
                    lock (this)
                    {
                        if (hasExited)
                            return true;
                        if (processHandle == IntPtr.Zero)
                            throw new InvalidOperationException(HandleReleasedMessage);
                        return false;
                    }
                }
            }

            public int? ProcessId { get; private set; }

            public IntPtr ReleaseStandardOutputReadHandle()
            {
                IntPtr result = stdoutReadHandle;
                stdoutReadHandle = IntPtr.Zero;
                return result;
            }

            public void Terminate()
            {
                lock (this)
                {
                    if (processHandle != IntPtr.Zero)
                    {
                        Win32.TerminateProcess(processHandle, 777);
                        if (RedirectedStandardInput != null)
                            RedirectedStandardInput.Terminate();
                    }
                }
            }

            public override string ToString()
            {
                StringBuilder sb = new StringBuilder();
                if (RedirectedStandardInput != null)
                {
                    sb.AppendLine(RedirectedStandardInput.ToString())
                    .AppendLine("REDIRECTED TO");
                }
                sb.AppendLine("{")
                .Append(" FilePath = ")
                .Append(FilePath == null ? "(null)" : FilePath)
                .AppendLine(",")
                .Append(" BaseDirectory = ")
                .Append(BaseDirectory == null ? "(null)" : BaseDirectory)
                .AppendLine(",")
                .Append(" WorkingDirectory = ")
                .Append(InitialWorkingDirectory == null ? "(null)" : InitialWorkingDirectory)
                .AppendLine(",")
                .Append(" RedirectedStandardError = ")
                .Append(RedirectedStandardError == null ? "(null)" : RedirectedStandardError)
                .AppendLine(",")
                .Append(" StandardErrorAppend = ")
                .Append(StandardErrorAppend == null ? "(null)" : StandardErrorAppend.ToString())
                .AppendLine(",")
                .Append(" ProcessId = ")
                .AppendLine(ProcessId == null ? "(null)" : ProcessId.ToString())
                .AppendLine("}")
                .Append("Arguments:");
                if (arguments.Count == 0)
                    sb.AppendLine().Append(" (empty)");
                else
                    foreach (string s in arguments)
                        sb.AppendLine().Append(" ").Append(s);
                return sb.ToString();
            }
        }

        #endregion Piped process implementation

        #region Concatenate file implementation

        public sealed class ConcatenateFileStartInfo : ITeedProcessStartInfo
        {
            const string CannotInvokeMessage = "Could not invoke the object. The object might have been invoked.";

            public string FileName { get; private set; }

            public bool CanInvoke { get; private set; }

            public ConcatenateFileStartInfo(string fileName)
            {
                CanInvoke = false;
                if (fileName == null)
                    throw new ArgumentNullException("fileName");
                FileName = fileName;
                CanInvoke = true;
            }

            public ITeedProcess Invoke()
            {
                if (!CanInvoke)
                    throw new InvalidOperationException(CannotInvokeMessage);
                CanInvoke = false;
                return new ConcatenateFile(FileName);
            }

            public override string ToString()
            {
                return string.Format("{{ FileName = {0}, CanInvoke = {1} }}", FileName, CanInvoke);
            }
        }

        public sealed class ConcatenateFile : ITeedProcess
        {
            IntPtr fileHandle;
            
            public string FileName { get; private set; }

            public ConcatenateFile(string fileName)
            {
                fileHandle = Helper.OpenReadFile(fileName);
                FileName = fileName;
            }

            public bool HasExited
            {
                get
                {
                    return true;
                }
            }
            
            public void Terminate()
            {
            }

            public IntPtr ReleaseStandardOutputReadHandle()
            {
                IntPtr result = fileHandle;
                fileHandle = IntPtr.Zero;
                return result;
            }

            public override string ToString()
            {
                return string.Format("{{ FileName = {0} }}", FileName);
            }
        }

        #endregion Concatenate file implementation

    }
}