Compare Azure Resource Groups
  • 23 Dec 2019
  • 5 Minutes to read
  • Contributors
  • Comment
  • Dark
    Light
  • PDF

Compare Azure Resource Groups

  • Comment
  • Dark
    Light
  • PDF

Article Summary

I had a scenario where I was looking at a new project and I wanted to understand how in sync our environments are. In this case we are using different resource group for to hold the resources for each environment.

I suspected that the environments may not be in sync and that some resources were in one environment and not another. When you have been using Azure for a while its not always easy to workout if your environments are clean. Using Azure DevOps pipelines from scratch can help with this but often customers have multiple concurrent projects all changing stuff at the sametime. Once you are out of sync its not that easy to manage getting back insync.

What I wanted to do is to run a script and get a table of all of the resources in each resource group and to then create a table with a column showing if the resource is in each resource group.

I chose to implement a Powershell script which would do this and in the rest of this article I will discuss how I implemented it.

Virtualize the Resource Name

One of the challenges is that some resources need to have unique names. An example of this would be an azure function. The name is also used for DNS so it needs to be unique. When we compare resource groups a function app used for the same purpose should be in all environments but the physical name for each might be slightly different. This is common for a number of different types of resource.

I chose to tackle this by creating a "virtual name" for each resource. To do this I will iterate over all of the resources and then I will call a function which will check the name and possibly the resource type to try to convert the name to something common across resource groups. In my case I have a naming convention for resources which the name ends with the name of the environment. This made it not too difficult to convert the name to a virtual name by checking the end of the name.

If you chose to use this approach simply modify the VirtualiseResourceName function so that it does whatever you need to do to convert the name to a virtual name.

Below is my function to virtualise the resource name, yours may need to be modified.

function VirtualiseResourceName([string] $resourceName, [string] $resourceType)
{

    $virtualResourceName = $resourceName
    $virtualResourceName = $virtualResourceName -Replace "systemtest$", ""
    $virtualResourceName = $virtualResourceName -Replace "uat1$", ""
    $virtualResourceName = $virtualResourceName -Replace "uat2$", ""

    if($resourceName -ne $virtualResourceName)
    {
        Write-Host $resourceName ' has been changed to ' $virtualResourceName
    }
    else
    {
        Write-Host $resourceName ' is unchanged'
    }

    return $virtualResourceName
}

Note if you use different standards for different resources then you can implement it here too.

Output

When you execute the script it will iterate over the resources you have access to and will ignore ones which arent in the resource groups your interested in. It will virtualise the resource name so you can match resources even if their names are slightly different as discussed above.

The output will then be put in a CSV file and you can open it in excel and you will get a list of resources like shown below.

image.png

In the sample above I can identify the following issues:

  • The file system connector is not in UAT1
  • LogicApp2 is not in UAT2
  • The On Premise Data Gateway is not in UAT2
  • The SAP connector is not in either UAT environment

This is a much cut down example output but some of the things I have learnt when using this in the real world include:

  • Sometimes the naming conventions have not been used correctly so resources dont match
  • Sometimes some resources arent in the test environments

While the script can help you get a view of where you are now, it can also help you to manage resolving any issues by comparing environments to make sure you are fixing the issues.

Limitations

The main limitation here is that I am looking for the existance of resources. I am not looking at the configuration of those resources to identify differences. That maybe something for the future but I think the first step is to make sure all of the resources exist.

Idea

I think one recommendation I would have from what I learned while putting this together is that in Azure when you deploy things it is a great idea to use tags to add a tag called "Logical Resource Name" to your resources. This will allow you to match resources by tags which would get rid of the need to calculate the matching via things like regular expressions.

Final Script

The full final script is below. I will appologise to the Powershell experts out there as im sure the script can be written more efficiently but it does the job I needed and feel free to suggest better alternatives in the comments if anyone has suggestions to improve it.

Connect-AzureRmAccount

#List of resource groups I am interested in comparing
$resourceGroupsToDiff = @('SystemTest', 'UAT1', 'UAT2')

$hashTable = $null
$hashTable = @{}
$resourceKey = ''

#Function to create a virtual resource name to help match resources across  #resource groups when they have different names
function VirtualiseResourceName([string] $resourceName, [string] $resourceType)
{

    $virtualResourceName = $resourceName
    $virtualResourceName = $virtualResourceName -Replace "-systemtest$", ""
    $virtualResourceName = $virtualResourceName -Replace "-uat1$", ""
    $virtualResourceName = $virtualResourceName -Replace "-uat2$", ""

    if($resourceName -ne $virtualResourceName)
    {
        Write-Host $resourceName ' has been changed to ' $virtualResourceName
    }
    else
    {
        Write-Host $resourceName ' is unchanged'
    }

    return $virtualResourceName
}

foreach($resourceGroupToCheck in $resourceGroupsToDiff)
{
    Write-Host 'Checking Resource Group ' $resourceGroupToCheck
    $resources = Get-AzureRmResource -ResourceGroupName $resourceGroupToCheck
 
    foreach ($resource in $resources)
    {
        if($resourceGroupsToDiff.Contains($resource.ResourceGroupName))
        {                                                                                                                                                              
            Write-Host $resource.Name ' is in resource group ' $resource.ResourceGroupName

            $virtualResourceName = VirtualiseResourceName -resourceName $resource.Name -resourceType $resource.ResourceType

            $resourceKey = $resource.ResourceType + '-' + $virtualResourceName
            if($hashTable.ContainsKey($resourceKey) -eq $false)
            {
                Write-Host 'Adding new resource to model'


                $azureResource = New-Object -TypeName psobject
                $azureResource | Add-Member -MemberType NoteProperty -Name 'ResourceVirtualName' -Value $virtualResourceName
                $azureResource | Add-Member -MemberType NoteProperty -Name 'ResourceType' -Value $resource.ResourceType

                foreach($resourceGroup in $resourceGroupsToDiff)
                {
                    $azureResource | Add-Member -MemberType NoteProperty -Name $resourceGroup -Value ''
                }                

                $hashTable.add($resourceKey, $azureResource)

                Write-Host 'New Resource added: ' + $resourceKey
            }


            $propertyName = $resource.ResourceGroupName

            $azureResource = $hashTable[$resourceKey]
            $azureResource.$propertyName = $resource.Name
        }    
    }
}

#Convert hashtable to an array to allow writing to a table
$hashTable.GetEnumerator() | sort name
$resourceList = [System.Collections.ArrayList]::new(); 
foreach($val in $hashTable.Values)
{
    $resourceList.Add($val)    
}

#Write-Host ($resourceList | Format-Table | Out-String)
$resourceList | Export-Csv -Path C:\Temp\Resources.csv

Was this article helpful?