Azure Partner Rebates / Commission Query resource inventory with Azure Resource Graph
Imagine your Azure environment as a huge warehouse filled with boxes. Some boxes are labeled “prod,” some are taped together with emergency duct tape, and some are there because someone once said, “We’ll clean that up later.” Now picture trying to inventory it by walking from shelf to shelf, opening every box, and then writing everything down by hand. That’s what traditional inventory methods can feel like.
Azure Resource Graph (ARG) is the forklift that shows up, beeps politely, and moves you straight to the aisle you actually need. With ARG, you can query your Azure resources across subscriptions using a powerful query language designed for speed and scale. Instead of guessing, clicking around, or waiting for multiple exports, you can ask questions like: “What resources are missing tags?”, “How many storage accounts exist per region?”, “Where are the weirdly named things hiding?”, and “Which virtual machines are still using an old SKU?”
This article is a friendly, practical guide to querying your resource inventory with Azure Resource Graph. We’ll cover what ARG is, why it’s useful, how to think about the data model, and how to write queries that return results you can actually use. We’ll also sprinkle in some humor, because if your cloud inventory doesn’t make you at least mildly laugh sometimes, then you’re doing it wrong.
What is Azure Resource Graph?
Azure Resource Graph is an Azure service that lets you efficiently explore and query resources in your Azure environment. The key idea is that you can run queries that span many subscriptions and resource groups without needing to individually query each subscription. It builds an index of resource properties so queries can run quickly—even when you have a large footprint.
In other words: instead of treating your environment like one giant spreadsheet you can’t find the tabs for, ARG gives you a search engine designed specifically for Azure resource inventory.
Why query your resource inventory at all?
People query resource inventories for lots of reasons. Some are noble, like governance and compliance. Others are… less dramatic but still important, like:
- Finding orphaned resources nobody remembers (the IT equivalent of socks that vanish in the laundry).
- Validating naming conventions before they turn into a modern art exhibit.
- Checking whether required tags exist (and discovering that they do not, in fact, exist).
- Preparing for cost optimization by understanding what resources you actually have.
- Auditing configurations to meet security baselines.
- Responding to incidents with clarity instead of “let me check… let me check… let me check…”
Whether you’re building reports, supporting automation, or just trying to get your arms around the sprawl, querying your inventory is the step where confusion turns into knowledge.
How Azure Resource Graph fits into the toolbox
ARG doesn’t replace every tool you might use. It complements them. Think of ARG as the fast “read/query” layer for resource metadata.
You can still use tools like Azure Policy, Azure Resource Manager, Azure Monitor, and Activity Logs for actions, policy enforcement, and telemetry. But when you want an inventory view—especially across subscriptions—ARG is your go-to.
One common workflow looks like this:
- Use ARG to discover what’s out there.
- Use your findings to decide what to investigate deeper.
- Use other services to remediate, alert, or enforce controls.
It’s basically: “Tell me what exists” (ARG), then “Tell me why it matters” (policies, alerts, logs), then “Do something useful” (automation/remediation). Like a good detective story, except the villain is misconfigured infrastructure.
Core concepts you should know before querying
Before writing queries, it helps to understand the shape of the data.
Subscriptions and scopes
ARG can query across different scopes, such as management groups and subscriptions. Your query results will depend on what scope you run against. If you query only a small scope, you’ll understandably get fewer results—like fishing with a thimble.
Resource properties
ARG represents resources with a set of properties like resource type, location, name, tags, and IDs. The properties vary depending on the resource provider and resource type, but you generally have access to common metadata.
Querying the resources table
ARG queries typically target a “resources” dataset (often referred to as the resources table). You can filter, project, and aggregate data based on resource properties.
The query language is called Kusto Query Language (KQL), which is the same family used in Azure Data Explorer and Application Insights. If you already know KQL, you’re in luck. If you don’t, don’t worry. We’ll use patterns that are easy to adapt.
Getting started: your first inventory query
Let’s start simple. The goal is to list resources and verify that you can see what you expect.
List a sample of resources
Here’s a basic query idea:
Resources
| limit 20
This kind of query retrieves a small sample so you can confirm that:
- You’re querying the expected scope.
- You’re using the correct dataset.
- You’re seeing results with reasonable properties.
Even if you already “know” your environment, confirming the dataset is still worth doing. Otherwise you’ll later spend 40 minutes arguing with a query that’s perfectly correct for a scope you didn’t intend.
Show name, type, location, and subscription
Inventory queries often want these columns:
- Name
- Resource type
- Location
- Subscription ID (or sometimes subscription name)
A typical projection might look like:
Resources
| project name, type, location, subscriptionId
| limit 50
Now you can quickly spot patterns: are resources spread across the regions you expect? Are you accidentally mixing subscriptions? Are there resources with null/empty locations (rare, but sometimes interesting)?
Filtering your inventory: the difference between “everything” and “useful”
Raw resource lists are like listing every single book in a library without mentioning the categories. It’s accurate, sure. It’s also not helping you pick “the book you want to read tonight.”
Filter by resource type
Suppose you want only virtual machines:
Resources
| where type =~ 'microsoft.compute/virtualmachines'
| project name, location, subscriptionId
| limit 100
Or storage accounts:
Resources
| where type =~ 'microsoft.storage/storageaccounts'
| project name, location, subscriptionId
| limit 100
Azure Partner Rebates / Commission The operator “where” narrows results. The “=~” comparison is case-insensitive, which helps avoid silly mistakes when type strings vary in capitalization. (The cloud loves capitalization just enough to be annoying.)
Filter by location
If you’re migrating workloads out of a region, or just auditing where resources live:
Resources
| where location =~ 'eastus'
| project name, type, subscriptionId
| limit 200
To combine filters, just chain multiple where clauses:
Resources
| where type =~ 'microsoft.compute/virtualmachines'
| where location =~ 'eastus'
| project name, subscriptionId
| limit 200
Filter by resource group
Many inventory questions are really “which resource groups have X?” You can use resourceGroup property (when available) or parse from the resource ID. In many cases, resourceGroup is directly available.
Resources
| where resourceGroup =~ 'rg-prod-network'
| project name, type, location, subscriptionId
| limit 200
Azure Partner Rebates / Commission If resourceGroup isn’t available as a direct property in your environment, you can often extract it from the resource ID. The exact approach can depend on the dataset schema, but the pattern remains: locate the resource ID segments and then filter.
Using tags effectively (and coping with missing tags)
Tags are where hope goes to be either fulfilled or betrayed.
Find resources missing required tags
A common governance task is identifying resources without mandatory tags such as “owner,” “costCenter,” or “environment.” In KQL, you can check for null or empty tag values.
Conceptually, you’d write something like:
Resources
| where isnull(tags.owner) or tags.owner == ''
| project name, type, location, subscriptionId, tags
| limit 200
In practice, your tag key names must match exactly (including casing, depending on how they’re stored). If you’re not sure how tags appear in the dataset, start by projecting tags to inspect what keys exist.
List resources by environment tag
Maybe you want resources tagged “dev”:
Resources
| where tags.environment =~ 'dev'
| project name, type, location, subscriptionId
| limit 200
Or “prod,” but please with less drama than the last tag naming convention incident.
Resources
| where tags.environment =~ 'prod'
| project name, type, location, subscriptionId
| limit 200
Inventory aggregation: counts, breakdowns, and “how many is too many?”
Inventory isn’t only about listing. Sometimes you want totals and patterns. Aggregations answer questions like:
- How many resources per type exist?
- How many storage accounts are in each region?
- Azure Partner Rebates / Commission Which subscriptions have the most virtual machines?
Count resources by type
Resources
| summarize count() by type
| sort by count_ desc
This gives you a quick sense of what dominates your environment. If you see a surprise type spike, that’s your cue to investigate.
Count virtual machines by region
Resources
| where type =~ 'microsoft.compute/virtualmachines'
| summarize count() by location
| sort by count_ desc
This can help with capacity planning and regional strategy.
Count storage accounts by subscription
Resources
| where type =~ 'microsoft.storage/storageaccounts'
| summarize count() by subscriptionId
| sort by count_ desc
If one subscription has an “impossibly” high number of storage accounts, it might indicate:
- It’s the main shared services subscription.
- There’s been little cleanup.
- People created storage accounts for experiments and forgot them.
Or, as clouds often do, all three.
Finding “stale” inventory: spotting what’s probably unused
Resource Graph is primarily inventory and metadata, not behavior telemetry. But you can still do practical “staleness” discovery using properties that indicate creation time, last modified time, or version-like fields. Availability depends on the resource type.
Example: resources older than a threshold
If the dataset includes a field like “createdTime,” you could query for resources created before a certain date.
Resources
| where isnotnull(properties.createdTime)
| where properties.createdTime < datetime(2022-01-01)
| project name, type, location, subscriptionId, createdTime=properties.createdTime
| limit 200
Not every resource type exposes the same fields. The way to stay sane is: start by projecting the likely date fields, inspect results, then lock in the filters.
Security-oriented inventory queries
Azure Partner Rebates / Commission Inventory can be a security superpower. You can identify resources that are missing protections, expose risky settings, or don’t match policy expectations. The exact fields and available properties vary by resource type and API version, but ARG is great for getting a first pass at “what might be wrong.”
Find resources by naming pattern (because humans are creative)
Sometimes the only clue you get is the name. For example, find resources containing “test”:
Resources
| where name has 'test'
| project name, type, location, subscriptionId
| limit 200
Or “dev”:
Resources
| where name has 'dev'
| project name, type, location, subscriptionId
| limit 200
This is not perfect security analysis, but it can quickly highlight where you might find outdated environments.
Find resources with suspicious tag values
Maybe your governance is based on tags, and tag misuse is an early warning system.
Resources
| where tostring(tags.environment) !~ 'prod' and tostring(tags.environment) !~ 'dev' and tostring(tags.environment) !~ 'test'
| project name, type, subscriptionId, tags
| limit 200
This query is illustrative. You’d adjust acceptable values to match your organization. The important thing: define your “known good,” then list deviations.
Cost and optimization hints
Azure Partner Rebates / Commission ARG usually won’t give you full cost metrics. For cost you’d typically use Azure Cost Management or billing exports. But inventory helps you determine where costs might be coming from.
Find large compute footprints (inventory-driven)
If virtual machine SKU or size is available as a field, you can aggregate by size.
Resources
| where type =~ 'microsoft.compute/virtualmachines'
| summarize count() by tostring(properties.hardwareProfile.vmSize)
| sort by count_ desc
If your environment includes many large SKUs, you can target those for right-sizing.
Working across many subscriptions (without losing your mind)
One of the best reasons to use ARG is cross-subscription querying. Instead of running separate queries per subscription, you can query the environment in one go—assuming your access allows it.
Project subscription IDs to validate scope
Always include subscriptionId when you’re exploring broad scopes, at least until you’re confident the data looks correct.
Resources
| project name, type, subscriptionId, location
| limit 100
Once you’re confident, you can remove subscriptionId from projections if your end goal is purely aggregated.
Query patterns you’ll use constantly
Let’s gather some “repeat offenders” you’ll likely use in many queries.
Projection first, then filter (for clarity)
When exploring unknown fields, start by projecting candidate properties. Then filter.
Resources
| project name, type, location, tags
| limit 50
Then later, build on what you learned.
Use summarize for counts and groupings
Resources
| summarize count() by type, location
| sort by count_ desc
This quickly answers “what’s where.”
Sort by something meaningful
Sorting by count desc is usually helpful for inventory. If you’re listing resources, consider sorting by location then name, or by subscription then type.
Practical scenarios (with query ideas)
Now let’s walk through a few realistic use cases. These aren’t strict copy-paste “guaranteed correct” templates, because resource schemas vary. But they’ll get you thinking in the right direction, and you can adapt based on your environment.
Scenario 1: Find resources missing the “environment” tag
Resources
| where isnull(tags.environment) or tags.environment == ''
| project name, type, location, subscriptionId, tags
| limit 500
Deliverable idea: a list of candidates for tagging remediation, grouped by type and subscription.
Scenario 2: Inventory all resource types in a subscription
Resources
| where subscriptionId =~ 'YOUR_SUBSCRIPTION_ID'
| summarize count() by type
| sort by count_ desc
Deliverable idea: a “top resource types” report per subscription.
Scenario 3: List all storage accounts in a region with a naming mismatch
Suppose your policy says storage account names should start with “st” and your team is… well… human.
Resources
| where type =~ 'microsoft.storage/storageaccounts'
| where location =~ 'westus'
| where not (name startswith 'st')
| project name, subscriptionId, location
| limit 200
Deliverable idea: naming convention exceptions.
Scenario 4: Find resources tagged costCenter but with empty values
Resources
| where isnotnull(tags.costCenter)
| where tags.costCenter == ''
| project name, type, subscriptionId, tags
| limit 200
This highlights tag misuse: the key exists, but the value is blank. That’s like wearing a name badge that says “ ” (space). Technically present. Practically useless.
Debugging your queries (the part where the query refuses to behave)
At some point, you’ll run a query and it returns nothing. Or it returns too much. Or it errors with a message that sounds like it was written by a raccoon on espresso.
Here’s a pragmatic troubleshooting checklist.
1) Confirm your scope
If you expected results from multiple subscriptions but only see one, you might be querying a narrower scope than intended.
2) Inspect available fields
If you filter on a field that doesn’t exist for that resource type, you’ll either get errors or empty results. Start by projecting that field (or nearby fields) for a broad set of resources.
3) Use limit while exploring
When you’re still figuring out the right fields, always use limit. It makes iteration faster and reduces the temptation to doomscroll the results.
4) Check tag key casing and spelling
Tags are usually consistent, but inconsistent spelling is common. If your policy says “costCenter,” your query should look for exactly that key, unless you’re explicitly trying to catch typos.
5) Compare resource type strings carefully
Resource type strings can be long and easy to miswrite. Use known working types first, then refine.
Turning queries into useful reports
A query is just a question. Reports are the “yes, and here’s what it means.” When you create inventory queries, think about the output shape you need.
Choose the right columns
Inventory outputs often benefit from columns like:
- name
- type
- location
- subscriptionId
- resourceGroup (if available)
- tags (selectively)
For aggregated reports, columns are usually:
- grouping keys (type, location, subscriptionId)
- counts
Group for actionability
If your team will remediate issues, group by whatever the remediation owner needs: subscription, resource group, or resource type. A list of 500 resources across five subscriptions is not as helpful as a grouped breakdown with counts and example names.
Common mistakes to avoid
Let’s save you from a few classics.
- Over-filtering too early. Start broad, validate, then narrow. Otherwise you might filter out the exact resources you wanted to find.
- Ignoring null values. Tags and optional properties often show up as null. If you only check for empty strings, you might miss nulls.
- Assuming the schema is the same for every type. It isn’t. Project properties to confirm.
- Exporting “everything” instead of “actionable findings.” If you can’t act on the output, it’s not an inventory report—it’s just a very large printout.
Putting it all together: a sample end-to-end inventory query set
Here’s a coherent mini “inventory kit” you can adapt:
1) Baseline: show the top resource types
Resources
| summarize count() by type
| sort by count_ desc
2) Tag health: resources missing environment tag
Resources
| where isnull(tags.environment) or tags.environment == ''
| summarize count() by type
| sort by count_ desc
Azure Partner Rebates / Commission 3) Tag health: list missing-tag resources (for remediation candidates)
Resources
| where isnull(tags.environment) or tags.environment == ''
| project name, type, location, subscriptionId, resourceGroup
| limit 300
4) Regional distribution for a critical resource type
Resources
| where type =~ 'microsoft.storage/storageaccounts'
| summarize count() by location
| sort by count_ desc
5) Subscription concentration for the same resource type
Resources
| where type =~ 'microsoft.storage/storageaccounts'
| summarize count() by subscriptionId
| sort by count_ desc
That set gives you both perspective (counts and distribution) and action (lists to remediate).
Next steps: where to go from here
Once you’re comfortable with filtering, projecting, and summarizing, you can level up your inventory queries. Here are productive directions:
- Combine tag filtering with type filtering to target only certain workloads.
- Azure Partner Rebates / Commission Use naming pattern checks to identify convention drift.
- Build “policy-like” queries that reflect security or governance rules.
- Compare environments (dev vs prod) by resource counts and types.
- Create periodic reports so inventory doesn’t slowly turn into a haunted house.
And if you ever feel like your inventory is out of control, remember: that’s not a failure. That’s just evidence that your next query is needed. The cloud is dynamic; your job is to keep up with it using questions that are sharper than the chaos.
Conclusion: inventory, but make it fast
Querying your Azure resource inventory with Azure Resource Graph helps you replace guessing with evidence. You can discover resources across subscriptions, filter to the exact scenarios you care about, and aggregate results into meaningful insights. Whether you’re working on tagging governance, security discovery, regional planning, or cost-oriented analysis, ARG gives you a fast way to see what’s actually deployed.
So go ahead: ask the warehouse where the missing boxes are. Then label the shelves. Then ask again later, because the warehouse always fills back up. With Azure Resource Graph, at least you’ll be carrying a forklift instead of a flashlight and a clipboard.

