AZ-104Chapter 114 of 168Objective 3.4

Azure Resource Graph Queries

This chapter covers Azure Resource Graph (ARG), a powerful Azure service that enables efficient exploration and querying of Azure resources across subscriptions. For the AZ-104 exam, Resource Graph queries appear in approximately 5-10% of questions, often integrated with Azure Policy, cost management, and automation scenarios. Understanding how to write KQL queries, use projections, filters, and joins, and interpret the schema of the 'resources' table is critical for answering scenario-based questions. This chapter provides a deep dive into ARG's architecture, query mechanics, and practical exam-focused patterns.

25 min read
Intermediate
Updated May 31, 2026

Azure Resource Graph as a Universal Search Engine

Imagine you are a facility manager responsible for thousands of buildings across a city. Each building has its own filing cabinet with blueprints, maintenance logs, and asset inventories. To find all buildings with a specific type of HVAC system, you would normally need to walk to each building, open its cabinet, and manually search through folders. This is like querying individual Azure resources via the Azure portal or REST API—each query returns data from a single resource or resource group. Now, suppose you have a centralized search engine that indexes every document from every building into a single, unified database. You can type a query like "find all buildings with HVAC model X and Y" and get results in seconds. This search engine updates automatically as new documents are added or changed. Azure Resource Graph (ARG) works exactly like this: it indexes Azure resource properties across all subscriptions into a single data store, enabling fast, complex queries using the Kusto Query Language (KQL). Instead of making hundreds of API calls to list resources in each subscription, you run one KQL query against the Resource Graph endpoint, which returns results from millions of resources in milliseconds. The index is refreshed continuously, so query results are eventually consistent but typically within minutes of any change.

How It Actually Works

What is Azure Resource Graph and Why Does It Exist?

Azure Resource Graph (ARG) is a service in Azure that provides a unified, scalable, and efficient way to query resource metadata across subscriptions, management groups, and tenants. It is built on top of Azure Resource Manager (ARM) and uses the same resource provider data, but it stores that data in an optimized, indexed, and queryable store separate from the control plane. The primary goal of ARG is to replace the inefficient practice of enumerating resources via ARM API calls—each of which returns only a single resource or a page of resources from one subscription—with a single query that can filter, group, and aggregate across millions of resources in seconds.

On the AZ-104 exam, you will encounter ARG in several contexts: as a tool for inventory and compliance (often combined with Azure Policy), as a data source for dashboards (via Azure Workbooks), and as a mechanism for triggering automation (e.g., via Azure Logic Apps or PowerShell). The exam expects you to know the KQL syntax for common operations, the schema of the resources table, and the differences between ARG and other query tools like the Azure CLI az resource list or the REST API.

How It Works Internally

ARG is not a live query against the ARM control plane. Instead, it maintains a separate, continuously updated index of resource properties. This index is stored in a distributed, replicated data store that supports Kusto Query Language (KQL)—the same query language used by Azure Data Explorer and Azure Monitor Logs.

When you submit a query to ARG, the following occurs:

1.

Authentication and Authorization: The request is authenticated via Azure AD and authorized using Azure RBAC. The service evaluates your effective permissions at the management group, subscription, or resource group level. ARG only returns resources you have read access to.

2.

Query Parsing and Optimization: The KQL query is parsed, optimized, and planned. The optimizer can push down filters (where clauses) and projections (project clauses) to the storage layer, reducing the amount of data scanned.

3.

Index Lookup: The query is executed against the indexed store. The store is organized by resource types and properties, so queries that filter on type, resourceGroup, subscriptionId, or tags are extremely fast because these fields are indexed.

4.

Result Aggregation and Paging: Results are returned in pages (default 1000, max 5000 per page). If the query includes aggregation (summarize), the aggregation is performed server-side. You can use the $skip and $top parameters or the limit operator in KQL to control paging.

5.

Consistency: ARG is eventually consistent. Changes to resources (create, update, delete) are reflected in ARG within minutes (typically 5-10 minutes). For critical scenarios where up-to-the-second accuracy is required, you should use the ARM API directly.

Key Components, Values, Defaults, and Timers

Tables: The primary table for resource metadata is resources. Other tables include resourcecontainers (for subscriptions, resource groups, and management groups) and policyresources (for Azure Policy compliance state).

Schema of `resources` table: Key columns include id (resource ID), name, type (e.g., microsoft.compute/virtualmachines), location, resourceGroup, subscriptionId, tenantId, tags (dynamic), properties (dynamic), sku, plan, zones, extendedLocation, identity, kind, managedBy, and createdTime/changedTime.

Query limits: Maximum query timeout is 30 seconds. Maximum result size per page is 5000 rows. Maximum query length is 4096 characters (for URL-based queries; SDKs can send larger via POST).

Rate limits: 1000 requests per hour per user per tenant (soft limit, can be increased).

KQL operators: Common operators include where, project, extend, summarize, join, mv-expand, order by, limit, and distinct.

Projections: Use project to select specific columns and reduce result size. Use project-away to exclude columns.

Filtering on dynamic properties: Use bracket notation, e.g., where properties['provisioningState'] == 'Succeeded'. For nested properties: where properties.storageProfile.osDisk.osType == 'Windows'.

Working with tags: Tags are stored as a dynamic object. To filter by tag: where tags['Environment'] == 'Production'. To list all tags: project tags.

Joins: You can join the resources table with itself or with resourcecontainers. For example, to get subscription name alongside resources: resources | join kind=leftouter (resourcecontainers | where type == 'microsoft.resources/subscriptions' | project subscriptionName=name, subscriptionId) on subscriptionId.

Time filters: Use where createdTime > ago(7d) or where changedTime > datetime(2023-01-01).

Azure Policy integration: Use policyresources table to query policy states. Common columns: policyAssignmentId, policyDefinitionId, complianceState, resourceId. Example: policyresources | where complianceState == 'NonCompliant'.

Configuration and Verification Commands

ARG is a read-only service; there is no configuration to enable it—it is always on for all Azure subscriptions. However, you can control access via RBAC. To query ARG, you need at least Reader permission on the resources you want to query.

Using Azure CLI:

az graph query -q "resources | where type =~ 'Microsoft.Compute/virtualMachines' | project name, location, resourceGroup | limit 10"

Using PowerShell:

Search-AzGraph -Query "resources | where type =~ 'Microsoft.Compute/virtualMachines' | project name, location, resourceGroup | limit 10"

Using REST API:

POST https://management.azure.com/providers/Microsoft.ResourceGraph/resources?api-version=2021-03-01
Content-Type: application/json
Authorization: Bearer <token>

{
  "query": "resources | where type =~ 'Microsoft.Compute/virtualMachines' | project name, location, resourceGroup | limit 10",
  "subscriptions": ["sub1", "sub2"]
}

Using SDK (Python):

from azure.mgmt.resourcegraph import ResourceGraphClient
from azure.mgmt.resourcegraph.models import QueryRequest
from azure.identity import DefaultAzureCredential

client = ResourceGraphClient(DefaultAzureCredential())
query = QueryRequest(query="resources | limit 5", subscriptions=["sub-id"])
result = client.resources(query)

Testing queries in Azure portal: Navigate to Azure Resource Graph Explorer (https://portal.azure.com/#blade/HubsExtension/ArgQueryBlade). This provides a UI to write, run, and save queries, and export results to CSV or JSON.

How It Interacts with Related Technologies

Azure Policy: ARG is the underlying engine for Azure Policy's compliance state. When you run a policy evaluation, the policy engine uses ARG to get the current resource state. You can also query policyresources table to see compliance details.

Azure Workbooks: Workbooks can use ARG as a data source. You can write KQL queries to build interactive dashboards that show resource counts, cost distribution, or compliance status.

Azure Logic Apps / Power Automate: You can trigger workflows based on ARG query results. For example, run a scheduled Logic App that queries for resources missing a specific tag and then applies the tag via ARM.

Azure CLI / PowerShell: These tools use ARG under the hood when you use az graph query or Search-AzGraph. The az resource list command still uses ARM API directly.

Azure Monitor Logs: While both use KQL, ARG queries resource metadata (properties, tags, types), whereas Azure Monitor Logs queries operational logs (events, performance counters). The schema and tables are different.

Advanced Query Patterns

Find orphaned disks:

resources
| where type =~ 'Microsoft.Compute/disks'
| where managedBy == ''
| project name, resourceGroup, diskSizeGB, sku.name

Count resources by type across subscriptions:

resources
| summarize count() by type, subscriptionId
| order by count_ desc

Get all VMs with public IPs:

resources
| where type =~ 'Microsoft.Network/publicIPAddresses'
| join kind=inner (
    resources
    | where type =~ 'Microsoft.Network/networkInterfaces'
    | mv-expand ipconfigurations = properties.ipConfigurations
    | project nicId = id, publicIpId = tostring(ipconfigurations.properties.publicIPAddress.id)
) on $left.id == $right.publicIpId
| project name, ipAddress = properties.ipAddress

Find resources with no tags:

resources
| where tags == '' or isempty(tags)
| project name, type, resourceGroup

Get resources changed in last 24 hours:

resources
| where changedTime > ago(24h)
| project name, type, changedTime, changeType

Walk-Through

1

Open Azure Resource Graph Explorer

Navigate to the Azure portal and open the Resource Graph Explorer tool. You can find it by searching 'Resource Graph Explorer' in the top search bar, or go to All Services > Resource Graph Explorer. This tool provides a web-based interface to write and execute KQL queries against the ARG data store. The query editor supports syntax highlighting, auto-complete, and a results pane. You can also save queries for later use. The explorer uses the same underlying REST API as the CLI and SDKs.

2

Write a basic query to list resources

Start with a simple query: `resources | limit 10`. This returns the first 10 resources from the `resources` table. The `resources` table contains one row per Azure resource. The default columns include `id`, `name`, `type`, `location`, `resourceGroup`, `subscriptionId`, `tags`, and `properties`. The `limit` operator restricts the number of rows returned. Without a filter, the query may take longer if you have many resources, but the index helps. Always use `where` filters to narrow down the scope for better performance.

3

Apply filters using 'where' clause

Use the `where` operator to filter resources by type, location, subscription, or tags. For example: `resources | where type =~ 'Microsoft.Compute/virtualMachines' and location == 'eastus'`. The `=~` operator is case-insensitive. For dynamic properties, use bracket notation: `where properties['provisioningState'] == 'Running'`. Filters on indexed columns (type, subscriptionId, resourceGroup) are very fast. The query engine pushes these filters to the storage layer.

4

Project specific columns

Use the `project` operator to select only the columns you need. This reduces the result size and improves performance. Example: `resources | where type =~ 'Microsoft.Compute/virtualMachines' | project name, location, resourceGroup, properties.storageProfile.osDisk.osType`. You can also rename columns: `project VMName=name, OS=properties.storageProfile.osDisk.osType`. Use `project-away` to exclude columns instead.

5

Use aggregation and grouping

The `summarize` operator allows you to count, sum, or average values across groups. For example, to count VMs by OS type: `resources | where type =~ 'Microsoft.Compute/virtualMachines' | summarize VMCount=count() by OS=properties.storageProfile.osDisk.osType`. You can also use `count()` with `by` for grouping. For complex aggregations, use `make_list` or `make_set` to create arrays. Aggregation happens server-side.

6

Join tables for cross-referencing

You can join the `resources` table with itself or with `resourcecontainers`. For example, to list resources with their subscription name: `resources | join kind=leftouter (resourcecontainers | where type == 'microsoft.resources/subscriptions' | project subscriptionName=name, subscriptionId) on subscriptionId | project name, type, subscriptionName`. The `join` operator supports different kinds: `inner`, `leftouter`, `rightouter`, `fullouter`. Use `kind=leftouter` to preserve all rows from the left table.

What This Looks Like on the Job

Enterprise Scenario 1: Cloud Inventory and Compliance Reporting

A large enterprise with 500+ subscriptions and 50,000 resources needs to generate a weekly report of all resources that are missing mandatory tags (e.g., 'CostCenter', 'Environment'). Previously, the team used a PowerShell script that looped through each subscription and called Get-AzResource, which took over 6 hours to complete. They switched to Azure Resource Graph. The query resources | where tags['CostCenter'] == '' or isempty(tags['CostCenter']) | project name, type, subscriptionId, resourceGroup runs in under 10 seconds across all subscriptions. The results are exported to CSV and emailed via a Logic App scheduled trigger. The team also set up an Azure Workbook that uses ARG queries to display real-time compliance dashboards. The main challenge was ensuring the RBAC permissions were correct—the service principal running the query needed Reader role on all subscriptions, which was managed via Azure Lighthouse or a management group scope.

Enterprise Scenario 2: Automated Resource Cleanup

A DevOps team manages a non-production environment with 20 subscriptions. Developers often create and forget to delete resources, leading to cost overruns. The team uses ARG to identify 'orphaned' resources—disks not attached to VMs, NICs not attached to VMs, and public IPs not associated with any resource. A daily Azure Automation runbook runs ARG queries like resources | where type =~ 'Microsoft.Compute/disks' | where managedBy == '' | project name, resourceGroup, subscriptionId. The runbook then deletes these resources after sending a notification. The challenge was handling the eventual consistency of ARG: a disk that was just attached might still appear as orphaned for up to 5 minutes. To mitigate, the runbook adds a delay or double-checks via ARM API before deletion. Performance is not an issue because the query returns in milliseconds.

Enterprise Scenario 3: Security and Compliance Auditing

A financial services company must ensure that no virtual machines are exposed to the internet with open management ports (e.g., RDP on 3389, SSH on 22). They use ARG to query all Network Security Groups (NSGs) and their rules. The query joins resources for NSGs with their subnets or NICs, then filters for rules allowing inbound traffic on those ports from 'Any' or 'Internet'. The query uses mv-expand to flatten the security rules array. The results are fed into a security dashboard and trigger alerts via Azure Sentinel. The biggest challenge was the complexity of the KQL query—nested properties and arrays require careful use of mv-expand. The team also had to account for NSGs applied at both subnet and NIC levels, which required a union of two queries.

How AZ-104 Actually Tests This

What AZ-104 Tests on Azure Resource Graph

The AZ-104 exam objective '3.4 Manage compute resources' includes Azure Resource Graph queries as a tool for inventory and compliance. Specifically, you should be able to:

Write KQL queries to retrieve resource metadata (type, location, tags, properties).

Filter resources using where clauses on indexed columns (type, subscriptionId, resourceGroup).

Use project to shape output.

Use summarize for counting/grouping.

Join the resources table with resourcecontainers to get subscription names.

Query Azure Policy compliance state using policyresources table.

Identify orphaned resources (disks, NICs, public IPs).

Most Common Wrong Answers and Why Candidates Choose Them

1.

Choosing Azure CLI `az resource list` over ARG: Many candidates assume az resource list is faster because it's simpler. However, az resource list makes individual API calls per subscription and is slow for large environments. The exam expects you to know that ARG is the correct tool for cross-subscription queries.

2.

Using `Get-AzResource` in PowerShell: Similar to above, candidates familiar with PowerShell might default to Get-AzResource. The exam wants you to recognize that Search-AzGraph (or the Az.ResourceGraph module) is the appropriate cmdlet for ARG queries.

3.

Misunderstanding KQL syntax: Common errors include using == instead of =~ for case-insensitive string comparison, forgetting to use tostring() for dynamic properties, or using where after summarize (should use having). The exam may present a query with a deliberate syntax error and ask you to identify the fix.

4.

Confusing ARG with Azure Monitor Logs: Some candidates think ARG queries can access log data like performance counters. ARG only queries resource metadata, not operational logs. The exam may have a scenario asking for a tool to query VM performance data—that would be Azure Monitor Logs, not ARG.

Specific Numbers, Values, and Terms That Appear on the Exam

Default page size: 1000 rows, max 5000 rows per page.

Query timeout: 30 seconds.

Rate limit: 1000 requests per hour per user per tenant.

Tables: resources, resourcecontainers, policyresources.

KQL operators: where, project, summarize, join, mv-expand, limit, distinct, order by.

Case-insensitive comparison: =~.

Dynamic property access: properties['key'] or properties.key.

Tag filtering: tags['TagName'].

Time filters: ago(7d), datetime(2023-01-01).

Edge Cases and Exceptions the Exam Loves to Test

Empty tags: where tags == '' or isempty(tags).

Null properties: Use isnotnull(properties).

Arrays in properties: Use mv-expand to flatten, e.g., mv-expand properties.ipConfigurations.

Case sensitivity: Type names are case-insensitive in ARG, but property names may be case-sensitive. Use =~ for type.

Management group scope: You can query across all subscriptions in a management group by specifying the management group ID in the query request (in CLI: --management-groups).

How to Eliminate Wrong Answers Using the Underlying Mechanism

If a question asks for the fastest way to get a list of all VMs across 100 subscriptions with a specific tag, think: ARG uses an indexed store and can answer in seconds. Any option that suggests looping through subscriptions (e.g., using az vm list in a script) is slower and should be eliminated. If the question involves querying past log data (e.g., CPU usage), ARG cannot do that—the answer must involve Azure Monitor Logs. If the question involves policy compliance, the policyresources table is the correct source. By understanding what ARG stores (current resource metadata, not historical logs), you can quickly eliminate wrong answers.

Key Takeaways

Azure Resource Graph uses Kusto Query Language (KQL) to query resource metadata across subscriptions.

The primary table is 'resources'; other tables include 'resourcecontainers' and 'policyresources'.

Default page size is 1000 rows, max 5000 rows per page; query timeout is 30 seconds.

Use 'where type =~' for case-insensitive filtering on resource type.

Access dynamic properties using bracket notation: properties['key'].

Tags are stored as a dynamic object; filter with tags['TagName'].

Use 'summarize count() by' for grouping and counting.

Join 'resources' with 'resourcecontainers' to get subscription names.

Query policy compliance using 'policyresources' table.

ARG is eventually consistent; for up-to-date data, use ARM API.

Easy to Mix Up

These come up on the exam all the time. Here's how to tell them apart.

Azure Resource Graph (ARG)

Queries across multiple subscriptions in a single call

Uses KQL for complex filtering, grouping, and joining

Returns results in milliseconds for large environments

Eventually consistent (up to 5 min delay)

Supports server-side aggregation (summarize)

Azure CLI az resource list

Queries one subscription per call; requires scripting for multiple

Simple filtering with --query parameter (JMESPath)

Slower for large environments due to per-subscription API calls

Returns live data from ARM

No server-side aggregation; must process results client-side

Watch Out for These

Mistake

Azure Resource Graph returns live, up-to-the-second data.

Correct

ARG is eventually consistent. Changes to resources may take up to 5-10 minutes to appear. For real-time data, use ARM REST API directly.

Mistake

ARG can query any data in Azure, including logs and metrics.

Correct

ARG only queries resource metadata (properties, tags, types). It does not contain operational logs, metrics, or activity logs. Those are in Azure Monitor Logs and Azure Activity Log.

Mistake

You need special permissions beyond Reader to use ARG.

Correct

ARG respects Azure RBAC. You need at least Reader permission on the resources you query. No additional ARG-specific role is required.

Mistake

ARG queries are limited to a single subscription.

Correct

ARG can query across multiple subscriptions, management groups, and even tenants (with appropriate permissions). You specify the list of subscriptions or management groups in the query request.

Mistake

The 'resources' table contains all properties of a resource, including nested JSON structures.

Correct

The 'properties' column is a dynamic JSON object that contains the same properties as the ARM resource object. However, not all properties are indexed; some may be missing if they are not exposed by the resource provider. You can access nested properties using dot notation or bracket notation.

Do You Actually Know This?

Reveal each answer, then mark whether you got it right. Score 60%+ to unlock the next chapter.

Frequently Asked Questions

What is the difference between Azure Resource Graph and Azure Resource Manager?

Azure Resource Manager (ARM) is the control plane for managing Azure resources—it handles create, read, update, and delete operations. Azure Resource Graph (ARG) is a read-only service that provides an indexed, queryable view of resource metadata. ARM is live and transactional; ARG is eventually consistent but much faster for cross-subscription queries. Use ARM for single-resource operations, ARG for inventory and compliance.

How do I query Azure Policy compliance using Azure Resource Graph?

Use the 'policyresources' table. Example query: 'policyresources | where complianceState == 'NonCompliant' | project resourceId, policyAssignmentId, policyDefinitionId'. You can join with 'resources' to get more details. Note that the 'policyresources' table is populated only after policy evaluation.

Can I use Azure Resource Graph to query resources across multiple tenants?

Yes, but you need to authenticate with a user or service principal that has access to each tenant. In the query request, you can specify an array of subscriptions from different tenants. The REST API and SDKs support cross-tenant queries. In the portal, you can switch directories.

What is the maximum number of subscriptions I can query in a single ARG request?

There is no hard limit, but the query timeout is 30 seconds. For very large numbers of subscriptions (e.g., 1000+), the query may time out. It's recommended to use management groups to aggregate subscriptions. The maximum number of subscriptions per query is not documented but practically limited by performance.

How do I handle nested JSON arrays in ARG queries?

Use the 'mv-expand' operator to flatten arrays. For example, to expand IP configurations on a NIC: 'resources | where type =~ 'Microsoft.Network/networkInterfaces' | mv-expand ipconfigurations = properties.ipConfigurations | project ipconfigurations.properties.privateIPAddress'. You can then filter or project individual elements.

Is there a way to save and share ARG queries?

Yes, in the Azure portal's Resource Graph Explorer, you can save queries to your favorites or share them via a link. You can also export the query as a JSON file and import it later. Saved queries are stored per user.

Can I use ARG to find resources that are not in a specific state (e.g., not running)?

Yes, if the resource type exposes a provisioning state or power state in its properties. For VMs, you can query: 'resources | where type =~ 'Microsoft.Compute/virtualMachines' | where properties.extended.instanceView.powerState.code != 'PowerState/running''. Note that power state may not be available for all resources and may require additional permissions.

Terms Worth Knowing

Ready to put this to the test?

You've just covered Azure Resource Graph Queries — now see how well it sticks with free AZ-104 practice questions. Full explanations included, no account needed.

Done with this chapter?