Search code examples
powershellbatch-filecmdescapingquoting

Running a Powershell Start-Process command, with arguments containing spaces, from a batch script


I can't get this batch script to work, even though I tried a lot of things, I'm now stuck.

The intention is for the batch script to re-run itself elevated, using PowerShell and Start-Process, if the admin check fails.

As long as the passed argument(s), do not contain spaces it works fine. However I need to forward the original batch arguments, (file paths), and can't figure out how to handle the case where they contain spaces.

Here is a small example of what I'm trying to achieve, in this example the echo doesn't correctly print out Arg 1 and Arg 2:

@echo off
setlocal enabledelayedexpansion

echo %1
echo %2

net session >nul 2>&1
if %ERRORLEVEL% neq 0 (
    echo Need to fix association but missing admin rights...
    echo Elevating privileges...

    REM Once it work, 'Arg 1' and 'Arg 2' will be replaced by %1 and %2 
    REM Which are gonna be path enclosed with double quote and containing space ie : "C:\Path to\A folder\"
    powershell -Command "$args = 'Arg 1', 'Arg 2'; Start-Process -Verb RunAs -FilePath '%~dpnx0' -ArgumentList $args"
    pause
    exit /b
)

pause
endlocal

Solution

  • There are multiple challenges:

    • When using the -Commmand (-c) parameter of powershell.exe, the Windows PowerShell CLI, any pass-through " characters, i.e. those that should be seen as part of the command for PowerShell to execute, must be escaped as \".

    • A long-standing bug in Start-Process requires that elements of an array passed to -ArgumentList that contain spaces be manually enclosed in embedded "..." in order to be passed correctly - see GitHub issue #5576

      • Note that the bug will not be fixed, so as not to break backward compatibility; a new parameter that effects the proper behavior has been proposed, but the discussion has stalled.

      • In light of the bug, it is usually conceptually clearer to use a single string with -ArgumentList that encodes all arguments, with embedded quoting as necessary, as shown below; see this answer for background information:

    • There are additional complications relating to batch files, specifically, which - in short - require calling your batch file indirectly, via cmd /c, plus an extra layer of embedded double-quoting.

    Therefore, use the following (note that I'm omitting enabledelayedexpansion, as it isn't necessary here, and can cause problems with what should be verbatim ! characters):

    @echo off
    setlocal
    
    net session >nul 2>&1 || (
        echo Need to fix association but missing admin rights...
        echo Elevating privileges...
    
        powershell -noprofile -Command $argStr = '\"%~1\" \"%~2\"'; Start-Process -Verb RunAs cmd.exe '/c', \"`\"`\"%~f0`\" $argStr`\"\"
        pause
        exit /b
    )
    
    echo Now running with elevation; arguments received:
    echo [%1]
    echo [%2]
    
    pause
    endlocal
    

    Note:

    • %~f0 is a shorter alternative to %~dpnx0 for obtaining the running batch file's full file path.

    • %~1 and %~2 ensures that enclosing double quotes, if present, are stripped from the values of %1 and %2, so that these values can be double-quoted predictably as part of the PowerShell command, with the required \-escaping.

    • The fact that '...' is used around the value assigned to $argStr assumes that the value itself, specifically the values of %~1 and %~2, do not contain '; handling this case would require a bit more work.