/// <summary>
        /// Executes the <paramref name="executable"/> asynchronously and waits a maximum time of <paramref name="maxWaitMs"/> for completion.
        /// </summary>
        /// <param name="executable">Program to execute</param>
        /// <param name="arguments">Program arguments</param>
        /// <param name="idWrapper"><see cref="WindowsIdentityWrapper"/> used to impersonate the external process</param>
        /// <param name="debugLogger">Debug logger for debug output</param>
        /// <param name="priorityClass">Process priority</param>
        /// <param name="maxWaitMs">Maximum time to wait for completion</param>
        /// <returns>> <see cref="ProcessExecutionResult"/> object that respresents the result of executing the Program</returns>
        /// <remarks>
        /// This method throws an exception only if process.Start() fails (in partiular, if the <paramref name="executable"/> doesn't exist).
        /// Any other error in managed code is signaled by the returned task being set to Faulted state.
        /// If the program itself does not result in an ExitCode of 0, the returned task ends in RanToCompletion state;
        /// the ExitCode of the program will be contained in the returned <see cref="ProcessExecutionResult"/>.
        /// This method is nearly identical to <see cref="ProcessUtils.ExecuteAsync"/>; it is necessary to have this code duplicated
        /// because AsyncImpersonationProcess hides several methods of the Process class and executing these methods on the base class does
        /// therefore not work. If this method is changed it is likely that <see cref="ProcessUtils.ExecuteAsync"/> also
        /// needs to be changed.
        /// </remarks>
        internal static Task <ProcessExecutionResult> ExecuteAsync(string executable, string arguments, WindowsIdentityWrapper idWrapper, ILogger debugLogger, ProcessPriorityClass priorityClass = ProcessPriorityClass.Normal, int maxWaitMs = ProcessUtils.DEFAULT_TIMEOUT)
        {
            var  tcs     = new TaskCompletionSource <ProcessExecutionResult>();
            bool exited  = false;
            var  process = new AsyncImpersonationProcess(debugLogger)
            {
                StartInfo = new ProcessStartInfo(executable, arguments)
                {
                    UseShellExecute        = false,
                    CreateNoWindow         = true,
                    RedirectStandardOutput = true,
                    RedirectStandardError  = true,
                    RedirectStandardInput  = true,
                    StandardOutputEncoding = ProcessUtils.CONSOLE_ENCODING,
                    StandardErrorEncoding  = ProcessUtils.CONSOLE_ENCODING
                },
                EnableRaisingEvents = true
            };

            // We need to read standardOutput and standardError asynchronously to avoid a deadlock
            // when the buffer is not big enough to receive all the respective output. Otherwise the
            // process may block because the buffer is full and the Exited event below is never raised.
            var standardOutput        = new StringBuilder();
            var standardOutputResults = new TaskCompletionSource <string>();

            process.OutputDataReceived += (sender, args) =>
            {
                if (args.Data != null)
                {
                    standardOutput.AppendLine(args.Data);
                }
                else
                {
                    standardOutputResults.SetResult(standardOutput.Length > 0 ? ProcessUtils.RemoveEncodingPreamble(standardOutput.ToString()) : null);
                }
            };

            var standardError        = new StringBuilder();
            var standardErrorResults = new TaskCompletionSource <string>();

            process.ErrorDataReceived += (sender, args) =>
            {
                if (args.Data != null)
                {
                    standardError.AppendLine(args.Data);
                }
                else
                {
                    standardErrorResults.SetResult(standardError.Length > 0 ? ProcessUtils.RemoveEncodingPreamble(standardError.ToString()) : null);
                }
            };

            var processStart = new TaskCompletionSource <bool>();

            // The Exited event is raised in any case when the process has finished, i.e. when it gracefully
            // finished (ExitCode = 0), finished with an error (ExitCode != 0) and when it was killed below.
            // That ensures disposal of the process object.
            process.Exited += async(sender, args) =>
            {
                exited = true;
                try
                {
                    await processStart.Task;
                    // standardStreamTasksReady is only disposed when starting the process was not successful,
                    // in which case the Exited event is never raised.
                    // ReSharper disable once AccessToDisposedClosure
                    tcs.TrySetResult(new ProcessExecutionResult
                    {
                        ExitCode = process.ExitCode,
                        // standardStreamTasksReady makes sure that we do not access the standard stream tasks before they are initialized.
                        // For the same reason it is intended that these tasks (as closures) are modified (i.e. initialized).
                        // We need to take this cumbersome way because it is not possible to access the standard streams before the process
                        // is started. If on the other hand the Exited event is raised before the tasks are initialized, we need to make
                        // sure that this method waits until the tasks are initialized before they are accessed.
                        // ReSharper disable PossibleNullReferenceException
                        // ReSharper disable AccessToModifiedClosure
                        StandardOutput = await standardOutputResults.Task,
                        StandardError  = await standardErrorResults.Task
                                         // ReSharper restore AccessToModifiedClosure
                                         // ReSharper restore PossibleNullReferenceException
                    });
                }
                catch (Exception e)
                {
                    tcs.TrySetException(e);
                }
                finally
                {
                    process.Dispose();
                }
            };

            bool processStarted = false;

            using (var tokenWrapper = idWrapper.TokenWrapper)
                processStarted = process.StartAsUser(tokenWrapper.Token);
            processStart.SetResult(processStarted);
            if (processStarted)
            {
                process.BeginOutputReadLine();
                process.BeginErrorReadLine();
                try
                {
                    // This call may throw an exception if the process has already exited when we get here.
                    // In that case the Exited event has already set tcs to RanToCompletion state so that
                    // the TrySetException call below does not change the state of tcs anymore. This is correct
                    // as it doesn't make sense to change the priority of the process if it is already finished.
                    // Any other "real" error sets the state of tcs to Faulted below.
                    process.PriorityClass = priorityClass;
                }
                catch (InvalidOperationException e)
                {
                    // This exception indicates that the process is no longer available which is probably
                    // because the process has exited already. The exception should not be logged because
                    // there is no guarantee that the exited event has finished setting the task to the
                    // RanToCompletion state before this exception sets it to the Faulted state.
                    if (!exited && !process.HasExited && tcs.TrySetException(e))
                    {
                        debugLogger.Error("AsyncImpersonationProcess ({0}): Exception while setting the PriorityClass", e, executable);
                    }
                }
                catch (Exception e)
                {
                    if (tcs.TrySetException(e))
                    {
                        debugLogger.Error("AsyncImpersonationProcess ({0}): Exception while setting the PriorityClass", e, executable);
                    }
                }
            }
            else
            {
                exited = true;
                standardOutputResults.SetResult(null);
                standardErrorResults.SetResult(null);

                debugLogger.Error("AsyncImpersonationProcess ({0}): Could not start process", executable);
                return(Task.FromResult(new ProcessExecutionResult {
                    ExitCode = Int32.MinValue
                }));
            }

            // Here we take care of the maximum time to wait for the process if such was requested.
            if (maxWaitMs != ProcessUtils.INFINITE)
            {
                Task.Delay(maxWaitMs).ContinueWith(task =>
                {
                    try
                    {
                        // Cancel the state of tcs if it was not set to Faulted or
                        // RanToCompletion before.
                        tcs.TrySetCanceled();
                        // Always kill the process if is running.
                        if (!exited && !process.HasExited)
                        {
                            process.Kill();
                            debugLogger.Warn("AsyncImpersonationProcess ({0}): Process was killed because maxWaitMs was reached.", executable);
                        }
                    }
                    // An exception is thrown in process.Kill() when the external process exits
                    // while we set tcs to canceled. In that case there is nothing to do anymore.
                    // This is not an error. In case of other errors that may happen, we log it anyways
                    catch (Exception e)
                    {
                        debugLogger.Error("AsyncImpersonationProcess ({0}): Exception while trying to kill the process", e, executable);
                    }
                });
            }
            return(tcs.Task);
        }
    /// <summary>
    /// Executes the <paramref name="executable"/> asynchronously and waits a maximum time of <paramref name="maxWaitMs"/> for completion.
    /// </summary>
    /// <param name="executable">Program to execute</param>
    /// <param name="arguments">Program arguments</param>
    /// <param name="idWrapper"><see cref="WindowsIdentityWrapper"/> used to impersonate the external process</param>
    /// <param name="debugLogger">Debug logger for debug output</param>
    /// <param name="priorityClass">Process priority</param>
    /// <param name="maxWaitMs">Maximum time to wait for completion</param>
    /// <returns>> <see cref="ProcessExecutionResult"/> object that respresents the result of executing the Program</returns>
    /// <remarks>
    /// This method throws an exception only if process.Start() fails (in partiular, if the <paramref name="executable"/> doesn't exist).
    /// Any other error in managed code is signaled by the returned task being set to Faulted state.
    /// If the program itself does not result in an ExitCode of 0, the returned task ends in RanToCompletion state;
    /// the ExitCode of the program will be contained in the returned <see cref="ProcessExecutionResult"/>.
    /// This method is nearly identical to <see cref="ProcessUtils.ExecuteAsync"/>; it is necessary to have this code duplicated
    /// because AsyncImpersonationProcess hides several methods of the Process class and executing these methods on the base class does
    /// therefore not work. If this method is changed it is likely that <see cref="ProcessUtils.ExecuteAsync"/> also
    /// needs to be changed.
    /// </remarks>
    internal static Task<ProcessExecutionResult> ExecuteAsync(string executable, string arguments, WindowsIdentityWrapper idWrapper, ILogger debugLogger, ProcessPriorityClass priorityClass = ProcessPriorityClass.Normal, int maxWaitMs = ProcessUtils.DEFAULT_TIMEOUT)
    {
      var tcs = new TaskCompletionSource<ProcessExecutionResult>();
      var process = new AsyncImpersonationProcess(debugLogger)
      {
        StartInfo = new ProcessStartInfo(executable, arguments)
        {
          UseShellExecute = false,
          CreateNoWindow = true,
          RedirectStandardOutput = true,
          RedirectStandardError = true,
          StandardOutputEncoding = ProcessUtils.CONSOLE_ENCODING,
          StandardErrorEncoding = ProcessUtils.CONSOLE_ENCODING
        },
        EnableRaisingEvents = true
      };

      // We need to read standardOutput and standardError asynchronously to avoid a deadlock
      // when the buffer is not big enough to receive all the respective output. Otherwise the
      // process may block because the buffer is full and the Exited event below is never raised.
      Task<string> standardOutputTask = null;
      Task<string> standardErrorTask = null;
      var standardStreamTasksReady = new ManualResetEventSlim();

      // The Exited event is raised in any case when the process has finished, i.e. when it gracefully
      // finished (ExitCode = 0), finished with an error (ExitCode != 0) and when it was killed below.
      // That ensures disposal of the process object.
      process.Exited += (sender, args) =>
      {
        try
        {
          // standardStreamTasksReady is only disposed when starting the process was not successful,
          // in which case the Exited event is never raised.
          // ReSharper disable once AccessToDisposedClosure
          standardStreamTasksReady.Wait();
          tcs.TrySetResult(new ProcessExecutionResult
          {
            ExitCode = process.ExitCode,
            // standardStreamTasksReady makes sure that we do not access the standard stream tasks before they are initialized.
            // For the same reason it is intended that these tasks (as closures) are modified (i.e. initialized).
            // We need to take this cumbersome way because it is not possible to access the standard streams before the process
            // is started. If on the other hand the Exited event is raised before the tasks are initialized, we need to make
            // sure that this method waits until the tasks are initialized before they are accessed.
            // ReSharper disable PossibleNullReferenceException
            // ReSharper disable AccessToModifiedClosure
            StandardOutput = standardOutputTask.Result,
            StandardError = standardErrorTask.Result
            // ReSharper restore AccessToModifiedClosure
            // ReSharper restore PossibleNullReferenceException
          });
        }
        catch (Exception e)
        {
          debugLogger.Error("AsyncImpersonationProcess ({0}): Exception while executing the Exited handler", e, executable);
          tcs.TrySetException(e);
        }
        finally
        {
          process.Dispose();
        }
      };

      using (var tokenWrapper = idWrapper.TokenWrapper)
        if (!process.StartAsUser(tokenWrapper.Token))
        {
          debugLogger.Error("AsyncImpersonationProcess ({0}): Could not start process", executable);
          standardStreamTasksReady.Dispose();
          return Task.FromResult(new ProcessExecutionResult { ExitCode = int.MinValue });
        }

      try
      {
        // This call may throw an exception if the process has already exited when we get here.
        // In that case the Exited event has already set tcs to RanToCompletion state so that
        // the TrySetException call below does not change the state of tcs anymore. This is correct
        // as it doesn't make sense to change the priority of the process if it is already finished.
        // Any other "real" error sets the state of tcs to Faulted below.
        process.PriorityClass = priorityClass;
      }
      catch (Exception e)
      {
        debugLogger.Error("AsyncImpersonationProcess ({0}): Exception while setting the PriorityClass", e, executable);
        tcs.TrySetException(e);
      }

      standardOutputTask = process.StandardOutput.ReadToEndAsync();
      standardErrorTask = process.StandardError.ReadToEndAsync();
      standardStreamTasksReady.Set();

      // Here we take care of the maximum time to wait for the process if such was requested.
      if (maxWaitMs != ProcessUtils.INFINITE)
        Task.Delay(maxWaitMs).ContinueWith(task =>
        {
          try
          {
            // We only kill the process if the state of tcs was not set to Faulted or
            // RanToCompletion before.
            if (tcs.TrySetCanceled())
            {
              process.Kill();
              debugLogger.Warn("AsyncImpersonationProcess ({0}): Process was killed because maxWaitMs was reached.", executable);
            }
          }
          // An exception is thrown in process.Kill() when the external process exits
          // while we set tcs to canceled. In that case there is nothing to do anymore.
          // This is not an error. In case of other errors that may happen, we log it anyways
          catch (Exception e)
          {
            debugLogger.Error("AsyncImpersonationProcess ({0}): Exception while trying to kill the process", e, executable);
          }
        });
      return tcs.Task;
    }