6

I am using following code to start another process with argument, here i pass a string path as argument, path returned as c:\documents and settings\\local settings: :

string path = Directory.GetParent(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)).FullName(); //path = c:\documents and settings\<username>\local settings

ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.Arguments = path;
Process.Start(startInfo);

I would like to pass the path as one argument, a whole string. But I found out the path has been separated by multiple arguments, it actually separated by every space. In this case, it pass c:\documents as first argument, and as second argument and settings\\local as third argument...

I want to pass them as one argument rather than 4 arguments. How can do that with StartInfo.Argument?

Soner Gönül
  • 97,193
  • 102
  • 206
  • 364
Chelseajcole
  • 487
  • 1
  • 9
  • 16
  • 2
    You want to pass a filepath as an argument? shouldn't you be setting that as the filename? what exactly are you trying to do here? – Derek Mar 12 '13 at 15:17
  • [MSND ProcessStartInfo Class and Example](http://msdn.microsoft.com/en-us/library/system.diagnostics.processstartinfo.aspx) – MethodMan Mar 12 '13 at 15:20
  • @Derek, I want to pass a file path as an argument. I cannot set that as a file name since i have to dynamically get the path. I am trying pass a file path as one argument. – Chelseajcole Mar 12 '13 at 16:17

4 Answers4

7

I am not sure, if it works, but try to use " around your path:

startInfo.Arguments = "\"" + path + "\"";

This will wrap your string in " and so, whitespaces will be ignored.

bash.d
  • 13,029
  • 3
  • 29
  • 42
5

The Process object treats arguments in exactly the same way as the Console. Wrap any arguments containing spaces with double quotes: "\"c:\documents and settings\local settings\"".

A good tip is to try to run the process from the console with whatever arguments you've supplied. This makes it easier to understand error feedback than running from a Process object.

pixelbadger
  • 1,556
  • 9
  • 24
4

Wrap your argument in quotation marks:

startInfo.Arguments = "\"" + path + "\"";
Rotem
  • 21,452
  • 6
  • 62
  • 109
4

The rules

The rules for parsing escapes are documented here: https://msdn.microsoft.com/en-us/library/17w5ykft.aspx

Microsoft C/C++ startup code uses the following rules when interpreting arguments given on the operating system command line:

  1. Arguments are delimited by white space, which is either a space or a tab.

  2. The caret character (^) is not recognized as an escape character or delimiter. The character is handled completely by the command-line parser in the operating system before being passed to the argv array in the program.

  3. A string surrounded by double quotation marks ("string") is interpreted as a single argument, regardless of white space contained within. A quoted string can be embedded in an argument.

  4. A double quotation mark preceded by a backslash (\") is interpreted as a literal double quotation mark character (").
  5. Backslashes are interpreted literally, unless they immediately precede a double quotation mark.
  6. If an even number of backslashes is followed by a double quotation mark, one backslash is placed in the argv array for every pair of backslashes, and the double quotation mark is interpreted as a string delimiter.
  7. If an odd number of backslashes is followed by a double quotation mark, one backslash is placed in the argv array for every pair of backslashes, and the double quotation mark is "escaped" by the remaining backslash, causing a literal double quotation mark (") to be placed in argv.

Application to generation

Unfortunately there is no good documentation on how to properly escape arguments, i.e. how to apply the above rules to ensure that an array of arguments is passed correctly to the target application. Here are the rules I followed for escaping each argument:

  1. If the argument contains a space or tab, wrap it in " (double quote) characters.

  2. If the argument contains a " (double quote), preceded by \ (backslash) characters, escape the preceding \ (backslash) characters with \ (backslash) before appending the escaped " (double quote).

  3. If the argument ends with one or more \ (backslash), and contains white space, escape the final \ (backslash) characters with \ (backslash) before adding the enclosing " (double quote).

The code

/// <summary>
/// Convert an argument array to an argument string for using
/// with Process.StartInfo.Arguments.
/// </summary>
/// <param name="argument">
/// The args to convert.
/// </param>
/// <returns>
/// The argument <see cref="string"/>.
/// </returns>
public static string EscapeArguments(string argument)
{
    using (var characterEnumerator = argument.GetEnumerator())
    {
        var escapedArgument = new StringBuilder();
        var backslashCount = 0;
        var needsQuotes = false;

        while (characterEnumerator.MoveNext())
        {
            switch (characterEnumerator.Current)
            {
                case '\\':
                    // Backslashes are simply passed through, except when they need
                    // to be escaped when followed by a \", e.g. the argument string
                    // \", which would be encoded to \\\"
                    backslashCount++;
                    escapedArgument.Append('\\');
                    break;

                case '\"':
                    // Escape any preceding backslashes
                    for (var c = 0; c < backslashCount; c++)
                    {
                        escapedArgument.Append('\\');
                    }

                    // Append an escaped double quote.
                    escapedArgument.Append("\\\"");

                    // Reset the backslash counter.
                    backslashCount = 0;
                    break;

                case ' ':
                case '\t':
                    // White spaces are escaped by surrounding the entire string with
                    // double quotes, which should be done at the end to prevent 
                    // multiple wrappings.
                    needsQuotes = true;

                    // Append the whitespace
                    escapedArgument.Append(characterEnumerator.Current);

                    // Reset the backslash counter.
                    backslashCount = 0;
                    break;

                default:
                    // Reset the backslash counter.
                    backslashCount = 0;

                    // Append the current character
                    escapedArgument.Append(characterEnumerator.Current);
                    break;
            }
        }

        // No need to wrap in quotes
        if (!needsQuotes)
        {
            return escapedArgument.ToString();
        }

        // Prepend the "
        escapedArgument.Insert(0, '"');

        // Escape any preceding backslashes before appending the "
        for (var c = 0; c < backslashCount; c++)
        {
            escapedArgument.Append('\\');
        }

        // Append the final "
        escapedArgument.Append('\"');

        return escapedArgument.ToString();
    }
}

/// <summary>
/// Convert an argument array to an argument string for using
/// with Process.StartInfo.Arguments.
/// </summary>
/// <param name="args">
/// The args to convert.
/// </param>
/// <returns>
/// The argument <see cref="string"/>.
/// </returns>
public static string EscapeArguments(params string[] args)
{
    var argEnumerator = args.GetEnumerator();
    var arguments = new StringBuilder();

    if (!argEnumerator.MoveNext())
    {
        return string.Empty;
    }

    arguments.Append(EscapeArguments((string)argEnumerator.Current));

    while (argEnumerator.MoveNext())
    {
        arguments.Append(' ');
        arguments.Append(EscapeArguments((string)argEnumerator.Current));
    }

    return arguments.ToString();
}

Test Cases

Here are the test cases I used in verifying the above code (the harness is left as an exercise for the reader)

NOTE: My test case was to take a random number of the below cases as an input args array, encode it into an argument string, pass the string to a new process which output the arguments as a JSON array, and verify that the input args array matches the output JSON array.

+---------------------------------------+--------------------------------------------+
| Input String                          | Escaped String                             |
+---------------------------------------+--------------------------------------------+
| quoted argument                       | "quoted argument"                          |
| "quote                                | \"quote                                    |
| "wrappedQuote"                        | \"wrappedQuote\"                           |
| "quoted wrapped quote"                | "\"quoted wrapped quote\""                 |
| \backslashLiteral                     | \backslashLiteral                          |
| \\doubleBackslashLiteral              | \\doubleBackslashLiteral                   |
| trailingBackslash\                    | trailingBackslash\                         |
| doubleTrailingBackslash\\             | doubleTrailingBackslash\\                  |
| \ quoted backslash literal            | "\ quoted backslash literal"               |
| \\ quoted double backslash literal    | "\\ quoted double backslash literal"       |
| quoted trailing backslash\            | "quoted trailing backslash\\"              |
| quoted double trailing backslash\\    | "quoted double trailing backslash\\\\"     |
| \"\backslashQuoteEscaping             | "\\\"\backslashQuoteEscaping "             |
| \\"\doubleBackslashQuoteEscaping      | "\\\\\"\doubleBackslashQuoteEscaping "     |
| \\"\\doubleBackslashQuoteEscaping     | "\\\\\"\\doubleBackslashQuoteEscaping "    |
| \"\\doubleBackslashQuoteEscaping      | "\\\"\\doubleBackslashQuoteEscaping "      |
| \"\backslash quote escaping           | "\\\"\backslash quote escaping "           |
| \\"\double backslash quote escaping   | "\\\\\"\double backslash quote escaping "  |
| \\"\\double backslash quote escaping  | "\\\\\"\\double backslash quote escaping " |
| \"\\double backslash quote escaping   | "\\\"\\double backslash quote escaping "   |
| TrailingQuoteEscaping"                | TrailingQuoteEscaping\"                    |
| TrailingQuoteEscaping\"               | TrailingQuoteEscaping\\\"                  |
| TrailingQuoteEscaping\"\              | TrailingQuoteEscaping\\\"\                 |
| TrailingQuoteEscaping"\               | TrailingQuoteEscaping\"\                   |
| Trailing Quote Escaping"              | "Trailing Quote Escaping\""                |
| Trailing Quote Escaping\"             | "Trailing Quote Escaping\\\""              |
| Trailing Quote Escaping\"\            | "Trailing Quote Escaping\\\"\\"            |
| Trailing Quote Escaping"\             | "Trailing Quote Escaping\"\\"              |
+---------------------------------------+--------------------------------------------+

There are other answers to this question here on SO. I simply prefer a coded state machine to regular expressions (also, it runs faster). https://stackoverflow.com/a/6040946/3591916 has a good explanation of how to do it.

Community
  • 1
  • 1
Paul
  • 1,011
  • 10
  • 13
  • 1
    Shame on Microsoft that such a security sensitive operation is so badly documented, with no official example code let alone an API or framework util for us to call. Thanks for this answer. – Day Mar 07 '17 at 22:19