How to prevent exit of host and return exit code?

梦想与她 提交于 2020-04-10 05:18:07

问题


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 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]
* 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!