Exemple #1
0
        private bool WaitForProcessToFinish(Process process, ProcessRunnerArguments runnerArgs)
        {
            process.BeginErrorReadLine();
            process.BeginOutputReadLine();

            // Warning: do not log the raw command line args as they
            // may contain sensitive data
            LogDebug(CFamilyStrings.MSG_ExecutingFile,
                     runnerArgs.ExeName,
                     runnerArgs.AsLogText(),
                     runnerArgs.WorkingDirectory,
                     runnerArgs.TimeoutInMilliseconds,
                     process.Id);

            var succeeded = process.WaitForExit(runnerArgs.TimeoutInMilliseconds);

            if (succeeded)
            {
                process.WaitForExit(); // Give any asynchronous events the chance to complete
            }

            // false means we asked the process to stop but it didn't.
            // true: we might still have timed out, but the process ended when we asked it to
            if (succeeded)
            {
                LogDebug(CFamilyStrings.MSG_ExecutionExitCode, process.ExitCode);
                ExitCode = process.ExitCode;

                if (process.ExitCode != 0 && !runnerArgs.CancellationToken.IsCancellationRequested)
                {
                    LogError(CFamilyStrings.ERROR_ProcessRunner_Failed, process.ExitCode);
                }
            }
            else
            {
                ExitCode = ErrorCode;

                try
                {
                    process.Kill();
                    LogWarning(CFamilyStrings.WARN_ExecutionTimedOutKilled, runnerArgs.TimeoutInMilliseconds,
                               runnerArgs.ExeName);
                }
                catch
                {
                    LogWarning(CFamilyStrings.WARN_ExecutionTimedOutNotKilled, runnerArgs.TimeoutInMilliseconds,
                               runnerArgs.ExeName);
                }
            }

            return(succeeded && ExitCode == 0);
        }
Exemple #2
0
        /// <summary>
        /// Runs the specified executable and returns a boolean indicating success or failure
        /// </summary>
        /// <remarks>The standard and error output will be streamed to the logger. Child processes do not inherit the env variables from the parent automatically</remarks>
        public bool Execute(ProcessRunnerArguments runnerArgs)
        {
            if (runnerArgs == null)
            {
                throw new ArgumentNullException(nameof(runnerArgs));
            }
            Debug.Assert(!string.IsNullOrWhiteSpace(runnerArgs.ExeName), "Process runner exe name should not be null/empty");

            if (!File.Exists(runnerArgs.ExeName))
            {
                LogError(CFamilyStrings.ERROR_ProcessRunner_ExeNotFound, runnerArgs.ExeName);
                ExitCode = ErrorCode;
                return(false);
            }

            var psi = new ProcessStartInfo()
            {
                FileName = runnerArgs.ExeName,
                RedirectStandardError  = true,
                RedirectStandardOutput = true,
                UseShellExecute        = false, // required if we want to capture the error output
                ErrorDialog            = false,
                CreateNoWindow         = true,
                Arguments        = runnerArgs.GetEscapedArguments(),
                WorkingDirectory = runnerArgs.WorkingDirectory
            };

            SetEnvironmentVariables(psi, runnerArgs.EnvironmentVariables);

            bool succeeded;

            using (var process = new Process())
            {
                process.StartInfo           = psi;
                process.ErrorDataReceived  += OnErrorDataReceived;
                process.OutputDataReceived += OnOutputDataReceived;

                process.Start();
                process.BeginErrorReadLine();
                process.BeginOutputReadLine();

                // Warning: do not log the raw command line args as they
                // may contain sensitive data
                LogDebug(CFamilyStrings.MSG_ExecutingFile,
                         runnerArgs.ExeName,
                         runnerArgs.AsLogText(),
                         runnerArgs.WorkingDirectory,
                         runnerArgs.TimeoutInMilliseconds,
                         process.Id);

                succeeded = process.WaitForExit(runnerArgs.TimeoutInMilliseconds);
                if (succeeded)
                {
                    process.WaitForExit(); // Give any asynchronous events the chance to complete
                }

                // false means we asked the process to stop but it didn't.
                // true: we might still have timed out, but the process ended when we asked it to
                if (succeeded)
                {
                    LogDebug(CFamilyStrings.MSG_ExecutionExitCode, process.ExitCode);
                    ExitCode = process.ExitCode;
                }
                else
                {
                    ExitCode = ErrorCode;

                    try
                    {
                        process.Kill();
                        LogWarning(CFamilyStrings.WARN_ExecutionTimedOutKilled, runnerArgs.TimeoutInMilliseconds, runnerArgs.ExeName);
                    }
                    catch
                    {
                        LogWarning(CFamilyStrings.WARN_ExecutionTimedOutNotKilled, runnerArgs.TimeoutInMilliseconds, runnerArgs.ExeName);
                    }
                }

                succeeded = succeeded && (ExitCode == 0);
            }

            return(succeeded);
        }
        /// <summary>
        /// Runs the specified executable and communicates with it via Standard IO streams.
        /// The method blocks until the handler has read to the end of the output stream, or when the cancellation token is cancelled.
        /// </summary>
        /// <remarks>
        /// Child processes do not inherit the env variables from the parent automatically.
        /// The stream reader callbacks are executed on the original calling thread.
        /// Errors and timeouts are written to the logger, which in turn writes to the output window. The caller won't see them and has no way of checking the outcome.
        /// </remarks>
        public void Execute(ProcessRunnerArguments runnerArgs)
        {
            if (runnerArgs == null)
            {
                throw new ArgumentNullException(nameof(runnerArgs));
            }

            Debug.Assert(!string.IsNullOrWhiteSpace(runnerArgs.ExeName),
                         "Process runner exe name should not be null/empty");

            if (!File.Exists(runnerArgs.ExeName))
            {
                LogError(CFamilyStrings.ERROR_ProcessRunner_ExeNotFound, runnerArgs.ExeName);
                ExitCode = ErrorCode;
                return;
            }

            var psi = new ProcessStartInfo
            {
                FileName = runnerArgs.ExeName,
                RedirectStandardError  = true,
                RedirectStandardOutput = true,
                RedirectStandardInput  = true,
                UseShellExecute        = false, // required if we want to capture the error output
                ErrorDialog            = false,
                CreateNoWindow         = true,
                Arguments        = runnerArgs.GetEscapedArguments(),
                WorkingDirectory = runnerArgs.WorkingDirectory
            };

            SetEnvironmentVariables(psi, runnerArgs.EnvironmentVariables);

            var hasProcessStarted         = false;
            var isRunningProcessCancelled = false;

            using (var process = new Process())
                using (runnerArgs.CancellationToken.Register(() =>
                {
                    LogMessage(CFamilyStrings.MSG_ExecutionCancelled);

                    lock (process)
                    {
                        if (!hasProcessStarted)
                        {
                            // Cancellation was requested before process started - do nothing
                            return;
                        }
                    }
                    // Cancellation was requested after process started - kill it
                    isRunningProcessCancelled = true;
                    KillProcess(process);
                }))
                {
                    process.ErrorDataReceived += OnErrorDataReceived;
                    process.StartInfo          = psi;

                    lock (process)
                    {
                        if (!runnerArgs.CancellationToken.IsCancellationRequested)
                        {
                            process.Start();
                            hasProcessStarted = true;
                        }
                        else
                        {
                            LogMessage(CFamilyStrings.MSG_ExecutionCancelled);
                            return;
                        }
                    }

                    process.BeginErrorReadLine();

                    // Warning: do not log the raw command line args as they
                    // may contain sensitive data
                    LogDebug(CFamilyStrings.MSG_ExecutingFile,
                             runnerArgs.ExeName,
                             runnerArgs.AsLogText(),
                             runnerArgs.WorkingDirectory,
                             process.Id);

                    try
                    {
                        runnerArgs.HandleInputStream?.Invoke(process.StandardInput);

                        // the caller needs to start a blocking read operation, otherwise the method would exit.
                        runnerArgs.HandleOutputStream?.Invoke(process.StandardOutput);

                        // Give any asynchronous events the chance to complete
                        process.WaitForExit();
                        ExitCode = process.ExitCode;
                        LogDebug(CFamilyStrings.MSG_ExecutionExitCode, process.ExitCode);
                    }
                    catch (Exception ex) when(isRunningProcessCancelled && !ErrorHandler.IsCriticalException(ex))
                    {
                        // If a process is cancelled mid-stream, an exception will be thrown.
                    }
                }
        }