[玩转系统] 您想了解的有关异常的所有信息
作者:精品下载站 日期:2024-12-14 03:04:11 浏览:15 分类:玩电脑
您想了解的有关异常的所有信息
当涉及到编写代码时,错误处理只是生活的一部分。我们经常可以检查和验证预期行为的条件。当意外发生时,我们转向异常处理。您可以轻松处理其他人的代码生成的异常,也可以生成自己的异常供其他人处理。
笔记
本文的原始版本出现在@KevinMarquette 撰写的博客上。 PowerShell 团队感谢 Kevin 与我们分享这些内容。请查看他的博客:PowerShellExplained.com。
基本术语
在我们开始讨论这个问题之前,我们需要先了解一些基本术语。
例外
异常就像正常错误处理无法处理问题时创建的事件。尝试将数字除以零或内存不足都是导致异常的示例。有时,您所使用的代码的作者会在某些问题发生时创建异常。
投掷和接住
当异常发生时,我们说抛出异常。要处理抛出的异常,您需要捕获它。如果抛出异常并且没有被某些东西捕获,则脚本将停止执行。
调用堆栈
调用堆栈是相互调用的函数的列表。当一个函数被调用时,它会被添加到堆栈或列表的顶部。当函数退出或返回时,它将从堆栈中删除。
当引发异常时,将检查该调用堆栈,以便异常处理程序捕获它。
终止和非终止错误
异常通常是终止错误。抛出的异常要么被捕获,要么终止当前执行。默认情况下,Write-Error 会生成非终止错误,并将错误添加到输出流而不引发异常。
我指出这一点是因为 Write-Error
和其他非终止错误不会触发 catch
。
吞掉异常
这是当您捕获错误只是为了抑制它时。请谨慎执行此操作,因为这会使故障排除变得非常困难。
基本命令语法
以下是 PowerShell 中使用的基本异常处理语法的快速概述。
扔
要创建我们自己的异常事件,我们使用 throw
关键字抛出异常。
function Start-Something
{
throw "Bad thing happened"
}
这会创建一个运行时异常,即终止错误。它由调用函数中的 catch
处理,或者使用类似这样的消息退出脚本。
PS> Start-Something
Bad thing happened
At line:1 char:1
+ throw "Bad thing happened"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (Bad thing happened:String) [], RuntimeException
+ FullyQualifiedErrorId : Bad thing happened
写入错误-ErrorAction 停止
我提到过,Write-Error
默认情况下不会引发终止错误。如果您指定 -ErrorAction Stop
,Write-Error
会生成一个终止错误,可以使用 catch
来处理该错误。
Write-Error -Message "Houston, we have a problem." -ErrorAction Stop
感谢 Lee Dailey 提醒您以这种方式使用 -ErrorAction Stop
。
Cmdlet -ErrorAction 停止
如果您在任何高级函数或 cmdlet 上指定 -ErrorAction Stop
,它会将所有 Write-Error
语句转换为停止执行或可由 处理的终止错误捕获
。
Start-Something -ErrorAction Stop
有关 ErrorAction 参数的详细信息,请参阅 about_CommonParameters。有关 $ErrorActionPreference
变量的详细信息,请参阅 about_Preference_Variables。
尝试/捕捉
PowerShell(以及许多其他语言)中异常处理的工作方式是,您首先尝试
一段代码,如果它抛出错误,您可以捕获
它。这是一个快速示例。
try
{
Start-Something
}
catch
{
Write-Output "Something threw an exception"
Write-Output $_
}
try
{
Start-Something -ErrorAction Stop
}
catch
{
Write-Output "Something threw an exception or used Write-Error"
Write-Output $_
}
catch
脚本仅在出现终止错误时运行。如果 try
正确执行,则会跳过 catch
。您可以使用 $_
变量访问 catch
块中的异常信息。
尝试/最后
有时,您不需要处理错误,但仍然需要在异常发生与否时执行一些代码。 finally
脚本正是这样做的。
看一下这个例子:
$command = [System.Data.SqlClient.SqlCommand]::New(queryString, connection)
$command.Connection.Open()
$command.ExecuteNonQuery()
$command.Connection.Close()
每当您打开或连接到资源时,都应该将其关闭。如果 ExecuteNonQuery()
引发异常,则连接不会关闭。这是 try/finally
块内的相同代码。
$command = [System.Data.SqlClient.SqlCommand]::New(queryString, connection)
try
{
$command.Connection.Open()
$command.ExecuteNonQuery()
}
finally
{
$command.Connection.Close()
}
在此示例中,如果出现错误,连接将关闭。如果没有错误,它也会关闭。 finally
脚本每次都会运行。
因为您没有捕获异常,所以它仍然会在调用堆栈中传播。
尝试/抓住/最后
将 catch
和 finally
一起使用是完全有效的。大多数时候您会使用其中之一,但您可能会发现同时使用两者的场景。
$PS项目
现在我们已经了解了基础知识,我们可以更深入地挖掘。
在 catch
块内,有一个 ErrorRecord
类型的自动变量($PSItem
或 $_
),其中包含有关异常的详细信息。以下是一些关键属性的快速概述。
对于这些示例,我在 ReadAllText
中使用了无效路径来生成此异常。
[System.IO.File]::ReadAllText( '\test\no\filefound.log')
PSItem.ToString()
这为您提供了在日志记录和一般输出中使用的最干净的消息。如果将 $PSItem
放置在字符串内,则会自动调用 ToString()
。
catch
{
Write-Output "Ran into an issue: $($PSItem.ToString())"
}
catch
{
Write-Output "Ran into an issue: $PSItem"
}
$PSItem.InspirationInfo
此属性包含 PowerShell 收集的有关引发异常的函数或脚本的附加信息。以下是我创建的示例异常中的 InitationInfo
。
PS> $PSItem.InvocationInfo | Format-List *
MyCommand : Get-Resource
BoundParameters : {}
UnboundArguments : {}
ScriptLineNumber : 5
OffsetInLine : 5
ScriptName : C:\blog\throwerror.ps1
Line : Get-Resource
PositionMessage : At C:\blog\throwerror.ps1:5 char:5
+ Get-Resource
+ ~~~~~~~~~~~~
PSScriptRoot : C:\blog
PSCommandPath : C:\blog\throwerror.ps1
InvocationName : Get-Resource
这里的重要细节显示了 ScriptName
、代码的 Line
以及调用开始处的 ScriptLineNumber
。
$PSItem.ScriptStackTrace
此属性显示使您到达生成异常的代码的函数调用顺序。
PS> $PSItem.ScriptStackTrace
at Get-Resource, C:\blog\throwerror.ps1: line 13
at Start-Something, C:\blog\throwerror.ps1: line 5
at <ScriptBlock>, C:\blog\throwerror.ps1: line 18
我只在同一脚本中调用函数,但是如果涉及多个脚本,这将跟踪调用。
$PSItem.Exception
这是实际引发的异常。
$PSItem.Exception.Message
这是描述异常的一般消息,是故障排除时的良好起点。大多数异常都有默认消息,但也可以在引发异常时设置为自定义消息。
PS> $PSItem.Exception.Message
Exception calling "ReadAllText" with "1" argument(s): "The network path was not found."
如果 ErrorRecord
上没有设置,这也是调用 $PSItem.ToString()
时返回的消息。
$PSItem.Exception.InnerException
异常可以包含内部异常。当您调用的代码捕获异常并引发不同的异常时,通常会出现这种情况。原始异常被放置在新异常中。
PS> $PSItem.Exception.InnerExceptionMessage
The network path was not found.
稍后当我谈论重新抛出异常时我会再次讨论这一点。
$PSItem.Exception.StackTrace
这是异常的StackTrace
。我在上面展示了一个 ScriptStackTrace,但这个是用于调用托管代码的。
at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean
useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs,
String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32
bufferSize, FileOptions options, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean
checkHost)
at System.IO.StreamReader..ctor(String path, Encoding encoding, Boolean detectEncodingFromByteOrderMarks,
Int32 bufferSize, Boolean checkHost)
at System.IO.File.InternalReadAllText(String path, Encoding encoding, Boolean checkHost)
at CallSite.Target(Closure , CallSite , Type , String )
仅当从托管代码引发事件时,您才会获得此堆栈跟踪。我直接调用 .NET 框架函数,因此这就是我们在此示例中看到的全部内容。通常,当您查看堆栈跟踪时,您正在寻找代码停止的位置和系统调用开始的位置。
处理异常
异常除了基本语法和异常属性之外还有更多内容。
捕获类型异常
您可以选择性地排除您发现的例外情况。异常有一个类型,您可以指定要捕获的异常类型。
try
{
Start-Something -Path $path
}
catch [System.IO.FileNotFoundException]
{
Write-Output "Could not find $path"
}
catch [System.IO.IOException]
{
Write-Output "IO error with the file: $path"
}
检查每个 catch
块的异常类型,直到找到与您的异常匹配的异常类型。重要的是要认识到异常可以从其他异常继承。在上面的示例中,FileNotFoundException
继承自IOException
。因此,如果 IOException 是第一个,那么它将被调用。即使存在多个匹配项,也只会调用一个 catch 块。
如果我们有一个 System.IO.PathTooLongException ,则 IOException 会匹配,但如果我们有一个 InsufficientMemoryException ,那么什么都不会捕获它并且它会传播向上堆栈。
一次捕获多种类型
可以使用相同的 catch
语句捕获多种异常类型。
try
{
Start-Something -Path $path -ErrorAction Stop
}
catch [System.IO.DirectoryNotFoundException],[System.IO.FileNotFoundException]
{
Write-Output "The path or file was not found: [$path]"
}
catch [System.IO.IOException]
{
Write-Output "IO error with the file: [$path]"
}
感谢 Reddit 用户 u/Sheppard_Ra
建议添加此内容。
抛出类型异常
您可以在 PowerShell 中引发类型化异常。而不是使用字符串调用 throw
:
throw "Could not find: $path"
使用这样的异常加速器:
throw [System.IO.FileNotFoundException] "Could not find: $path"
但是当您这样做时,您必须指定一条消息。
您还可以创建要抛出的异常的新实例。当您执行此操作时,该消息是可选的,因为系统对所有内置异常都有默认消息。
throw [System.IO.FileNotFoundException]::new()
throw [System.IO.FileNotFoundException]::new("Could not find path: $path")
如果您不使用 PowerShell 5.0 或更高版本,则必须使用旧的 New-Object
方法。
throw (New-Object -TypeName System.IO.FileNotFoundException )
throw (New-Object -TypeName System.IO.FileNotFoundException -ArgumentList "Could not find path: $path")
通过使用类型化异常,您(或其他人)可以按上一节中提到的类型捕获异常。
写入错误异常
我们可以将这些类型异常添加到Write-Error
中,并且我们仍然可以通过异常类型来捕获
错误。使用Write-Error
,如以下示例所示:
# with normal message
Write-Error -Message "Could not find path: $path" -Exception ([System.IO.FileNotFoundException]::new()) -ErrorAction Stop
# With message inside new exception
Write-Error -Exception ([System.IO.FileNotFoundException]::new("Could not find path: $path")) -ErrorAction Stop
# Pre PS 5.0
Write-Error -Exception ([System.IO.FileNotFoundException]"Could not find path: $path") -ErrorAction Stop
Write-Error -Message "Could not find path: $path" -Exception (New-Object -TypeName System.IO.FileNotFoundException) -ErrorAction Stop
然后我们可以这样捕获它:
catch [System.IO.FileNotFoundException]
{
Write-Log $PSItem.ToString()
}
.NET 异常的大列表
我在 Reddit r/PowerShell 社区的帮助下编译了一个主列表,其中包含数百个 .NET 异常,以补充本文。
- .NET 异常的大列表
我首先在该列表中搜索那些感觉非常适合我的情况的例外情况。您应该尝试在基本 System
命名空间中使用异常。
异常是对象
如果您开始使用大量类型化异常,请记住它们是对象。不同的异常有不同的构造函数和属性。如果我们查看 System.IO.FileNotFoundException 的 FileNotFoundException 文档,我们会发现我们可以传入消息和文件路径。
[System.IO.FileNotFoundException]::new("Could not find file", $path)
它有一个 FileName
属性来公开该文件路径。
catch [System.IO.FileNotFoundException]
{
Write-Output $PSItem.Exception.FileName
}
您应该查阅 .NET 文档以了解其他构造函数和对象属性。
重新抛出异常
如果您在 catch
块中要做的只是抛出
相同的异常,那么就不要catch
它。您应该只捕获
您计划在发生时处理或执行某些操作的异常。
有时您想要对异常执行操作,但要重新抛出异常,以便下游可以处理它。我们可以在发现问题的地方附近写一条消息或记录问题,但在堆栈中进一步处理问题。
catch
{
Write-Log $PSItem.ToString()
throw $PSItem
}
有趣的是,我们可以从 catch
中调用 throw
,它会重新抛出当前异常。
catch
{
Write-Log $PSItem.ToString()
throw
}
我们希望重新抛出异常以保留原始执行信息,例如源脚本和行号。如果我们此时抛出一个新的异常,它会隐藏异常开始的位置。
重新抛出新的异常
如果您捕获一个异常但想抛出另一个异常,那么您应该将原始异常嵌套在新异常中。这允许堆栈下方的人员将其作为 $PSItem.Exception.InnerException
进行访问。
catch
{
throw [System.MissingFieldException]::new('Could not access field',$PSItem.Exception)
}
$PSCmdlet.ThrowTerminateError()
我不喜欢对原始异常使用 throw 的一件事是错误消息指向 throw 语句并指示该行是问题所在。
Unable to find the specified file.
At line:31 char:9
+ throw [System.IO.FileNotFoundException]::new()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [], FileNotFoundException
+ FullyQualifiedErrorId : Unable to find the specified file.
如果错误消息告诉我我的脚本已损坏,因为我在第 31 行调用了 throw
,那么对于脚本的用户来说,这是一条不好的消息。它没有告诉他们任何有用的东西。
Dexter Dhami 指出我可以使用 ThrowTerminateError() 来纠正这个问题。
$PSCmdlet.ThrowTerminatingError(
[System.Management.Automation.ErrorRecord]::new(
([System.IO.FileNotFoundException]"Could not find $Path"),
'My.ID',
[System.Management.Automation.ErrorCategory]::OpenError,
$MyObject
)
)
如果我们假设在名为 Get-Resource
的函数内调用了 ThrowTerminateError()
,那么这就是我们将看到的错误。
Get-Resource : Could not find C:\Program Files (x86)\Reference
Assemblies\Microsoft\Framework\.NETPortable\v4.6\System.IO.xml
At line:6 char:5
+ Get-Resource -Path $Path
+ ~~~~~~~~~~~~
+ CategoryInfo : OpenError: (:) [Get-Resource], FileNotFoundException
+ FullyQualifiedErrorId : My.ID,Get-Resource
您是否看到它如何指出 Get-Resource
函数是问题的根源?这告诉用户一些有用的信息。
因为$PSItem
是一个ErrorRecord
,所以我们也可以使用ThrowTerminateError
这种方式来重新抛出。
catch
{
$PSCmdlet.ThrowTerminatingError($PSItem)
}
这会将错误源更改为 Cmdlet,并向 Cmdlet 用户隐藏函数的内部结构。
尝试可能会产生终止错误
Kirk Munro 指出,某些异常仅在 try/catch 块内执行时才终止错误。这是他给我的示例,该示例生成除以零的运行时异常。
function Start-Something { 1/(1-1) }
然后像这样调用它以查看它生成错误并仍然输出消息。
&{ Start-Something; Write-Output "We did it. Send Email" }
但是,通过将相同的代码放入 try/catch
中,我们会看到其他情况发生。
try
{
&{ Start-Something; Write-Output "We did it. Send Email" }
}
catch
{
Write-Output "Notify Admin to fix error and send email"
}
我们看到错误变成了终止错误并且不输出第一条消息。我不喜欢这个的地方是,您可以在函数中包含此代码,但如果有人使用 try/catch
,它的行为会有所不同。
我自己没有遇到过这个问题,但这是需要注意的极端情况。
try/catch 中的 $PSCmdlet.ThrowTerminateError()
$PSCmdlet.ThrowTerminateError()
的一个细微差别是,它会在 Cmdlet 内创建终止错误,但在离开 Cmdlet 后会变成非终止错误。这给函数的调用者留下了决定如何处理错误的负担。他们可以使用 -ErrorAction Stop
或从 try{...}catch{...}
中调用它,将其转回终止错误。
公共函数模板
我与 Kirk Munro 交谈时的最后一个方法是,他在每个 begin
周围放置一个 try{...}catch{...}
, >process
和 end
块在他的所有高级功能中。在这些通用的 catch 块中,他使用 $PSCmdlet.ThrowTerminateError($PSItem)
来处理离开其函数的所有异常。
function Start-Something
{
[CmdletBinding()]
param()
process
{
try
{
...
}
catch
{
$PSCmdlet.ThrowTerminatingError($PSItem)
}
}
}
因为所有内容都在其函数内的 try
语句中,所以所有内容的行为都是一致的。这也为最终用户提供了干净的错误,从而隐藏了生成错误的内部代码。
陷阱
我重点关注异常的 try/catch
方面。但在结束之前我需要提及一个遗留功能。
陷阱被放置在脚本或函数中以捕获该范围内发生的所有异常。当异常发生时,会执行trap
中的代码,然后继续正常的代码。如果发生多个异常,则陷阱会被一遍又一遍地调用。
trap
{
Write-Log $PSItem.ToString()
}
throw [System.Exception]::new('first')
throw [System.Exception]::new('second')
throw [System.Exception]::new('third')
我个人从未采用过这种方法,但我可以在记录任何和所有异常的管理或控制器脚本中看到值,然后仍然继续执行。
结束语
向脚本添加适当的异常处理不仅可以使它们更加稳定,还可以使您更轻松地排除这些异常。
我花了很多时间谈论抛出
,因为它是谈论异常处理时的核心概念。 PowerShell 还为我们提供了 Write-Error
来处理所有需要使用 throw
的情况。因此,阅读本文后,不要认为您需要使用 throw
。
现在我已经花时间详细介绍了异常处理,我将转而使用 Write-Error -Stop
在我的代码中生成错误。我还将采纳 Kirk 的建议,将 ThrowTerminateError 设置为每个函数的 goto 异常处理程序。
猜你还喜欢
- 03-30 [玩转系统] 如何用批处理实现关机,注销,重启和锁定计算机
- 02-14 [系统故障] Win10下报错:该文件没有与之关联的应用来执行该操作
- 01-07 [系统问题] Win10--解决锁屏后会断网的问题
- 01-02 [系统技巧] Windows系统如何关闭防火墙保姆式教程,超详细
- 12-15 [玩转系统] 如何在 Windows 10 和 11 上允许多个 RDP 会话
- 12-15 [玩转系统] 查找 Exchange/Microsoft 365 中不活动(未使用)的通讯组列表
- 12-15 [玩转系统] 如何在 Windows 上安装远程服务器管理工具 (RSAT)
- 12-15 [玩转系统] 如何在 Windows 上重置组策略设置
- 12-15 [玩转系统] 如何获取计算机上的本地管理员列表?
- 12-15 [玩转系统] 在 Visual Studio Code 中连接到 MS SQL Server 数据库
- 12-15 [玩转系统] 如何降级 Windows Server 版本或许可证
- 12-15 [玩转系统] 如何允许非管理员用户在 Windows 中启动/停止服务
取消回复欢迎 你 发表评论:
- 精品推荐!
-
- 最新文章
- 热门文章
- 热评文章
[影视] 黑道中人 Alto Knights(2025)剧情 犯罪 历史 电影
[古装剧] [七侠五义][全75集][WEB-MP4/76G][国语无字][1080P][焦恩俊经典]
[实用软件] 虚拟手机号 电话 验证码 注册
[电视剧] 安眠书店/你 第五季 You Season 5 (2025) 【全10集】
[电视剧] 棋士(2025) 4K 1080P【全22集】悬疑 犯罪 王宝强 陈明昊
[软件合集] 25年6月5日 精选软件22个
[软件合集] 25年6月4日 精选软件36个
[短剧] 2025年06月04日 精选+付费短剧推荐33部
[短剧] 2025年06月03日 精选+付费短剧推荐25部
[软件合集] 25年6月3日 精选软件44个
[剧集] [央视][笑傲江湖][2001][DVD-RMVB][高清][40集全]李亚鹏、许晴、苗乙乙
[电视剧] 欢乐颂.5部全 (2016-2024)
[电视剧] [突围] [45集全] [WEB-MP4/每集1.5GB] [国语/内嵌中文字幕] [4K-2160P] [无水印]
[影视] 【稀有资源】香港老片 艺坛照妖镜之96应召名册 (1996)
[剧集] 神经风云(2023)(完结).4K
[剧集] [BT] [TVB] [黑夜彩虹(2003)] [全21集] [粤语中字] [TV-RMVB]
[实用软件] 虚拟手机号 电话 验证码 注册
[资源] B站充电视频合集,包含多位重量级up主,全是大佬真金白银买来的~【99GB】
[影视] 内地绝版高清录像带 [mpg]
[书籍] 古今奇书禁书三教九流资料大合集 猎奇必备珍藏资源PDF版 1.14G
[电视剧] [突围] [45集全] [WEB-MP4/每集1.5GB] [国语/内嵌中文字幕] [4K-2160P] [无水印]
[剧集] [央视][笑傲江湖][2001][DVD-RMVB][高清][40集全]李亚鹏、许晴、苗乙乙
[电影] 美国队长4 4K原盘REMUX 杜比视界 内封简繁英双语字幕 49G
[电影] 死神来了(1-6)大合集!
[软件合集] 25年05月13日 精选软件16个
[精品软件] 25年05月15日 精选软件18个
[绝版资源] 南与北 第1-2季 合集 North and South (1985) /美国/豆瓣: 8.8[1080P][中文字幕]
[软件] 25年05月14日 精选软件57个
[短剧] 2025年05月14日 精选+付费短剧推荐39部
[短剧] 2025年05月15日 精选+付费短剧推荐36部
- 最新评论
-
- 热门tag