Azure Resource Graph queries are one of the fastest ways to stop guessing what’s happening across Azure. When the portal feels like a cupboard of mystery cables, Resource Graph gives you a searchable inventory, a drift detector and a governance flashlight in one tool.
Here are seven guardrail queries to adapt: inventory, missing tags, orphaned resources, storage and Key Vault posture, policy compliance and recent changes.
Admin quick take
Resource Graph turns cloud inventory into repeatable governance.
- First move: run the inventory, tag, public IP and disk queries against a non-production subscription.
- Automation move: save approved queries and schedule them through your reporting pipeline.
- Watch-out: results only help if someone owns the remediation queue.
Why Azure Resource Graph queries belong in your toolkit
Resource Graph is built for efficient resource exploration across subscriptions and tenants. It uses a Kusto-style language with tables for resources, policy, Advisor, authorization, App Service and more, and it can query resource property changes. That makes it ideal for guardrails: recurring checks that catch drift before it becomes a ticket storm.
| Admin problem | How Resource Graph helps | Next step |
|---|---|---|
| “What do we have?” | Query across subscriptions, management groups or tenants. | Build a weekly inventory snapshot. |
| “What changed?” | Inspect recent resource changes at scale. | Correlate with incidents or deployment windows. |
| “Are policies working?” | Query Azure Policy compliance data. | Prioritize non-compliant resources. |
| “Where’s the mess?” | Find missing tags, orphaned resources and risky settings. | Create remediation items with owners. |
Before you run them
Run queries in Resource Graph Explorer, with az graph query, with Search-AzGraph in PowerShell, or from your own automation.
- Scope intentionally: one subscription or management group before the whole estate.
- Validate field names: schemas vary by provider, so inspect a sample resource if a property is missing.
- Decide ownership for every recurring guardrail.
- Pair with Azure Policy: Resource Graph finds the mess; policy stops some of it returning.
- Report before remediating: automate fixes only once exceptions, approvals and rollback exist.
1. Count resources by type and location
Resources
| summarize resourceCount = count() by type, location
| order by resourceCount desc
Use it for inventory baselines, regional drift, migration planning and “why did the bill jump?” conversations.
2. Find resources missing required tags
Resources
| extend owner = tostring(tags.Owner), costCenter = tostring(tags.CostCenter), environment = tostring(tags.Environment)
| where isempty(owner) or isempty(costCenter) or isempty(environment)
| project name, type, resourceGroup, subscriptionId, location, owner, costCenter, environment
| order by type asc, name asc
Tags drive cost allocation, ownership and cleanup. Clean up existing resources with this query, then use Azure Policy to audit or require tags on new deployments, or you’re mopping with the tap running. Azure Policy remediation tasks can add missing tags at scale.
3. Find unattached managed disks
Resources
| where type =~ 'microsoft.compute/disks'
| where isempty(managedBy)
| project name, resourceGroup, location, sku = tostring(sku.name), diskSizeGB = tostring(properties.diskSizeGB), timeCreated = todatetime(properties.timeCreated), id
| order by timeCreated asc
Detached disks are a classic cost leak, but some are kept deliberately for recovery. Check age, owner, backups and change history before deleting.
4. Find unassociated public IP addresses
Resources
| where type =~ 'microsoft.network/publicipaddresses'
| extend ipConfiguration = tostring(properties.ipConfiguration.id), allocationMethod = tostring(properties.publicIPAllocationMethod), ipSku = tostring(sku.name)
| where isempty(ipConfiguration)
| project name, resourceGroup, location, allocationMethod, ipSku, id
| order by resourceGroup asc, name asc
These add cost and exposure confusion. Some teams stage IPs for disaster recovery, cutovers or allow-lists, so treat results as candidates, not verdicts.
5. Check storage account public access and TLS
Resources
| where type =~ 'microsoft.storage/storageaccounts'
| extend allowBlobPublicAccess = tostring(properties.allowBlobPublicAccess), minTlsVersion = tostring(properties.minimumTlsVersion), supportsHttpsOnly = tostring(properties.supportsHttpsTrafficOnly)
| project name, resourceGroup, location, allowBlobPublicAccess, minTlsVersion, supportsHttpsOnly, id
| order by allowBlobPublicAccess desc, minTlsVersion asc
Storage settings sit close to data risk. Some workloads need specific settings, but document each exception.
6. Review Key Vault purge protection
Resources
| where type =~ 'microsoft.keyvault/vaults'
| extend purgeProtection = tostring(properties.enablePurgeProtection), softDeleteRetentionDays = tostring(properties.softDeleteRetentionInDays)
| project name, resourceGroup, location, purgeProtection, softDeleteRetentionDays, tenantId = tostring(properties.tenantId), id
| order by purgeProtection asc, name asc
Purge protection guards against accidental or malicious deletion of secrets, keys and certificates. It affects recovery and lifecycle behavior, so agree changes with security and platform owners first. Pair it with Azure Resource Locks on production vaults.
7. Summarize policy compliance and recent changes
Resource Graph gets more powerful when inventory meets compliance and change history.
PolicyResources
| where type =~ 'microsoft.policyinsights/policystates'
| extend complianceState = tostring(properties.complianceState), assignment = tostring(properties.policyAssignmentName), resourceType = tostring(properties.resourceType)
| summarize resources = count() by complianceState, assignment, resourceType
| order by complianceState asc, resources desc
resourcechanges
| extend changeTime = todatetime(properties.changeAttributes.timestamp), changedBy = tostring(properties.changeAttributes.changedBy), clientType = tostring(properties.changeAttributes.clientType), operation = tostring(properties.changeAttributes.operation), targetResourceId = tostring(properties.targetResourceId)
| where changeTime > ago(24h)
| project changeTime, operation, changedBy, clientType, targetResourceId
| order by changeTime desc
Use it for incident timelines, deployment validation, remediation planning and spotting manual changes. If production broke at 10:12 and a setting changed at 10:07, you have a thread to pull.
Turn Azure Resource Graph queries into guardrails
A clever query becomes a guardrail through repetition: run it on a cadence, route results to owners, track exceptions and close the loop with policy or automation.
- Report first, so teams trust the findings.
- Assign owners: every exception needs a team, ticket or documented reason.
- Prevent repeat drift by converting mature checks into Azure Policy, templates or deployment guardrails.
The same pattern works in Microsoft 365: find exposure, prioritize risk, assign owners and automate the boring parts. See Restricted Content Discovery for SharePoint, Microsoft 365 Copilot watermarks and the SharePoint agents governance checklist.
Last reviewed: October 2026
Sources
- Overview of Azure Resource Graph (Microsoft Learn)
- Azure Resource Graph query language (Microsoft Learn)
- Azure Resource Graph starter queries (Microsoft Learn)
- Get Azure Policy compliance data (Microsoft Learn)
- Get resource changes with Azure Resource Graph (Microsoft Learn)
Discover more from Cloud Osprey
Subscribe to get the latest posts sent to your email.