PowerShell 和进程退出代码 [英] PowerShell and process exit codes

查看:29
本文介绍了PowerShell 和进程退出代码的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

解决方案

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


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

设置一个退出代码,至少传达成功(0)与失败(非零,通常)是一个离开的重要机制调用者知道您的 PowerShell 代码是否整体成功,例如从计划任务或自动化服务器(如 Jenkins)通过PowerShell CLI(命令-线路接口) - pwsh PowerShell [Core] 与 powershell.exe 用于 Windows PowerShell.

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

  • -文件<脚本>[args...] 需要一个 脚本文件 (*.ps1) 执行的路径,后面可以选择参数.

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

    • 为了安全起见,将 exit 用作该命令字符串的直接部分 - 通常用作最后一条语句.

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

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

  • 脚本终止(致命)错误要么是使用 throw 语句从 PowerShell 代码生成的,要么是通过使用
    -ErrorAction 升级不太严重的本机 PowerShell 错误停止$ErrorActionPreference = 'Stop',或按 Ctrl-C 强制终止脚本.

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

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


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

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

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

    • 如果脚本直接执行exit 变成退出代码(嵌套调用中的此类语句无效).

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

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

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

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

      • 如果 $? 是:

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

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

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

  • 可以说,-Command (-c) 应该报告最后一条语句的特定退出代码——前提是它有一个- 而不是抽象的 01.例如, pwsh -c 'findstr';$LASTEXITCODE 应该报告2findstr.exe 的具体退出代码,而不是抽象的1 - 见GitHub 问题 #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 如果 设置了退出代码) - 无论是在脚本内部执行还是在 之前 执行.

    • 换句话说:

      • 使用-File,与-Command 不同,在没有退出的情况下,退出代码被明确设置为0 语句(除非异常终止).
      • 在会话中,退出代码(反映在 $LASTEXITCODE 中)根本没有设置对于脚本作为一个整体没有 exit 语句.
    • 请参阅 GitHub 问题 #11712.

This self-answered question tries to address two distinct aspects of dealing with process exit codes in PowerShell:

  • In PowerShell code, how can you query the exit code set by an external process (a call to an external program), and how do such exit codes integrate with PowerShell's error handling?

  • When someone else calls PowerShell via its CLI, pwsh (PowerShell Core) / powershell.exe (Windows PowerShell), what determines the PowerShell process' exit code that communicates success vs. failure to the calling process (which could be a build / CI / automation-server task, a scheduled task, or a different shell, for instance).

解决方案

Current as of PowerShell [Core] 7.1.0-preview.6.

PowerShell-internal use of exit codes:

PowerShell-internally, where native PowerShell commands generally run in-process, exit codes from child processes that run external programs play a very limited role:

  • Native PowerShell commands generally don't set exit codes and don't act on them.

  • PowerShell has an abstract counterpart to exit codes: the automatic, Boolean success-status variable $?:

  • It reflects whether the most recently executed command had any errors, but in practice it is rarely used, not least because - up to version 6.x - something as seemingly inconsequential as enclosing a command in (...) resets $? to $true - see 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 for a future version.

  • While $? also reflects (immediately afterwards) whether an external program reported an exit code of 0 (signaling success, making $? report $true) or a nonzero exit code (typically signaling failure, making $? $false), it is the automatic $LASTEXICODE variable that contains the specific exit code as an integer, and that value is retained until another external program, if any, is called in the same session.

    • Caveat: Due to cmd.exe's quirks, a batch file's exit code isn't reliably reported, but you can work around that with cmd /c <batch-file> ... `& exit - see this answer; GitHub issue #15143 additionally suggests building this workaround into PowerShell itself.
    • Also, up to and as of v7.1, $? can report false negatives if the external program reports exit code 0 while also producing stderr output and there is also a PowerShell redirection involving 2> or *> - see this answer and GitHub issue #3996; as of PowerShell PowerShell Core 7.2.0-preview.4; the corrected behavior is a available as experimental feature PSNotApplyErrorActionToStderr.
  • Unlike terminating errors or non-terminating errors reported by PowerShell-native commands, nonzero exit codes from external programs can not be automatically acted upon by the $ErrorActionPreference preference variable; that is, you cannot use that variable to silence stderr output from external programs nor can you, more importantly, choose to abort a script via value 'Stop' when an external program reports a nonzero exit code.

    • Better integration of external programs into PowerShell's error handling is being proposed in RFC #277.

How to control what PowerShell reports as its exit code when it is called from the outside:

Setting an exit code that at least communicates success (0) vs. failure (nonzero, typically) is an important mechanism for letting outside callers know whether your PowerShell code succeeded overall or not, such as when being called from a scheduled task or from an automation server such as Jenkins via the PowerShell CLI (command-line interface) - pwsh for PowerShell [Core] vs. powershell.exe for Windows PowerShell.

The CLI offers two ways to execute PowerShell code, and you can use exit <n> to set an exit code, where <n> is the desired exit code:

  • -File <script> [args...] expects the path of a script file (*.ps1) to execute, optionally followed by arguments.

    • Executing exit <n> directly inside such a script file (not inside another script that you call from that script) makes the PowerShell process report its exit code as <n>.
  • -Command <powershell-code> expects a string containing one or more PowerShell commands.

    • To be safe, use exit <n> as a direct part of that command string - typically, as the last statement.

If your code is called from tools that check success by exit code, make sure that all code paths explicitly use exit <n> to terminate.

Caveat: If the PowerShell process terminates due to an unhandled script-terminating error - irrespective of whether the CLI was invoked with -File or -Command - the exit code is always 1.

  • A script-terminating (fatal) error is either generated from PowerShell code with the throw statement or by escalating a less a severe native PowerShell error with
    -ErrorAction Stop or $ErrorActionPreference = 'Stop', or by pressing Ctrl-C to forcefully terminate a script.

  • If exit code 1 isn't specific enough (it usually is, because typically only success vs. failure needs to be communicated), you can wrap your code in a try / catch statement and use exit <n> from the catch block.

The exact rules for how PowerShell sets its process exit code are complex; find a summary below.


How PowerShell sets its process exit code:

  • If an unhandled script-terminating error occurs, the exit code is always 1.

  • With -File, executing a script file (*.ps1):

    • If the script directly executes exit <n>, <n> becomes the exit code (such statements in nested calls are not effective).

    • Otherwise, it is 0, even if non-terminating or statement-terminating errors occurred during script execution.

  • With -Command, executing a command string containing one or more statements:

    • If an exit <n> statement is executed directly as one of the statements passed in the command string (typically, the last statement), <n> becomes the exit code.

    • Otherwise, it is the success status of the last statement executed, as implied by $?, that determines the exit code:

      • If $? is:

        • $true -> exit code 0
        • $false -> exit code 1 - even in the case where the last executed statement was an external program that reported a different nonzero exit code.
      • Given that the last statement in your command string may not be the one whose success vs. failure you want to signal, use exit <n> explicitly to reliably control the exit code, which also allows you to report specific nonzero exit codes.

        • For instance, to faithfully relay the exit code reported by an external program, append ; exit $LASTEXITCODE to the string you pass to -Command.

Inconsistencies and pitfalls as of PowerShell 7.0:

  • Arguably, -Command (-c) should report the specific exit code of the last statement - provided it has one - instead of the abstract 0 vs. 1. For instance, pwsh -c 'findstr'; $LASTEXITCODE should report 2, findstr.exe's specific exit code, instead of the abstract 1 - see GitHub issue #13501.

  • Exit-code reporting with *.ps1 files / the -File CLI parameter:

    • It is only an explicit exit <n> statement that meaningfully sets an exit code; instead, it should again be the last statement executed in the script that determines the exit code (which, of course, could be an exit statement), as is the case in POSIX-compatible shells and with -Command, albeit in the suboptimal manner discussed.

    • When you call a *.ps1 script via -File or as the last statement via -Command, PowerShell's exit code in the absence of the script exiting via an exit statement is always 0 (except in the exceptional Ctrl-C / throw cases, where it becomes 1).

    • By contrast, when called in-session, again in the absence of exit, $LASTEXICODE reflects the exit code of whatever external program (or other *.ps1 if it set an exit code) was executed last - whether executed inside the script or even before.

    • In other words:

      • With -File, unlike with -Command, the exit code is categorically set to 0 in the absence of an exit statement (barring abnormal termination).
      • In-session, the exit code (as reflected in $LASTEXITCODE) is not set at all for the script as a whole in the absence of an exit statement.
    • See GitHub issue #11712.

这篇关于PowerShell 和进程退出代码的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆