Public/Set-AzureUtilsTagFromResourceGroup.ps1
|
function Set-AzureUtilsTagFromResourceGroup { <# .SYNOPSIS Inherits resource-group tags down onto the resources inside each group. .DESCRIPTION For every resource in scope, compares its tags with its parent resource group's tags (read together in a single Azure Resource Graph query) and applies the group's tags onto the resource with Update-AzTag -Operation Merge. By default only tag keys the resource is MISSING are added (existing values are never touched); use -Overwrite to also replace values that differ from the group's. Restrict which keys are inherited with -Tag. Rows are grouped by subscription (the context is switched per group). This cmdlet changes Azure resources, so it supports -WhatIf and -Confirm. Requires Az.Resources (Update-AzTag). Messages are en-US. .PARAMETER SubscriptionId One or more subscription IDs. Default: every enabled subscription. .PARAMETER ManagementGroupId One or more management groups to scan (requires Az.ResourceGraph). .PARAMETER Tag Only inherit these tag keys. Default: every tag on the resource group. .PARAMETER Overwrite Also overwrite resource tag values that differ from the group's value. Without this switch, only missing tag keys are added. .PARAMETER Quiet Suppress the per-resource '[INFO] ...' lines (errors are still shown). .EXAMPLE Set-AzureUtilsTagFromResourceGroup -WhatIf Previews which resources would inherit which group tags. .EXAMPLE Set-AzureUtilsTagFromResourceGroup -Tag costCenter, environment -SubscriptionId $sub Inherits only those two keys from each resource group onto its resources. .LINK https://github.com/hendersonandrade/powershell-module-azureUtils .OUTPUTS None. Writes a console report. #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium', DefaultParameterSetName = 'Subscriptions')] param( [Parameter(ParameterSetName = 'Subscriptions')] [string[]] $SubscriptionId, [Parameter(ParameterSetName = 'ManagementGroup', Mandatory)] [string[]] $ManagementGroupId, [string[]] $Tag, [switch] $Overwrite, [switch] $Quiet ) $null = Assert-AzureUtilsContext if (-not (Get-Command -Name 'Update-AzTag' -ErrorAction SilentlyContinue)) { throw [System.Management.Automation.RuntimeException]::new( "Set-AzureUtilsTagFromResourceGroup requires the 'Az.Resources' module. Install it with: Install-Module Az.Resources -Scope CurrentUser" ) } $resolved = Resolve-AzureUtilsScope -SubscriptionId $SubscriptionId -ManagementGroupId $ManagementGroupId if ($resolved.Type -eq 'Subscription' -and -not $resolved.HasSubscriptions) { Write-Warning 'No accessible subscriptions in scope.' return } $query = New-AzureUtilsInheritedTagQuery $scope = $resolved.Scope Write-Verbose "Inherited-tag query:`n$query" # Build the { ResourceId, Tags } work list: for each resource, the group tags it # should inherit (missing keys, or differing keys when -Overwrite). $apply = [System.Collections.Generic.List[object]]::new() Invoke-AzureUtilsGraphQuery -Query $query @scope | ForEach-Object { $resTags = ConvertTo-AzureUtilsHashtable -InputObject $_.tags $rgTags = ConvertTo-AzureUtilsHashtable -InputObject $_.rgTags $inherit = @{} foreach ($key in $rgTags.Keys) { if ($Tag -and ($Tag -notcontains $key)) { continue } $value = $rgTags[$key] if ($resTags.ContainsKey($key)) { if (-not $Overwrite) { continue } if ([string]$resTags[$key] -eq [string]$value) { continue } } $inherit[$key] = $value } if ($inherit.Count -gt 0) { $apply.Add([pscustomobject]@{ ResourceId = [string]$_.id; Tags = $inherit }) } } $groups = $apply | Group-Object { if ($_.ResourceId -match '/subscriptions/([0-9a-fA-F-]{36})') { $Matches[1] } else { '' } } $subCount = @($groups | Where-Object { $_.Name }).Count Write-Host '' Write-Host 'Azure Resource-Group Tag Inheritance' -ForegroundColor Cyan Write-Host ('-' * 51) -ForegroundColor DarkGray Write-Host (" Scope: {0} [{1}]" -f $resolved.Label, $resolved.Type) Write-Host (" Resources to update: {0}" -f $apply.Count) Write-Host (" Subscriptions: {0}" -f $subCount) Write-Host (" Mode: {0}" -f $(if ($Overwrite) { 'add missing + overwrite differing' } else { 'add missing keys only' })) Write-Host '' Write-Host 'Starting apply...' if ($apply.Count -eq 0) { Write-AzureUtilsStatus -Level WARN -Message 'No resources need inherited tags.' return } $applied = 0 $errorCount = 0 $n = 0 $original = Get-AzContext try { foreach ($group in $groups) { $subId = $group.Name if ($subId) { try { $null = Set-AzContext -SubscriptionId $subId -ErrorAction Stop } catch { $errorCount += $group.Count Write-AzureUtilsStatus -Level ERROR -Message ("Cannot switch to subscription {0}; skipping {1} resource(s): {2}" -f $subId, $group.Count, $_.Exception.Message) $n += $group.Count continue } } foreach ($rec in $group.Group) { $n++ $summary = (($rec.Tags.Keys | Sort-Object | ForEach-Object { '{0}={1}' -f $_, $rec.Tags[$_] }) -join '; ') if ($PSCmdlet.ShouldProcess($rec.ResourceId, "Inherit tags: $summary")) { try { $null = Update-AzTag -ResourceId $rec.ResourceId -Tag $rec.Tags -Operation Merge -ErrorAction Stop $applied++ if (-not $Quiet) { Write-AzureUtilsStatus -Level INFO -Message ("{0} of {1} inherited {2} tag(s) onto {3}: {4}" -f $n, $apply.Count, $rec.Tags.Count, $rec.ResourceId, $summary) } } catch { $errorCount++ Write-AzureUtilsStatus -Level ERROR -Message ("{0}: {1}" -f $rec.ResourceId, $_.Exception.Message) } } } } } finally { if ($original) { $null = Set-AzContext -Context $original -ErrorAction SilentlyContinue } } Write-AzureUtilsStatus -Level INFO -Message 'Apply Finish' Write-Host ("Updated {0} of {1} resource(s){2}." -f $applied, $apply.Count, $(if ($errorCount) { ", $errorCount error(s)" } else { '' })) } |