如何防止主机退出并返回退出码?

lit*_*lit 5 powershell exit-code

每当启动新的 PowerShell 进程时,Ansgar Wiechers 的答案都很有效。/sf/answers/3514186441/这适用于 cmd.exe 和 powershell.exe。

C:>type .\exit1.ps1
function ExitWithCode($exitcode) {
  $host.SetShouldExit($exitcode)
  exit $exitcode
}
ExitWithCode 23
Run Code Online (Sandbox Code Playgroud)

在 cmd.exe 交互式 shell 中。

C:>powershell -NoProfile -Command .\exit1.ps1
C:>echo %ERRORLEVEL%
23
C:>powershell -NoProfile -File .\exit1.ps1
C:>echo %ERRORLEVEL%
23
Run Code Online (Sandbox Code Playgroud)

在 PowerShell 交互式 shell 中。

PS C:>powershell -NoProfile -Command .\exit1.ps1
PS C:>$LASTEXITCODE
23
PS C:>powershell -NoProfile -File .\exit1.ps1
PS C:>$LASTEXITCODE
23
Run Code Online (Sandbox Code Playgroud)

但是...在现有交互式 PowerShell 主机内运行 .ps1 脚本将完全退出主机。

PS C:>.\exit1.ps1
    <<<poof! gone! outahere!>>>
Run Code Online (Sandbox Code Playgroud)

如何阻止它退出主机 shell?

mkl*_*nt0 3

不要使用$host.SetShouldExit():它不应该由用户代码调用。相反,它由 PowerShell在内部使用,以响应exit用户代码中的语句

exit 23只需直接在您的脚本中使用exit1.ps1,它将执行您想要的操作:

  • 当在 PowerShell 会话中运行时,脚本将设置退出代码23,而不退出整个 PowerShell 进程;之后用于$LASTEXITCODE查询它。

      .\exit.ps1; $LASTEXITCODE # -> 23
    
    Run Code Online (Sandbox Code Playgroud)
  • 通过PowerShell CLI运行时:

  • 使用 时-File,脚本设置的退出代码会自动成为 PowerShell 进程的退出代码,调用者可以检查该退出代码;当从 调用时cmd.exe%ERRORLEVEL%反映该退出代码。

         powershell -File .\exit.ps1
         :: This outputs 23
         echo %ERRORLEVEL%
    
    Run Code Online (Sandbox Code Playgroud)
  • 使用 时-Command,需要进行额外的工作,因为 PowerShell 只是将任何非零退出代码映射到1,这会导致特定的退出代码丢失;为了弥补这一点,只需执行exit $LASTEXITCODE最后一条语句

         powershell -Command '.\exit.ps1; exit $LASTEXITCODE'
         :: This outputs 23
         echo %ERRORLEVEL%
    
    Run Code Online (Sandbox Code Playgroud)

有关PowerShell 如何设置退出代码的更多信息请参阅此答案


如果

  • 您无法控制如何通过 CLI 调用脚本,但必须确保即使通过以下方式调用脚本也能报告正确的退出代码-Command

  • 并且您愿意承担使用的风险$host.SetShouldExit(),即使它不是为直接使用而设计的,

您可以尝试以下操作:

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
Run Code Online (Sandbox Code Playgroud)

函数在进程命令行上查找执行脚本的文件名,以检测是否通过 CLI 直接调用包含脚本的自动$PSCommandPath变量(其中包含脚本的完整路径)。

如果是这样,则$host.SetShouldExit()应用该调用以确保退出代码按预期设置,即使在通过 调用的情况下也是如此-Command
请注意,这相当于有效内部方法的重新利用。 令人惊讶的是,即使在字符串内的脚本调用之后出现了其他命令,这种重新调整用途也会起作用,但请注意,这总是意味着真正的最后一个命令的成功状态(如果不是脚本调用)将被有效地忽略。 .SetShouldExit()
-Command

这种方法并非万无一失[1],但在实践中可能效果很好。


[1]

  • 考虑到仅查找文件而不查找扩展名(因为允许省略正在调用的脚本的扩展名),可能会出现误报。-Command.ps1
  • 如果通过另一个脚本或别名调用该脚本,则可能会出现漏报。