powershellbatch-file

Preserving quotes in Batch file %*


I want to forward %* to a powershell script from a Batch script, which already sort of works. The only problem is that I can't forward arguments with spaces cleanly.

Example.bat..

@echo off

set cwd=%~dp0
set argv=%*
powershell -NoProfile -ExecutionPolicy Bypass -Command "& '%cwd%Example.ps1' %argv%"

Example.ps1..

Param(
  $Message
)
If (!$Message)
{
  $Message = "Hello!!!"
}
Write-Host $Message

I can do ./Example.bat Oh just fine, which prints Oh. However, ./Example.bat "Oh no" only prints Oh as well. ./Example "'Oh no'" works but is not clean, as then the usage of the scripts isn't identical anymore, which means any use of it becomes tightly coupled, which I don't want.


Solution

  • Fabrice SANGA's helpful answer explains the need to escape the " chars. embedded %* well and offers a solution that will typically work, using \" escaping.

    However, it isn't robust, because it breaks cmd.exe's own parsing with "..."-enclosed arguments that contain cmd.exe metacharacters such as & or |, e.g. "you & I"

    Update: The linked answer has since been updated with an alternative solution that attempts to provide a robust solution, but the one below is simpler.

    A robust solution requires a different - obscure - form of escaping embedded " chars., namely as "^"" [sic] rather than as \"


    Here's a robust, streamlined solution that also uses this form of escaping to enclose the target *.ps1 path, so as to also handle paths with embedded ' chars. correctly.
    Also note the use of %~dpn0.ps1, which obviates the need to repeat the base file name, Example:

    @echo off & setlocal
    
    :: Escape any " chars. embedded in %* as "^""
    set argv=%*
    if defined argv set argv=%argv:"="^""%
    
    powershell -NoProfile -ExecutionPolicy Bypass -Command "& "^""%~dpn0.ps1"^"" %argv%"
    

    setlocal is used to ensure that the variables defined in the batch file are scoped to that file; by default, they would be defined session-globally.

    Tip of the hat to João Mac's answer for the reminder of the if defined somevar technique as a variable emptiness/non-existence test, which is simpler than if not %somevar%.==.


    Taking a step back:

    If your intent is solely to call a *.ps1 file with arguments, it is better to use the -File (-f) CLI parameter rather than -Command (-c)

    Doing so has two advantages:

    @echo off & setlocal
    
    powershell -NoProfile -ExecutionPolicy Bypass -File "%~dpn0.ps1" %*
    

    For detailed guidance on when to use -File vs. -Command, see this answer.