【问题标题】:PowerShell try-catch loses repeated access errorsPowerShell tr​​y-catch 丢失重复的访问错误
【发布时间】:2019-10-25 12:29:29
【问题描述】:

Try-catch 似乎无法可靠地捕获所有错误。 get-ChildItem 上的 Try-catch 不会报告在 try-catch 之外报告的所有访问错误。

编辑:try-catch 不可靠是不正确的,它只报告一个错误是有充分理由的。请参阅我对已接受答案的评论,了解我对这个问题的误解。

在运行 PowerShell 5.1 的 Windows 10 Pro 64 中,此脚本:

$sScriptName  = "ErrorTest.ps1"

write-host ("I will get the file structure of ""C:\Windows\System32"", but this yields two access-denied errors:")

$aoFileSystem = @(get-ChildItem "C:\Windows\System32" -recurse -force)

write-host ("I will now do it again and trap for those errors. But I only get one of them:")

try {$aoFileSystem = @(get-ChildItem $sPath -recurse -force -ErrorAction Stop)}
catch [System.UnauthorizedAccessException]
   {$sErrorMessage = $_.ToString()
    write-host ("System.UnauthorizedAccessException: " + $sErrorMessage.substring(20, $sErrorMessage.length - 32))}

以管理员身份在 ISE 中运行,得到以下输出:

PS C:\WINDOWS\system32> D:\<path>\ErrorTest.ps1
I will get the file structure of "C:\Windows\System32", but this yields two access-denied errors:
get-ChildItem : Access to the path 'C:\Windows\System32\config\systemprofile\AppData\Local\Microsoft\Windows\INetCache\Content.IE5' is denied.
At D:\<path>\ErrorTest.ps1:5 char:19
+ ... aoFileSystem = @(get-ChildItem "C:\Windows\System32" -recurse -force)
+                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : PermissionDenied: (C:\Windows\Syst...che\Content.IE5:String) [Get-ChildItem], UnauthorizedAccessException
    + FullyQualifiedErrorId : DirUnauthorizedAccessError,Microsoft.PowerShell.Commands.GetChildItemCommand

get-ChildItem : Access to the path 'C:\Windows\System32\LogFiles\WMI\RtBackup' is denied.
At D:\<path>\ErrorTest.ps1:5 char:19
+ ... aoFileSystem = @(get-ChildItem "C:\Windows\System32" -recurse -force)
+                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : PermissionDenied: (C:\Windows\Syst...es\WMI\RtBackup:String) [Get-ChildItem], UnauthorizedAccessException
    + FullyQualifiedErrorId : DirUnauthorizedAccessError,Microsoft.PowerShell.Commands.GetChildItemCommand

I will now do it again and trap for those errors. But I only get one of them:
System.UnauthorizedAccessException: C:\WINDOWS\system32\config\systemprofile\AppData\Local\Microsoft\Windows\INetCache\Content.IE5

PS C:\WINDOWS\system32> 

我想记录访问错误,但是当我尝试捕获它们时,我只得到第一个。当我诱捕他们时,我需要做什么才能让第二个出现?

编辑:我收到一条消息,告诉我我需要编辑我的问题以解释它与Can PowerShell trap errors in GetChildItem and continue looping? 的不同之处。因此,我将在此重复我在回应 Scepticalist 下面的评论时所说的话。这个问题是针对get-ChildItemForEach 循环中的。我的问题不涉及这样的循环。尽管如此,我还是从那里接受的答案中尝试了一些东西,使用-ErrorAction SilentlyContinue,但这隐藏了错误而不捕获它们。但是,在我找到并即将发布的解决方案中,我确实将-ErrorAction SilentlyContinue-ErrorVariable 结合使用。

【问题讨论】:

  • 这里已经回答了这个问题:stackoverflow.com/questions/6942747/…
  • @Sceptalist :不,我看过了。那是在ForEach 循环中的get-ChildItem。我的问题不涉及这样的循环。尽管如此,我还是在那里尝试了接受的答案,即使用-ErrorAction SilentlyContinue,这只会隐藏错误,不会捕获它们。
  • 那么我怀疑做你需要的唯一方法是在你去的时候构建一个新数组,标记被拒绝的文件。
  • 那为什么还要使用 try-catch 呢?只需使用$error 变量即可捕获相同的数据。

标签: powershell error-handling


【解决方案1】:
  • Get-ChildItem "C:\Windows\System32" -recurse -force 发出的错误是非终止错误,并且根据定义,这些错误不会终止命令,单个命令可能会发出多个此类错误。

    • cmdlet 发出的所有非终止错误都可以在指定变量中捕获,您将其名称传递给 -ErrorVariable 通用参数。
    • 此外,所有错误(包括非终止和终止错误)默认记录在会话全局自动$Error 集合变量中。
  • try / catch 只对terminating 错误起作用,因此默认情况下它对只发出非终止错误的命令没有影响,例如Get-ChildItem "C:\Windows\System32" -recurse -force

    • 您可以通过添加-ErrorAction Stop(使用-ErrorAction 通用参数)指示命令将遇到的第一个非终止错误转换为终止错误,在这种情况下,包含try / catch 确实捕获了错误,但请注意,无论哪种方式遇到第一个错误后,执行都会停止。

警告:有两种类型的终止错误(事实上可能存在历史事故);虽然 两种 类型都可以用 try / catch 捕获,但它们的默认行为不同:

  • 语句-终止错误,仅终止当前语句,并在发出错误消息后默认继续执行脚本;通常,如果遇到不限于手头输入且不允许它们继续处理进一步输入的错误,这些错误将由(编译的)cmdlet 发出。

  • 脚本-终止错误(运行空间终止错误),默认情况下完全中止处理。使用 Throw 语句的 PowerShell 代码会生成此类错误,将 -ErrorAction Stop 传递给发出非终止错误的命令也是如此。

    • 警告:虽然-ErrorAction Stop语句终止错误没有影响,但看似等效的首选项变量设置$ErrorActionPreference = 'Stop' ,意外地将语句-终止错误提升为脚本-终止错误。

进一步阅读:

  • 如需全面了解 PowerShell(极其复杂)的错误处理,请参阅 this GitHub docs issue

  • 关于在创作命令时何时报告终止与非终止错误,请参阅this answer

【讨论】:

  • 感谢您非常清楚地解释我的愚蠢错误。我现在看到我没有足够仔细地阅读 ErrorAction 文档 (docs.microsoft.com/en-us/powershell/module/…)。它说,“-ErrorAction:Stop 显示错误消息并停止执行命令。”所以当然它只会发现一个错误,因为语句在到达第二个之前就停止执行了!所以我很欣慰知道 try-catch 的行为是正确的,我的错误想法是不正确的。
【解决方案2】:

我对@9​​87654325@ 参数进行了一些研究,然后发现了ErrorVariable 参数。 (两者都是common parameters,因此它们不会出现在get-ChildItem 的文档中。)从那里,我能够弄清楚如何正确捕获错误。

下面的新脚本显示了执行此操作的四种方法。方法 2 和 3 工作正常。方法 4 不起作用,因为它 incorrectly attempts to use the parameter InputObject in ForEach-object。方法一,原来的try-catch方法,不行,我还是不知道为什么。这很麻烦,因为错误捕获按预期工作很重要。如果我不知道会出现两个错误,我就不会知道 try-catch 没有给我正确的输出。

我不接受这个(我自己的)答案,因为,虽然下面的脚本显示了两种正确捕获这些错误的方法,如果有人能解释为什么要尝试,那就更好了-catch 对此不起作用。

新脚本:

$sScriptName  = "ErrorTest.ps1"

$sPath = "C:\Windows\System32"

write-host ("I will get the file structure of """ + $sPath + """, but this yields two access-denied errors:")
$aoFileSystem = @(get-ChildItem $sPath -recurse -force)

write-host ("I will now do it again and trap for those errors.")

write-host ("`r`nMethod 1: Original method using try-catch (incorrect results; only finds one of the two errors; why?):")
try {$aoFileSystem = @(get-ChildItem $sPath -recurse -force -ErrorAction Stop)}
catch [System.UnauthorizedAccessException]
   {$sErrorMessage = $_.ToString()
    write-host ("System.UnauthorizedAccessException: " + $sErrorMessage.substring(20, $sErrorMessage.length - 32))}

write-host ("`r`nGet array for Methods 2 to 4.")
$aoFileSystem = @(get-ChildItem $sPath -recurse -force -ErrorAction SilentlyContinue -ErrorVariable aoChildItemError)

write-host ("`r`nMethod 2: Output by piping to ForEach-object (correct results):")
$aoChildItemError | 
    ForEach-object `
       {$oErrorRecord = $_
        write-host ($oErrorRecord.CategoryInfo.reason + ": """ + $oErrorRecord.TargetObject + """")}

write-host ("`r`nMethod 3: Output by for loop (correct results):")
for ($nCount = 0; $nCount -lt $aoChildItemError.count; $nCount++)
   {$oErrorRecord = $aoChildItemError[$nCount]
    write-host ($oErrorRecord.CategoryInfo.reason + ": """ + $oErrorRecord.TargetObject + """")}

write-host ("`r`nMethod 4: Output by ForEach-object loop without pipeline (incorrect results because it incorrectly attempts to use the parameter ""InputObject"" in ""ForEach-object""):")
ForEach-object -InputObject $aoChildItemError `
   {$oErrorRecord = $_
    write-host ($oErrorRecord.CategoryInfo.reason + ": """ + $oErrorRecord.TargetObject + """")}

新输出:

PS C:\WINDOWS\system32> D:\_\z-temp\ErrorTest.ps1
I will get the file structure of "C:\Windows\System32", but this yields two access-denied errors:
get-ChildItem : Access to the path 'C:\Windows\System32\config\systemprofile\AppData\Local\Microsoft\Windows\INetCache\Content.IE5' is denied.
At D:\_\z-temp\ErrorTest.ps1:6 char:19
+ $aoFileSystem = @(get-ChildItem $sPath -recurse -force)
+                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : PermissionDenied: (C:\Windows\Syst...che\Content.IE5:String) [Get-ChildItem], UnauthorizedAccessException
    + FullyQualifiedErrorId : DirUnauthorizedAccessError,Microsoft.PowerShell.Commands.GetChildItemCommand

get-ChildItem : Access to the path 'C:\Windows\System32\LogFiles\WMI\RtBackup' is denied.
At D:\_\z-temp\ErrorTest.ps1:6 char:19
+ $aoFileSystem = @(get-ChildItem $sPath -recurse -force)
+                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : PermissionDenied: (C:\Windows\Syst...es\WMI\RtBackup:String) [Get-ChildItem], UnauthorizedAccessException
    + FullyQualifiedErrorId : DirUnauthorizedAccessError,Microsoft.PowerShell.Commands.GetChildItemCommand

I will now do it again and trap for those errors.

Method 1: Original method using try-catch (incorrect results; only finds one of the two errors; why?):
System.UnauthorizedAccessException: C:\Windows\System32\config\systemprofile\AppData\Local\Microsoft\Windows\INetCache\Content.IE5

Get array for Methods 2 to 4.

Method 2: Output by piping to ForEach-object (correct results):
UnauthorizedAccessException: "C:\Windows\System32\config\systemprofile\AppData\Local\Microsoft\Windows\INetCache\Content.IE5"
UnauthorizedAccessException: "C:\Windows\System32\LogFiles\WMI\RtBackup"

Method 3: Output by for loop (correct results):
UnauthorizedAccessException: "C:\Windows\System32\config\systemprofile\AppData\Local\Microsoft\Windows\INetCache\Content.IE5"
UnauthorizedAccessException: "C:\Windows\System32\LogFiles\WMI\RtBackup"

Method 4: Output by ForEach-object loop without pipeline (incorrect results because it incorrectly attempts to use the parameter "InputObject" in "ForEach-object"):

UnauthorizedAccessException UnauthorizedAccessException : " C:\Windows\System32\config\systemprofile\AppData\Local\Microsoft\Windows\INetCache\Content.IE5 C:\Window
s\System32\LogFiles\WMI\RtBackup "

PS C:\WINDOWS\system32>  

【讨论】:

  • foreach-object 失败,因为您将整个对象数组作为一个对象处理。您想使用管道来处理数组的每个元素。使用$aoChildItemError | Foreach-Object { ..... }
  • @AdminOfThings :我接受了您通过管道执行此操作的建议,并将这种方式添加到脚本中。但这并不能解释为什么没有管道的ForEach 不起作用。 ForEach 应该分别对输入数组的每个元素执行其处理块,但它以某种方式在此处合并数组的元素。我想了解原因并解决它。
  • 在之前的评论中,ForEach = ForEach-object
  • $array | foreach-object 将作用于单个元素。
  • 我现在已经单独发布了这些问题:stackoverflow.com/questions/58589314/…
【解决方案3】:

很简单!您在第二个目录访问语句中指定了参数 -ErrorAction Stop。将其切换到 -ErrorAction Continue,您会收到两条错误消息。

【讨论】:

  • 你确定吗?如果我尝试这样做,我会得到 2 个未捕获的错误。据我了解,主题启动者想要捕获这些错误......
  • @PeterPesch 的评论与我得到的一致。我很高兴尝试这个简单的解决方案,但是当我在脚本中将“停止”更改为“继续”时,结果是 try-catch 给我的输出与我从 get-ChildItem 得到的输出相同,而没有 try-catch,即,两个错误都未被捕获。所以这不是正确的解决方案。
猜你喜欢
  • 1970-01-01
  • 2011-10-10
  • 2020-02-09
  • 2022-12-03
  • 1970-01-01
  • 2015-12-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多