Search code examples
powershell

Why does a space at the end of a pwsh command launch it as a separate window/process?


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

Solution

  • 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.