Ansgar Wiechers' answer works well whenever starting a new PowerShell process. https://stackoverflow.com/a/50202663/447901 This works in both cmd.exe and powershell.exe.
C:>type .\exit1.ps1
function ExitWithCode($exitcode) {
$host.SetShouldExit($exitcode)
exit $exitcode
}
ExitWithCode 23
In a cmd.exe interactive shell.
C:>powershell -NoProfile -Command .\exit1.ps1
C:>echo %ERRORLEVEL%
23
C:>powershell -NoProfile -File .\exit1.ps1
C:>echo %ERRORLEVEL%
23
In a PowerShell interactive shell.
PS C:>powershell -NoProfile -Command .\exit1.ps1
PS C:>$LASTEXITCODE
23
PS C:>powershell -NoProfile -File .\exit1.ps1
PS C:>$LASTEXITCODE
23
HOWEVER... Running the .ps1 script inside an existing interactive PowerShell host will exit the host completely.
PS C:>.\exit1.ps1
<<<poof! gone! outahere!>>>
How can I prevent it from exiting the host shell?
Do not use $host.SetShouldExit()
: it is not meant to be called by user code.
Instead, it is used internally by PowerShell in response to an exit
statement in user code.
Simply use exit 23
directly in your exit1.ps1
script, which will do what you want:
When run inside a PowerShell session, the script will set exit code 23
without exiting the PowerShell process as a whole; use $LASTEXITCODE
to query it afterwards.
.\exit.ps1; $LASTEXITCODE # -> 23
When run via the PowerShell CLI:
with -File
, the exit code set by the script automatically becomes the PowerShell process' exit code, which the caller can examine; when called from cmd.exe
, %ERRORLEVEL%
reflects that exit code.
powershell -File .\exit.ps1
:: This outputs 23
echo %ERRORLEVEL%
with -Command
, additional work is needed, because PowerShell then simply maps any nonzero exit code to 1
, which causes the specific exit code to be lost; to compensate for that, simply execute exit $LASTEXITCODE
as the last statement:
powershell -Command '.\exit.ps1; exit $LASTEXITCODE'
:: This outputs 23
echo %ERRORLEVEL%
For more information about how PowerShell sets exit codes, see this answer.
If:
you do not control how your script is invoked via the CLI, yet must ensure that the correct exit code is reported even when the script is invoked via -Command
,
and you're willing to assume the risk of using $host.SetShouldExit()
, even though it isn't designed for direct use,
you can try the following:
function ExitWithCode($exitcode) {
if ([Environment]::CommandLine -match ( # Called via the CLI? (-File or -Command)
' .*?\b' +
[regex]::Escape([IO.Path]::GetFileNameWithoutExtension($PSCommandPath)) +
'(?:\.ps1\b| |$)')
) {
# CAVEAT: While this sets the exit code as desired even with -Command,
# the process terminates instantly.
$host.SetShouldExit($exitcode)
}
else {
# Exit normally, which in interactive session exits the script only.
exit $exitcode
}
}
ExitWithCode 23
The function looks for the file name of the executing script on the process command line to detect whether the enclosing script is being invoked directly via the CLI, via the automatic $PSCommandPath
variable, which contains the script's full path.
If so, the $host.SetShouldExit()
call is applied to ensure that the exit code is set as intended even in the case of invocation via -Command
.
Note that this amounts to a repurposing of the effectively internal .SetShouldExit()
method.
Surprisingly, this repurposing works even if additional commands come after the script call inside the -Command
string, but note that this invariably means that the success status of the truly last command - if it isn't the script call - is then effectively ignored.
This approach isn't foolproof[1],but probably works well enough in practice.
[1]
-Command
allows omitting the .ps1
extension of scripts being called).