Skip to content

Add ProcessStartInfo.ArgumentList #23347

@mklement0

Description

@mklement0

Note: Updated into an API proposal, with feedback incorporated.

Rationale

On Unix platforms, external executables (binaries) receive their arguments as an array of string literals, which makes for robust, predictable passing of arguments (see Linux system function execvp(), for instance).

Regrettably, in Windows there is no equivalent mechanism: a pseudo shell "command line" must be passed as a single string encoding all arguments, and it is up to the target executable itself to parse that line into individual arguments.

The ProcessStartInfo class currently only supports the Windows approach directly, by exposing a string Arguments property that expects the whole command line.

On Unix platforms, this means that even if you start out with an array of arguments, you must currently artificially assemble its elements into a single pseudo shell command line, only to have CoreFX split that back into an array of individual arguments behind the scenes so as to be able to invoke the platform-native process-creation function, which takes an array of arguments.

Not only is this an unnecessary burden on the user and inefficient, it is error-prone. It is easy to accidentally assemble a command-line string that does not round-trip as expected.

As a real-world use case, consider the ongoing quoting woes PowerShell experiences.
At least on Unix platforms, PowerShell should be able to simply pass the arguments that are the result of its parsing as-is to external utilities.

Having the ability to pass an array of arguments would be of benefit on Windows too, as it is fair to assume that the more typical use case is to build the desired arguments as a list / array rather than to piece together a single-string command line with intricate quoting.
(The latter should only be needed if you're passing an preexisting string through or if you're invoking an executable that has custom argument-parsing rules.)

Proposed API

Add a IReadOnlyList<String> ArgumentList property (conceptually, an array of argument string literals) to the ProcessStartInfo class, to complement the existing string Arguments (pseudo shell command line) property, and let each update the other lazily, on demand, when accessed:

  • If .Arguments was (last) assigned to, do the following when .ArgumentList is accessed:
    Call ParseArgumentsIntoList(), which splits the string into individual arguments based on the rules for MS C++ programs and return the resulting list.

  • If .ArgumentList was (last) assigned to, do the following when .Arguments is accessed:
    Synthesize the pseudo shell command line from the individual arguments and assign the result to using the above rules in reverse (enclose in "..." if a given argument has embedded whitespace, ..., as already implemented for internal use in System.PasteArguments.Paste()) and return the resulting string.

    • As @TSlivede proposes, it's worth extending the conversion algorithm to also double-quote arguments that contain ' (single quotes) lest they be interpreted as having syntactic function, which to some programs they do (e.g., Ruby, Cygwin).

That way, both .Arguments and .ArgumentList can be assigned to, and the respective other property contains the equivalent in terms of the official (MS C++) parsing rules.

A ProcessStartInfo instance constructed this way can therefore be used on all supported platforms:

  • On Windows, pass the .Arguments property value to the CreateProcess() / ShellExecuteEx() Windows API functions, as before.

  • On Unix platforms, pass the .ArgumentList property value via .ToArray() to ForkAndExecProcess().

Additionally, to complement the suggested behind-the-scenes conversion between the array form and the single-string form, public utility methods should be implemented that perform these conversions explicitly, on demand.

@atsushikan proposes the following signatures:

public static IReadOnlyList<String> SplitArguments(String commandLine) { ... }

public static String CombineArguments(IReadOnlyList<String> argumentList) { ... }

Open Questions

  • How exactly should the existing, currently (effectively) private utility methods referenced above be surfaced publicly (namespace, class, signature)? Currently, they're in different classes, and one of them is internal (System.PasteArguments); @atsushikan suggests making them public methods of the System.Diagnostics.Process class.

Usage

// The array of arguments to pass.
string[] args = { "hello", "sweet world" };

// ---- New ProcessStartInfo.ArgumentList property.

// Assign it to the new .ArgumentList property of a ProcessStartInfo instance.
var psi = new ProcessStartInfo();
psi.ArgumentList = args;

// Accessing .Arguments now returns the pseudo shell command line that is the
// equivalent of the array of arguments:
//   @"hello ""sweet world"""
string pseudoShellCommandLine = psi.Arguments; 

// ---- New utility methods for conversion on demand.
//         EXACT NAMES AND SIGNATURES TBD.

// Arguments array (list) -> pseudo shell command line
string[] args = { "hello", "sweet world" };
string pseudoShellCommandLine = System.Diagnostics.Process.CombineArguments(args);

// Pseudo shell command line -> arguments array (list)
IReadOnlyList<string> argList = System.Diagnostics.Process.SplitArguments(@"hello ""sweet world""");

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions