当前位置:网站首页 > 更多 > 玩电脑 > 正文

[玩转系统] 实用PowerShell:错误处理

作者:精品下载站 日期:2024-12-14 04:14:44 浏览:14 分类:玩电脑

实用PowerShell:错误处理


处理代码中出现问题时的意外情况

编写 PowerShell 脚本可以是一项很有成就感的任务。毕竟,您编写一些内容是为了协助完成任务或过程,这样您就可以专注于结果,而不是任务本身。但是,如果您的脚本尝试运行操作但不成功,例如,当脚本尝试操作的用户无效或登录帐户没有足够的权限来运行 cmdlet 时,该怎么办?并且不要忘记网络世界的特殊性,例如网络连接断开或身份验证令牌过期。

这就是编写弹性和“不太乐观的版本”脚本时经常被低估的方面之一:异常处理。

异常处理意味着添加代码来处理意外或预期的情况。这就是为什么经常创造的术语“错误处理”可能会产生误导,因为有时“错误”可能是由命令确定的结果。 “找不到用户”并不是真正的错误,不是吗,除非它意外发生? 脚本可能期望找不到用户帐户或其他对象,在这种情况下,“找不到用户”正是您期望发生的情况。

当命令的默认行为不受欢迎或者处理不当甚至可能产生问题时,代码应该预见到问题。一个简单的示例是,当您创建 Entra ID 用户帐户并想要设置其一些与邮件相关的属性时。只有创建成功后才能进行操作。

处理异常

您希望如何管理异常取决于具体情况,例如:

  • 当遇到的问题可能是暂时的时重试操作。例如,访问 Entra ID 帐户时,必须等待更改传播到 Exchange Online,然后再配置邮件相关属性。
  • 继续进一步加工。例如,当更改一组用户的属性时,单个用户的失败是否会导致完全失败并阻止其余用户执行该任务?此处正确记录问题可能更合适,以便操作员可以修复情况并为失败的子集重新运行脚本。
  • 优雅地管理异常。这不仅允许您以更丰富的信息方式处理异常,而且还允许您进行一些必要的清理,防止脚本过早退出而使其交互的环境处于不确定状态。

ErrorAction 和 $Error

在深入探讨如何预测异常之前,我想先讨论一下 ErrorAction$Error

ErrorAction(简称 EA)是所有 PowerShell cmdlet 支持的通用参数之一。最常用的设置是:

  • 继续将错误消息输出到输出流(控制台)并继续执行代码。这是默认行为。
  • 停止输出错误并终止执行。
  • SilentlyContinue 抑制错误消息并继续执行。所有错误消息都会发送到错误流,您可以使用自动变量 $Error 或使用公共参数 $ErrorVariable 指定的变量进行查询。

其他选项包括查询、忽略、暂停和中断。有关这些选项的更多信息可在线获取。当脚本中未指定 ErrorAction 时,全局变量 $ErrorActionPreference 定义 cmdlet 应如何响应非终止错误。

当您为 cmdlet 指定 -ErrorAction Stop 时,cmdlet 会将非终止错误转换为终止异常,例如,以下命令将继续使用 CSV 文件检索邮箱信息,即使单个条目找不到:

Import-Csv -Path .\Users.csv | ForEach-Object {
 Get-Mailbox -Identity $_.EmailAddress
}

当遇到非终止错误时,例如找不到邮箱,ErrorActionPreference默认设置Continue允许继续处理通过管道。

但是,如果我们更新脚本以使 Get-Mailbox cmdlet 使用 ErrorAction Stop,则脚本会在无法找到邮箱时停止:

Import-Csv -Path .\Users.csv | ForEach-Object {
 Get-Mailbox -Identity $_.EmailAddress -ErrorAction Stop
}

提示:与 Write-OutputWrite-HostWrite-Verbose 使用的输出流类似,您可以使用 Write -Error 将非终止错误消息写入错误输出流。 Write-Error 还可以选择指定其他属性,例如错误号(类似于类别。更多信息可以在此处找到。

自动变量 $Error 是一个数组,存储当前 PowerShell 会话中遇到的所有错误。这可以是最近的错误($Error[0])或历史记录。数组中的每一项都包含错误及其消息以及一些信息属性,例如 CategoryInfo(错误类别)、invoiceInfo(错误发生时执行的内容)、ScriptStackTrace(错误发生时调用的内容)和异常信息。

尝试/抓住/最后

接下来,我们讨论如何捕获和预测终止错误。为了实现这一点,我们需要将 Try/Catch 结构添加到我们的脚本中:

Try {
 <code>
}
Catch [[error type[, errortype]*] {
 <code to handle exception>
}
Finally {
 <code to execute, whether try code successfully or not>
}

这个结构的简要解释:

  • Try 后跟一个包含您要执行的代码的脚本块。如果运行此代码时遇到任何终止错误,脚本会将错误传递给 catch 块(如果存在)。
  • Catch 是可选的,指定在运行 Try 块时发生错误时要运行的代码。错误作为自动变量 $_ 从 try 块传递。以最简单的形式,catch 将捕获所有错误。当您想要预测多种错误类型时,可以为每种类型的错误指定一个 catch 块。为此,您需要指定其错误类型。您可以通过创建问题并检查异常并获取其错误类型的全名来快速确定错误类型,例如$error[0].exception.getType().Fullname 将返回 System.Exception。虽然在这种情况下错误类型听起来很通用,但您尝试完成的内容的上下文也应该指出可能出现的错误。

    有关如何合并错误类型捕获(例如 System.Exception),请参阅查看下面示例中的 Catch [System.Exception] 块。请注意,指定多个 catch 块时,可以使用一个没有错误类型指定的 catch 块作为默认的 catch 块。
  • 最后,无论 try 块是否成功,此处找到的代码始终会运行。将其用于家务管理目的。当您运行停止 cmdlet(例如从 catch 块之一退出)时,Finally 块也会运行。

以下脚本摘录为给定的用户集获取邮箱对象并处理一般异常以及系统异常,如前所述:

ForEach( $User in $Users){
 Write-Host -Message ( 'Fetching mailbox {0}' -f $User.Identity)
 Try {
 $Mailbox= Get-Mailbox -Identity $User.Identity -ErrorAction Stop
 }
 Catch [System.Exception] {
 Write-Error( '{0} System Exception: {1}' -f $User.Identity, $_.Exception.Message)
 }
 Catch {
 Write-Error -Message ('{0} Default Catch: {1}' -f $User.Identity, $_.Exception.Message)
 }
 Finally {
 # Finally
 }
}

正如您所看到的,在示例中,我们使用 Get-MailboxErrorAction 设置为 Stop,因为获取不存在的邮箱并不是终止错误。另外,需要注意的是,如果您将诸如 Get-Mailbox 之类的 cmdlet 放入带有 catch 块的 try 块中,您将不会看到任何错误输出,因为错误会传递给 catch堵塞。也就是说,除非您执行自己奇特的错误消息显示或其他日志记录方式。

其他一些建议:

  • 尽量避免将多个容易失败的命令放在一个 try 块中。但是,当您这样做时,请注意 catch 块将由第一个异常触发,并且您的 catch 不知道哪个命令导致了错误。
  • 在 try 块中包含单个操作可以限制潜在的失败范围。这使得处理错误并继续其余过程变得更加容易。
  • 尝试避免使用状态变量来控制逻辑流程。例如,我有时会看到以下模式:
Try {
 <Something>
 $DoThis= $true
}
Catch {
 $DoThis= $false
}
If( $DoThis) {
 <Try was successful, perform further processing>
}

为了提高可读性,请在 try 块中包含处理。

Try {
 <Something>
 <Perform further processing>
}
Catch {
 <..>
}
  • 当检查 catch 块中的最后一个错误 $Error[0] 变量时,首先将其复制到另一个变量。 catch 块中的命令可能会返回新的错误结果,从而将您感兴趣的错误移到数组中。
  • 您可以嵌套 try/catch/finally,但不建议这样做,因为它会增加复杂性。此外,嵌套可能会令人困惑,因为错误报告将来自最高级别,例如,当我们从前面摘录脚本并从另一个小脚本调用它时,会发生的情况是错误和行报告将来自调用者,而不是被调用的脚本:
[PS]> try {
 .\Sample1.ps1 -CsvFile .\Users.csv
}
catch {
 Write-Error -Message ('The whole thing failed')
}

Fetching mailbox [email protected]
Write-Error:
Line |
 2 | .\Sample1.ps1 -CsvFile .\Users.csv
 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | System Exception called for [email protected]: Ex6F9304|Microsoft.Exchange.Configuration.Tasks.ManagementObjectNotFoundException|The operation couldn't be performed because object '[email protected]' couldn't be found on 'AM6PR05A02DC002.EURPR05A002.prod.outlook.com'.

输出在第 2 行显示错误:调用脚本块中的行,而不是实际代码中的行。

投掷和陷阱

最后,我们来讨论一下投掷和陷阱。 Throw 是用于生成终止错误的关键字。它将停止当前命令、函数或脚本的代码执行。抛出错误时可以传递任何表达式,该表达式至少应该是一条信息性消息。例如:

Try {
 <Something>
}
Catch {
 Throw( 'Error occurred, exiting.')
}
Finally {
 <Finally>
}

您还可以抛出 ErrorRecord 对象,允许您提供有关返回的错误的更多详细信息。有关投掷的更多信息请参见此处。

前面我们讨论了 Try/Catch 结构中 Catch 的功能。当发生终止错误时,将进行默认错误处理。如果您不希望发生默认错误处理,Trap 允许您在未指定 Try/Catch 构造时创建一个包罗万象的终止错误。您可以选择仅指定错误类型以捕获该类型的异常。事例往往胜于雄辩。以下是示例脚本摘录:

trap [System.Exception] {
 Write-Error -Message 'An error trapped'
}

ThisCommandDoesNotExit

当我们运行脚本时,陷阱捕获通过调用不存在的命令生成的异常(恰好是 System.Exception 错误类型):

[玩转系统] 实用PowerShell:错误处理

该脚本执行陷阱中的代码并显示自定义错误消息。但是,陷阱的默认行为是在失败的代码段之后继续执行,在这种情况下这意味着显示错误消息。我们可以使用 Stop cmdlet 来管理此问题,例如使用 Break 来报告错误并终止执行,或使用 Continue 来抑制错误并继续执行。您可以在此处找到有关 Trap 的更多信息。

如果您有疑问或意见,请随时在评论中联系。如果没有,直到下一篇文章,我将讨论日志记录和报告,这是许多管理员最喜欢的主题。

您需要 登录账户 后才能发表评论

取消回复欢迎 发表评论:

关灯