Get resource changes
Resources change through the course of daily use, reconfiguration, and even redeployment. Most change is by design, but sometimes it isn't. You can:
- Find when changes were detected on an Azure Resource Manager property.
- View property change details.
- Query changes at scale across your subscriptions, management group, or tenant.
In this article, you learn:
- What the payload JSON looks like.
- How to query resource changes through Resource Graph using either the CLI, PowerShell, or the Azure portal.
- Query examples and best practices for querying resource changes.
- Change analysis uses Change Actor functionality:
changedBy
: Who initiated a change in your resource, like an app ID or authorized person's email address.clientType
: Which client made the change, like Azure portal.operation
: Which operation was called, likeMicrosoft.Compute/virtualmachines/write
.
Prerequisites
- To enable Azure PowerShell to query Azure Resource Graph, add the module.
- To enable Azure CLI to query Azure Resource Graph, add the extension.
Understand change event properties
When a resource is created, updated, or deleted, a new change resource (Microsoft.Resources/changes
) is created to extend the modified resource and represent the changed properties. Change records should be available in less than five minutes. The following example JSON payload demonstrates the change resource properties:
{
"targetResourceId": "/subscriptions/11111111-1111-1111-1111-111111111111/resourceGroups/myResourceGroup/providers/microsoft.compute/virtualmachines/myVM",
"targetResourceType": "microsoft.compute/virtualmachines",
"changeType": "Update",
"changeAttributes": {
"previousResourceSnapshotId": "11111111111111111111_22222222-3333-aaaa-bbbb-444444444444_5555555555_6666666666",
"newResourceSnapshotId": "33333333333333333333_44444444-5555-ffff-gggg-666666666666_7777777777_8888888888",
"correlationId": "11111111-1111-1111-1111-111111111111",
"changedByType": "User",
"changesCount": 2,
"clientType": "Azure Portal",
"changedBy": "john@contoso.com",
"operation": "microsoft.compute/virtualmachines/write",
"timestamp": "2024-06-12T13:26:17.347+00:00"
},
"changes": {
"properties.provisioningState": {
"newValue": "Succeeded",
"previousValue": "Updating",
"changeCategory": "System",
"propertyChangeType": "Update",
"isTruncated": "true"
},
"tags.key1": {
"newValue": "NewTagValue",
"previousValue": "null",
"changeCategory": "User",
"propertyChangeType": "Insert"
}
}
}
See the full reference guide for change resource properties.
Run a query
Try out a tenant-based Resource Graph query of the resourcechanges
table. The query returns the first five most recent Azure resource changes with the change time, change type, target resource ID, target resource type, and change details of each change record.
# Login first with az login if not using Cloud Shell
# Run Azure Resource Graph query
az graph query -q 'resourcechanges | project properties.changeAttributes.timestamp, properties.changeType, properties.targetResourceId, properties.targetResourceType, properties.changes | limit 5'
You can update this query to specify a more user-friendly column name for the timestamp property.
# Run Azure Resource Graph query with 'extend'
az graph query -q 'resourcechanges | extend changeTime=todatetime(properties.changeAttributes.timestamp) | project changeTime, properties.changeType, properties.targetResourceId, properties.targetResourceType, properties.changes | limit 5'
To limit query results to the most recent changes, update the query to order by
the user-defined changeTime
property.
# Run Azure Resource Graph query with 'order by'
az graph query -q 'resourcechanges | extend changeTime=todatetime(properties.changeAttributes.timestamp) | project changeTime, properties.changeType, properties.targetResourceId, properties.targetResourceType, properties.changes | order by changeTime desc | limit 5'
You can also query by management group or subscription with the -ManagementGroup
or -Subscription
parameters, respectively.
Note
If the query does not return results from a subscription you already have access to, then the Search-AzGraph
PowerShell cmdlet defaults to subscriptions in the default context.
Resource Graph Explorer also provides a clean interface for converting the results of some queries into a chart that can be pinned to an Azure dashboard.
Query resource changes
With Resource Graph, you can query either the resourcechanges
, resourcecontainerchanges
, or healthresourcechanges
tables to filter or sort by any of the change resource properties. The following examples query the resourcechanges
table, but can also be applied to the resourcecontainerchanges
or healthresourcechanges
table.
Note
Learn more about the healthresourcechanges
data in the Project Flash documentation.
Examples
Before querying and analyzing changes in your resources, review the following best practices.
- Query for change events during a specific window of time and evaluate the change details.
- This query works best during incident management to understand potentially related changes.
- Keep an up-to-date Configuration Management Database (CMDB).
- Instead of refreshing all resources and their full property sets on a scheduled frequency, you only receive their changes.
- Understand which other properties were changed when a resource changes compliance state.
- Evaluation of these extra properties can provide insights into other properties that might need to be managed via an Azure Policy definition.
- The order of query commands is important. In the following examples, the
order by
must come before thelimit
command.- The
order by
command orders the query results by the change time. - The
limit
command then limits the ordered results to ensure that you get the five most recent results.
- The
- What does Unknown mean?
- Unknown is displayed when the change happened on a client that's unrecognized. Clients are recognized based on the user agent and client application ID associated with the original change request.
- What does System mean?
- System is displayed as a
changedBy
value when a background change occurred that wasn't correlated with any direct user action.
- System is displayed as a
All changes in the past 24-hour period
resourcechanges
| 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(1d)
| order by changeTime desc
| project changeTime, targetResourceId, changeType, correlationId, changeCount, changedProperties
Resources deleted in a specific resource group
resourcechanges
| where resourceGroup == "myResourceGroup"
| extend changeTime = todatetime(properties.changeAttributes.timestamp), targetResourceId = tostring(properties.targetResourceId),
changeType = tostring(properties.changeType), correlationId = properties.changeAttributes.correlationId
| where changeType == "Delete"
| order by changeTime desc
| project changeTime, resourceGroup, targetResourceId, changeType, correlationId
Changes to a specific property value
resourcechanges
| extend provisioningStateChange = properties.changes["properties.provisioningState"], changeTime = todatetime(properties.changeAttributes.timestamp), targetResourceId = tostring(properties.targetResourceId), changeType = tostring(properties.changeType)
| where isnotempty(provisioningStateChange)and provisioningStateChange.newValue == "Succeeded"
| order by changeTime desc
| project changeTime, targetResourceId, changeType, provisioningStateChange.previousValue, provisioningStateChange.newValue
Changes in past seven days by who and which client and ordered by count
resourcechanges
| extend changeTime = todatetime(properties.changeAttributes.timestamp),
targetResourceId = tostring(properties.targetResourceId),
changeType = tostring(properties.changeType), changedBy = tostring(properties.changeAttributes.changedBy),
changedByType = properties.changeAttributes.changedByType,
clientType = tostring(properties.changeAttributes.clientType)
| where changeTime > ago(7d)
| project changeType, changedBy, changedByType, clientType
| summarize count() by changedBy, changeType, clientType
| order by count_ desc
Changes in virtual machine size
resourcechanges
| extend vmSize = properties.changes["properties.hardwareProfile.vmSize"], changeTime = todatetime(properties.changeAttributes.timestamp), targetResourceId = tostring(properties.targetResourceId), changeType = tostring(properties.changeType)
| where isnotempty(vmSize)
| order by changeTime desc
| project changeTime, targetResourceId, changeType, properties.changes, previousSize = vmSize.previousValue, newSize = vmSize.newValue
Count of changes by change type and subscription name
resourcechanges
| extend changeType = tostring(properties.changeType), changeTime = todatetime(properties.changeAttributes.timestamp), targetResourceType=tostring(properties.targetResourceType)
| summarize count() by changeType, subscriptionId
| join (resourcecontainers | where type=='microsoft.resources/subscriptions' | project SubscriptionName=name, subscriptionId) on subscriptionId
| project-away subscriptionId, subscriptionId1
| order by count_ desc
Latest resource changes for resources created with a certain tag
resourcechanges
|extend targetResourceId = tostring(properties.targetResourceId), changeType = tostring(properties.changeType), createTime = todatetime(properties.changeAttributes.timestamp)
| where createTime > ago(7d) and changeType == "Create" or changeType == "Update" or changeType == "Delete"
| project targetResourceId, changeType, createTime
| join ( resources | extend targetResourceId=id) on targetResourceId
| where tags ['Environment'] =~ 'prod'
| order by createTime desc
| project createTime, id, resourceGroup, type