|
|
@ -2,20 +2,69 @@ |
|
|
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
|
|
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
|
|
|
|
|
|
|
|
|
|
|
using System; |
|
|
|
using System; |
|
|
|
|
|
|
|
using System.Collections; |
|
|
|
using System.Collections.Generic; |
|
|
|
using System.Collections.Generic; |
|
|
|
|
|
|
|
using System.ComponentModel; |
|
|
|
using System.Diagnostics; |
|
|
|
using System.Diagnostics; |
|
|
|
|
|
|
|
using System.IO; |
|
|
|
|
|
|
|
using System.IO.Pipes; |
|
|
|
|
|
|
|
using System.Linq; |
|
|
|
|
|
|
|
using System.Runtime.InteropServices; |
|
|
|
|
|
|
|
using System.Security; |
|
|
|
using System.Text; |
|
|
|
using System.Text; |
|
|
|
using System.Threading; |
|
|
|
using System.Threading; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
using System.Threading.Tasks; |
|
|
|
using ICSharpCode.Core; |
|
|
|
using ICSharpCode.Core; |
|
|
|
|
|
|
|
using Microsoft.Win32.SafeHandles; |
|
|
|
|
|
|
|
using ICSharpCode.SharpDevelop.Gui; |
|
|
|
|
|
|
|
using ICSharpCode.SharpDevelop.Util; |
|
|
|
|
|
|
|
|
|
|
|
namespace ICSharpCode.SharpDevelop |
|
|
|
namespace ICSharpCode.SharpDevelop |
|
|
|
{ |
|
|
|
{ |
|
|
|
|
|
|
|
[Flags] |
|
|
|
|
|
|
|
public enum ProcessCreationFlags |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
None = 0, |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// Creates a new console instead of inheriting the parent console.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
CreateNewConsole = 0x00000010, |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// Launches a console application without a console window.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
CreateNoWindow = 0x08000000 |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public interface IProcessRunner : IDisposable |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
Task<int> RunInOutputPadAsync(MessageViewCategory outputCategory, string program, params string[] arguments); |
|
|
|
|
|
|
|
string WorkingDirectory { get; set; } |
|
|
|
|
|
|
|
ProcessCreationFlags CreationFlags { get; set; } |
|
|
|
|
|
|
|
IDictionary<string, string> EnvironmentVariables { get; } |
|
|
|
|
|
|
|
bool RedirectStandardOutput { get; set; } |
|
|
|
|
|
|
|
bool RedirectStandardError { get; set; } |
|
|
|
|
|
|
|
bool RedirectStandardOutputAndErrorToSingleStream { get; set; } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void Start(string program, params string[] arguments); |
|
|
|
|
|
|
|
void StartCommandLine(string commandLine); |
|
|
|
|
|
|
|
void Kill(); |
|
|
|
|
|
|
|
Task WaitForExitAsync(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Stream StandardOutput { get; } |
|
|
|
|
|
|
|
Stream StandardError { get; } |
|
|
|
|
|
|
|
StreamReader OpenStandardOutputReader(); |
|
|
|
|
|
|
|
StreamReader OpenStandardErrorReader(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// <summary>
|
|
|
|
/// Runs a process that sends output to standard output and to
|
|
|
|
/// Class for starting external processes.
|
|
|
|
/// standard error.
|
|
|
|
/// Very similar to System.Diagnostics.Process, but supports binary stdout/stderr (not only text),
|
|
|
|
|
|
|
|
/// and allows using the same pipe for both stdout and stderr.
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// Also, implements an interface to support mocking in unit tests.
|
|
|
|
/// </summary>
|
|
|
|
/// </summary>
|
|
|
|
public class ProcessRunner : IDisposable |
|
|
|
public class ProcessRunner : IProcessRunner, IDisposable |
|
|
|
{ |
|
|
|
{ |
|
|
|
public static Encoding OemEncoding { |
|
|
|
public static Encoding OemEncoding { |
|
|
|
get { |
|
|
|
get { |
|
|
@ -23,267 +72,532 @@ namespace ICSharpCode.SharpDevelop |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Process process; |
|
|
|
#region SafeProcessHandle
|
|
|
|
StringBuilder standardOutput = new StringBuilder(); |
|
|
|
[SecurityCritical] |
|
|
|
StringBuilder standardError = new StringBuilder(); |
|
|
|
sealed class SafeProcessHandle : SafeHandleZeroOrMinusOneIsInvalid |
|
|
|
ManualResetEventSlim endOfOutput = new ManualResetEventSlim(false); |
|
|
|
{ |
|
|
|
int outputStreamsFinished; |
|
|
|
// this private ctor is required for SafeHandle implementations
|
|
|
|
|
|
|
|
SafeProcessHandle() : base(true) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
internal SafeProcessHandle(IntPtr handle) : base(true) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
base.SetHandle(handle); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[SecurityCritical] |
|
|
|
|
|
|
|
protected override bool ReleaseHandle() |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
return NativeMethods.CloseHandle(handle); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
#region Native structures
|
|
|
|
/// Triggered when the process has exited.
|
|
|
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] |
|
|
|
/// </summary>
|
|
|
|
protected struct STARTUPINFO |
|
|
|
public event EventHandler ProcessExited; |
|
|
|
{ |
|
|
|
|
|
|
|
public uint cb; |
|
|
|
|
|
|
|
public string lpReserved; |
|
|
|
|
|
|
|
public string lpDesktop; |
|
|
|
|
|
|
|
public string lpTitle; |
|
|
|
|
|
|
|
public uint dwX; |
|
|
|
|
|
|
|
public uint dwY; |
|
|
|
|
|
|
|
public uint dwXSize; |
|
|
|
|
|
|
|
public uint dwYSize; |
|
|
|
|
|
|
|
public uint dwXCountChars; |
|
|
|
|
|
|
|
public uint dwYCountChars; |
|
|
|
|
|
|
|
public uint dwFillAttribute; |
|
|
|
|
|
|
|
public uint dwFlags; |
|
|
|
|
|
|
|
public short wShowWindow; |
|
|
|
|
|
|
|
public short cbReserved2; |
|
|
|
|
|
|
|
public IntPtr lpReserved2; |
|
|
|
|
|
|
|
public SafePipeHandle hStdInput; |
|
|
|
|
|
|
|
public SafePipeHandle hStdOutput; |
|
|
|
|
|
|
|
public SafePipeHandle hStdError; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
[StructLayout(LayoutKind.Sequential)] |
|
|
|
/// Triggered when a line of text is read from the standard output.
|
|
|
|
protected struct PROCESS_INFORMATION |
|
|
|
/// </summary>
|
|
|
|
{ |
|
|
|
public event EventHandler<LineReceivedEventArgs> OutputLineReceived; |
|
|
|
public IntPtr hProcess; |
|
|
|
|
|
|
|
public IntPtr hThread; |
|
|
|
|
|
|
|
public int dwProcessId; |
|
|
|
|
|
|
|
public int dwThreadId; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
#region Native methods
|
|
|
|
/// Triggered when a line of text is read from the standard error.
|
|
|
|
[DllImport("kernel32.dll", EntryPoint = "CreateProcess", CharSet = CharSet.Unicode, SetLastError = true)] |
|
|
|
/// </summary>
|
|
|
|
[return: MarshalAs(UnmanagedType.Bool)] |
|
|
|
public event EventHandler<LineReceivedEventArgs> ErrorLineReceived; |
|
|
|
static extern bool NativeCreateProcess( |
|
|
|
|
|
|
|
string lpApplicationName, |
|
|
|
|
|
|
|
StringBuilder lpCommandLine, |
|
|
|
|
|
|
|
IntPtr lpProcessAttributes, |
|
|
|
|
|
|
|
IntPtr lpThreadAttributes, |
|
|
|
|
|
|
|
[MarshalAs(UnmanagedType.Bool)] bool bInheritHandles, |
|
|
|
|
|
|
|
uint dwCreationFlags, |
|
|
|
|
|
|
|
string lpEnvironment, |
|
|
|
|
|
|
|
string lpCurrentDirectory, |
|
|
|
|
|
|
|
[In] ref STARTUPINFO lpStartupInfo, |
|
|
|
|
|
|
|
out PROCESS_INFORMATION lpProcessInformation |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)] |
|
|
|
|
|
|
|
[return: MarshalAs(UnmanagedType.Bool)] |
|
|
|
|
|
|
|
static extern bool TerminateProcess(SafeProcessHandle processHandle, int exitCode); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)] |
|
|
|
|
|
|
|
[return: MarshalAs(UnmanagedType.Bool)] |
|
|
|
|
|
|
|
static extern bool GetExitCodeProcess(SafeProcessHandle processHandle, out int exitCode); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)] |
|
|
|
|
|
|
|
static extern IntPtr GetStdHandle(int nStdHandle); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[DllImport("shell32.dll", SetLastError = true, CharSet = CharSet.Unicode)] |
|
|
|
|
|
|
|
static extern unsafe char** CommandLineToArgvW([MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine, out int pNumArgs); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[DllImport("kernel32.dll")] |
|
|
|
|
|
|
|
static extern IntPtr LocalFree(IntPtr hMem); |
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#region CommandLine <-> Argument Array
|
|
|
|
/// <summary>
|
|
|
|
/// <summary>
|
|
|
|
/// Creates a new instance of the <see cref="ProcessRunner"/>.
|
|
|
|
/// Decodes a command line into an array of arguments according to the CommandLineToArgvW rules.
|
|
|
|
/// </summary>
|
|
|
|
/// </summary>
|
|
|
|
public ProcessRunner() |
|
|
|
/// <remarks>
|
|
|
|
|
|
|
|
/// Command line parsing rules:
|
|
|
|
|
|
|
|
/// - 2n backslashes followed by a quotation mark produce n backslashes, and the quotation mark is considered to be the end of the argument.
|
|
|
|
|
|
|
|
/// - (2n) + 1 backslashes followed by a quotation mark again produce n backslashes followed by a quotation mark.
|
|
|
|
|
|
|
|
/// - n backslashes not followed by a quotation mark simply produce n backslashes.
|
|
|
|
|
|
|
|
/// </remarks>
|
|
|
|
|
|
|
|
public static unsafe string[] CommandLineToArgumentArray(string commandLine) |
|
|
|
{ |
|
|
|
{ |
|
|
|
this.LogStandardOutputAndError = true; |
|
|
|
if (string.IsNullOrEmpty(commandLine)) |
|
|
|
|
|
|
|
return new string[0]; |
|
|
|
|
|
|
|
int numberOfArgs; |
|
|
|
|
|
|
|
char** arr = CommandLineToArgvW(commandLine, out numberOfArgs); |
|
|
|
|
|
|
|
if (arr == null) |
|
|
|
|
|
|
|
throw new Win32Exception(); |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
string[] result = new string[numberOfArgs]; |
|
|
|
|
|
|
|
for (int i = 0; i < numberOfArgs; i++) { |
|
|
|
|
|
|
|
result[i] = new string(arr[i]); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return result; |
|
|
|
|
|
|
|
} finally { |
|
|
|
|
|
|
|
// Free memory obtained by CommandLineToArgW.
|
|
|
|
|
|
|
|
LocalFree(new IntPtr(arr)); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
static readonly char[] charsNeedingQuoting = { ' ', '\t', '\n', '\v', '"' }; |
|
|
|
/// Gets or sets the process's working directory.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public string WorkingDirectory { get; set; } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// Gets or sets whether standard output is logged to the "StandardOutput" and "StandardError"
|
|
|
|
|
|
|
|
/// properties. When this property is false, output is still redirected to the
|
|
|
|
|
|
|
|
/// OutputLineReceived and ErrorLineReceived events, but the ProcessRunner uses less memory.
|
|
|
|
|
|
|
|
/// The default value is true.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public bool LogStandardOutputAndError { get; set; } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// <summary>
|
|
|
|
/// Gets the standard output returned from the process.
|
|
|
|
/// Escapes a set of arguments according to the CommandLineToArgvW rules.
|
|
|
|
/// </summary>
|
|
|
|
/// </summary>
|
|
|
|
public string StandardOutput { |
|
|
|
/// <remarks>
|
|
|
|
get { |
|
|
|
/// Command line parsing rules:
|
|
|
|
lock (standardOutput) |
|
|
|
/// - 2n backslashes followed by a quotation mark produce n backslashes, and the quotation mark is considered to be the end of the argument.
|
|
|
|
return standardOutput.ToString(); |
|
|
|
/// - (2n) + 1 backslashes followed by a quotation mark again produce n backslashes followed by a quotation mark.
|
|
|
|
|
|
|
|
/// - n backslashes not followed by a quotation mark simply produce n backslashes.
|
|
|
|
|
|
|
|
/// </remarks>
|
|
|
|
|
|
|
|
public static string ArgumentArrayToCommandLine(params string[] arguments) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if (arguments == null) |
|
|
|
|
|
|
|
return null; |
|
|
|
|
|
|
|
StringBuilder b = new StringBuilder(); |
|
|
|
|
|
|
|
for (int i = 0; i < arguments.Length; i++) { |
|
|
|
|
|
|
|
if (i > 0) |
|
|
|
|
|
|
|
b.Append(' '); |
|
|
|
|
|
|
|
AppendArgument(b, arguments[i]); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return b.ToString(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
static void AppendArgument(StringBuilder b, string arg) |
|
|
|
/// Gets the standard error output returned from the process.
|
|
|
|
{ |
|
|
|
/// </summary>
|
|
|
|
if (arg.Length > 0 && arg.IndexOfAny(charsNeedingQuoting) < 0) { |
|
|
|
public string StandardError { |
|
|
|
b.Append(arg); |
|
|
|
get { |
|
|
|
} else { |
|
|
|
lock (standardError) |
|
|
|
b.Append('"'); |
|
|
|
return standardError.ToString(); |
|
|
|
for (int j = 0; ; j++) { |
|
|
|
|
|
|
|
int backslashCount = 0; |
|
|
|
|
|
|
|
while (j < arg.Length && arg[j] == '\\') { |
|
|
|
|
|
|
|
backslashCount++; |
|
|
|
|
|
|
|
j++; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (j == arg.Length) { |
|
|
|
|
|
|
|
b.Append('\\', backslashCount * 2); |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
} else if (arg[j] == '"') { |
|
|
|
|
|
|
|
b.Append('\\', backslashCount * 2 + 1); |
|
|
|
|
|
|
|
b.Append('"'); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
b.Append('\\', backslashCount); |
|
|
|
|
|
|
|
b.Append(arg[j]); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
b.Append('"'); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
#region RunInOutputPad
|
|
|
|
/// Releases resources held by the <see cref="ProcessRunner"/>
|
|
|
|
public async Task<int> RunInOutputPadAsync(MessageViewCategory outputCategory, string program, params string[] arguments) |
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public void Dispose() |
|
|
|
|
|
|
|
{ |
|
|
|
{ |
|
|
|
process.Dispose(); |
|
|
|
RedirectStandardOutputAndErrorToSingleStream = true; |
|
|
|
endOfOutput.Dispose(); |
|
|
|
Start(program, arguments); |
|
|
|
|
|
|
|
StringBuilder printedCommandLine = new StringBuilder(); |
|
|
|
|
|
|
|
if (WorkingDirectory != null) { |
|
|
|
|
|
|
|
printedCommandLine.Append(WorkingDirectory); |
|
|
|
|
|
|
|
printedCommandLine.Append("> "); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
printedCommandLine.Append(CommandLine); |
|
|
|
|
|
|
|
outputCategory.AppendLine(printedCommandLine.ToString()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
using (TextReader reader = OpenStandardOutputReader()) { |
|
|
|
|
|
|
|
await reader.CopyToAsync(new MessageViewCategoryTextWriter(outputCategory)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
await WaitForExitAsync(); |
|
|
|
|
|
|
|
outputCategory.AppendLine(StringParser.Parse("${res:XML.MainMenu.ToolMenu.ExternalTools.ExitedWithCode} " + this.ExitCode)); |
|
|
|
|
|
|
|
return this.ExitCode; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#region Start Info Properties
|
|
|
|
/// <summary>
|
|
|
|
/// <summary>
|
|
|
|
/// Gets the process exit code.
|
|
|
|
/// Gets or sets the process's working directory.
|
|
|
|
/// </summary>
|
|
|
|
/// </summary>
|
|
|
|
public int ExitCode { |
|
|
|
public string WorkingDirectory { get; set; } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ProcessCreationFlags creationFlags = ProcessCreationFlags.CreateNoWindow; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public ProcessCreationFlags CreationFlags { |
|
|
|
|
|
|
|
get { return creationFlags; } |
|
|
|
|
|
|
|
set { creationFlags = value; } |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
IDictionary<string, string> environmentVariables; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public IDictionary<string, string> EnvironmentVariables { |
|
|
|
get { |
|
|
|
get { |
|
|
|
int exitCode = 0; |
|
|
|
if (environmentVariables == null) { |
|
|
|
if (process != null) { |
|
|
|
environmentVariables = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); |
|
|
|
exitCode = process.ExitCode; |
|
|
|
foreach (DictionaryEntry e in Environment.GetEnvironmentVariables()) { |
|
|
|
|
|
|
|
environmentVariables.Add((string)e.Key, (string)e.Value); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
return exitCode; |
|
|
|
return environmentVariables; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public string CommandLine { get; private set; } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public bool RedirectStandardOutput { get; set; } |
|
|
|
|
|
|
|
public bool RedirectStandardError { get; set; } |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// <summary>
|
|
|
|
/// Waits for the process to exit.
|
|
|
|
/// Gets whether to use a single stream for both stdout and stderr.
|
|
|
|
/// </summary>
|
|
|
|
/// </summary>
|
|
|
|
public void WaitForExit() |
|
|
|
public bool RedirectStandardOutputAndErrorToSingleStream { get; set; } |
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#region Start
|
|
|
|
|
|
|
|
bool wasStarted; |
|
|
|
|
|
|
|
SafeProcessHandle safeProcessHandle; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public void Start(string program, params string[] arguments) |
|
|
|
{ |
|
|
|
{ |
|
|
|
WaitForExit(Int32.MaxValue); |
|
|
|
StringBuilder commandLine = new StringBuilder(); |
|
|
|
|
|
|
|
AppendArgument(commandLine, program); |
|
|
|
|
|
|
|
if (arguments != null) { |
|
|
|
|
|
|
|
for (int i = 0; i < arguments.Length; i++) { |
|
|
|
|
|
|
|
commandLine.Append(' '); |
|
|
|
|
|
|
|
AppendArgument(commandLine, arguments[i]); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
StartCommandLine(commandLine.ToString()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
public void StartCommandLine(string commandLine) |
|
|
|
/// Waits for the process to exit.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
/// <param name="timeout">A timeout in milliseconds.</param>
|
|
|
|
|
|
|
|
/// <returns><see langword="true"/> if the associated process has
|
|
|
|
|
|
|
|
/// exited; otherwise, <see langword="false"/></returns>
|
|
|
|
|
|
|
|
public bool WaitForExit(int timeout) |
|
|
|
|
|
|
|
{ |
|
|
|
{ |
|
|
|
if (process == null) { |
|
|
|
lock (lockObj) { |
|
|
|
throw new InvalidOperationException("no process is running"); |
|
|
|
if (wasStarted) |
|
|
|
|
|
|
|
throw new InvalidOperationException(); |
|
|
|
|
|
|
|
DoStart(commandLine); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected virtual void DoStart(string commandLine) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
this.CommandLine = commandLine; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const uint STARTF_USESTDHANDLES = 0x00000100; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const int STD_INPUT_HANDLE = -10; |
|
|
|
|
|
|
|
const int STD_OUTPUT_HANDLE = -11; |
|
|
|
|
|
|
|
const int STD_ERROR_HANDLE = -12; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const int CREATE_UNICODE_ENVIRONMENT = 0x00000400; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
STARTUPINFO startupInfo = new STARTUPINFO(); |
|
|
|
|
|
|
|
startupInfo.cb = (uint)Marshal.SizeOf(typeof(STARTUPINFO)); |
|
|
|
|
|
|
|
startupInfo.dwFlags = STARTF_USESTDHANDLES; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Create pipes
|
|
|
|
|
|
|
|
startupInfo.hStdInput = new SafePipeHandle(GetStdHandle(STD_INPUT_HANDLE), ownsHandle: false); |
|
|
|
|
|
|
|
if (RedirectStandardOutput || RedirectStandardOutputAndErrorToSingleStream) { |
|
|
|
|
|
|
|
standardOutput = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable); |
|
|
|
|
|
|
|
startupInfo.hStdOutput = standardOutput.ClientSafePipeHandle; |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
startupInfo.hStdOutput = new SafePipeHandle(GetStdHandle(STD_OUTPUT_HANDLE), ownsHandle: false); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (RedirectStandardOutputAndErrorToSingleStream) { |
|
|
|
|
|
|
|
standardError = standardOutput; |
|
|
|
|
|
|
|
startupInfo.hStdError = standardError.ClientSafePipeHandle; |
|
|
|
|
|
|
|
} else if (RedirectStandardError) { |
|
|
|
|
|
|
|
standardError = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable); |
|
|
|
|
|
|
|
startupInfo.hStdError = standardError.ClientSafePipeHandle; |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
startupInfo.hStdError = new SafePipeHandle(GetStdHandle(STD_ERROR_HANDLE), ownsHandle: false); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
bool exited = process.WaitForExit(timeout); |
|
|
|
uint flags = (uint)this.CreationFlags; |
|
|
|
|
|
|
|
|
|
|
|
if (exited) { |
|
|
|
string environmentBlock = null; |
|
|
|
endOfOutput.Wait(timeout == int.MaxValue ? Timeout.Infinite : timeout); |
|
|
|
if (environmentVariables != null) { |
|
|
|
|
|
|
|
environmentBlock = BuildEnvironmentBlock(environmentVariables); |
|
|
|
|
|
|
|
flags |= CREATE_UNICODE_ENVIRONMENT; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return exited; |
|
|
|
PROCESS_INFORMATION processInfo = new PROCESS_INFORMATION(); |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
CreateProcess(null, new StringBuilder(commandLine), IntPtr.Zero, IntPtr.Zero, true, flags, environmentBlock, WorkingDirectory, ref startupInfo, out processInfo); |
|
|
|
|
|
|
|
wasStarted = true; |
|
|
|
|
|
|
|
} finally { |
|
|
|
|
|
|
|
if (processInfo.hProcess != IntPtr.Zero && processInfo.hProcess != new IntPtr(-1)) { |
|
|
|
|
|
|
|
safeProcessHandle = new SafeProcessHandle(processInfo.hProcess); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (processInfo.hThread != IntPtr.Zero && processInfo.hThread != new IntPtr(-1)) { |
|
|
|
|
|
|
|
NativeMethods.CloseHandle(processInfo.hThread); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// Dispose the client side handles of the pipe.
|
|
|
|
|
|
|
|
// They got copied into the new process, we don't need our local copies anymore.
|
|
|
|
|
|
|
|
startupInfo.hStdInput.Dispose(); |
|
|
|
|
|
|
|
startupInfo.hStdOutput.Dispose(); |
|
|
|
|
|
|
|
startupInfo.hStdError.Dispose(); |
|
|
|
|
|
|
|
if (!wasStarted) { |
|
|
|
|
|
|
|
// In case of error, dispose the server side of the pipes as well
|
|
|
|
|
|
|
|
if (standardOutput != null) { |
|
|
|
|
|
|
|
standardOutput.Dispose(); |
|
|
|
|
|
|
|
standardOutput = null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (standardError != null) { |
|
|
|
|
|
|
|
standardError.Dispose(); |
|
|
|
|
|
|
|
standardError = null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
//StartStreamCopyAfterProcessCreation();
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public bool IsRunning { |
|
|
|
static string BuildEnvironmentBlock(IEnumerable<KeyValuePair<string, string>> environment) |
|
|
|
get { |
|
|
|
{ |
|
|
|
bool isRunning = false; |
|
|
|
StringBuilder b = new StringBuilder(); |
|
|
|
|
|
|
|
foreach (var pair in environment.OrderBy(p => p.Key, StringComparer.OrdinalIgnoreCase)) { |
|
|
|
if (process != null) { |
|
|
|
b.Append(pair.Key); |
|
|
|
isRunning = !process.HasExited; |
|
|
|
b.Append('='); |
|
|
|
} |
|
|
|
b.Append(pair.Value); |
|
|
|
|
|
|
|
b.Append('\0'); |
|
|
|
return isRunning; |
|
|
|
} |
|
|
|
|
|
|
|
b.Append('\0'); |
|
|
|
|
|
|
|
return b.ToString(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected virtual void CreateProcess( |
|
|
|
|
|
|
|
string lpApplicationName, |
|
|
|
|
|
|
|
StringBuilder lpCommandLine, |
|
|
|
|
|
|
|
IntPtr lpProcessAttributes, |
|
|
|
|
|
|
|
IntPtr lpThreadAttributes, |
|
|
|
|
|
|
|
bool bInheritHandles, |
|
|
|
|
|
|
|
uint dwCreationFlags, |
|
|
|
|
|
|
|
string lpEnvironment, |
|
|
|
|
|
|
|
string lpCurrentDirectory, |
|
|
|
|
|
|
|
ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if (!NativeCreateProcess(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, |
|
|
|
|
|
|
|
lpEnvironment, lpCurrentDirectory, ref lpStartupInfo, out lpProcessInformation)) { |
|
|
|
|
|
|
|
throw new Win32Exception(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
Dictionary<string, string> environmentVariables = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); |
|
|
|
public void Dispose() |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if (safeProcessHandle != null) |
|
|
|
|
|
|
|
safeProcessHandle.Dispose(); |
|
|
|
|
|
|
|
if (standardOutput != null) |
|
|
|
|
|
|
|
standardOutput.Dispose(); |
|
|
|
|
|
|
|
if (standardError != null) |
|
|
|
|
|
|
|
standardError.Dispose(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public Dictionary<string, string> EnvironmentVariables { |
|
|
|
#region HasExited / ExitCode / Kill
|
|
|
|
get { return environmentVariables; } |
|
|
|
public bool HasExited { |
|
|
|
|
|
|
|
get { return WaitForExit(0); } |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// <summary>
|
|
|
|
/// Starts the process.
|
|
|
|
/// Gets the process exit code.
|
|
|
|
/// </summary>
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="command">The process filename.</param>
|
|
|
|
public int ExitCode { |
|
|
|
/// <param name="arguments">The command line arguments to
|
|
|
|
get { |
|
|
|
/// pass to the command.</param>
|
|
|
|
if (!WaitForExit(0)) |
|
|
|
public void Start(string command, string arguments) |
|
|
|
throw new InvalidOperationException("Process has not yet exited"); |
|
|
|
{ |
|
|
|
return exitCode; // WaitForExit has the side effect of setting exitCode
|
|
|
|
Encoding encoding = OemEncoding; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
process = new Process(); |
|
|
|
|
|
|
|
process.StartInfo.CreateNoWindow = true; |
|
|
|
|
|
|
|
process.StartInfo.FileName = command; |
|
|
|
|
|
|
|
process.StartInfo.WorkingDirectory = WorkingDirectory; |
|
|
|
|
|
|
|
process.StartInfo.RedirectStandardOutput = true; |
|
|
|
|
|
|
|
process.StartInfo.StandardOutputEncoding = encoding; |
|
|
|
|
|
|
|
process.OutputDataReceived += OnOutputLineReceived; |
|
|
|
|
|
|
|
process.StartInfo.RedirectStandardError = true; |
|
|
|
|
|
|
|
process.StartInfo.StandardErrorEncoding = encoding; |
|
|
|
|
|
|
|
process.ErrorDataReceived += OnErrorLineReceived; |
|
|
|
|
|
|
|
process.StartInfo.UseShellExecute = false; |
|
|
|
|
|
|
|
process.StartInfo.Arguments = arguments; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var pair in environmentVariables) |
|
|
|
|
|
|
|
process.StartInfo.EnvironmentVariables.Add(pair.Key, pair.Value); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (ProcessExited != null) { |
|
|
|
|
|
|
|
process.EnableRaisingEvents = true; |
|
|
|
|
|
|
|
process.Exited += OnProcessExited; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool started = false; |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
process.Start(); |
|
|
|
|
|
|
|
started = true; |
|
|
|
|
|
|
|
} finally { |
|
|
|
|
|
|
|
if (!started) { |
|
|
|
|
|
|
|
process.Exited -= OnProcessExited; |
|
|
|
|
|
|
|
process = null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
process.BeginOutputReadLine(); |
|
|
|
|
|
|
|
process.BeginErrorReadLine(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// <summary>
|
|
|
|
/// Starts the process.
|
|
|
|
/// Sends the kill signal to the process.
|
|
|
|
|
|
|
|
/// Does not wait for the process to complete to exit after being killed.
|
|
|
|
/// </summary>
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="command">The process filename.</param>
|
|
|
|
public void Kill() |
|
|
|
public void Start(string command) |
|
|
|
|
|
|
|
{ |
|
|
|
{ |
|
|
|
Start(command, String.Empty); |
|
|
|
if (!wasStarted) |
|
|
|
|
|
|
|
throw new InvalidOperationException("Process was not started"); |
|
|
|
|
|
|
|
if (!TerminateProcess(safeProcessHandle, -1)) { |
|
|
|
|
|
|
|
int err = Marshal.GetLastWin32Error(); |
|
|
|
|
|
|
|
// If TerminateProcess fails, maybe it's because the process has already exited.
|
|
|
|
|
|
|
|
if (!WaitForExit(0)) |
|
|
|
|
|
|
|
throw new Win32Exception(err); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
#region WaitForExit
|
|
|
|
/// Kills the running process.
|
|
|
|
sealed class ProcessWaitHandle : WaitHandle |
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
public void Kill() |
|
|
|
|
|
|
|
{ |
|
|
|
{ |
|
|
|
if (process != null) { |
|
|
|
public ProcessWaitHandle(SafeProcessHandle processHandle) |
|
|
|
if (!process.HasExited) { |
|
|
|
{ |
|
|
|
try { |
|
|
|
var currentProcess = new HandleRef(this, NativeMethods.GetCurrentProcess()); |
|
|
|
process.Kill(); |
|
|
|
SafeWaitHandle safeWaitHandle; |
|
|
|
} catch (InvalidOperationException) { |
|
|
|
if (!NativeMethods.DuplicateHandle(currentProcess, processHandle, currentProcess, out safeWaitHandle, 0, false, 2)) { |
|
|
|
// race condition (if the process has already exited)
|
|
|
|
throw new Win32Exception(); |
|
|
|
} |
|
|
|
|
|
|
|
// don't call process.Dispose() here - that causes the OnProcessExited
|
|
|
|
|
|
|
|
// event not to fire correctly
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
endOfOutput.Wait(); |
|
|
|
base.SafeWaitHandle = safeWaitHandle; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
bool hasExited; |
|
|
|
/// Raises the <see cref="ProcessExited"/> event.
|
|
|
|
int exitCode; |
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
protected void OnProcessExited(object sender, EventArgs e) |
|
|
|
public void WaitForExit() |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (ProcessExited != null) { |
|
|
|
WaitForExit(Timeout.Infinite); |
|
|
|
if (endOfOutput != null) { |
|
|
|
} |
|
|
|
endOfOutput.Wait(); |
|
|
|
|
|
|
|
|
|
|
|
public bool WaitForExit(int millisecondsTimeout) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if (hasExited) |
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
if (!wasStarted) |
|
|
|
|
|
|
|
throw new InvalidOperationException("Process was not yet started"); |
|
|
|
|
|
|
|
if (safeProcessHandle.IsClosed) |
|
|
|
|
|
|
|
throw new ObjectDisposedException("ProcessRunner"); |
|
|
|
|
|
|
|
using (var waitHandle = new ProcessWaitHandle(safeProcessHandle)) { |
|
|
|
|
|
|
|
if (waitHandle.WaitOne(millisecondsTimeout, false)) { |
|
|
|
|
|
|
|
if (!GetExitCodeProcess(safeProcessHandle, out exitCode)) |
|
|
|
|
|
|
|
throw new Win32Exception(); |
|
|
|
|
|
|
|
// Wait until the output is processed
|
|
|
|
|
|
|
|
// if (standardOutputTask != null)
|
|
|
|
|
|
|
|
// standardOutputTask.Wait();
|
|
|
|
|
|
|
|
// if (standardErrorTask != null)
|
|
|
|
|
|
|
|
// standardErrorTask.Wait();
|
|
|
|
|
|
|
|
hasExited = true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
ProcessExited(this, e); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return hasExited; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
readonly object lockObj = new object(); |
|
|
|
|
|
|
|
TaskCompletionSource<object> waitForExitTCS; |
|
|
|
|
|
|
|
ProcessWaitHandle waitForExitAsyncWaitHandle; |
|
|
|
|
|
|
|
RegisteredWaitHandle waitForExitAsyncRegisteredWaitHandle; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// <summary>
|
|
|
|
/// Raises the <see cref="OutputLineReceived"/> event.
|
|
|
|
/// Asynchronously waits for the process to exit.
|
|
|
|
/// </summary>
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="sender">The event source.</param>
|
|
|
|
public Task WaitForExitAsync() |
|
|
|
/// <param name="e">The line received event arguments.</param>
|
|
|
|
|
|
|
|
protected void OnOutputLineReceived(object sender, DataReceivedEventArgs e) |
|
|
|
|
|
|
|
{ |
|
|
|
{ |
|
|
|
if (e.Data == null) { |
|
|
|
if (hasExited) |
|
|
|
if (Interlocked.Increment(ref outputStreamsFinished) == 2) |
|
|
|
return Task.FromResult(true); |
|
|
|
endOfOutput.Set(); |
|
|
|
if (!wasStarted) |
|
|
|
return; |
|
|
|
throw new InvalidOperationException("Process was not yet started"); |
|
|
|
} |
|
|
|
if (safeProcessHandle.IsClosed) |
|
|
|
if (LogStandardOutputAndError) { |
|
|
|
throw new ObjectDisposedException("ProcessRunner"); |
|
|
|
lock (standardOutput) { |
|
|
|
lock (lockObj) { |
|
|
|
standardOutput.AppendLine(e.Data); |
|
|
|
if (waitForExitTCS == null) { |
|
|
|
|
|
|
|
waitForExitTCS = new TaskCompletionSource<object>(); |
|
|
|
|
|
|
|
waitForExitAsyncWaitHandle = new ProcessWaitHandle(safeProcessHandle); |
|
|
|
|
|
|
|
waitForExitAsyncRegisteredWaitHandle = ThreadPool.RegisterWaitForSingleObject(waitForExitAsyncWaitHandle, WaitForExitAsyncCallback, null, -1, true); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return waitForExitTCS.Task; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void WaitForExitAsyncCallback(object context, bool wasSignaled) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
waitForExitAsyncRegisteredWaitHandle.Unregister(null); |
|
|
|
|
|
|
|
waitForExitAsyncRegisteredWaitHandle = null; |
|
|
|
|
|
|
|
waitForExitAsyncWaitHandle.Close(); |
|
|
|
|
|
|
|
waitForExitAsyncWaitHandle = null; |
|
|
|
|
|
|
|
// Wait until the output is processed
|
|
|
|
|
|
|
|
// if (standardOutputTask != null)
|
|
|
|
|
|
|
|
// await standardOutputTask;
|
|
|
|
|
|
|
|
// if (standardErrorTask != null)
|
|
|
|
|
|
|
|
// await standardErrorTask;
|
|
|
|
|
|
|
|
waitForExitTCS.SetResult(null); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#region StandardOutput/StandardError
|
|
|
|
|
|
|
|
AnonymousPipeServerStream standardOutput; |
|
|
|
|
|
|
|
AnonymousPipeServerStream standardError; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public Stream StandardOutput { |
|
|
|
|
|
|
|
get { |
|
|
|
|
|
|
|
if (standardOutput == null) |
|
|
|
|
|
|
|
throw new InvalidOperationException(wasStarted ? "stdout was not redirected" : "Process not yet started"); |
|
|
|
|
|
|
|
return standardOutput; |
|
|
|
} |
|
|
|
} |
|
|
|
if (OutputLineReceived != null) { |
|
|
|
} |
|
|
|
OutputLineReceived(this, new LineReceivedEventArgs(e.Data)); |
|
|
|
|
|
|
|
|
|
|
|
public Stream StandardError { |
|
|
|
|
|
|
|
get { |
|
|
|
|
|
|
|
if (standardError == null) |
|
|
|
|
|
|
|
throw new InvalidOperationException(wasStarted ? "stderr was not redirected" : "Process not yet started"); |
|
|
|
|
|
|
|
return standardError; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// <summary>
|
|
|
|
/// Raises the <see cref="ErrorLineReceived"/> event.
|
|
|
|
/// Opens a text reader around the standard output.
|
|
|
|
/// </summary>
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="sender">The event source.</param>
|
|
|
|
public StreamReader OpenStandardOutputReader() |
|
|
|
/// <param name="e">The line received event arguments.</param>
|
|
|
|
|
|
|
|
protected void OnErrorLineReceived(object sender, DataReceivedEventArgs e) |
|
|
|
|
|
|
|
{ |
|
|
|
{ |
|
|
|
if (e.Data == null) { |
|
|
|
return new StreamReader(this.StandardOutput, OemEncoding); |
|
|
|
if (Interlocked.Increment(ref outputStreamsFinished) == 2) |
|
|
|
} |
|
|
|
endOfOutput.Set(); |
|
|
|
|
|
|
|
return; |
|
|
|
/// <summary>
|
|
|
|
} |
|
|
|
/// Opens a text reader around the standard error.
|
|
|
|
if (LogStandardOutputAndError) { |
|
|
|
/// </summary>
|
|
|
|
lock (standardError) { |
|
|
|
public StreamReader OpenStandardErrorReader() |
|
|
|
standardError.AppendLine(e.Data); |
|
|
|
{ |
|
|
|
} |
|
|
|
return new StreamReader(this.StandardError, OemEncoding); |
|
|
|
} |
|
|
|
|
|
|
|
if (ErrorLineReceived != null) { |
|
|
|
|
|
|
|
ErrorLineReceived(this, new LineReceivedEventArgs(e.Data)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
#endregion
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|