PowerShell如何依靠全局错误处理并执行脚本
没有任何脚本或程序能够保证在任何情况下毫无错误地执行,在外界条件变化的情况下,需要预防可能出错之处。本文将介绍PowerShell如何依靠全局错误处理检测捕捉并处理执行脚本或代码引起的错误,要注意的是其中的例子包含错误处理方法和调试信息。本文还将举例说明如何在反常条件下、错误的输入数据,以及隐含的错误情况下捕获异常。
PowerShell中的错误(Error)分为终止(terminating)和非终止(nonterminating),第1种类似很多编程语言中的异常(exception),通常是因为命令解释器遇到异常条件而不能继续。一旦抛出这类错误,后面的代码执行均将结束。典型的终止错误由脚本语法错误、调用不存在的命令或者类似的错误引起,开发人员使用throw语句抛出的错误也会终止程序继续执行。
非终止错误通常用于标识当前命令不能完整执行,但是不影响继续执行后续代码。PowerShell提供为错误保留的管道,类似正常对象输出管道。此类管道专为非终止错误准备,默认为用红色字体显示所有信息。典型的非终止错误是文件检测操作,其中的原因有多种,大多数情况下会选择继续执行。
1 常见错误处理参数
如下代码在Error-NonTermination.ps1脚本文件中,其中定义了一个名为“Raise-NonTerminatingError”的函数用于触发非终止错误: function Raise-NonTerminatingError{ Del nosuchfile.txt}Write-Host “script start”Raise-NonTerminatingErrorWrite-Host “script end”
上例分别在调用Raise-NonTerminatingError函数前后向控制台输出诊断信息,图1所示为执行脚本时的输出。
图1 执行脚本时的输出
虽然在执行过程中获得错误信息,但是显示了错误函数前后的诊断信息,说明脚本在发生错误之后仍然继续执行。
下面的脚本文件调用包含终止错误的Raise-TeminatingError函数: function Raise-NonTerminatingError{ Del nosuchfile.txt }function Raise-TerminatingError{ Del nosuchfile-terminating.txt -ErrorAction Stop } Write-Host "script start" Raise-NonTerminatingError Raise-TerminatingError Write-Host "script end"
其中后一个函数中删除不存在文件的操作将终止所有操作,即抛出了终止错误,图2所示为执行脚本时的输出。
图2 执行脚本时的输出
在上例中显示两次错误提示,nonterminating函数尝试删除nonexistent文件在先。第2个错误抛出之后未显示这个函数后面的调试信息,说明脚本在执行到错误发生的临界状态下中止。读者也可以尝试交换两个函数的执行顺序,以查看执行结果。
所有PowerShell的cmdlet均支持一系列错误处理的参数,用户可以运行任何cmdlet指定在错误发生时产生特定附加输出信息或者执行特定的操作,ErrorAction、ErrorVariable、Debug和Verbose是每个cmdlet支持的常见错误处理参数。
ErrorAction参数的取值如下。
(1)Continue:默认值,报告错误并继续执行后面的代码。
(2)Stop:报告错误并停止执行后续代码,此选项会使cmdlet抛出终止错误,通常会终止后续所有代码的执行。这里所说的“通常”意味着也可以使用错误陷阱捕获和处理错误,而不是终止整个脚本的执行。
(3)SilentlyContinue:静默继续,忽略错误并继续执行后面的代码,但是不输出任何错误信息。图3所示为忽略文件删除失败。
图3 忽略文件删除失败
(4)Inqurie:询问用户操作的取舍,由用户决定下一步如何操作,如图4所示。
图4 出错时询问用户需要的操作
选择Yes或Yes to All,显示一个错误信息并继续执行;选择Halt(终止),结果类似Stop选项,则报告错误并停止执行后续代码;选择Suspend(挂起)选项,则临时挂起程序。此时可以在暂时保留当前现场的前提下退出提示符并执行某个操作,然后可以使用exit命令返回到之前保留的现场中。图5所示为选择Suspend后的输出。
图5 出错时用户选择Suspend对错误挂起可恢复出错现场
PowerShell提供的ErrorVariable参数用于获取cmdlet产生的错误对象。当程序中使用了SilentlyContinue(静默继续)这个ErrorAction隐藏了错误输出时,需要根据错误对象了解发生的错误。在程序中可以用检测配置为错误容器的变量是否包含错误来验证是否有错误产生。创建一个新的脚本文件“ErrorVariable.ps1”,在其中操作错误变量测试错误条件,代码如下: $files = dir C: -ErrorAction SilentlyContinue ` -ErrorVariable errorVar if ($errorVar) { Write-Host "A error occurred getting files!" } del nosuchfile.txt -ErrorAction SilentlyContinue ` -ErrorVariable errorVar if ($errorVar) { $realError = $errorVar[0] $realError | ` Select FullQualifiedErrorId,CategoryInfo,TargetObject | ` Format-List $realError.InvocationInfo | ` Select ScriptNamme,ScripptLineNumber,Line | Format-List }
发生错误时$errorVar变量可以保存一个集合。第2个if语句块获取错误对象的属性并使用Format-List语句打印到控制台,执行脚本时的输出图6所示。
图6 执行脚本时的输出
其中获取的错误对象是System.Management.Automation.ErrorRecord,FullQualifiedErrorId是分辨不同错误的唯一标识。由于不同语言的Windows操作系统中的错误信息不同,所以兼容性好的脚本不应使用字符串操作判断错误信息。ObjectNotFound为错误分类,在类似的错误中经常出现。TargetObject属性指向发生错误时脚本操作的对象,Exception属性中包含错误记录的真实的.NET异常。上面的脚本中包含很多冗长的解释操作,因而输出了记录错误属性的InvocationInfo对象。通过该对象用户可以获得产生错误的具体脚本行,从而定位到相应脚本文件的具体位置。
Verbose和Debug参数用于产生主要用于脚本和cmdlet调试的附加调试信息,应输出到控制台。通常情况下不会输出此类详细的信息,以免给用户造成误会,但它对于调试和排除错误非常有效。
2 诱捕错误
在调用带有SilentlyContinue的ErrorAction操作后带一个if语句块来检测是否发生错误是调试较少命令组成的脚本块时的较好选择,这是一种很有效的方法;缺点是限制了调用cmdlet的操作,仅可捕获由其他操作抛出的错误。
PowerShell采用错误陷阱的语法结构来解决这个问题,陷阱是处理特定错误的代码块结构。在脚本文件、函数或者脚本块中注册,当错误发生时将执行的这段代码块。当输入错误的字符串格式时,[datetime]操作符将会抛出错误。下面使用陷阱写一个脚本文件“Date-Traps.ps1”,在其中将会遍历字符串集合并转换为日期。能够看到PowerShell抛出了System.Management.Autommation.PSInvalidCastException错误并通过添加陷阱来操作,代码如下: trap [System.Management.Automation.PSInvalidCastException]{Write-Host "Error converting a string to date!"$realException = $_.Exception.InnerExceptionWrite-Host "Inner Exception: $($realException.GetType())"}$dateStrings = "11/16/2008", "bad/date/format"foreach ($dateString in $dateStrings){$date = [datetime] $dateStringWrite-Host $date}
当trap语句接受脚本块时,完全照传递的参数转换并将当前的ErrorRecord传递给$_变量,Exception属性包含由Shell抛出的PSInvalidCastException错误。
如图7所示,捕获异常并显示错误提示。其中成功转换第1个数据。转换第2个数据触发错误,执行陷阱后显示抛出的异常是System.FormatException。
图7 捕获异常并显示错误提示
(1)终止错误并抛出异常
为了隐藏错误,需要使用continue语句终止错误输出,并且执行后续的脚本语句。接下来将上例修改为“Date-TrapsWithContinue.ps1”文件,在其中增加时间转换操作,并在循环前后分别增加诊断语句。然后增加continue语句来处理错误,代码如下: trap [System.Management.Automation.PSInvalidCastException]{Write-Host "Error converting a string to date!"$realException = $_.Exception.InnerExceptionWrite-Host "Inner Exception: $($realException.GetType())"continue}Write-Host "script begin"$dateStrings = "11/16/2008", "bad/date/format", "03/20/2009"foreach ($dateString in $dateStrings){$date = [datetime] $dateStringWrite-Host $date}Write-Host "script end"
执行结果如图8所示,其中并未包含默认的错误信息,continue语句终止当前循环并开始执行下一个循环。正如所看到的,并未转换有意在错误字符串后面放置的“03/20/2009”的字符串。因为整个循环本身已经由于错误的存在而被终止,所以执行循环以外的输出语句。
图8 执行结果
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
正如所看到的,陷阱代码被执行,而且执行了发生错误之前的输出语句。如果遇到这种问题,并且不能确定是否遇到解析时错误,则在脚本开始放置简单的Write-Host输出语句。如果输出语句尚未执行报错,而且设置的陷阱未起作用,则为解析时错误,返回脚本查代码。
3 捕捉非终止性错误
非终止性错误不会终止脚本执行,并且不能被错误陷阱捕获。为了能够保存由非终止性错误生成的ErrorRecord,可以使用ErrorVariable参数存储错误信息用于后续的访问。或者使用错误管道重定向。或者检查全局的$Error变量。使用ErrorVariable需要传递额外的参数给所有cmdlet,并且不能处理其他代码抛出的非终止性错误。
错误重定向类似输出重定向,它使用2>重定向操作符。图18所示为要删除一个不存在的文件,并将错误信息输出到记录文件中。
图18 输出错误到记录文件中
如果将错误重定向到$null变量中,则不需要在硬盘中创建文件,如图19所示。
图19 将错误重定向到$null中
为了方便,通常会将标准输出写入stdout流中,其ID为1;将所有错误输出写入stderr流中,其ID为2。这些定义适用于cmd.exe和linux bash,这样2>操作符重定向输出到stderr。即错误不会输出到屏幕,而是写到文件中。如果指定$null,则不输出到屏幕,也不写入到特定文件中
使用全局$Error变量是获取非终止性错误的唯一可信赖方式,PowerShell包含一个$MaximumErrorCount变量用来限制保存错误记录最大的条数,默认值为256。$Error集合将最后一个错误放在最前面,即$Error[0]包含最后一条错误,上一个错误包含在$Error[1]中,可以将这个集合看做是一个队列。上例中的输出可以通过如图20所示的方法读取错误信息。
图20 通过全局变量$Error索引值读取错误记录
可以使用$Error集合创建日志工具来追踪任何两个执行点之间错误,为此需要通过获取该集合中包含的错误数得到基准点,并在执行一些操作后处理错误。下面创建一个名为“Log-NonTerminatingErrors.ps1”的脚本,其中包含两个错误。并使用不同的技术终止这两个错误,代码如下:
trap{continue}Write-Host "setting error baseline"$initialErrorCount = $error.count$denominator = 04/$denominatordel nosuchfile.txt 2> $null$errorCount = $error.count - $initialErrorCountWrite-Host "Errors since baseline"for ($i = $errorCount - 1; $i -ge 0; $i--){$error[$i]}
零除错误是个常见的终止性错误,在代码中用陷阱捕获它并继续执行下面的代码。删除操作失败,但是其错误管道信息被重定向到$null。错误报告机制获取初始化的错误数量,最后获取当前错误数量。并根据错误发生的先后顺序输出错误信息,如图21所示。
图21 通过全局变量$Error获取脚本执行的错误记录
4 抛出错误
很多情况下脚本由程序来调用,因为需要由程序初始化一些条件,用户直接调用并不能提供这些条件。不正常的条件包括错误的输入和不具有的执行权限等。对于良好的用户体验来说需要检测存在问题的条件,抛出异常提示并调用代码处理这些异常。而调用程序本身需要提示用户是否处理错误或继续执行,或改变输入数据,或直接终止程序的执行。
(1)抛出终止错误
终止错误是由于输入异常或不具备执行环境条件而产生的不能继续执行的错误,如脚本用于处理特定格式的文本文件,但用户提供的是二进制的.exe可执行文件。这时脚本无法继续执行,所以需要使用throw语句抛出异常。
为了演示如何抛出终止错误,这里创建一个名为“Function-Parameters.ps1”的脚本文件,用于定义需要一个文件名作为参数的Find-TextFile函数。如果提供的文件名后缀不是.txt,则这个函数将会抛出异常,代码如下:
function Find-TextFile($name){if ($name -notlike "*.txt"){throw "Find-TextFile: Expecting *.txt files only"}return Get-Item $name}Find-TextFile "Function-Parameters.ps1"
可以看到函数会检查传递的文件名后缀是否为.txt,如果不是,则抛出终止错误。在脚本中在调用函数时使用了一个非法的文件名,执行结果如图22所示。
图22 执行结果
改写上面的代码,保存为“Function-ParametersWithTraps.ps1”脚本文件,使用陷阱来捕获其中的终止错误,代码如下:
trap{Write-Host "$($_.Exception.GetType())"continue}function Find-TextFile($name){if ($name -notlike "*.txt"){throw "Find-TextFile: Expecting *.txt files only"}return Get-Item $name}Find-TextFile "Function-Parameters.ps1"
脚本执行结果如图23所示。
图23 执行结果
因为衡量标准不一样,所以区分不同类型的错误成为一个问题。最好的方法是创建一个错误对象,通过抛出它来区分不同的错误。重写上面的代码,创建名为“Function-ParametersWithExceObj.ps1”的脚本文件,代码如下:
trap [System.ArgumentException]{Write-Host "$($_.Exception.Message)"continue}function Find-TextFile($name){if ($name -notlike "*.txt"){$message = "Find-TextFile: Expecting *.txt files only"throw (New-Object System.ArgumentException -arg $message)}return Get-Item $name}Find-TextFile "Function-Parameters.ps1"
脚本执行结果如图24所示。
图24 执行结果
需要强调的是在上面的代码中使用New-Object cmdlet创建了.NET的System.ArgumentException对象,并传递错误信息作为结构体的参数,这样允许编写更多特定的错误陷阱来捕获特定的异常。
之前的文章中演示如何在表达式中使用throw语句,这是通过为函数参数指定默认值的形式实现的。如果值没有提供的话,则抛出相应的错误。这种对工具参数的巧妙改写能够更广泛地扩展标准的.NET异常对象。为了在上例基础上继续演示,创建名为“Function-ParametersWithExceObjExpression.ps1”的脚本文件。在其中添加判断$name参数是否为$null的语句,代码如下:
trap [System.ArgumentException]{Write-Host "$($_.Exception.Message)"continue}function Find-TextFile($name = `$(throw New-Object System.ArgumentNullException -arg "name")){if ($name -notlike "*.txt"){$message = "Find-TextFile: Expecting *.txt files only"throw (New-Object System.ArgumentException -arg $message)}return Get-Item $name}Find-TextFile "Function-Parameters.ps1"Find-TextFile
在上面函数的定义中,通过指定参数默认值表达式为$name参数创建新的System.ArgumetNullException对象。如果$name为空,则抛出这个错误。在.NET中ArgumentNullException类继承于ArgumentException类型,即所有的ArgumentNullException对象也都是ArgumentException对象,并且为ArgumentException创建的陷阱也会捕获相关衍生类型的错误。脚本执行结果如图25所示。
图25 执行结果
需要强调的是为不同调用获取了两个不同的错误,ArgumentNullException异常适用于格式化参数缺失的错误信息。这样可提供比较好的用户体验,甚至能够为非英语Windows系统提供区域化的错误信息。
(2)抛出非终止错误
并不是所有遇到的错误对于脚本的执行都是致命的。有时还会出现存在一定警告的情况下继续执行脚本业务逻辑的情况。也许这些警告信息只是为了帮助客户端代码更加的智能,或为用户提供帮助而已。Write-Error cmdlet这个cmdlet可携带信息字符串、异常或者ErrorRecord对象并将其传递到错误管道中。为了演示其功能,创建名为“Function-ParametersNonTerminating.ps1”的脚本文件,在其中创建名为“Find-TextFile”的函数用于返回指定路径的文件。如果文件不存在,则生成非终止性错误。代码如下:
function Find-TextFile($name){if (!(Test-Path $name)){Write-Error -Message "$name does not exist"return $null}else{return Get-Item $name}}Write-Host "Script start"Find-TextFile "nosuchfile.txt"Write-Host "Script end"
由于在脚本前后分别添加了调试用输出,所以在报错后继续执行控制台输出语句,脚本执行结果如图26所示。
图26 执行结果
也可以通过将Write-Error输出的错误管道对象重定向到$null中关闭错误提示,如图27所示。
图27 关闭错误提示
5 总 结
错误处理和脚本调试的主题相互错综复杂地关联在一起,加强脚本的错误处理逻辑后可以实现代码的自诊断。这样即可减少脚本的调试工作,快速而有效地达到目标需求。同样也可以通过错误处理的相关技术,如错误陷阱输出错误信息来快速调试程序。
为代码添加好的错误处理处理机制是开发人员应该注意的问题,这样能够大大缩短调试的时间,而且任何使用代码的人都可以从中受益。本文介绍了PowerShell如何依靠全局错误处理检测捕捉并处理执行脚本或代码引起的错误,要注意的是其中的例子包含错误处理方法和调试信息。本文还举例说明了如何在反常条件下、错误的输入数据,以及隐含的错误情况下捕获异常。
严重的错误需要特殊处理,如清理现场并通知用户,以及停止执行后续代码,此时需要使用break语句。将上例修改为“Date-TrapsWithBreak.ps1”文件,其中陷阱部分的代码如下: trap [System.Management.Automation.PSInvalidCastException]{Write-Host "Error converting a string to date!"$realException = $_.Exception.InnerExceptionWrite-Host "Inner Exception: $($realException.GetType())"break}
执行结果如图9所示,其中提示用户错误格式错误,并且停止执行后续代码,这样用户可以在改正代码问题后继续。
图9 执行结果
可以注册的陷阱数没有限制,这样能够处理不同的错误。为了演示如何处理不同数据格式的转换错误,创建一个名为“Number-Trap.ps1”的脚本。其中尝试将字符串转换为数字,并包含零除错误,代码如下: Write-Host "script begin"[int] "not a number"$denominator = 0$result = 50 / $denominatorWrite-Host "script end"trap [System.Management.Automation.PSInvalidCastException]{Write-Host "Error converting a string to a number!"continue}trap [System.DivideByZeroException]{Write-Host "Attempted to divide by zero!"continue}
代码中的错误会先后被不同的两个陷阱捕获,并输出自定义的提示。陷阱声明的位置并不影响陷阱捕获异常,如果不考虑代码的可读性的话,甚至可以把异常的捕获放置到正常的代码行之间。并且定义的陷阱顺序也无关紧要。同时不需要区分陷阱的优先级来排列陷阱的顺序,因为PowerShell会在执行代码之前首先解析代码并创建所有陷阱。并且忽略用户以何种顺序排列陷阱,都会按照自己的解析模式来抛出响应的异常,感兴趣的读者可以切换陷阱的顺序来验证。脚本的执行结果如图10所示。
图10 执行结果
很多时候,用户并不能总知道所有可能出现错误的类型,或者需要通过一段代码作为陷阱抛出所有可能出现的错误。为简单地忽略陷阱声明中的错误类型,错误处理中的代码通过$_变量及其属性区分不同错误。将下面的代码保存为“Number-GenericTrap.ps1”脚本文件,其中通过输出FullyQualifiedErrorId、Exception和Exception.InnerException属性查找和排除错误: trap{$exceptionType = $_.Exception.GetType()$innerExceptionType = "No inner exception"if ($_.Exception.InnerException){$innerExceptionType = $_.Exception.InnerException.GetType()}Write-Host "FullyQualifiedErrorId: $($_.FullyQualifiedErrorId)"Write-Host "Exception: $exceptionType"Write-Host "InnerException: $innerExceptionType"continue}Write-Host "script begin"[int] "not a number"$denominator = 0$result = 50 / $denominatorWrite-Host "script end"
执行结果如图11所示。
能够看到先后捕获了PSInvalidCastException和DivideByZeroException错误,与之前一个版本中捕获异常的类型一致。而FullQualifiedErrorId属性的两个错误结果均为通用的RuntimeException。为了区分前后两个错误类型,需要查看Excepiton和Exception.InnerException对象。需要强调的是Exception.InnerException属性不一定包含真实对象,如同前面的DivedByZeroException属性未包含相应对象。
使用通用陷阱可以捕获所有的错误记录对象,并获取对象Exception属性中包含的内容,然后即可移除通用陷阱并使用特定的异常类型创建特定的错误陷阱来处理。
图11 执行结果
其他编程语言提供了可以在特定代码块中为特定错误指定陷阱以捕获异常的结构,在PowerShell中的错误陷阱通常为当前执行上下文注册。如果需要为小段代码指定特定陷阱,则需要将陷阱的代码转移到单独的脚本块和函数中,这样即可创建嵌套陷阱。在小段代码中关注特定错误的陷阱可以转移到函数体和脚本块中,这样可以将更多的通用处理放置在外面。如果错误在函数中发生,Shell会在其中查找已注册的陷阱。如果没有找到,则在其父作用域中查找。以此类推,直到脚本的全局作用域,从而使得陷阱有了类似变量在脚本中存在的多层作用域的性质。为了演示这一点,在脚本文件Nested-Traps.ps1中定义一个函数Generate-DiviedByZero,其中会抛出一个System.DivideByZeroException错误,并且包含一个错误陷阱用于向控制台输出特定的错误信息;另外在函数外脚本会生成一个字符串转换为数字的错误,并在全局错误陷阱中被捕获,下面是该脚本的代码: trap{$error = $_.Exception.GetType()Write-Host "Caught $error outside function"continue}function Generate-DivideByZero{trap{$error = $_.Exception.GetType()Write-Host "Caught $error inside function"continue}$denominator = 0$result = 50 / $denominator}Write-Host "script begin"Generate-DivideByZero[int] "not a number"Write-Host "script end"
代码执行时会捕获两个错误,通过控制台输出可以看到两个异常分别在函数体及其父作用域中被捕获,如图12所示。
图12 分作用域设置陷阱捕获错误
如果需要,低级别的陷阱能够将错误传递到高作用域中。这是个非常有用的特性,允许用户创建错误处理链。以实现在子作用域中响应错误,而将错误通过处理链传递到上级作用域处理。为此,需要将continue语句替换为break语句。下面创建名为“Nested-TrapsToUpLevel.ps1”的脚本,代码如下: trap{$error = $_.Exception.GetType()Write-Host "Caught $error outside function"continue}function Generate-DivideByZero{trap{$error = $_.Exception.GetType()Write-Host "Caught $error inside function"break}$denominator = 0$result = 50 / $denominator}Write-Host "script begin"Generate-DivideByZero[int] "not a number"Write-Host "script end"
执行结果如图13所示,从控制台的输出可以看到在函数体内的陷阱和在函数体外的全局函数区中都处理了函数中的错误System.DivideByZeroException。
最后讨论如何将脚本发布给用户使用时为脚本作者有效的报告出现的错误,相对较好的方法是在出现错误时将出现错误的环境信息输出为文件,并由用户发送给作者尽快修复。可以通过注册全局错误陷阱获取错误的相关信息并输出到指定的位置,用户只需要发送一份邮件即可将出现错误的堆栈信息反馈给作者。为了演示,创建名为“Log-AllErrors.ps1”的脚本用于尝试删除某个文件。如果文件不存在,则会出现终止错误。这时将会生成错误诊断日志,并由用户发送诊断文件给脚本作者。代码如下: trap{$dumpFile = "error-dump.xml"$message = @"A critical error occurred!The error has been dumped to $dumpFile. Please include that filein all error reports you send to scriptauthor@test.com"@Write-Host $message -ForegroundColor Red$_ | Export-CliXml $dumpFilebreak}del veryimportantfile.txt -ErrorAction Stop
该脚本中使用了Export-CliXml cmdlet序列化ErrorRecord对象并将其写到XML文件中,这样即可抓取并发送所有相关环境信息。由脚本作者解析并恢复错误现场,然后分析脚本错误。脚本的执行结果如图14所示。
生成的错误日志文件error-dump.xml的格式如下所示: <?xml version="1.0" encoding="utf-16"?><Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"> <Obj RefId="0"> <TN RefId="0"> <T>System.Management.Automation.ErrorRecord</T> <T>System.Object</T> </TN> …… <Nil N="PSMessageDetails" /> </MS> </Obj></Objs>
图14 执行结果
当用户将上面生成的脚本错误日志文件error-dump.xml根据提示的邮件地址发送给脚本作者后,脚本作者可能还需要一个错误日志解析程序阅读。下面创建名为“Analy-AllErrorsLog.ps1”的脚本用于解析上述的日志文件,代码如下: $e = Import-Clixml error-dump.xml$e.FullyQualifiedErrorId$e.InvocationInfo
执行上述脚本的结果如图15所示,其中解析出主要的出错信息,包括脚本名、运行的路径及引起错误的指定行。
图15 执行结果
(2)不能捕获的错误
大多数情况下,语法和解析错误算是比较严重的错误。必须要修正,而不能屏蔽或者忽略,这种异常不可能通过脚本内的陷阱捕获。PowerShell不能捕获语法和解析错误,大多数情况下解析错误很容易被发现,由非法脚本块,如不可识别的语句、错乱的分支、括号或者引号引起。PowerShell在解析时优化(parse-time optimizations)尝试执行类似4+2一类的表达式,对于通过陷阱捕获任何在表达式执行时出现的错误是个问题。下面创建脚本DivideByZero.ps1,在其中尝试捕获DivideByZeroException,代码如下: trap{Write-Host "Caught $($_.Exception.GetType())"}Write-Host "Script start"3 / 0
执行这个脚本返回标准的错误信息,而不是在脚本陷阱中的提示信息,如图16所示。
图16 返回标准的错误信息
PowerShell中的脚本在执行之前会被读入做预解析,文字表达式会被事先处理。相关的对象会被初始化,并且陷阱也会被解析准备在后续执行时随时捕获异常,然后按照脚本的先后顺序执行。由于这里文字表达式是数字型并在预解析时发现零除错误,Shell报错,因而控制台的输出语句尚未执行,脚本已经由于语法错误而停止执行。为了使陷阱起作用,只需要排除文字表达式在解析时出现的错误。可以将0赋值给某个变量并除一个数字,尽管也是零除,但是避免了在解析时被终止。将修改后的代码命名为“DivideByZeroTraps.ps1”,代码如下: trap{Write-Host "Caught $($_.Exception.GetType())"continue}Write-Host "Script start"$zero = 03 / $zero
脚本执行结果如图17所示。
图17 执行结果