PowerShell try-catch loses repeated access errors - powershell

Try-catch appears to not reliably trap all errors. Try-catch on get-ChildItem does not report all access errors that are reported outside of try-catch.
Edit: It is not true that try-catch is being unreliable, and there is a sound reason why it reported only one error. See my comment to the accepted answer to see my misunderstanding behind this question.
In Windows 10 Pro 64, running PowerShell 5.1, this script:
$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))}
running in the ISE as administrator, gets this output:
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>
I want to log the access errors, but when I try to trap for them, I only get the first one. What do I need to do to get the second one to show up when I trap for them?
Edit: I got a message telling me that I need to edit my question to explain how it's different from Can PowerShell trap errors in GetChildItem and continue looping?. So I'll repeat here what I said in response to the comment below from Scepticalist. That question is for get-ChildItem in a ForEach loop. My problem does not involve such a loop. Still, I tried something from the accepted answer there, using -ErrorAction SilentlyContinue, but that hides the errors without trapping them. However, in the solution I found and am about to post as an answer, I do use -ErrorAction SilentlyContinue in combination with -ErrorVariable.

The errors emitted by Get-ChildItem "C:\Windows\System32" -recurse -force are non-terminating errors, and, given that such errors by definition don't terminate a command, a single command may emit multiple errors of this type.
All non-terminating errors emitted by a cmdlet can be captured in a designated variable whose name you pass to the -ErrorVariable common parameter.
Additionally, all errors (both non-terminating and terminating ones) are by default recorded in the session-global automatic $Error collection variable.
try / catch only acts on terminating errors, so by default it has no effect on commands that emit non-terminating errors only, such as Get-ChildItem "C:\Windows\System32" -recurse -force
You can instruct the command to turn the first non-terminating error encountered into a terminating one by adding -ErrorAction Stop (using the -ErrorAction common parameter), in which case an enclosing try / catch does catch the error, but note that execution then stops after the first error encountered either way.
Caveat: There are two types of terminating errors (and the fact that there are may be a historical accident); while both types can be caught with try / catch, their default behavior differs:
Statement-terminating errors, which only terminate the statement at hand and, after emitting an error message, by default continue script execution; typically, these errors are emitted by (compiled) cmdlets if they encounter errors that aren't limited to the input at hand and doesn't allow them to continue processing further input.
Script-terminating errors (runspace-terminating errors), which abort processing altogether by default. PowerShell code that uses the Throw statement generates such errors, as does passing -ErrorAction Stop to a command that emits non-terminating errors.
Caveat: While -ErrorAction Stop has no effect on statement-terminating errors, the seemingly equivalent preference-variable setting, $ErrorActionPreference = 'Stop', unexpectedly promotes statement-terminating errors to script-terminating ones.
Further reading:
For a comprehensive overview of PowerShell's - bewilderingly complex - error handling, see this GitHub docs issue.
As for when to report a terminating vs. a non-terminating error when authoring commands, see this answer.

I did some research on the ErrorAction parameter, and doing that, discovered the ErrorVariable parameter. (Both are common parameters, so they don't show up in the documentation of get-ChildItem.) And from there, I was able to figure out how to correctly trap for the errors.
The new script below shows four ways of doing this. Methods 2 and 3 work okay. Method 4 doesn't work because it incorrectly attempts to use the parameter InputObject in ForEach-object. Method 1, the original try-catch method, doesn't work and I still don't know why. This is troubling because it's important that error trapping works as expected. If I did not know to expect two errors, I would not have known that try-catch was not giving me the right output.
I am not accepting this (my own) answer because, although the script below shows two methods to correctly trap for these errors, it would still be better if someone can explain why try-catch does not work for this.
New script:
$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 + """")}
New output:
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>

Quite simple! You specified the parameter -ErrorAction Stop in your second directory access statement. Switch it to -ErrorAction Continue and you get both error messages.

Related

Error not caught in PowerShell try/catch/finally if not thrown explicitly [duplicate]

This question already has answers here:
How to handle failed variable assignments in powershell? [duplicate]
(1 answer)
Try/catch does not seem to have an effect
(7 answers)
Closed 2 years ago.
I'm trying to fetch a file using Get-SCPFile from the Posh-SSH module and when such a file doesn't exists, an error is thrown. I want to catch said error and attempt the operation with a different file name.
try {
Write-Host "Primary attempt with" $target
# throw "up"
Get-SCPFile -RemoteFile ($origin + $target + ".csv") ...
}
catch {
$target = $target -replace "1$", "2"
Write-Host "Secondary attempt with" $target
Get-SCPFile -RemoteFile ($origin + $target + ".csv") ...
}
finally {
Write-Host "Final action performed with" $target
}
For some reason, when the file doesn't exist, I get to finally and the screen shows red error but the catch part is omitted. However, when I activate my throw up, the exception is caught as expected and the statement in catch executes.
Googling along the lines of PS error not caught gave me a very widespread and uninformative results.
I omitted the exact call for brevity reasons since the invokation as such seems to work properly but for the curious reader (and in case I'm missing a relevant detail), here it is.
Get-SCPFile -ComputerName $server
-Credential $credential
-RemoteFile ($origin + $target + ".csv")
-LocalFile ($destination + $target + ".csv")
Optimally, I'd also like to test the existence of the file prior to actually fetching it. However, the package I'm using only has documentation on Test-SFTP, not Test-SCP. Also, even more importantly in this context, the question of the uncaught exception is the main subject.

How do I evaluate if data or an error returned in PowerShell?

I am trying to test if a DNS zone exists in Powershell using the following cmdlet:
Get-DNSServerZone abc.com
That works great, but what I need to do now is turn that into a True/False evaluation depending on if there was an error or if there was data returned.
For example, this is a TRUE scenario:
$a = Get-DnsServerZone abc.com
$a
ZoneName ZoneType IsAutoCreated IsDsIntegrated IsReverseLookupZone IsSigned
-------- -------- ------------- -------------- ------------------- --------
abc.com Secondary False False False
Whereas this is a FALSE scenario:
$a = Get-DnsServerZone def.com
Get-DnsServerZone : The zone def.com was not found on server DNSSERVER1.
At line:1 char:6
+ $a = Get-DnsServerZone def.com
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (def.com:root/Microsoft/...S_DnsServerZone) [Get-DnsServerZone], CimException
+ FullyQualifiedErrorId : WIN32 9601,Get-DnsServerZone
What I'm struggling with is how to evaluate on that? In layman's terms I need to check if $a has actual data or not.
Many thanks!
what I need to do now is turn that into a True/False evaluation depending on if there was an error or if there was data returned.
If you can simply ignore failures and don't need the object returned by Get-DNSServerZone, you can do the following:
$zoneExist = [bool] (Get-DNSServerZone abc.com -ErrorAction Ignore)
-ErrorAction Ignore quietly ignores any (non-terminating) errors.
Passing Ignore to the common -ErrorAction parameter) simply discards the error, whereas SilentlyContinue, while also not outputting an error, still records it in the automatic $Error collection, allowing later inspection.
You could also complement -ErrorAction SilentlyContinue with something like
-ErrorVariable err, which would additionally record the command-specific errors in self-chosen variable $err, via the common -ErrorVariable parameter.
Cast [bool] maps Get-DNSServerZone's output to $true (an object describing the zone was output) or $false (nothing was output (to the success output stream), because an error occurred), taking advantage of PowerShell's implicit to-Boolean conversion.
If you want the zone-description object too:
$zoneExist = [bool] ($zoneInfo = Get-DNSServerZone abc.com -ErrorAction Ignore)
An alternative is to capture the zone-information first, and to query the automatic $? variable afterwards, which contains a Boolean value ($true or $false) that indicates whether the most recently executed statement caused any errors (whether suppressed or not).
$zoneInfo = Get-DNSServerZone abc.com -ErrorAction Ignore
$zoneExist = $?
You could wrap it in a try..catch:
try {
Get-DNSServerZone abc.com -ErrorAction Stop
}
catch {
Write-Warning "zone abc.com doesn't exist"
}
Or go the other way and ignore errors:
$a = Get-DNSServerZone abc.com -ErrorAction SilentlyContinue
if (!$a) {
Write-Warning "abc.com doesn't exist"
}

Powershell Like operator invokes REST error

I am working on a custom PoweShell module picked up from the internet for our BI application.
My question is rather simple, the code below does not work:
Get-QlikDataConnection -filter "name -like 'Data*'"
And throws an error like:
Invoke-RestMethod : The remote server returned an error: (400) Bad Request.
At C:\Program Files\WindowsPowerShell\Modules\Qlik-Cli\1.13\functions\core.ps1:32 char:15
+ ... $result = Invoke-RestMethod -Method $method -Uri $path #params -Web ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebExc
eption
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand
However the code below works fine and shows me the correct output:
Get-QlikDataConnection -filter "name eq 'DataPrepAppCache'"
Am I doing something wrong or do some modules not understand a few operators?
After having a look at the source of the module you're using, Qlik-Admin-Utils, I would not use the -filter param, as the input you specify there gets processed by this block within the Invoke-QlikGet cmdlet:
If( $filter ) {
If( $path.contains("?") ) {
$path += "&filter=$filter"
} else {
$path += "?filter=$filter"
}
}
This script appends your filter as a query parameter in the URL, and it doesn't support regular PowerShell formatting, but sends the filter over to qLik's REST API.
If I were writing this, I'd ignore their filtering and do the following:
$Connections = Get-QlikDataConnection
$DataConnection = $Connections | Where name -like "Data*"
This is more likely to just work with less fiddling.
However, if you want to use Qlik's Filter support, I found this so you can read up on the syntax of it here.
It looks like they do offer a filter of their own which might help, it's the Starts With filter, defined as SW, for a syntax of Name sw 'Data'. You might try this and see if it works instead.
Get-QlikDataConnection -filter "name sw 'Data'"

Powershell: how to remove stack trace when command returns an error?

When a command returns an error, I get like an error message, plus what looks like a full stack of the error:
C:\> dir foo
dir : Cannot find path 'C:\foo' because it does not exist.
At line:1 char:1
+ dir foo
+ ~~~~~~~
+ CategoryInfo : ObjectNotFound: (C:\foo:String) [Get-ChildItem], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand
Is there a way to only see the error (that's the only thing usefull for me) and not display the full stack ?
Like:
C:\> dir foo
dir : Cannot find path 'C:\foo' because it does not exist.
You need to catch the error if you want to control how or what is displayed:
try {
dir foo -ErrorAction Stop
} catch {
Write-Host $_
}
Sometimes you'll need to add -ErrorAction Stop (or $ErrorActionPreference = 'Stop') to ensure that all errors are terminating (so they can be caught).
All powershell errors are captured in the auto variable $error. The item at index zero ($error[0]) is the most recent, index 1 next to most recent, etc. Each object in the array is a System.Management.Automation.ErrorRecord. An ErrorRecord contains a number of properties. You can use the select-object cmdlet to see a subset of properties:
$error[0]|select * -excludeproperty *stacktrace
If you want to be able to view the error record at an arbitrary while you're developing a script I'd suggest a function in your profile maybe like:
function show-myError {
param( $errorIndex = 0)
# You can also add anything else you want to exclude
$error[0]|select * -exclude *stacktrace
}
sal err show-myError
On the other hand if you're getting an error at a specific place in a specific script you can use catch/try as suggested in earlier answer. Or if you don't have a specific place, but do have a specific script, then I suggest trap, which you can place at the top of a function or top of a PS1 file. In the catch block or trap block you can output $error[0] using select with the -exclude parameter.

powershell: how to print the total call stacks when error happen?

suppose I have the following code, when the error happens, I'd like to see the error that the error first happened at function b, and then happened at function a. But in fact it only tells me the error happen at function a, since function a could be called many times, I don't know which outer function calling function a caused the problem
cls
function a{
Remove-Item "not-exist-item"
}
function b{
a
}
b
Remove-Item : Cannot find path 'C:\Program Files (x86)\Microsoft SQL Server\100\Tools\Binn\not-exis
t-item' because it does not exist.
At C:\Users\Daniel.Wu\AppData\Local\Temp\2\a.ps1:***3 char:14***
+ Remove-Item <<<< "not-exist-item"
+ CategoryInfo : ObjectNotFound: (C:\Program File...\not-exist-item:String) [Remove-Item], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.RemoveItemCommand
If you are on PowerShell v2.0, use Get-PSCallStack. If you're still on v1, use a function like this:
function Get-CallStack {
trap { continue }
1..100 | foreach {
$var = Get-Variable -scope $_ MyInvocation
$var.Value.PositionMessage -replace "`n"
}
}
One option is to set
$ErrorActionPreference = 'inquire'
and then invoke the problematic script. On error you are prompted for choices, choose Suspend to enter into the nested prompt mode. Then type
Get-PSCallStack
or even
Get-PSCallStack | fl
to get the current call stack information.
Does get-help about_debuggers provide any illumination?