PowerShell笔记

本系列是一个重新学习PowerShell的笔记,内容引用自PowerShell中文博客

what-if

自动化具有高度方便的特点,同时也可能会自动产生一些不可避免的错误。这也就是Powershell为什么会有一些专门来防止和处理危险的机制:这些机制会对接下来要执行的操作进行确认。

试运行:模拟操作

如果你想知道一个确定的命令会产生什么影响,你可以进行试运行。这时,Powershell不会执行任何对系统有影响的操作,只会告诉你如果没有模拟运行,可能产生什么影响和后果。通过-whatif 参数。事实上,许多cmdltes都支持试运行。

##如果执行stop-process -name *a*会终止下面的进程。
PS C:PowerShell> Stop-Process -Name *Micro* -WhatIf                                                                    What if: Performing the operation "Stop-Process" on target "Microsoft.Notes (14976)".
What if: Performing the operation "Stop-Process" on target "Microsoft.ServiceHub.Controller (20708)".
What if: Performing the operation "Stop-Process" on target "MicrosoftSearchInBing (5828)".
What if: Performing the operation "Stop-Process" on target "MicrosoftSqlToolsServiceLayer (24872)".

当然如果你想让自己的脚本和函数也支持模拟运行,只需要进行简单的整合。多增加一个switch参数。

function TestWhatif ([int]$num,[switch]$WhatIf) {
    If ($whatif)
    {
        Write-Host "WhatIf: 计算一个平方 $num * $num"
    }
    Else
    {
       $num * $num
    }
}

TestWhatif -WhatIf
TestWhatif 2

PS C:PowerShell> test.ps1                                                                            WhatIf: 计算一个平方 0 * 0
4

逐步确认:逐个查询

正如之前看到的那样,在使用“*”通配符时,可能会在瞬间产生许多任务。为了防止产生失误操作,可以逐个进行确认,逐个进行放行。

PS C:PowerShell> Stop-Process -Name *Micro* -Confirm                                                                   
Confirm
Are you sure you want to perform this action?
Performing the operation "Stop-Process" on target "Microsoft.Notes (14976)".
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"): N

Confirm
Are you sure you want to perform this action?
Performing the operation "Stop-Process" on target "Microsoft.ServiceHub.Controller (20708)".
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"): N

Confirm
Are you sure you want to perform this action?
Performing the operation "Stop-Process" on target "MicrosoftSearchInBing (5828)".
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"): N

Confirm
Are you sure you want to perform this action?
Performing the operation "Stop-Process" on target "MicrosoftSqlToolsServiceLayer (24872)".
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"): N

确认程序提供了6个选项:

是 – 仅继续执行操作的下一步骤。
全是 – 继续执行操作的所有步骤。
否 – 跳过此操作并继续执行下一操作。
不全是 – 跳过此操作及所有后续操作。
挂起 – 暂停当前管道并返回到命令提示符。键入“exit”可继续执行该管道。
帮助 – 提供帮助信息

自动对危险的操作进行操作

因为一些操作的危险系数会比其它的操作的危险系数高,所以Powershell脚本的开发者会对每一条命令进行风险评估。
例如Stop-Process,它会终止当前正在运行的程序或者进程。它的危险系数被设置为中等。因为正常情况下,可能会产生的不可恢复并且不可预知的危险。例如Exchange中的命令删除用户的邮件被归类为高危险性,因为邮件一旦删除那就意味着所有的邮件内容都不复存在。

你不可能改变这些风险评估,但是你可以对此作出回应。powershell的默认设置会将自动化的操作归类为危险操作,即使你没有指定-confirm参数。这一标准的设置被存储在全局变量(ConfirmPreference中。)ConfirmPreference可以对默认设置或者其它更严格的设置作出判断与回应。但是如果你将$ConfirmPreference的值设置为“None”,Powershell就不会进行操作前询问确认,即使可能面临高风险的操作。

#查看$ConfirmPreference支持的设置
[ENUM]::GetNames($ConfirmPreference.GetType())
None
Low
Medium
High
 
#查看当前的$ConfirmPreference
$ConfirmPreference
High

总结一下,就会有两种情况。

危险环境:如果你对要执行的操作的危险性不确定,最好将$ConfirmPreference 设置为high,这样可以更安全一点。
安全环境:如果你在某些情况下要执行大量无关安全的操作,为了方便起见可以将$ConfirmPreference设置为None,因为针对每个命令都去设置-confirm 为false也是一件很繁琐的事情。但是建议最好在脚本中将$ConfirmPreference设置为”None”,这样可以将放权设置在最小的风险下。

定义容错度

Powershell拥有非常强大的容错度。只要你设定好参数,即使某一条,某一行命令执行发生错误,脚本还可以继续执行。例如你在复制一大批文件,大概需要一天时间,在你执行脚本时,你去做其它事情,结果异常发生了,如果脚本不继续执行,那当你一天以后去看结果,还可能有90%的文件没有拷贝,那么这一天的时间就白白浪费了。但是如果脚本出错了继续执行,结果可能就完成了99.99%的工作,只剩下一个文件需要重新拷贝,明显提高了效率。

PS C:PowerShell> Remove-Item mossfly.com ; Write-Host "工作完成"                                                       Remove-Item : Cannot find path 'C:PowerShellmossfly.com' because it does not exist.
At line:1 char:1
+ Remove-Item mossfly.com ; Write-Host "工作完成"
+ ~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:PowerShellmossfly.com:String) [Remove-Item], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.RemoveItemCommand

工作完成

自动化变量(ErrorView的默认值为:NormalView,如果将)ErrorView设置为categoryview 异常就会只显示一行,看起来更加清爽,适合专业人士应用奥!

PS C:PowerShell> $ErrorView                                                                                            NormalView
PS C:PowerShell> $ErrorView = "categoryview"                                                                           PS C:PowerShell> Remove-Item mossfly.com ; Write-Host "工作完成"                                                       ObjectNotFound: (C:PowerShellmossfly.com:String) [Remove-Item], ItemNotFoundException
工作完成

不过,Powershell在执行某条命令是也可以指定对错误的处理模式。那就是ErrorAction。

PS C:PowerShell> Remove-Item mossfly.com -ErrorAction "Continue"; Write-Host "工作完成"                                ObjectNotFound: (C:PowerShellmossfly.com:String) [Remove-Item], ItemNotFoundException
工作完成

如果将ErrorAction的值设置为默认值Stop,发生错误下面的命令就会终止

PS C:PowerShell> Remove-Item mossfly.com -ErrorAction "Stop"; Write-Host "工作完成"                                    ObjectNotFound: (C:PowerShellmossfly.com:String) [Remove-Item], ItemNotFoundException
PS C:PowerShell>  

每次执行命令都去设定ErrorAction那也忒麻烦了。有没有全局的设置呢,那就是$ErrorActionPreference,甚至还可以针对某段函数,某个脚本设置$ErrorActionPreference

PS C:PowerShell> $ErrorActionPreference                                                                                Continue
PS C:PowerShell> $ErrorActionPreference = "Stop"                                                                       PS C:PowerShell> Remove-Item mossfly.com ; Write-Host "工作完成"                                                       ObjectNotFound: (C:PowerShellmossfly.com:String) [Remove-Item], ItemNotFoundException
PS C:PowerShell> $ErrorActionPreference = "Continue"                                                                   PS C:PowerShell> Remove-Item mossfly.com ; Write-Host "工作完成"                                                       ObjectNotFound: (C:PowerShellmossfly.com:String) [Remove-Item], ItemNotFoundException
工作完成

以下为$ErrorActionPreference-ErrorAction选项:


PS C:PowerShell> $ErrorActionPreference.GetType().FullName                                                                               System.Management.Automation.ActionPreference
PS C:PowerShell> [System.Enum]::GetNames([System.Management.Automation.ActionPreference])                              SilentlyContinue #错误不抛出,脚本也会继续执行。
Stop     #错误发生时,终止脚本执行
Continue #将错误抛出来,但是脚本会继续往下执行。
Inquire  #提供选项由用户选择Error Action。
Ignore   #直接忽略错误
Suspend  #挂起,经尝试无法设置该选项

The value Suspend is not supported for an ActionPreference variable. The provided value should be used only as a value
for a preference parameter. For more information, see the Help topic, "about_Preference_Variables."
At line:1 char:1
+ $ErrorActionPreference = "Suspend"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : MetadataError: (:) [], ArgumentTransformationMetadataException
    + FullyQualifiedErrorId : RuntimeException

识别和处理异常

如果你想对错误处理,并且输出友好的信息。须要两点:
第一,抑制内置的错误信息;
第二,一个能够发现异常是否发生的机制。
在之前的文章中我们已经知道怎样抑制内置的错误消息了,是通过$ErrorAction。你如果将$ErrorAction设置为SilentlyContinue,错误信息就不会输出了。


#$?中的错误状态
#$?变量会保存上一步操作是否成功或者失败,也就是告诉你是否发生了错误。可以写一段脚本进行测试。

Remove-Item "文件不存在" -ErrorAction "SilentlyContinue"
If (!$?) {
    "删除文件操作失败";
    break
};
"删除文件成功!"

PS C:> test.ps1
删除文件操作失败

因为文件不存在,所在Remove-Item失败,失败异常标志$?会变为$false。语句块 !$? 的值会变为$true,进而提示删除文件失败。如果你喜欢像正常的输出一样,将异常的message输出。


#$?中的错误状态
#$?变量会保存上一步操作是否成功或者失败,也就是告诉你是否发生了错误。可以写一段脚本进行测试。

Remove-Item "文件不存在" -ErrorAction "SilentlyContinue"
If (!$?) {
    "删除文件操作失败,异常信息{$error[0]}";
    break
};
"删除文件成功!"

PS C:PowerShell> .	est.ps1                                                                            删除文件操作失败,异常信息{Cannot find path 'C:PowerShell文件不存在' because it does not exist. Cannot find path 'C:UsersNB874XE文件不存在' because it does not exist. Cannot find path 'C:UsersNB874XE文件不存在' because it does not exist. The value Suspend is not supported for an ActionPreference variable. The provided value should be used only as a value for a preference parameter. For more information, see the Help topic, "about_Preference_Variables."[0]}

使用Traps

使用Traps可以捕获异常,在捕获到异常时,可以在做相应的处理。例如我们在Powershell自动化工作中,出现了异常可以发一封邮件给管理员。

Trap { "自动化工作发生异常,正在发邮件联系管理员给管理员!"}
1/$null

PS C:PowerShell> test.ps1                                                                            自动化工作发生异常,正在发邮件联系管理员给管理员!
Attempted to divide by zero.
At test.ps1:2 char:1
+ 1/$null
+ ~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], RuntimeException
    + FullyQualifiedErrorId : RuntimeException

到目前为止,并没有对异常进行处理。

Traps未处理的异常

上面的异常发生在运行时,但是有的异常发生在编译时,例如:

Trap {"我想捕获异常"}
1/0

PS C:PowerShell> test.ps1                                                                            我想捕获异常
Attempted to divide by zero.
At test.ps1:2 char:1
+ 1/0
+ ~~~
    + CategoryInfo          : NotSpecified: (:)

这次Trap没有生效,因为0为常量,该异常会在编译时被抛出,并且Traps对此无能为力。但是对于cmdlets中内部异常却可以捕获。

PS C:PowerShell> Trap {"捉个虫"};1/0                                                                                   捉个虫
Attempted to divide by zero.
At line:1 char:14
+ Trap {"捉个虫"};1/0
+              ~~~
    + CategoryInfo          : NotSpecified: (:) [], RuntimeException
    + FullyQualifiedErrorId : RuntimeException

使用Break和Continue来对异常作后续处理

Traps异常发生时使用Stop中断脚本执行

Trap { "异常发生,我想终止脚本!" }
stop-service -name "NoSuchService" -ErrorAction "Stop"

PS C:PowerShell> test.ps1                                                                            异常发生,我想终止脚本!
stop-service : Cannot find any service with service name 'NoSuchService'.
At test.ps1:2 char:1
+ stop-service -name "NoSuchService" -ErrorAction "Stop"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (NoSuchService:String) [Stop-Service], ServiceCommandException

trap语句提供了一种简单的方法来广泛地确保处理范围内的所有终止错误。要进行更细粒度的错误处理,请使用 try/catch块,其中使用catch语句定义陷阱。这些 catch语句仅适用于关联try 语句内的代码。

Try Catch

在函证体内可使用TryCatch对异常进行详细控制,并通过finally执行后续流程。
在实现时可以嵌套,在嵌套的作用域内发生的异常会被当前的Catch捕获,如没有被当前捕获则会继续向上抛。

function Test {
    param (
        $num
    )
    try {
        try {
            1 / 0
        }
        catch {
            Write-Host "我是虫子1"
        }
        1 / 0
    }
    catch {
        Write-Host "我是虫子2" + $_.Exception.Message
    }
    finally {
        $num   
    }
    
}
Test "放个虫子"

PS C:PowerShell> 	est.ps1                                                                            我是虫子1
我是虫子2 + Attempted to divide by zero.
放个虫子

错误记录:详细错误

你可以通过Powershell中的自动化变量(Error和Trap 语句块中的)_的内置变量来输出异常的详细信息。在这篇文章来深入了解Error中的记录。在Powershell控制台中一旦错误发生,异常信息会被以红色显示。
红色的的设置保存在:

$host.PrivateData.ErrorForegroundColor
# Red
$host.PrivateData.ErrorBackgroundColor
# Black

Powershell控制台输出的错误信息为文本,只包含了部分的错误信息。如果想访问Error的对象信息,可以通过以下的途径:
Redirection:将错误流重定向到一个变量;
ErrorVariable parameter : 将异常保存到一个变量中;
$Error:也会以数组的形式保存出现的所有异常。

重定向错误记录

如果你想把一个命令的执行结果重定向,使用重定向操作符>

PS C:PowerShell> dir "NoSuchDirectory" > Error.txt
dir : Cannot find path 'C:PowerShellNoSuchDirectory' because it does not exist.
At line:1 char:1
+ dir "NoSuchDirectory" > Error.txt
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:PowerShellNoSuchDirectory:String) [Get-ChildItem], ItemNotFoundExce
   ption
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

不幸的是,Error 信息并没有保存到Error.txt 因为Error信息不在标准输出流上,而在Error流上,需要使用”2>”操作符。

PS C:PowerShell> dir "NoSuchDirectory" 2> Error.txt
PS C:PowerShell> Get-Content .Error.txt
dir : Cannot find path 'C:PowerShellNoSuchDirectory' because it does not exist.
At line:1 char:1
+ dir "NoSuchDirectory" 2> Error.txt
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:PowerShellNoSuchDirectory:String) [Get-ChildItem], ItemNotFoundExce
   ption
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

将异常重定向至变量:

PS C:PowerShell> $errinfo = dir "NoSuchDirectory" 2>&1
PS C:PowerShell> $errinfo
dir : Cannot find path 'C:PowerShellNoSuchDirectory' because it does not exist.
At line:1 char:12
+ $errinfo = dir "NoSuchDirectory" 2>&1
+            ~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:PowerShellNoSuchDirectory:String) [Get-ChildItem], ItemNotFoundExce
   ption
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

PS C:PowerShell> $errinfo.Exception
Cannot find path 'C:PowerShellNoSuchDirectory' because it does not exist.

重定向有时也没必要,因为绝大多数Cmdltes都支持-ErrorVariable 参数。只要你将变量名称传递给-ErrorVariable,Powershell就会自动把出现的错误保存到这个变量中,并且这种保存机制不受ErrorAction配置的影响。

PS C:PowerShell> Remove-Item "NoSuchDirectory" -ErrorVariable ErrorStore -ErrorAction "SilentlyContinue"               PS C:PowerShell> $ErrorStore                                                                                           Remove-Item : Cannot find path 'C:PowerShellNoSuchDirectory' because it does not exist.
At line:1 char:1
+ Remove-Item "NoSuchDirectory" -ErrorVariable ErrorStore -ErrorAction  ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:PowerShellNoSuchDirectory:String) [Remove-Item], ItemNotFoundExcept
   ion
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.RemoveItemCommand

PS C:PowerShell> $ErrorStore[0].GetType()                                                                              
IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     ErrorRecord                              System.Object


PS C:PowerShell> $ErrorStore[0].GetType().FullName                                                                     System.Management.Automation.ErrorRecord
PS C:PowerShell> $ErrorStore[0] | Get-Member                                                                           

   TypeName: System.Management.Automation.ErrorRecord

Name                  MemberType     Definition
----                  ----------     ----------
Equals                Method         bool Equals(System.Object obj)
GetHashCode           Method         int GetHashCode()
GetObjectData         Method         void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System....
GetType               Method         type GetType()
ToString              Method         string ToString()
CategoryInfo          Property       System.Management.Automation.ErrorCategoryInfo CategoryInfo {get;}
ErrorDetails          Property       System.Management.Automation.ErrorDetails ErrorDetails {get;set;}
Exception             Property       System.Exception Exception {get;}
FullyQualifiedErrorId Property       string FullyQualifiedErrorId {get;}
InvocationInfo        Property       System.Management.Automation.InvocationInfo InvocationInfo {get;}
PipelineIterationInfo Property       System.Collections.ObjectModel.ReadOnlyCollection[int] PipelineIterationInfo {g...
ScriptStackTrace      Property       string ScriptStackTrace {get;}
TargetObject          Property       System.Object TargetObject {get;}
PSMessageDetails      ScriptProperty System.Object PSMessageDetails {get=& { Set-StrictMode -Version 1; $this.Except...


PS C:PowerShell> $ErrorStore[0].TargetObject                                                                           C:PowerShellNoSuchDirectory
PS C:PowerShell> $ErrorStore[0].CategoryInfo                                                                           

Category   : ObjectNotFound
Activity   : Remove-Item
Reason     : ItemNotFoundException
TargetName : C:PowerShellNoSuchDirectory
TargetType : String

从上面的例子发现ErrorVariable存储类型为数组,数组元素的类型为:ErrorRecord。另一个问题是,为什么ErrorVariable存储类型为数组?
因为有可能碰到下面的情况:

#还有一个小技巧,可以给-ErrorVariable参数前加一个“+”,代表追加存储错误,可以将一连串的错误合并在一个数组,后面可以统一进行分析处理,如下:
PS C:PowerShell> Get-Item "NOSuchDir1","NOSuchDir2","NOSuchDir3" -ErrorAction "SilentlyContinue" -ErrorVariable ErrorStore                                                                                                                     PS C:PowerShell> $ErrorStore.Count                                                                                     3
PS C:PowerShell> Get-Item "NOSuchDir1" -ErrorAction "SilentlyContinue" -ErrorVariable +ErrorStore                      PS C:PowerShell> $ErrorStore.Count                                                                                     4

通过$Error查看错误信息

即使你忘记使用ErrorVariable参数去收集异常,Powershell也会自动收集异常,存储在自动化变量$Error中。$Error同样也是数组,每次会把最后发生的异常保存在索引为0的位置,所以查看最后的一个异常可以使用(Error[0]。有没有一种可能,这个数组拼命地存储异常,也会占用内存资源啊。所以可以使用`)Error.Clear(),清空异常。即使你不清理异常,这个数组也有最大值的,最大值存储在$MaximumErrorCount`自动化变量中,默认为256.

PS C:PowerShell> $MaximumErrorCount                                                                                    256
PS C:PowerShell> $Error.Count                                                                                          25
PS C:PowerShell> $Error[0]                                                                                             Get-Item : Cannot find path 'C:PowerShellNOSuchDir1' because it does not exist.
At line:1 char:1
+ Get-Item "NOSuchDir1" -ErrorAction "SilentlyContinue" -ErrorVariable  ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:PowerShellNOSuchDir1:String) [Get-Item], ItemNotFoundException
    +
Trap
{
"完蛋了, 貌似出错了: $($_.Exception.Message)";
Continue;
}
Remove-Item "Books" -ea Stop
Write-Host "不管他继续整"

PS C:PowerShell> test.ps1                                                                            完蛋了, 貌似出错了: Cannot find path 'C:PowerShellBooks' because it does not exist.
不管他继续整

通过Traps查看错误信息

最后Trap语句中也提供了一种存储错误的方式,自动化变量(_ ,你可以对)_进行深加工,定制你自己的异常信息。

trap {
    Write-Host  "Error trapped"
    Write-Host $_.Exception.Message;
    Continue
}
stop-service -name "NoSuchService" -ErrorAction Stop

"Function completed."

PS C:PowerShell> test.ps1                                                                            Error trapped
Cannot find any service with service name 'NoSuchService'.
Function completed.
PS C:PowerShell>

理解异常

“Exception”每天都会遇到。但是在当今的IT世界里,”Errors”和”Bugs”往往是可以避免的。当一个error发生时,程序会通过抛出一个异常来弥补。
作为上层的调用者,最好能对捕获和处理潜在的异常,否则在Powershell控制台上会红色高亮显示出错。
但是在Powershell中$Error中元素的类型为:错误记录。每个Error元素的中包含属性Exception,这个Exception和.NET中的Exception对应。

PS C:PowerShell> $Error | Where-Object {$_.Exception -ne $null} | Foreach {$_.Exception.GetType().FullName}            System.Management.Automation.RuntimeException
System.Management.Automation.RuntimeException
Microsoft.PowerShell.Commands.ServiceCommandException
Microsoft.PowerShell.Commands.ServiceCommandException
Microsoft.PowerShell.Commands.ServiceCommandException
Microsoft.PowerShell.Commands.ServiceCommandException
System.Management.Automation.ItemNotFoundException
System.Management.Automation.ItemNotFoundException
System.Management.Automation.ItemNotFoundException

处理特定的异常

在使用Trap处理异常时,可以像.NET中的Try一样捕获特定的异常,并且做特定的处理。

Trap [System.DivideByZeroException] {
    "除数为空!";
    Continue
}
Trap [System.Management.Automation.ParameterBindingException] {
    "参数不正确!";
    Continue
}
     
Trap [System.Net.WebException] {
    "网络异常!"
    Continue
}
     
1 / $null
Dir -MacGuffin
$wc = new-object System.Net.WebClient
$wc.DownloadFile("http://www.mossfly.com/powershell.txt", "C:PowerShellps.txt")
     
PS C:PowerShell> 	est.ps1                                                                            除数为空!
参数不正确!
网络异常!

抛出自定义异常信息

如果你想抛出自己的异常信息,可以通过Throw关键字,下面的例子验证第二个参数的传入的值,如果传入的值为NULL,抛出异常。

Function FuncTest($a, $b) {
    if ($b -eq $null) {
        throw "参数b 不能为空!"
    }
    "{0}+{1}={2}" -f $a, $b, ($a + $b)
}
FuncTest -a 10

PS C:PowerShell> 	est.ps1                                                                            参数b 不能为空!
At 	est.ps1:3 char:9
+         throw "参数b 不能为空!"
+         ~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (参数b 不能为空!:String) [], RuntimeException
    + FullyQualifiedErrorId : 参数b 不能为空!

上面函数的参数检查写在参数定义中效果会更好,即给参数设定默认值,默认的值就是抛出一个异常,如果没有传入的参数,会以默认值为准,也就是抛出异常。

Function Func-Test($a, $b = $(throw "参数B 不能为空!")) {
    "{0}+{1}={2}" -f $a, $b, ($a + $b)
}
Func-Test -a 10 -b 9
Func-Test -a 10

PS C:PowerShell> 	est.ps1                                                                            10+9=19
参数B 不能为空!
At 	est.ps1:1 char:31
+ Function Func-Test($a, $b = $(throw "参数B 不能为空!")) {
+                               ~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (参数B 不能为空!:String) [], RuntimeException
    + FullyQualifiedErrorId : 参数B 不能为空!

在函数中捕获异常

决定异常产生后脚本是继续执行还是停止执行与ErrorAction和Traps有关。因为Traps运行在ErrorAction之后,即使ErrorAction的参数为Stop,也有可能被Trap的设置覆盖。
因为系统中$ErrorActionPreference的默认值为Continue,下面函数中的脚本遇到错误时,会继续执行。但是Trap只会执行一次。

Function Test-Func
{
Trap { "Trap 到异常了." }
1/$null
Get-Process "NoSuchProcess"
Dir MossFly:
}
Test-Func

PS C:PowerShell> 	est.ps1                                                                            Trap 到异常了.
Attempted to divide by zero.
At 	est.ps1:4 char:1
+ 1/$null
+ ~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], RuntimeException
    + FullyQualifiedErrorId : RuntimeException

Get-Process : Cannot find a process with the name "NoSuchProcess". Verify the process name and call the cmdlet again.
At 	est.ps1:5 char:1
+ Get-Process "NoSuchProcess"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (NoSuchProcess:String) [Get-Process], ProcessCommandException
    + FullyQualifiedErrorId : NoProcessFoundForGivenName,Microsoft.PowerShell.Commands.GetProcessCommand

Dir : Cannot find drive. A drive with the name 'MossFly' does not exist.
At 	est.ps1:6 char:1
+ Dir MossFly:
+ ~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (MossFly:String) [Get-ChildItem], DriveNotFoundException
    + FullyQualifiedErrorId : DriveNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

因为如果ErrorAction没有设置为Stop,ErrorAction不会向被调用者报告每个异常。一旦将ErrorAction设置为Stop,则会每次报告异常,每次都会被Trap到。

Function Test-Func {
    Trap {
        "Trap 到异常了." 
        #Continue
    }
    1 / $null
    Get-Process "NoSuchProcess" -ErrorAction Stop
    Dir MossFly: -ErrorAction Stop
}
Test-Func


PS C:PowerShell> 	est.ps1                                                                            Trap 到异常了.
Attempted to divide by zero.
At 	est.ps1:6 char:5
+     1 / $null
+     ~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], RuntimeException
    + FullyQualifiedErrorId : RuntimeException

Trap 到异常了.
Get-Process : Cannot find a process with the name "NoSuchProcess". Verify the process name and call the cmdlet again.
At 	est.ps1:7 char:5
+     Get-Process "NoSuchProcess" -ErrorAction Stop
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (NoSuchProcess:String) [Get-Process], ProcessCommandException
    + FullyQualifiedErrorId : NoProcessFoundForGivenName,Microsoft.PowerShell.Commands.GetProcessCommand

Trap 到异常了.
Dir : Cannot find drive. A drive with the name 'MossFly' does not exist.
At 	est.ps1:8 char:5
+     Dir MossFly: -ErrorAction Stop
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (MossFly:String) [Get-ChildItem], DriveNotFoundException
    + FullyQualifiedErrorId : DriveNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

如果在Trap中使用了Continue,可以屏蔽默认的异常输出,也可以定制自己的异常。

Function Test-Func {
    Trap {
        "Trap 到异常了.{$_.Exception.Message}" 
        Continue
    }
    1 / $null
    Get-Process "NoSuchProcess" -ErrorAction Stop
    Dir MossFly: -ErrorAction Stop
}
Test-Func

PS C:PowerShell> 	est.ps1                                                                            Trap 到异常了.{Attempted to divide by zero..Exception.Message}
Trap 到异常了.{Cannot find a process with the name "NoSuchProcess". Verify the process name and call the cmdlet again..Exception.Message}
Trap 到异常了.{Cannot find drive. A drive with the name 'MossFly' does not exist..Exception.Message}

如果你想让脚本在第一次遇到异常就停止工作,可以在Trap中添加Break,如下:

Function Test-Func {
    Trap {
        "Trap 到异常了.{$_.Exception.Message}" 
        Break
    }
    1 / $null
    Get-Process "NoSuchProcess" -ErrorAction Stop
    Dir MossFly: -ErrorAction Stop
}
Test-Func

PS C:PowerShell> 	est.ps1                                                                            Trap 到异常了.{Attempted to divide by zero..Exception.Message}
Attempted to divide by zero.
At 	est.ps1:6 char:5
+     1 / $null
+     ~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : RuntimeException

如果将Trap放在函数的调用者中,并跟上Continue,这样函数中第一条错误发生,脚本就会终止执行,并且屏蔽Powershell默认的错误输出。

function Caller {
    Trap {
        "Trap Error: $($_.Exception.Message)";
        Continue
    }
    Test-Func
}
function Test-Func {
    1 / $null
    Get-Process "nosuchthing" -ea Stop
    Dir xyz: -ea Stop
}
Caller

PS C:PowerShell> 	est.ps1                                                                            Trap Error: Attempted to divide by zero.

在Powershell中还可以嵌套函数,也就是函数中定义函数。Trap 定义在外部函数,内部函数的异常,会在外部被捕获。此时的内部函数就像一连串的脚本块。

function Caller {
    Trap {
        "Trap Error: $($_.Exception.Message)";
        Continue
    }
    function Test-Func {
        1 / $null
        Get-Process "nosuchthing" -ea Stop
        Dir xyz: -ea Stop
    }
    Test-Func
}

Caller

PS C:PowerShell> 	est.ps1                                                                            Trap Error: Attempted to divide by zero.

其实也没有必要定义内部函数,因为脚本块也可以做到这一点。可以通过&符号定义脚本块。

function Caller {
    Trap {
        "Trap Error: $($_.Exception.Message)";
        Continue
    }
    & {
        1 / $null
        Get-Process "nosuchthing" -ea Stop
        Dir xyz: -ea Stop
    }
}

Caller

PS C:PowerShell> 	est.ps1                                                                            Trap Error: Attempted to divide by zero.

调试信息

在脚本和函数中使用断点停止脚本执行,方便调试Powershell脚本的异常和错误。如果逻辑不是很复杂,直接输出一些帮助信息,也可以实现调试的目的。最简单的使用调试输出命令,如果你在控制台输入:

Write-Debug “输出一行调试信息,看看发生了神马?”

你可能会很奇怪,命令没有输出任何信息,感觉好像没做什么。事实上,Write-debug 的行为受$DebugPreference的影响,$DebugPreference值默认为SilentlyContinue,此时Write-debug不会输出任何信息。

$DebugPreference可选的配置如下:

SilentlyContinue:调试关闭
Stop:输出调试信息,终止脚本执行
Continue:输出调试信息,继续执行脚本
Inquire:输出调试信息,询问用户是否继续执行。

可以通过下面的例子对比:

PS C:PowerShell> $DebugPreference="silentlycontinue"                                                                   PS C:PowerShell> Write-Debug "放个虫"                                                                                  PS C:PowerShell> $DebugPreference="Stop"                                                                               PS C:PowerShell> Write-Debug "还放个虫"                                                                                DEBUG: 还放个虫
Write-Debug : The running command stopped because the preference variable "DebugPreference" or common parameter is set
to Stop: 还放个虫
At line:1 char:1
+ Write-Debug "还放个虫"
+ ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (:) [Write-Debug], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : ActionPreferenceStop,Microsoft.PowerShell.Commands.WriteDebugCommand

PS C:PowerShell> $DebugPreference="Inquire"                                                                            PS C:PowerShell> Write-Debug "还还放个虫"                                                                              DEBUG: 还还放个虫

Confirm
Continue with this operation?
[Y] Yes  [A] Yes to All  [H] Halt Command  [S] Suspend  [?] Help (default is "Y"): N
[Y] Yes  [A] Yes to All  [H] Halt Command  [S] Suspend  [?] Help (default is "Y"): Y

除了自动化变量 $DebugPreference能配置调试信息输出的方式和行为,还有一些自动化变量可以实现类似的目的。

这里稍作回顾和总结:

ConfirmPreference:设置提问确认的级别。
DebugPreference:设置debug信息的显示级别。
ErrorActionPreference:设置发生错误后的执行动作。
ErrorView:设置错误的显示模式。
ProgressPreference:设置进度条的显示模式。
ReportErrorShowExceptionClass:显示异常所在的类。
ReportErrorShowInnerException:显示异常内部异常信息。
ReportErrorShowSource:显示异常的来源。
ReportErrorShowStackTrace:显示异常的错误跟踪栈。
VerbosePreference:设置详细信息的显示模式。
WarningPreference:设置警告信息的显示模式。

单步跟踪:逐行执行

我们可以在 Powershell ISE 中通过F9断点执行Powershell脚本。但是即使没有ISE也可以单步跟踪。
只需要Set-PSDebug -step,Powershell会每只行一段代码,就会向用户询问是否继续执行。

function Test {
    param (
        $num
    )
    try {
        try {
            1 / 0
        }
        catch {
            Write-Host "我是虫子1"
        }
        1 / 0
    }
    catch {
        Write-Host "我是虫子2" + $_.Exception.Message
    }
    finally {
        $num   
    }
    
}
Test "放个虫子"

PS C:PowerShell> Set-PSDebug -Step                                                                                     PS C:PowerShell> test.ps1                                                                            
Continue with this operation?
   1+  >>>> test.ps1
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"): y
DEBUG:    1+  >>>> test.ps1

Continue with this operation?
  22+  >>>> Test "放个虫子"
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"): y
DEBUG:   22+  >>>> Test "放个虫子"

Continue with this operation?
   1+ function Test  >>>> {
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"): y
DEBUG:    1+ function Test  >>>> {

Continue with this operation?
   7+              >>>> 1 / 0
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"): y
DEBUG:    7+              >>>> 1 / 0

Continue with this operation?
  10+              >>>> Write-Host "我是虫子1"
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"): y
DEBUG:   10+              >>>> Write-Host "我是虫子1"
我是虫子1

Continue with this operation?
  12+          >>>> 1 / 0
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"): y
DEBUG:   12+          >>>> 1 / 0

Continue with this operation?
  15+          >>>> Write-Host "我是虫子2" + $_.Exception.Message
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"): y
DEBUG:   15+          >>>> Write-Host "我是虫子2" + $_.Exception.Message
我是虫子2 + Attempted to divide by zero.

Continue with this operation?
  18+          >>>> $num
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"): y
DEBUG:   18+          >>>> $num
放个虫子

Continue with this operation?
  21+  >>>> }
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"): y
DEBUG:   21+  >>>> }
原文地址:https://www.cnblogs.com/MerLin-LiuNian/p/15293675.html