Search code examples
powershellshellcmd

Pass multi variable command line to cmd.exe with PowerShell Start-Process


i am trying to pass cmd.exe multi variable command line the code is running in PowerShell script as part of larger script .

the code below fails to pass the correct parameter's.

$msbuild = "C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\msbuild.exe"
$buildCommand = 'C:\Git\Asd\build\XDA_2019.sln /m /t:Build /p:Configuration=Debug /p:Platform="ASX Platform"'
Write-Host "buildCommand: $buildCommand"
Write-Host "msbuild: $msbuild"

$startProcessArgs = @('/c', $msbuild, $buildCommand)
Start-Process -FilePath cmd.exe -ArgumentList $startProcessArgs -PassThru -Wait -NoNewWindow   

Error

buildCommand: C:\CamtekGit\BIS\build\Falcon_2019.sln /m /t:Build /p:Configuration=Debug /p:Platform="Eagle Platform"
msbuild: C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\msbuild.exe
'C:\Program' is not recognized as an internal or external command,
operable program or batch file.

tried running the command direct in cmd and it works cmd.exe also read through and could not fix it Powershell: Start-Process doesn't pass arguments to cmd.exe Running cmd.exe through start-process but unable to pass the command to cmd.exe

Thanks for any help


Solution

  • To synchronously execute console applications or batch files in the current window, call them directly, do not use Start-Process (or the System.Diagnostics.Process API it is based on) - see this answer.
    GitHub docs issue #6239 provides guidance on when use of Start-Process is and isn't appropriate.

    Direct execution not also allows you to directly capture an external program's output, it also reflects its process exit code in the automatic $LASTEXITCODE variable afterwards.

    Therefore:

    cmd /c "`"$msbuild`" $buildCommand"
    

    Note the need to use embedded double quotes around the executable path in the command line passed to cmd.exe, given that it contains spaces.

    Their effective absence is also was caused the problem in your Start-Process call:

    • A long-standing bug in Start-Process unfortunately requires use of embedded double-quoting around arguments that contain spaces, e.g. -ArgumentList '-foo', '"bar baz"'.

    • It is therefore generally better to encode all arguments in a single string, e.g. -ArgumentList '-foo "bar baz"', because the situational need to use embedded double-quoting is then obvious. See this answer for details.

    In your specific case, you could make your Start-Process call work as follows:

    $startProcessArgs = "/c `"`"$msbuild`" $buildCommand`""
    

    Note: That this works - despite the lack of escaping of the doubly nested " chars. - is a testament to cmd.exe's "quirks" when it comes to quoting.


    Taking a step back:

    • msbuild.exe, as any external program, can also be invoked directly from PowerShell. However, behind the scenes PowerShell transforms arguments such as /p:Platform="ASX Platform" to "/p:Platform=ASX Platform", which msbuild.exe may not understand.

    • There are several workarounds, with the call via cmd /c being the conceptually cleanest.

      • Another one is use of --%, the stop-parsing token, but it comes with severe limitations:

        & "C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\msbuild.exe" C:\Git\Asd\build\XDA_2019.sln /m /t:Build /p:Configuration=Debug --% /p:Platform="ASX Platform"
        
      • Another one - limited to Windows PowerShell - is use to use embedded double quotes, which, however, relies on PowerShell's fundamentally broken handling of embedded double quotes in external-program arguments:

        & "C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\msbuild.exe" C:\Git\Asd\build\XDA_2019.sln /m /t:Build /p:Configuration=Debug '/p:Platform="ASX Platform"'
        
    • Another one - with all the disadvantages of Start-Process use - is to use Start-Process to call msbuild.exe directly (not via cmd.exe), which in your case would simplify your call to:

      Start-Process -FilePath $msbuild -ArgumentList $buildCommand -PassThru -Wait -NoNewWindow