Our build system uses pwsh (Powershell 7.4.0) to execute a load of commands to build and test our software. We recently encountered a problem where something like the following example launched our test harness in a new window resulting in our tests appearing to pass instead of fail. Why is there a difference between these two and how could we harden things (other than removing the space)?
This one silently failes as it appear to launch the test
PS C:\Temp>& "${Env:NUNIT_LOCATION}\nunit-console-x86.exe " CodeUnderTest.dll
PS C:\Temp>$LastExitCode
0
This one works as expected and reports failures on the command line (and modifies the return code):
PS C:\Temp>& "${Env:NUNIT_LOCATION}\nunit-console-x86.exe" CodeUnderTest.dll
<test error outputs here>
PS C:\Temp>$LastExitCode
35
You're seeing what is arguably a bug, present in both Windows PowerShell (the legacy, ships-with-Windows, Windows-only edition of PowerShell whose latest and last version is 5.1) and PowerShell (Core) 7 (the modern, cross-platform, install-on-demand edition) as of 7.5.0:[1]
With the trailing space in the path, PowerShell doesn't recognize the executable as an executable (application).
Instead, it treats it like a document and therefore performs the equivalent of a Start-Process
call[2] in order to delegate the invocation to the Windows (GUI) shell, which has two implications:
The console application you're trying to launch opens in a new console window that automatically closes when the application exits.
It launches asynchronously, and therefore its exit code is not captured in the automatic $LASTEXITCODE
variable, so whatever $LASTEXITCODE
was previously in effect remains in effect.
how could we harden things
Short of writing a generic wrapper function that trims trailing spaces from the executable path, you can perform trimming ad hoc; e.g.:
$exePath = "${Env:NUNIT_LOCATION}\nunit-console-x86.exe "
& $exePath.Trim() CodeUnderTest.dll
[1] The bug has been reported in GitHub issue #24990.
[2] Start-Process
can be made to act synchronously with the -Wait
switch, and the -PassThru
switch can be used to make the call output a System.Diagnostics.Process
instance whose .ExitCode
property can then be queried, but note that, except in unusual scenarios, there is no good reason to use Start-Process
for executing console applications, not least because you won't be able to directly capture their output. See this answer for more information.