Cascading Resource Group Tags in Azure
Resource Manager Policies in Azure are the way to define and enforce a tagging system.
You can define in a json format rules that must be adhered to for new resources that are deployed.
eg.
{
"if": {
"allOf": [
{
"field": "tags",
"exists": "true"
},
{
"field": "tags.costCenter",
"exists": "false"
}
]
},
"then": {
"effect": "append",
"details": [
{
"field": "tags.costCenter",
"value": "myDepartment"
}
]
}
}
For resources that you’ve already created, you’ll need to decide on the appropriate strategy. One that I’ve recently put together is a script that cascades the tags you define at the Resource Group level down to the individual resources (VM’s, vNETs, etc etc).
It doesn’t override any of the existing tags that a resource has, simply ensuring that each of the resources has at a minimum the tags that are defined at the Resource Group level.
This version isn’t optimised for running on a schedule in Azure Automation as it’s not a powershell workflow so doesn’t parallelise the foreach loops.
#Gordon.byers@microsoft.com
#Powershell script is provided as-is and without any warranty of any kind
function Add-ResourceGroupTagsToResources()
{
#V1.0 2016-06-29
param (
[Parameter(Mandatory=$true)]
[string] $resourceGroupName
)
$taggedResourceGroups = $null
$resourceGroup = Get-AzureRmResourceGroup -Name $resourceGroupName -ErrorAction SilentlyContinue
if($resourceGroup -eq $null) {
Write-Error "Resource Group : $resourceGroupName not found in subscription"
return;
}
$taggedResourceGroups = $resourceGroup | where-object {$_.Tags.count -gt 0}
if ($taggedResourceGroups -eq $null)
{
Write-Warning "$resourceGroupName found - no tags defined to add to resources"
}
else {
Write-Host "Finding Resources to tag for resourcegroup : $($resourceGroup.ResourceGroupName)"
$rgTagsTable = $resourceGroup.TagsTable
$rgTagCount = $resourceGroup.Tags.count
$rgTagKeys = @()
$resourceGroup.Tags | %{$rgTagKeys += $_["Name"]}
$resoucesToTag = Get-AzureRmResource -ResourceGroupName $resourceGroup.ResourceGroupName -ResourceName " "
ForEach($resource in $resoucesToTag)
{
If($resource.Tags.Count -eq 0)
{
Write-Host "Resource $($resource.Name) has no tags, adding full set of $rgTagCount tags"
Set-AzureRmResource -ResourceId $resource.ResourceId -Tag $resourceGroup.Tags -Force
}
else {
Write-Verbose "Resource $($resource.Name) ($($resource.ResourceType)) has existing tags found"
$resourceTagKeys = @()
ForEach($tag in $resource.tags)
{
$resourceTagKeys += $tag["Name"]
}
$extraTags = Compare-Object -ReferenceObject $rgTagKeys -DifferenceObject $resourceTagKeys
if($extraTags -eq $null)
{
Write-Host "Resource $($resource.Name) ($($resource.ResourceType)) tags are up to date"
}
else {
Write-Verbose "Merging tags for $($resource.Name) ($($resource.ResourceType))"
$tagKeysToAdd = $($extraTags | Where-Object { $_.SideIndicator -eq "<="})
if ($tagKeysToAdd -eq $null) {
$resourceSpecificTags = $($extraTags | Select-Object -ExpandProperty InputObject) -join ', '
Write-Host "Resource $($resource.Name) ($($resource.ResourceType)) has extra tags from its Resource Group ($resourceSpecificTags). inherited tags are up to date"
}
else {
$tagsToUpdate = $resource.tags
ForEach ($tagKeyToAdd in $tagKeysToAdd) {
Write-Verbose "For $($resource.Name) ($($resource.ResourceType)) tag $($tagKeyToAdd.InputObject) is missing."
$value = $($rg.Tags | Where-Object {$_["Name"] -eq $tagKeyToAdd.InputObject})["Value"]
$tagsToUpdate += @{Name=$tagKeyToAdd.InputObject;Value=$value}
}
Write-Host "Resource $($resource.Name) ($($resource.ResourceType)) : updating extra tags"
Set-AzureRmResource -ResourceId $resource.ResourceId -Tag $tagsToUpdate -Force
}
}
}
}
}
}
Login-AzureRmAccount
$allResourceGroups = Get-AzureRmResourceGroup
ForEach ($resourceGroup in $allResourceGroups) {
Add-ResourceGroupTagsToResources -resourceGroupName $resourceGroup.ResourceGroupName
}
For the latest version, use the GitHub link.
https://github.com/Gordonby/PowershellSnippets/blob/master/Add-ResourceGroupTagsToResources.ps1