Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,10 @@ public ProcessStartInfo(string fileName, System.Collections.Generic.IEnumerable<
public bool RedirectStandardInput { get { throw null; } set { } }
public bool RedirectStandardOutput { get { throw null; } set { } }
public System.Text.Encoding? StandardErrorEncoding { get { throw null; } set { } }
public Microsoft.Win32.SafeHandles.SafeFileHandle? StandardError { get { throw null; } set { } }
public Microsoft.Win32.SafeHandles.SafeFileHandle? StandardInput { get { throw null; } set { } }
public Microsoft.Win32.SafeHandles.SafeFileHandle? StandardOutput { get { throw null; } set { } }
public bool LeaveHandlesOpen { get { throw null; } set { } }
public System.Text.Encoding? StandardInputEncoding { get { throw null; } set { } }
public System.Text.Encoding? StandardOutputEncoding { get { throw null; } set { } }
[System.Diagnostics.CodeAnalysis.AllowNullAttribute]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,9 @@
<data name="CantRedirectStreams" xml:space="preserve">
<value>The Process object must have the UseShellExecute property set to false in order to redirect IO streams.</value>
</data>
<data name="CantSetHandleAndRedirect" xml:space="preserve">
<value>The StandardInput, StandardOutput, and StandardError handle properties cannot be used together with RedirectStandardInput, RedirectStandardOutput, and RedirectStandardError.</value>
</data>
<data name="DirectoryNotValidAsInput" xml:space="preserve">
<value>The FileName property should not be a directory unless UseShellExecute is set.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1284,6 +1284,27 @@ public bool Start()
throw new InvalidOperationException(SR.CantRedirectStreams);
}

bool anyHandle = startInfo.StandardInput is not null || startInfo.StandardOutput is not null || startInfo.StandardError is not null;
if (anyHandle)
{
if (startInfo.UseShellExecute)
{
throw new InvalidOperationException(SR.CantRedirectStreams);
}
if (startInfo.StandardInput is not null && startInfo.RedirectStandardInput)
{
throw new InvalidOperationException(SR.CantSetHandleAndRedirect);
}
if (startInfo.StandardOutput is not null && startInfo.RedirectStandardOutput)
{
throw new InvalidOperationException(SR.CantSetHandleAndRedirect);
}
if (startInfo.StandardError is not null && startInfo.RedirectStandardError)
{
throw new InvalidOperationException(SR.CantSetHandleAndRedirect);
}
}

//Cannot start a new process and store its handle if the object has been disposed, since finalization has been suppressed.
CheckDisposed();

Expand Down Expand Up @@ -1318,7 +1339,11 @@ public bool Start()

try
{
if (startInfo.RedirectStandardInput)
if (startInfo.StandardInput is not null)
{
childInputPipeHandle = startInfo.StandardInput;
}
else if (startInfo.RedirectStandardInput)
{
SafeFileHandle.CreateAnonymousPipe(out childInputPipeHandle, out parentInputPipeHandle);
}
Expand All @@ -1331,7 +1356,11 @@ public bool Start()
childInputPipeHandle = new SafeFileHandle(0, ownsHandle: false);
}

if (startInfo.RedirectStandardOutput)
if (startInfo.StandardOutput is not null)
{
childOutputPipeHandle = startInfo.StandardOutput;
}
else if (startInfo.RedirectStandardOutput)
{
SafeFileHandle.CreateAnonymousPipe(out parentOutputPipeHandle, out childOutputPipeHandle, asyncRead: OperatingSystem.IsWindows());
}
Expand All @@ -1344,7 +1373,11 @@ public bool Start()
childOutputPipeHandle = new SafeFileHandle(1, ownsHandle: false);
}

if (startInfo.RedirectStandardError)
if (startInfo.StandardError is not null)
{
childErrorPipeHandle = startInfo.StandardError;
}
else if (startInfo.RedirectStandardError)
{
SafeFileHandle.CreateAnonymousPipe(out parentErrorPipeHandle, out childErrorPipeHandle, asyncRead: OperatingSystem.IsWindows());
}
Expand Down Expand Up @@ -1385,9 +1418,20 @@ public bool Start()
// process will not receive EOF when the child process closes its handles.
// It's OK to do it for handles returned by Console.OpenStandard*Handle APIs,
// because these handles are not owned and won't be closed by Dispose.
childInputPipeHandle?.Dispose();
childOutputPipeHandle?.Dispose();
childErrorPipeHandle?.Dispose();
// When LeaveHandlesOpen is true, we must NOT close handles that were passed in
// by the caller via StartInfo.StandardInput/Output/Error.
if (startInfo.StandardInput is null || !startInfo.LeaveHandlesOpen)
{
childInputPipeHandle?.Dispose();
}
if (startInfo.StandardOutput is null || !startInfo.LeaveHandlesOpen)
{
childOutputPipeHandle?.Dispose();
}
if (startInfo.StandardError is null || !startInfo.LeaveHandlesOpen)
{
childErrorPipeHandle?.Dispose();
}
}

if (startInfo.RedirectStandardInput)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using Microsoft.Win32.SafeHandles;

namespace System.Diagnostics
{
Expand Down Expand Up @@ -117,6 +118,89 @@ public string Arguments
public bool RedirectStandardOutput { get; set; }
public bool RedirectStandardError { get; set; }

/// <summary>
/// Gets or sets a <see cref="SafeFileHandle"/> that will be used as the standard input of the child process.
/// When set, the handle is passed directly to the child process and <see cref="RedirectStandardInput"/> must be <see langword="false"/>.
/// </summary>
/// <remarks>
/// <para>
/// The handle does not need to be inheritable; the runtime will make it inheritable as needed.
/// Use <see cref="SafeFileHandle.CreateAnonymousPipe"/> to create a pair of connected pipe handles,
/// <see cref="IO.File.OpenHandle"/> to open a file handle,
/// <see cref="IO.File.OpenNullHandle"/> to discard input,
/// or <see cref="Console.OpenStandardInputHandle"/> to inherit the parent's standard input.
/// </para>
/// <para>
/// By default, <see cref="Process.Start()"/> will close this handle after starting the child process.
/// Set <see cref="LeaveHandlesOpen"/> to <see langword="true"/> to keep the handle open.
/// </para>
/// <para>
/// This property cannot be used together with <see cref="RedirectStandardInput"/>
/// and requires <see cref="UseShellExecute"/> to be <see langword="false"/>.
/// </para>
/// </remarks>
/// <value>A <see cref="SafeFileHandle"/> to use as the standard input handle of the child process, or <see langword="null"/> to use the default behavior.</value>
public SafeFileHandle? StandardInput { get; set; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any requirements around its inheritability that should be called out in the docs?

Maybe add some XML comments remarks around when this would be used, what APIs it might typically used in conjunction with, etc.?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added <remarks> sections to all three properties in 60844f3 covering: inheritability requirement, typical APIs to use (SafeFileHandle.CreateAnonymousPipe, File.OpenHandle), interaction with the new LeaveHandlesOpen property, and constraints (RedirectStandard* / UseShellExecute).


/// <summary>
/// Gets or sets a <see cref="SafeFileHandle"/> that will be used as the standard output of the child process.
/// When set, the handle is passed directly to the child process and <see cref="RedirectStandardOutput"/> must be <see langword="false"/>.
/// </summary>
/// <remarks>
/// <para>
/// The handle does not need to be inheritable; the runtime will make it inheritable as needed.
/// Use <see cref="SafeFileHandle.CreateAnonymousPipe"/> to create a pair of connected pipe handles,
/// <see cref="IO.File.OpenHandle"/> to open a file handle,
/// <see cref="IO.File.OpenNullHandle"/> to discard output,
/// or <see cref="Console.OpenStandardOutputHandle"/> to inherit the parent's standard output.
/// </para>
/// <para>
/// By default, <see cref="Process.Start()"/> will close this handle after starting the child process.
/// Set <see cref="LeaveHandlesOpen"/> to <see langword="true"/> to keep the handle open.
/// </para>
/// <para>
/// This property cannot be used together with <see cref="RedirectStandardOutput"/>
/// and requires <see cref="UseShellExecute"/> to be <see langword="false"/>.
/// </para>
/// </remarks>
/// <value>A <see cref="SafeFileHandle"/> to use as the standard output handle of the child process, or <see langword="null"/> to use the default behavior.</value>
public SafeFileHandle? StandardOutput { get; set; }

/// <summary>
/// Gets or sets a <see cref="SafeFileHandle"/> that will be used as the standard error of the child process.
/// When set, the handle is passed directly to the child process and <see cref="RedirectStandardError"/> must be <see langword="false"/>.
/// </summary>
/// <remarks>
/// <para>
/// The handle does not need to be inheritable; the runtime will make it inheritable as needed.
/// Use <see cref="SafeFileHandle.CreateAnonymousPipe"/> to create a pair of connected pipe handles,
/// <see cref="IO.File.OpenHandle"/> to open a file handle,
/// <see cref="IO.File.OpenNullHandle"/> to discard error output,
/// or <see cref="Console.OpenStandardErrorHandle"/> to inherit the parent's standard error.
/// </para>
/// <para>
/// By default, <see cref="Process.Start()"/> will close this handle after starting the child process.
/// Set <see cref="LeaveHandlesOpen"/> to <see langword="true"/> to keep the handle open.
/// </para>
/// <para>
/// This property cannot be used together with <see cref="RedirectStandardError"/>
/// and requires <see cref="UseShellExecute"/> to be <see langword="false"/>.
/// </para>
/// </remarks>
/// <value>A <see cref="SafeFileHandle"/> to use as the standard error handle of the child process, or <see langword="null"/> to use the default behavior.</value>
public SafeFileHandle? StandardError { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the <see cref="StandardInput"/>, <see cref="StandardOutput"/>,
/// and <see cref="StandardError"/> handles should be left open after the process is started.
/// </summary>
/// <remarks>
/// When <see langword="false"/> (the default), the handles are closed by <see cref="Process.Start()"/>
/// after starting the child process. When <see langword="true"/>, the caller is responsible for closing the handles.
/// </remarks>
/// <value><see langword="true"/> to leave the handles open; <see langword="false"/> to close them after the process starts. The default is <see langword="false"/>.</value>
public bool LeaveHandlesOpen { get; set; }

public Encoding? StandardInputEncoding { get; set; }

public Encoding? StandardErrorEncoding { get; set; }
Expand Down
Loading
Loading