Pass arguments to PowerShell with -File instead of -command
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.cmdtest.cmdrestore.cmdopen-vs.cmdopen-code.cmdeng/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
| Check | Before | After |
|---|---|---|
-projects "a.csproj;<statement>" | trailing entries executed, Visual Studio opened | passed through as a single string |
| exit code 8 | 1 | 8 |
| exit code 42 | 1 | 42 |
/p:X="--filter Name" | needed the doubled "'...'" form | same 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.
š¤