Forum Discussion
Exception data disappears?
- Aug 15, 2023
Hi, Julian.
I've used the following code to reproduce your symptoms, after which I get into the issues.
Test code
#region Test session-specific stuff. Remove for production use. Clear-Host; Remove-Variable -Name @("StringLabel", "Connected", "ErrorPropertyValue") -ErrorAction:SilentlyContinue; $StringLabel = "Foo"; #endregion try { dir B:\ -ErrorAction:Stop; } Catch [System.Exception] { $_.Exception.pstypenames; # Did not connect due to connection issue. Write-Host "$StringLabel Error: $($PSItem.ToString())" -ForegroundColor Red Switch ($PSItem.Exception.ToString()) { {$_.contains("Unable to connect")} { Write-Host "$StringLabel ERROR: [$($PSItem.Exception.ToString())]." -ForegroundColor Red $Connected = $False $ErrorPropertyValue = "Connection_Not_Available" } {$_.contains("Authentication failed")} { Write-Host "$StringLabel ERROR: [$($PSItem.Exception.ToString())]." -ForegroundColor Red $Connected = $False $ErrorPropertyValue = "Authentication_Failed" } Default { Write-Host "$StringLabel ERROR: [$($PSItem.Exception.ToString())]." -ForegroundColor Red $Connected = $False $ErrorPropertyValue = "Unknown_Error" } } }Exception output
There's two errors in this output:
- The one from the try block;
- A new one thrown from within the catch block.
Analysis
Firstly, $_ and $PSItem are precisely the same thing (the former being an alias of the latter), so you can get away with using just one or the other in code.
Next, there is indeed a "shelf life" - for multiple reasons:
- Something else comes onto the pipeline from a subsequent command;
- The scope changes.
What you're seeing is the effect of the latter, where the scope changes from the catch block to the switch statement/block (which itself is a subset of the broader catch block).
Outside of the switch block, $_ still equals your exception, but inside of the switch block, it equals the string emitted by the switch expression of:
$PSItem.Exception.ToString()
This explains the second error being thrown from the catch block, as a System.String object clearly doesn't have a property named "Exception" (and therefore no ToString() method below that, either), which is reflected in that second error message.
The best thing to do when working with the pipeline automatic variable in a complex block (irrespective of whether it's a catch block or any other) is to assign it to a variable upon entry to the block - unless you're not using nested references (you don't want to make things messy if you don't have to.)
The variable is usable from anywhere inside of that specific block, independent of the pipeline variable changing for whatever reason.
Taking this simple step of assigning the pipeline variable upon entry gives you this instead:
Adjusted code
#region Test session-specific stuff. Remove for production use. Clear-Host; Remove-Variable -Name @("StringLabel", "Connected", "ErrorPropertyValue") -ErrorAction:SilentlyContinue; $StringLabel = "Foo"; #endregion try { dir B:\ -ErrorAction:Stop; } Catch [System.Exception] { $e = $_; # $e.Exception.pstypenames; # Did not connect due to connection issue. Switch ($e.Exception.ToString()) { {$e.Exception.Message.Contains("Unable to connect")} { Write-Host "$StringLabel ERROR: [$($e.Exception.ToString())]." -ForegroundColor Red $Connected = $False $ErrorPropertyValue = "Connection_Not_Available" } {$e.Exception.Message.Contains("Authentication failed")} { Write-Host "$StringLabel ERROR: [$($e.Exception.ToString())]." -ForegroundColor Red $Connected = $False $ErrorPropertyValue = "Authentication_Failed" } Default { Write-Host "$StringLabel ERROR: [$($e.Exception.ToString())]." -ForegroundColor Red $Connected = $False $ErrorPropertyValue = "Unknown_Error" } } } $Connected; $ErrorPropertyValue;Output
Which is probably what you wanted.
If you want to get into the nuts and bolts of the topics this post touches on, here you go:
- about PSItem - PowerShell | Microsoft Learn
- about Automatic Variables - PowerShell | Microsoft Learn
- about Pipelines - PowerShell | Microsoft Learn
- about Scopes - PowerShell | Microsoft Learn
Cheers,
Lain
Hi, Julian.
I've used the following code to reproduce your symptoms, after which I get into the issues.
Test code
#region Test session-specific stuff. Remove for production use.
Clear-Host;
Remove-Variable -Name @("StringLabel", "Connected", "ErrorPropertyValue") -ErrorAction:SilentlyContinue;
$StringLabel = "Foo";
#endregion
try
{
dir B:\ -ErrorAction:Stop;
}
Catch [System.Exception]
{
$_.Exception.pstypenames;
# Did not connect due to connection issue.
Write-Host "$StringLabel Error: $($PSItem.ToString())" -ForegroundColor Red
Switch ($PSItem.Exception.ToString())
{
{$_.contains("Unable to connect")}
{
Write-Host "$StringLabel ERROR: [$($PSItem.Exception.ToString())]." -ForegroundColor Red
$Connected = $False
$ErrorPropertyValue = "Connection_Not_Available"
}
{$_.contains("Authentication failed")}
{
Write-Host "$StringLabel ERROR: [$($PSItem.Exception.ToString())]." -ForegroundColor Red
$Connected = $False
$ErrorPropertyValue = "Authentication_Failed"
}
Default
{
Write-Host "$StringLabel ERROR: [$($PSItem.Exception.ToString())]." -ForegroundColor Red
$Connected = $False
$ErrorPropertyValue = "Unknown_Error"
}
}
}
Exception output
There's two errors in this output:
- The one from the try block;
- A new one thrown from within the catch block.
Analysis
Firstly, $_ and $PSItem are precisely the same thing (the former being an alias of the latter), so you can get away with using just one or the other in code.
Next, there is indeed a "shelf life" - for multiple reasons:
- Something else comes onto the pipeline from a subsequent command;
- The scope changes.
What you're seeing is the effect of the latter, where the scope changes from the catch block to the switch statement/block (which itself is a subset of the broader catch block).
Outside of the switch block, $_ still equals your exception, but inside of the switch block, it equals the string emitted by the switch expression of:
$PSItem.Exception.ToString()
This explains the second error being thrown from the catch block, as a System.String object clearly doesn't have a property named "Exception" (and therefore no ToString() method below that, either), which is reflected in that second error message.
The best thing to do when working with the pipeline automatic variable in a complex block (irrespective of whether it's a catch block or any other) is to assign it to a variable upon entry to the block - unless you're not using nested references (you don't want to make things messy if you don't have to.)
The variable is usable from anywhere inside of that specific block, independent of the pipeline variable changing for whatever reason.
Taking this simple step of assigning the pipeline variable upon entry gives you this instead:
Adjusted code
#region Test session-specific stuff. Remove for production use.
Clear-Host;
Remove-Variable -Name @("StringLabel", "Connected", "ErrorPropertyValue") -ErrorAction:SilentlyContinue;
$StringLabel = "Foo";
#endregion
try
{
dir B:\ -ErrorAction:Stop;
}
Catch [System.Exception]
{
$e = $_;
# $e.Exception.pstypenames;
# Did not connect due to connection issue.
Switch ($e.Exception.ToString())
{
{$e.Exception.Message.Contains("Unable to connect")}
{
Write-Host "$StringLabel ERROR: [$($e.Exception.ToString())]." -ForegroundColor Red
$Connected = $False
$ErrorPropertyValue = "Connection_Not_Available"
}
{$e.Exception.Message.Contains("Authentication failed")}
{
Write-Host "$StringLabel ERROR: [$($e.Exception.ToString())]." -ForegroundColor Red
$Connected = $False
$ErrorPropertyValue = "Authentication_Failed"
}
Default
{
Write-Host "$StringLabel ERROR: [$($e.Exception.ToString())]." -ForegroundColor Red
$Connected = $False
$ErrorPropertyValue = "Unknown_Error"
}
}
}
$Connected;
$ErrorPropertyValue;
Output
Which is probably what you wanted.
If you want to get into the nuts and bolts of the topics this post touches on, here you go:
- about PSItem - PowerShell | Microsoft Learn
- about Automatic Variables - PowerShell | Microsoft Learn
- about Pipelines - PowerShell | Microsoft Learn
- about Scopes - PowerShell | Microsoft Learn
Cheers,
Lain
- JulianMilanoAug 15, 2023Copper ContributorYes- Scope of variables. Thank you!! I guess $PSItem / $_ are special dynamic variables used by the system and thus can change with each block of code executed. I have indeed saved the $PSItem values to variables as soon as the Catch block is executed and this works as you suggested. Thanks again!!