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.