问题
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?
回答1:
How can I prevent it from exiting the host shell?
You can check if the currently running PowerShell process is a child of another PowerShell parent process, and only call $host.SetShouldExit()
when that condition is true. For example:
function ExitWithCode($exitcode) {
# Only exit this host process if it's a child of another PowerShell parent process...
$parentPID = (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$PID" | Select-Object -Property ParentProcessId).ParentProcessId
$parentProcName = (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$parentPID" | Select-Object -Property Name).Name
if ('powershell.exe' -eq $parentProcName) { $host.SetShouldExit($exitcode) }
exit $exitcode
}
ExitWithCode 23
Hope this helps.
回答2:
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 fromcmd.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 to1
, which causes the specific exit code to be lost; to compensate for that, simply executeexit $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]
* There could be false positives, given that only the file name is looked for, without extension (because -Command
allows omitting the .ps1
extension of scripts being called).
* There could be false negatives, if the script is being called via another script.
来源:https://stackoverflow.com/questions/60311778/how-to-prevent-exit-of-host-and-return-exit-code