mkl*_*nt0 5 error-handling powershell powershell-remoting powershell-core
这个自我回答的问题是从 PowerShell (Core) v7.3.x 开始编写的,旨在解决以下症状:
有时,使用或似乎都无法有效地通过/语句捕获和处理错误。-ErrorAction -Stop$ErrorActionPreference = 'Stop'trycatch
这是两个例子:
Get-NetAdapter$ErrorActionPreference = 'Stop' 从脚本或函数中忽略(从全局范围以外的范围):
# Run on Windows. Affects both PowerShell editions.
# The `catch` block isn't triggered, and the (still non-terminating) error prints.
& { # Simulate running in a child scope.
$ErrorActionPreference = 'Stop'
try {
Get-NetAdapter nosuch
}
catch {
"Should get here, but don't."
}
}
Run Code Online (Sandbox Code Playgroud)
-ErrorAction Stop( Get-NetAdapter nosuch -ErrorAction Stop)确实可以按预期工作。 [这仅是PowerShell (Core) v7.4 预览版中的一个错误]Get-AppLockerFileInformation忽略非全局和;的使用。这里仅显示后者:$ErrorActionPreference = 'Stop' ErrorAction Stop
# Run on Windows. Affects PowerShell (Core) v7+ only
# The `catch` block isn't triggered, and the (still non-terminating) error prints.
try {
Get-AppLockerFileInformation NoSuch.exe -ErrorAction Stop
}
catch {
"Should get here, but don't in PowerShell (Core)."
}
Run Code Online (Sandbox Code Playgroud)
所以问题是:
什么决定了何时-ErrorAction和/或$ErrorActionPreference无效?
当它们不可用时,有哪些解决方法可用?
两个相关因素决定调用给定命令时是否有效-ErrorAction和/或无效:$ErrorActionPreference
从根本上来说,由于 PowerShell 作用域的工作方式,托管在脚本模块中的命令(即在 PowerShell 中实现的命令,而不是通过编译的 .NET 程序集)不会看到调用者的首选项变量,包括,但从全局$ErrorActionPreference作用域调用时除外,当您从(非点源)脚本或函数调用时,情况并非如此。
这个非常不幸的设计限制是GitHub 问题 #4568的主题。上述问题还提到了该PreferenceVariables模块,脚本模块作者可以使用该模块来解决该限制(有关详细信息,请参阅此博客文章)。
对于调用者,在每次调用的基础上使用(-ErrorAction Stop即相应的公共参数)是受到尊重的,但并不完全等效,因为它也不适用于语句终止错误,因此另外明确要求将后者转换为脚本- 使用/ with或等效语句终止(致命) - 请参阅下面的详细信息以及通过(临时)全局设置的替代解决方法。trycatchthrowtrap$ErrorActionPreference
使用隐式远程处理的模块(根据定义也是脚本模块)可能会引入额外的复杂性:
重要提示:以下内容不再适用于 Windows PowerShell 5.1 / PowerShell (Core) 7+ 或可能仅影响选定的模块- 我不知道是哪个;我有旧的注释表明隐式远程处理 Exchange cmdlet 在 Windows PowerShell 5中受到影响;如果您有更多信息,请告诉我们。
对于来自此类模块的命令,even-ErrorAction Stop绝对无效,因为此参数被传播到远程执行命令,并且根据设计,远程发生的终止错误会在本地转换为非终止错误。
当针对多个远程机器时,例如viaInvoke-Command的-ComputerName参数,远程发生的终止错误不应该终止整个多机操作,因此这种远程终止错误被转换为本地非终止错误。
[一般情况下不再正确]这种逻辑也适用于隐式远程处理的场景- 尽管根据定义,只有一台远程计算机成为目标。
虽然使用隐式远程处理通常需要显式操作(例如使用 cmdlet Import-PSSession),但在一种情况下,默认情况下会自动使用隐式远程处理:
在PowerShell (Core) 7+中,如果您尝试使用仅与 Windows PowerShell 兼容的模块中的命令,则默认情况下会通过Windows PowerShell 兼容性功能自动导入关联的模块,这涉及通过隐藏的powershell.exe子进程进行通信。
虽然这在技术上不是远程处理,但考虑到一切都发生在本地计算机上,底层跨进程通信机制与真正的远程处理相同,顺便说一句,这也会降低类型保真度- 请参阅此答案。
将这种远程发生的、总是非终止的错误转变为可以用/捕获的终止错误的唯一方法是(暂时)将变量的全局化身设置为,如下所示。trycatch$ErrorActionPreference'Stop'
解决方法:
要么:在每次调用的基础上,使用-ErrorAction Stop而不是$ErrorActionPreference = 'Stop', 与try/catch或结合使用以获得完全的鲁棒性trap,以确保(相对罕见的)语句终止错误也转换为脚本终止(致命)错误:
try {
Get-NetAdapter nosuch -ErrorAction Stop
}
catch {
Write-Verbose -Verbose "OK, caught: $_"
# If desired, re-throw to make script-terminating (fatal)
throw
}
# ------------------------
# ALTERNATIVE, via `trap`:
trap {
Write-Verbose -Verbose "OK, caught: $_"
# If desired, use `break` to ensure that the error is script-terminating (fatal).
# Use `continue` to quietly continue.
break
}
Get-NetAdapter nosuch -ErrorAction Stop
Run Code Online (Sandbox Code Playgroud)
或者:临时设置,即$global:ErrorActionPreference = 'Stop'全局设置偏好变量。
& { # Simulate running in a child scope.
$prevGlobalPref = $global:ErrorActionPreference
$global:ErrorActionPreference = 'Stop'
try {
Get-AppLockerFileInformation NoSuch.exe
}
catch {
Write-Verbose -Verbose "OK, caught: $_"
# If desired, re-throw to make script-terminating (fatal)
throw
} finally {
$global:ErrorActionPreference = $prevGlobalPref
}
}
Run Code Online (Sandbox Code Playgroud)
至于如何判断给定的命令是否来自任一模块类型(常规与隐式远程处理):
以下辅助函数Get-CommandModuleType告诉您给定命令来自什么类型的模块,输出指示'Script'常规(非隐式远程)脚本模块和'ImplicitRemoting'隐式远程脚本模块;有关详细信息,请参阅源代码中基于注释的帮助。
示例调用,来自 Windows 上的 PowerShell(核心):
# -> 'Script'
Get-CommandModuleType Get-NetAdapter
# -> 'ImplicitRemoting'
Get-CommandModuleType Get-AppLockerFileInformation
Run Code Online (Sandbox Code Playgroud)
function Get-CommandModuleType {
<#
.SYNOPSIS
Indicates a given command's module type.
.DESCRIPTION
Aliases are automatically resolved to their underlying commands.
Note:
* The output is a *string* that is one of the following:
* '(None)', If the command is a function *not from a module*.
* 'ImplicitRemoting' for an implicitly remoting module.
* 'Script' for regular script modules (that don't use implicit remoting)
* 'Binary' if the command is a *binary cmdlet* - even if its part of
a module that is a *script* module, due to *also* containing
PowerShell code. Strictly speaking, binary cmdlets are themselves
modules.
* Not all script modules report themselves as such via the underlying
module's manifest; some of them report just 'Manifest'. Conversely,
a script module can also contain *binary* cmdlets.
To see the actual value from the module definition, use the -Raw switch.
.EXAMPLE
Get-CommandModuleType Get-NetAdapter
Reports the *de facto* type of the Get-NetAdapter command.
.EXAMPLE
Get-CommandModuleType Get-NetAdapter -Raw
Reports the *formal* type of the module that hosts the Get-NetAdapter, as
specified in its manifest.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string] $CommandName,
[switch] $Raw
)
$command = Get-Command $CommandName
if ($command -and $command.ResolvedCommand) {
$command = $command.ResolvedCommand
Write-Verbose "Resolved command is: $command"
}
if ($module = $command.Module) {
Write-Verbose "Source module is: $module; command type is $($command.CommandType)"
if (-not $Raw -and $command.CommandType -in 'Function', 'Filter') {
# A function from a script module
if ($module.PrivateData.ImplicitRemoting) {
# an implicitly remoting module
'ImplicitRemoting'
}
else {
'Script'
}
}
elseif (-not $Raw -and $command.CommandType -eq 'Cmdlet') {
'Binary'
}
else {
if ($Raw) { Write-Verbose 'Reporting raw module type, i.e as manifested.' }
$module.ModuleType.ToString()
}
}
else {
'(None)'
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
360 次 |
| 最近记录: |