Tracking Azure History with Azure Resource Graph


When administrating an Azure environment, or any environment really, one will most likely find a way to track changes that were introduced. There are a number of ways to do this. Within Azure can query the Subscription or Resource Group Deployment, the downside though is this approach is limited to just the scope you are querying on. What if this is a larger organization with multiple subscriptions? You could also rely on a well-established CI/CD pipeline, a third-party governance tool, or in this case query Azure directly via the Resource Graph Explorer.


For this blog will focus on using the Azure Portal offering of the tool; however, want to note that since this is API driven there are numerous offerings such as Azure PowerShell, Azure CLI, .NET, even Ruby.



An understanding of Kusto Query Language (KQL) is very helpful
Knowing what types of tables are available in Resource Graph
The individual/process querying need to have read access over the available Azure resources


Documentation clearly supports will store only last 14 days worth of changes.
Latest documentation shows the Resource Change History API has been in preview since 4/23/19



We will be querying two tables for this exercise. It is fairly straight forward; however, something anyone can further customize by joining additional tables and/or filters two. This will deal with only two tables: resources and resource changes. To access the Resource Graph Explorer type ‘Resource Graph Explorer’ in the Azure search bar.



This table is defined as: “The default table if none defined in the query. Most Resource Manager resource types and properties are here.”

What this essentially translates to is that this table will hold the basic properties of all the resources.  As of this writing the here are some of the columns are available, and I am providing a brief description of what the values mean.

Column Name

Unique id of the Azure Resource. This will include subscription ID down to individual resource.

Name of the Azure Resource

The Microsoft defined type of resource. Complete list available here.

Azure AD Tenant associated with the resource

Not always present: however, some values could be “StorageV2” or “functionapp” for account or microsoft.web/sites respectively

What location within azure the resource is deployed to, ‘global’ for those that are global.

The Azure Resource Group the resource resides in

The associated Azure Subscription

Not always present; however, what pricing tier or sku the resource has been set to

This will include the provisioning status as well as the JSON Azure Resource Manager definition of the resource

Any tags that have been applied



This is the table that is preview and whose dataset is limited to 14 days as mentioned above. This table will follow the same schema; however, the all-important information on what changed is contained in the properties as a JSON object.


Screenshot of resourcechanges properties json


So now we have the two tables, the question becomes how to join them together. If new I highly advise brushing up on KQL.  Essentially our query will need to accomplish:

Expanding the resourcechanges properties to retrieve change details and targetResourceId of the change
Join resources to resourcechanges on the resourceId and targetResourceId field
pull any additional resource information for reporting purposes.

For our purposes I’ll pull the Resource Name, Resource Type, Subscription, and Resource Group Name as these could be beneficial for anyone doing reporting.


One thing that did trip me up when working with joins in KQL. All columns which will be used from the join table need to be exposed in the project command, this INCLUDES the field you are joining on.


The Query


| extend changeTime = todatetime(properties.changeAttributes.timestamp), targetResourceId = tostring(properties.targetResourceId),

changeType = tostring(properties.changeType), correlationId = properties.changeAttributes.correlationId,

changedProperties = properties.changes, changeCount = properties.changeAttributes.changesCount

| where changeTime > ago(180d)

| join kind=inner (resources | project resources_Name = name, resources_Type = type, resources_Subscription= subscriptionId, resources_ResourceGroup= resourceGroup, id) on $left.targetResourceId == $

| project resources_Name, resources_Type, resources_Subscription, resources_ResourceGroup, changeTime, targetResourceId, changeType, correlationId, changeCount, changedProperties


The Results



There you go. This may seem like a complex approach to something as easy as what’s changed across my Azure subscriptions; however, this approach does accurately achieve that goal. Furthermore, with the APIs being exposed across multiple programming languages leaves upon limitless possibilities as to what one can do.



No Comments

Leave a Reply

Microsoft Teams helps manage apps for everyday work functions and fit our modern hybrid needsUse Static Web Apps API and API Management Authorizations to integrate third party services

Contact Us

We are always here to help. Please reach out to us and we'll get back to you as soon as possible.


Maruti Court, Ground Floor
Mvuli Road, Nairobi, Kenya




Mon-Fri 8am – 5pm
Sat 9am – 1am
Sun & Holidays Closed

    Generated by Feedzy