Search code examples
c#powershellwindowquotes

Why are there two pairs of quotes for PowerShell arguments?


I was building a Windows Service that needs to periodically execute PowerShell scripts (*.ps1 -Option Value etc) via C#'s Process and I found this answer here about how to do this, but the example there uses two pairs of quotes (first double " then single ') for the Arguments which is quite confusing:

process.StartInfo.Arguments = "\"&'"+strCmdText+"'\"";

Are they both really necessary and if yes then why?


Solution

  • To offer a more PowerShell-focused answer:

    the example there uses two pairs of quotes (first double " then single ') for the Arguments (...) Are they both really necessary and if yes then why?

    • The quoting requirements depend on whether the -File or the -Command parameter of the PowerShell CLI (powershell.exe for Windows PowerShell, pwsh for PowerShell (Core) 7+) is used.

      • In the absence of either parameter, powershell.exe assumes -Command, pwsh.exe assumes -File.

      • -Command allows you to pass arbitrary PowerShell commands, with the code optionally spread across multiple arguments.

        • Unescaped " characters are stripped during the initial command-line parsing, but ' characters are passed through as-is.
        • Preserving " as part of the PowerShell code to execute require escaping them as \" (sic; even though PowerShell-internally `" must be used).
      • -File expects the path of a script file (*.ps1) to execute, with any additional arguments being passed as literal values to that script.

        • Only " quoting can be used with syntactic function - any ' characters are used verbatim.
    • Fundamentally - with both -File and -Command - a script-file path only needs quoting if it contains metacharacters, notably spaces.

    • When quoting is needed, the approaches differ depending on -Command vs. -File:

    • -File:

      • Use embedded "..." quoting around the file path (and around any pass-through arguments that need quoting) - escaped as ""..."" for the sake of embedding in the C# verbatim string:

        var scriptPath = @"C:\path with spaces\script.ps1";
        process.StartInfo.Arguments = $@"-File ""{scriptPath}""";
        
    • -Command:

      • While embedded '...' quoting, as in the question, could be used, doing so isn't fully robust, unless extra action is taken: ' is a legal character in file names, so a path containing ' would break the command (which could be avoided with extra effort; see the edge case below).

      • Using embedded "..." quoting avoids this problem, given that " isn't valid in file names (at least on Windows). For the reasons stated above, this requires \-escaping the " characters (spaces added for readability):

         var scriptPath = @"C:\path with spaces\script.ps1";
         process.StartInfo.Arguments = $@" -Command "" & \""{scriptPath}\"" "" ";
        
      • Note:

        • If the only operation you're performing is calling a script with literal arguments, there is no reason to use -Command - use -File instead.

        • It is a fundamental syntax requirement in PowerShell (which does't apply to -File) that a quoted command must be called via &, the call operator.

        • The code to execute is passed as a single argument, enclosed in "..." to -Command. This isn't strictly necessary in an invocation that doesn't involve a shell, but avoids whitespace normalization that would result from PowerShell "stitching" multiple arguments back together to form the code to execute.

        • There's a - largely hypothetical - edge case (which does not affect the -File parameter): If you had file paths that contain verbatim tokens such as $foo, they would be subject to - unwanted - string interpolation by PowerShell; in that case, embedded '...' quoting must be used instead, but with any embedded ' chars. escaped as '':

          var scriptPath = @"C:\path with spaces\script.ps1";
          process.StartInfo.Arguments = $@" -Command "" & '{scriptPath.Replace("'", "''")}' "" ";