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.
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 \"
powershell.exe
, the Windows PowerShell CLI; with pwsh.exe
, the PowerShell (Core) 7 CLI, use ""
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:
It obviates the need for escaping and therefore greatly simplifies your batch file (see below).
It protects your arguments from potentially unwanted interpretation by PowerShell: e.g., with -File
, passing verbatim $HOME
as an argument is passed through as-is, whereas with -Command
it would be expanded as PowerShell's automatic $HOME
variable.
@echo off & setlocal
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dpn0.ps1" %*
For detailed guidance on when to use -File
vs. -Command
, see this answer.