Pass arguments to PowerShell with -File instead of -command

Merged
NO
2mo ago
+31-7

The .cmd wrappers spliced %* into a powershell -command string, so anything the caller passed was parsed by PowerShell as source code instead of being treated as data.

A semicolon inside an argument therefore became a statement separator. This:

test.cmd -projects "test\A\A.csproj;test\B\B.csproj"

ran Build.ps1 -projects test\A\A.csproj and then executed the remaining .csproj paths as their own statements. On Windows .csproj is file-associated with Visual Studio, so every entry after the first opened an IDE. Three agents ran that form at the same time and opened eighteen instances of Visual Studio, which is how I found this. The semicolon separated list is not user error, Arcade's own help text documents -projects as "Semi-colon delimited list of sln/proj's to build".

-command also collapsed every non-zero exit code to 1, so the exit $LastExitCode at the end of eng/build.ps1 never forwarded the real code, including the 8 that means the filter matched no tests.

Changes

Switch the repo owned wrappers to -File, which passes everything after the script path as a literal argument:

  • build.cmd
  • test.cmd
  • restore.cmd
  • open-vs.cmd
  • open-code.cmd
  • eng/RestoreInternal.cmd

eng/common/* is left alone, those files come from Arcade and would have to be fixed there.

Because arguments are no longer re-parsed, MSBuild properties need one level of quoting instead of two, so the documented single test command changes from /p:TestRunnerAdditionalArguments="'--filter TestName'" to /p:TestRunnerAdditionalArguments="--filter TestName". Both deliver the same value to the script. AGENTS.md is updated, and gained a short section explaining why the wrappers use -File.

Verification

CheckBeforeAfter
-projects "a.csproj;<statement>"trailing entries executed, Visual Studio openedpassed through as a single string
exit code 818
exit code 42142
/p:X="--filter Name"needed the doubled "'...'" formsame value, single level of quoting

build.cmd succeeds locally with 0 errors and returns 0. test.cmd -help -projects "a.csproj;Write-Host INJECTED" prints usage, returns 0, does not run the injected statement, and starts no Visual Studio.

šŸ¤–