PowerShell hashtable values are not enumerated within Catch block when piped to ForEach-Object

59 views Asked by At

I've encountered a quirk which no doubt has something to do with the differences between ForEach vs ForEach-Object. I have a workaround but I'm curious to understand the behaviour to help further my knowledge of PowerShell.

When enumerating a hashtable and running it through a ForEach-Object with a Try/Catch/Finally block, the Catch block does not retrieve the keypairs from the hashtable; however the values are accessible within both the Try and Finally blocks. When I do the same thing using a foreach loop they do get passed though.

A couple of examples.

#Example 1: Inconsistent output ie keypairs not present in Catch block

$ht = @{
    1 = 'one'
    2 = 'two'
    3 = 'three'
    }

$ht.GetEnumerator() | ForEach-Object {
    Try {
        "Try block: $($_.Name) - $($_.Value)" ; Start-Something
    }
    Catch {
        "Catch block: $($_.Name) - $($_.Value)"
    }
    Finally {
        "Finally block: $($_.Name) - $($_.Value)"
    }
}

Example 1 output:

Try block: 3 - three
Catch block:  - 
Finally block: 3 - three
Try block: 2 - two
Catch block:  - 
Finally block: 2 - two
Try block: 1 - one
Catch block:  - 
Finally block: 1 - one

Using a similar approach with a foreach loop it behaves as expected:

#Example 2: Working ie keypairs present in Try/Catch/Finally

foreach ($h in $ht.GetEnumerator() ) {
    Try {
        "Try block: $($h.Name) - $($h.Value)" ; Start-Something
    }
    Catch {
        "Catch block: $($h.Name) - $($h.Value)"
    }
    Finally {
        "Finally block: $($h.Name) - $($h.Value)"
    }
}

Example 2 output:

Try block: 3 - three
Catch block: 3 - three
Finally block: 3 - three
Try block: 2 - two
Catch block: 2 - two
Finally block: 2 - two
Try block: 1 - one
Catch block: 1 - one
Finally block: 1 - one
1

There are 1 answers

0
Santiago Squarzon On

$_ represents the caught exception in the context of a catch block which explains this behavior. The caught exception no longer has a .Name or a .Value property, no longer represents the current pipeline object. See Accessing exception information

The workaround for exception handling to preserve the pipeline object is to assign it a variable:

$ht = @{
    1 = 'one'
    2 = 'two'
    3 = 'three'
}

$ht.GetEnumerator() | ForEach-Object {
    $pipelineObject = $_
    
    Try {
        "Try block: $($_.Name) - $($_.Value)" ; Start-Something
    }
    Catch {
        "Catch block: $($pipelineObject.Name) - $($pipelineObject.Value)"
    }
    Finally {
        "Finally block: $($_.Name) - $($_.Value)"
    }
}