Azure Resource Graph Queries: 7 Admin Guardrails

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 problemHow Resource Graph helpsNext 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.

  1. Scope intentionally: one subscription or management group before the whole estate.
  2. Validate field names: schemas vary by provider, so inspect a sample resource if a property is missing.
  3. Decide ownership for every recurring guardrail.
  4. Pair with Azure Policy: Resource Graph finds the mess; policy stops some of it returning.
  5. 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


Discover more from Cloud Osprey

Subscribe to get the latest posts sent to your email.

Cloud Osprey
Cloud Ospreyhttps://cloudosprey.com/about/
Hands-on Microsoft cloud practitioner writing practical, source-checked guides on Microsoft 365, SharePoint, Copilot, Entra ID, Power Platform and Azure. Formerly the author of SP Monkey, the SharePoint and Microsoft 365 blog, since 2016. Every guide links its sources and carries a "Last reviewed" date.

Recent Articles

Related Stories