Home > database >  $_ losing value during ForEach-Object
$_ losing value during ForEach-Object

Time:02-04

I'm writing a report to pull Audit Azure Role Assignments (which isn't important for this necessarily). The important part is I need to iterate through each one, add a number of additional member properties and then set those values later in the code based on other calculations.

The issue I'm running into is I'm utilizing the $_ variable and the member properties get added properly, and I can reference/set them early (in testing), but later in the code when I again attempt to set one of those properties, I get an error saying this newProperty could not be found on the object.

I've traced this down to the $_ variable changing somewhere along the line, and I don't understand why this would be happening. I'm not doing any further inline piping/looping/iteration.

Hopefully someone can help me out. I'm sure there are better ways to doing this, which I would entertain, but as of now I'm more interested into what is happening.

The following works

$AzRoleAssignments | ForEach-Object {

 # Add new custom member properties to be populated later, where applicable.
 $_ | Add-Member -NotePropertyName "ResourceType" -NotePropertyValue ""
 $_ | Add-Member -NotePropertyName "ResourceName" -NotePropertyValue ""
 $_ | Add-Member -NotePropertyName "ParentSubscriptionName" -NotePropertyValue ""
 $_ | Add-Member -NotePropertyName "ParentResourceGroupName" -NotePropertyValue ""
 $_ | Add-Member -NotePropertyName "Region" -NotePropertyValue ""

 $_.ResourceType = "test"
}

But the following yields the error noted above.

$AzRoleAssignments = Get-AzRoleAssignment 
    $AzRoleAssignments | ForEach-Object {

        # Add new custom member properties to be populated later, where applicable.
        $_ | Add-Member -NotePropertyName "ResourceType" -NotePropertyValue ""
        $_ | Add-Member -NotePropertyName "ResourceName" -NotePropertyValue ""
        $_ | Add-Member -NotePropertyName "ParentSubscriptionName" -NotePropertyValue ""
        $_ | Add-Member -NotePropertyName "ParentResourceGroupName" -NotePropertyValue ""
        $_ | Add-Member -NotePropertyName "Region" -NotePropertyValue ""
        
        # RoleAssignment.Scope property represents the unique ID of the resource
        $ResourceID = $_.Scope
        
        # The ResourceID is composed of a "breadcrumb" path delimited by / marks that describes 
        #  what kind of resource it is, where in the hierarchy it is, along with its name (if applicable).
        #
        #  Examples:
        #       Root:           /
        #       Subscription:   /subscriptions/8410b47a-12a3-45b6-78c9-de006e0f5719
        #       Resourcegroup:  /subscriptions/8410b47a-12a3-45b6-78c9-de006e0f5719/resourceGroups/testresourcegroup
        #       Resource:       /subscriptions/8410b47a-12a3-45b6-78c9-de006e0f5719/resourceGroups/testresourcegroup/providers/Microsoft.Storage/storageAccounts/teststorageaccount
        
        # Break ResourceID down into a / delimited array to pull these pieces of information.
        $ResourceIDArray = $ResourceID -Split "/"
        
        # Check second to last item in the array.  With the exception of a resourceID equaling "/",
        #  this value should reflect the resource type.
        #

        # Inspect second to last element to determine if assignment is at root, subscription, resource group, or resource level.
        #  This is required as different commands are needed depending on the resource.
        Switch ($ResourceIDArray[-2]) {   # <----- This seems to change value of $_
            "" {
                # Assignment is at root
                $_.ResourceName = "Root"
            }
            "subscriptions" {
                # Assignment is at Subscription level. Subscription information already obtained above.
                
                #  Set relevant custom added member properties
                $_.ResourceType = "Subscription"
                $_.ResourceName = $Subscription.Name
                
            }
            "resourceGroups" {
                # ResourceID represents a resource group. Get information about this resource group.
                $Resource = Get-AzResourceGroup -ID $ResourceID
                
                # Set relevant custom added member properties
                $_.ResourceType = "ResourceGroup"
                $_.ResourceName = $Resource.ResourceGroupName
                $_.Region = $Resource.Location
                $_.ParentSubscriptionName =  $Subscription.Name
            }
            Default {
                ### NOTE:  First loop iteration lands here and $_ no longer reflects the expected object reference.
                Write-Host "`$_ is: $_"
                # ResourceID represents a downlevel resource.  Get resource information.                
                $Resource = Get-AzResource -ResourceID $ResourceID
                
                # Set relevant custom added member properties
                $_.ResourceType = $Resource.ResourceType  # <<---- This errors out with property not found.
                $_.ResourceName = $Resource.Name
                $_.ParentResourceGroupName = (Get-AzResourceGroup -ID $ResourceID).ResourceGroupName
                $_.ParentSubscriptionName =  $Subscription.Name 
                $_.Region = $Resource.Location
            }
        }
    }

CodePudding user response:

The automatic $_ variable is defined in a variety of contexts - most notably, but not exclusively in script blocks passed to ForEach-Object and Where-Object.

Another such context is the switch statement, where both branch conditionals (if implemented as script blocks) and the action script blocks see the input object at hand as $_ as well.

Therefore, if you want to preserve the value of $_ that is in effect outside of a switch statement, use an auxiliary variable.

Here's a simplified example:

'a/b/c', 'd/e/f' | ForEach-Object {
  # Save the current value of $_, because `switch` will override it.
  $currentVal = $_  
  switch (($_ -split '/')[-2]) {
    'b' { $currentVal } # $_ in this context is 'b'
  }
}

This outputs 'a/b/c'.

For an overview of all contexts in which $_ is automatically defined, see the bottom section of this answer.

  • Related