PowerShell 和进程退出代码

mkl*_*nt0 16 error-handling powershell exit-code

这个自我回答的问题试图解决在 PowerShell中处理进程退出代码的两个不同方面:

  • 在 PowerShell 代码中,如何查询外部进程设置的退出代码(对外部程序的调用),这些退出代码如何与 PowerShell 的错误处理相结合?

  • 当其他人通过其 CLI pwsh(PowerShell Core)/ powershell.exe(Windows PowerShell)调用 PowerShell 时,什么决定了 PowerShell 进程的退出代码,该代码将成功与失败传达给调用进程(可能是构建/CI/自动化服务器任务) ,计划任务或不同的外壳,例如)。

mkl*_*nt0 23

当前 PowerShell [核心] 7.1.0-preview.6。

PowerShell-退出代码的内部使用:

PowerShell- 在内部,本地 PowerShell 命令通常进程内运行,运行外部程序的子进程的退出代码发挥的作用非常有限

  • 本机 PowerShell 命令通常不会设置退出代码,也不会对它们进行操作。

  • PowerShell 有一个抽象对应的退出代码自动的布尔成功状态变量$?

  • 它反映了最近执行的命令是否有任何错误,但在实践中很少使用,这不仅是因为-高达6.x版本-这是因为看似无关紧要的封闭一个命令(...)重置$?$true-看GitHub的问题#3359 -和因为Write-Error在用户函数中使用未设置$?$false- 参见GitHub 问题 #3629;但是,最终为用户代码提供$?显式设置的能力已在未来版本中获得批准。

  • 虽然$?也反映(紧接着)外部程序报告的退出代码是0(发出信号成功,制作$?报告$true)还是非零退出代码(通常发出信号失败,制作$? $false),但它是包含特定退出代码作为整数自动$LASTEXICODE变量,并且该值将一直保留,直到在同一会话中调用另一个外部程序(如果有)。

    • 警告:由于cmd.exe的怪癖,不能可靠地报告批处理文件的退出代码,但您可以解决这个问题- 请参阅此答案GitHub 问题 #15143还建议将此解决方法构建到 PowerShell 本身中。cmd /c <batch-file> ... `& exit
    • 此外,从 v7.1 开始,$?如果外部程序报告退出代码,0同时还产生 stderr 输出,并且还有涉及2>or的 PowerShell 重定向,则可以报​​告假阴性*>- 请参阅此答案GitHub 问题 #3996;从 PowerShell PowerShell Core 7.2.0-preview.4 开始;更正后的行为是一个可用的实验功能 PSNotApplyErrorActionToStderr
  • 与 PowerShell 本地命令报告的终止错误或非终止错误不同,来自外部程序的非零退出代码不能$ErrorActionPreference首选项变量自动处理;也就是说,您不能使用该变量来使外部程序的 stderr 输出静音,更重要的是,当外部程序报告非零退出代码时,您也不能选择通过值中止脚本'Stop'

    • 此 RFC中建议将外部程序更好地集成到 PowerShell 的错误处理中。

从外部调用时,如何控制 PowerShell 报告退出代码:

设置至少传达成功 ( 0) 与失败 (非零,通常)的退出代码是一种重要机制,可让外部调用者知道您的 PowerShell 代码是否总体成功,例如从计划任务或自动化调用时服务器,例如 Jenkins 通过PowerShell CLI(命令行界面) -pwsh适用于 PowerShell [Core] 与powershell.exe适用于 Windows PowerShell。

CLI 提供了两种执行 PowerShell 代码的方法,您可以使用它exit <n>来设置退出代码,其中<n>是所需的退出代码:

  • -File <script> [args...]期望执行脚本文件( *.ps1)的路径,可选后跟参数。

    • exit <n> 直接在这样的脚本文件中执行(而不是在您该脚本调用的另一个脚本中)会使 PowerShell 进程将其退出代码报告为<n>.
  • -Command <powershell-code> 需要一个包含一个或多个 PowerShell 命令的字符串。

    • 为安全起见,将其exit <n>用作该命令字符串的直接部分 - 通常用作最后一个语句。

如果您的代码是从通过退出代码检查成功的工具调用的,请确保所有代码路径都明确用于exit <n>终止。

警告:如果 PowerShell 进程由于未处理的脚本终止错误终止- 无论 CLI 是否使用-File或调用-Command退出代码始终为1

  • 要么从与PowerShell代码生成的脚本终止(致命的)错误throw声明或通过升级不太严重的天然的PowerShell误差
    -ErrorAction Stop$ErrorActionPreference = 'Stop',或通过按Ctrl-C要强行终止的脚本。

  • 如果退出代码1不够具体(通常,因为通常只需要传达成功与失败的信息),您可以将代码包装在try/catch语句中并exit <n>catch块中使用

PowerShell 如何设置其进程退出代码的确切规则很复杂;在下面找到一个摘要。


PowerShell 如何设置其进程退出代码:

  • 如果发生未处理的脚本终止错误,则退出代码始终为1

  • 使用-File,执行脚本文件( *.ps1):

    • 如果脚本直接执行exit <n><n>成为退出代码(在这样的语句嵌套调用是不是有效)。

    • 否则,0即使在脚本执行期间发生非终止或语句终止错误也是如此

  • 使用-Command,执行包含一个或多个语句的命令字符串:

    • 如果一个exit <n>语句被执行,直接在命令字符串(通常是通过报表的一个最后的陈述),<n>成为退出代码。

    • 否则,由 所暗示最后一条语句$?成功状态决定了退出代码:

      • 如果$?是:

        • $true -> 退出代码 0
        • $false-> 退出代码1- 即使在最后执行的语句是报告不同非零退出代码的外部程序的情况下。
      • 鉴于您的命令字符串中的最后一条语句可能不是您想要发出成功与失败信号的语句,请显式使用exit <n>以可靠地控制退出代码,它还允许您报告特定的非零退出代码。

        • 例如,要忠实地传递外部程序报告的退出代码,请将其附加; exit $LASTEXITCODE到您传递给-Command.

自 PowerShell 7.0 起的不一致和陷阱:

  • 可以说,-Command( -c) 应该报告最后一条语句的特定退出代码 - 如果它有一个 - 而不是抽象01. 例如,pwsh -c 'findstr'; $LASTEXITCODE应报告2,findstr.exe的特定退出代码,而不是摘要1 - 请参阅GitHub 问题 #13501

  • 带有*.ps1文件/ -FileCLI 参数的退出代码报告:

    • 它只是一个有意义地设置退出代码的显式exit <n>语句;相反,它应该再次是确定退出代码的脚本中执行的最后一条语句(当然,它可以是一个exit语句),就像在 POSIX 兼容的 shell 和 with 中的情况一样-Command,尽管以次优的方式讨论。

    • 当您*.ps1通过-File或 作为最后一条语句 via调用脚本时-Command,在没有脚本通过exit语句退出的情况下,PowerShell 的退出代码始终为0(除非在例外Ctrl-C/throw情况下,它变为1)。

    • 相比之下,当在会话中调用时,同样在没有 的情况下exit$LASTEXICODE反映了最后执行的任何外部程序(或其他,*.ps1 如果它设置了退出代码)的退出代码 - 无论是在脚本内部还是之前执行

    • 换句话说:

      • With-File与 with 不同-Command,退出代码0在没有exit语句的情况下被明确设置为(除非异常终止)。
      • 在会话中,在没有语句的情况下,$LASTEXITCODE根本不会为整个脚本设置退出代码(如 中所示)exit
    • 请参阅GitHub 问题 #11712