PowerShell 和进程退出代码

PowerShell and process exit codes

这个自我回答的问题试图解决在 PowerShell 中处理 process exit codes 的两个不同方面:

PowerShell [Core] 7.2.1 的当前版本

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

PowerShell-internally,其中本机 PowerShell 命令通常 运行 in-process,退出代码子进程 运行 外部程序 发挥非常有限的作用:

  • 本机 PowerShell 命令通常不设置退出代码,也不对其执行操作。

  • PowerShell 有一个 抽象副本 退出代码 : $?, 自动布尔成功状态变量:

  • 它反映了最近执行的命令是否有任何错误,但在实践中它很少被使用,尤其是因为 - 直到版本 6.x - 一些看似无关紧要的东西(...) 中的命令将 $? 重置为 $true - 请参阅 GitHub issue #3359 - and because using Write-Error in user functions doesn't set $? to $false - see GitHub issue #3629; however, eventually providing the ability for user code to set $? explicitly has been green-lit 了解未来版本。

  • 虽然 $? 也反映(紧接着)外部程序 是否报告了 0 的退出代码(信号成功, making $? 报告 $true) 或非零退出代码(通常表示失败,making $? $false),它是 自动 $LASTEXICODE variable 包含 specific 退出代码作为 integer,并且该值一直保留到另一个外部程序(如果有的话)在同一会话中调用。

    • 警告:由于cmd.exe的怪癖,批处理文件的退出代码不是' t reliably 报告,但您可以使用 cmd /c <batch-file> ... `& exit 解决这个问题 - 请参阅 ; GitHub issue #15143 另外建议将此解决方法构建到 PowerShell 本身。

    • 此外,到 v7.1,如果外部程序报告退出代码 0$? 可以报告漏报 而还产生 stderr 输出,还有涉及 2>*> 的 PowerShell 重定向 - 请参阅 and GitHub issue #3996; as of PowerShell PowerShell Core 7.2.0-preview.4; the corrected behavior is a available as experimental feature PSNotApplyErrorActionToStderr.

    • 最后,最好将$LASTEXITCODE视为只读并且只让PowerShell本身设置它。 (从技术上讲,该变量是可写的,并且位于 global 范围内,所以如果您确实要手动修改,毕竟一定要分配给 $global:LASTEXITCODE,以免不小心创建了一个无效的临时本地副本。)

  • 与 PowerShell 本地命令报告的终止错误或非终止错误不同,来自外部程序的非零退出代码可以不会自动由 $ErrorActionPreference 偏好变量 采取行动;也就是说,您不能使用该变量使外部程序的 stderr 输出静音,更重要的是,当外部程序报告非零退出代码。

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

当从外部调用时,如何控制 PowerShell 报告为退出代码的内容:

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

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

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

    • 直接在这样的脚本文件中执行exit <n> (而不是在您调用from[=331= 的另一个脚本中) ] 该脚本)使 PowerShell 进程将其退出代码报告为 <n>.

    • 如果给定脚本文件隐式或仅exit(没有退出代码参数)退出,退出代码0 报道。

  • -Command <powershell-code> 需要一个包含一个或多个 PowerShell 命令的字符串。

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

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

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

  • PowerShell 代码使用 throw 语句生成脚本终止(致命)错误,或者使用
    [= 升级不太严重的本机 PowerShell 错误54=] 或 $ErrorActionPreference = 'Stop',或按 Ctrl-C 强制终止脚本。

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

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


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

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

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

    • 如果脚本直接执行exit <n><n>成为退出码(嵌套调用中的此类语句无效)。

    • 否则就是0,即使在脚本执行过程中发生非终止或语句终止错误.

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

    • 如果执行exit <n>语句直接作为命令中传递的语句之一字符串(通常是 last 语句),<n> 成为退出代码。

    • 否则就是最后语句执行的成功状态,如$?,确定退出代码:

      • 如果$?是:

        • $true -> 退出代码 0
        • $false -> 退出代码 1 - 即使最后执行的语句是报告 不同 非零退出代码的外部程序.
      • 鉴于您的命令字符串中的 last 语句可能不是您想要发出成功与失败信号的语句,使用exit <n> 明确可靠地控制退出代码,这也允许您报告特定 非零退出代码。

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

从 PowerShell 7.0 开始的不一致和陷阱:

  • 可以说,-Command (-c) 应该报告最后一条语句的 特定 退出代码 - 前提是它有一个 -而不是抽象的 01。例如,pwsh -c 'findstr'; $LASTEXITCODE 应该报告 2findstr.exe 的具体退出代码,而不是抽象的 1 - 参见 GitHub issue #13501.

  • 使用 *.ps1 文件/-File CLI 参数的退出代码报告:

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

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

    • 相比之下,当调用in-session时,再次在没有exit的情况下,$LASTEXICODE反映退出代码最后执行的任何外部程序(或其他 *.ps1 if 它设置了退出代码) - 无论是在脚本内部执行还是在 before .

    • 换句话说:

      • -File 不同,与 -Command 不同,在没有 exit 语句(除非异常终止)的情况下,退出代码明确设置为 0
      • 在会话中,退出代码(如 $LASTEXITCODE 中所反映)根本未设置 整个脚本 缺少 exit 语句。
    • 参见GitHub issue #11712