Let's be honest. As a cloud engineer or DevOps professional managing a large Azure environment, running even a simple resource inventory query can feel like drinking from a firehose. You hit API limits, face slow performance, and struggle to get the complete picture of your estateβall because the data volume is overwhelming.
But it doesn't have to be this way!
This blog is your practical, hands-on guide to mastering two essential techniques for handling massive data volumes in Azure: using PowerShell and Azure Resource Graph (ARG): Skip Token (for full data retrieval) and Batching (for blazing-fast performance).
π TABLE OF CONTENTS
π GETTING STARTED
β
ββ Prerequisites: PowerShell 7+ & Az.ResourceGraph Module
ββ Introduction: Why Standard Queries Fail at Scale
π CORE CONCEPTS
β
ββ π Skip Token: The Data Completeness Tool
β ββ What is a Skip Token?
β ββ The Bookmark Analogy
β ββ PowerShell Implementation
β ββ π» Code Example: Pagination Loop
β
ββ β‘ Batching: The Performance Booster
ββ What is Batching?
ββ Performance Benefits
ββ Batching vs. Pagination
ββ Parallel Processing in PowerShell
ββ π» Code Example: Concurrent Queries
π DEEP DIVE
β
ββ Skip Token: Generic vs. Azure-Specific
ββ Azure Resource Graph (ARG) at Scale
ββ ARG Overview
ββ Why ARG Needs These Techniques
ββ π» Combined Example: Skip Token + Batching
β
BEST PRACTICES
β
ββ When to Use Each Technique
ββ Quick Reference Guide
π RESOURCES
ββ Official Documentation & References
Prerequisites
| Component | Requirement / Details | Command / Reference |
|---|---|---|
| PowerShell Version | The batching examples use ForEach-Object -Parallel, which requires PowerShell 7.0 or later. | Check version: $PSVersionTable.PSVersion Install PowerShell 7+: Install PowerShell on Windows, Linux, and macOS |
| Azure PowerShell Module | Az.ResourceGraph module must be installed. | Install module: Install-Module -Name Az.ResourceGraph -Scope CurrentUser |
Introduction: Why Standard Queries Don't Work at Scale
When you query a service designed for big environments, like Azure Resource Graph, you face two limits:
- Result Limits (Pagination): APIs won't send you millions of records at once. They cap the result size (often 1,000 items) and stop.
- Efficiency Limits (Throttling): Sending a huge number of individual requests is slow and can cause the API to temporarily block you (throttling).
Skip Token helps you solve the first limit by making sure you retrieve all results.
Batching solves the second by grouping your requests to improve performance.
Understanding Skip Token: The Continuation Pointer
What is a Skip Token?
A Skip Token (or continuation token) is a unique string value returned by an Azure API when a query result exceeds the maximum limit for a single response.
Think of the Skip Token as a βbookmarkβ that tells Azure where your last page ended β so you can pick up exactly where you left off in the next API call. Instead of getting cut off after 1,000 records, the API gives you the first 1,000 results plus the Skip Token. You use this token in the next request to get the next page of data. This process is called pagination.
Skip Token in Practice with PowerShell
To get the complete dataset, you must use a loop that repeatedly calls the API, providing the token each time until the token is no longer returned.
PowerShell Example: Using Skip Token to Loop Pages
# Define the query
$Query = "Resources | project name, type, location"
$PageSize = 1000
$AllResults = @()
$SkipToken = $null # Initialize the token
Write-Host "Starting ARG query..."
do {
Write-Host "Fetching next page. (Token check: $($SkipToken -ne $null))"
# 1. Execute the query, using the -SkipToken parameter
$ResultPage = Search-AzGraph -Query $Query -First $PageSize -SkipToken $SkipToken
# 2. Add the current page results to the main array
$AllResults += $ResultPage
# 3. Get the token for the next page, if it exists
$SkipToken = $ResultPage.SkipToken
Write-Host " -> Items in this page: $($ResultPage.Count). Total retrieved: $($AllResults.Count)"
} while ($SkipToken -ne $null) # Loop as long as a Skip Token is returned
Write-Host "Query finished. Total resources found: $($AllResults.Count)"
This do-while loop is the reliable way to ensure you retrieve every item in a large result set.
Understanding Batching: Grouping Requests
What is Batching?
Batching means taking several independent requests and combining them into a single API call. Instead of making N separate network requests for N pieces of data, you make one request containing all N sub-requests.
Batching is primarily used for performance. It improves efficiency by:
- Reducing Overhead: Fewer separate network connections are needed.
- Lowering Throttling Risk: Fewer overall API calls are made, which helps you stay under rate limits.
| Feature | Batching | Pagination (Skip Token) |
| Goal | Improve efficiency/speed. | Retrieve all data completely. |
| Input | Multiple different queries. | Single query, continuing from a marker. |
| Result | One response with results for all grouped queries. | Partial results with a token for the next step. |
Note: While Azure Resource Graph's REST API supports batch requests, the PowerShell Search-AzGraph cmdlet does not expose a -Batch parameter. Instead, we achieve batching by using PowerShell's ForEach-Object -Parallel (PowerShell 7+) to run multiple queries simultaneously.
Batching in Practice with PowerShell
Using parallel processing in PowerShell, you can efficiently execute multiple distinct Kusto queries targeting different scopes (like subscriptions) simultaneously.
| Method | 5 Subscriptions | 20 Subscriptions |
|---|---|---|
| Sequential | ~50 seconds | ~200 seconds |
| Parallel (ThrottleLimit 5) | ~15 seconds | ~45 seconds |
PowerShell Example: Running Multiple Queries in Parallel
# Define multiple queries to run together
$BatchQueries = @(
@{
Query = "Resources | where type =~ 'Microsoft.Compute/virtualMachines'"
Subscriptions = @("SUB_A") # Query 1 Scope
},
@{
Query = "Resources | where type =~ 'Microsoft.Network/publicIPAddresses'"
Subscriptions = @("SUB_B", "SUB_C") # Query 2 Scope
}
)
Write-Host "Executing batch of $($BatchQueries.Count) queries in parallel..."
# Execute queries in parallel (true batching)
$BatchResults = $BatchQueries | ForEach-Object -Parallel {
$QueryConfig = $_
$Query = $QueryConfig.Query
$Subs = $QueryConfig.Subscriptions
Write-Host "[Batch Worker] Starting query: $($Query.Substring(0, [Math]::Min(50, $Query.Length)))..." -ForegroundColor Cyan
$QueryResults = @()
# Process each subscription in this query's scope
foreach ($SubId in $Subs) {
$SkipToken = $null
do {
$Params = @{
Query = $Query
Subscription = $SubId
First = 1000
}
if ($SkipToken) {
$Params['SkipToken'] = $SkipToken
}
$Result = Search-AzGraph @Params
if ($Result) {
$QueryResults += $Result
}
$SkipToken = $Result.SkipToken
} while ($SkipToken)
}
Write-Host " [Batch Worker] β
Query completed - Retrieved $($QueryResults.Count) resources" -ForegroundColor Green
# Return results with metadata
[PSCustomObject]@{
Query = $Query
Subscriptions = $Subs
Data = $QueryResults
Count = $QueryResults.Count
}
} -ThrottleLimit 5
Write-Host "`nBatch complete. Reviewing results..."
# The results are returned in the same order as the input array
$VMCount = $BatchResults[0].Data.Count
$IPCount = $BatchResults[1].Data.Count
Write-Host "Query 1 (VMs) returned: $VMCount results."
Write-Host "Query 2 (IPs) returned: $IPCount results."
# Optional: Display detailed results
Write-Host "`n--- Detailed Results ---"
for ($i = 0; $i -lt $BatchResults.Count; $i++) {
$Result = $BatchResults[$i]
Write-Host "`nQuery $($i + 1):"
Write-Host " Query: $($Result.Query)"
Write-Host " Subscriptions: $($Result.Subscriptions -join ', ')"
Write-Host " Total Resources: $($Result.Count)"
if ($Result.Data.Count -gt 0) {
Write-Host " Sample (first 3):"
$Result.Data | Select-Object -First 3 | Format-Table -AutoSize
}
}
Azure Resource Graph (ARG) and Scale
Azure Resource Graph (ARG) is a service built for querying resource properties quickly across a large number of Azure subscriptions using the Kusto Query Language (KQL).
Because ARG is designed for large scale, it fully supports Skip Token and Batching:
- Skip Token: ARG automatically generates and returns the token when a query exceeds its result limit (e.g., 1,000 records).
- Batching: ARG's REST API provides a batch endpoint for sending up to ten queries in a single request. In PowerShell, we achieve similar performance benefits using ForEach-Object -Parallel to process multiple queries concurrently.
Combined Example: Batching and Skip Token Together
This script shows how to use Batching to start a query across multiple subscriptions and then use Skip Token within the loop to ensure every subscription's data is fully retrieved.
$SubscriptionIDs = @("SUB_A")
$KQLQuery = "Resources | project id, name, type, subscriptionId"
Write-Host "Starting BATCHED query across $($SubscriptionIDs.Count) subscription(s)..."
Write-Host "Using parallel processing for true batching...`n"
# Process subscriptions in parallel (batching)
$AllResults = $SubscriptionIDs | ForEach-Object -Parallel {
$SubId = $_
$Query = $using:KQLQuery
$SubResults = @()
Write-Host "[Batch Worker] Processing Subscription: $SubId" -ForegroundColor Cyan
$SkipToken = $null
$PageCount = 0
do {
$PageCount++
# Build parameters
$Params = @{
Query = $Query
Subscription = $SubId
First = 1000
}
if ($SkipToken) {
$Params['SkipToken'] = $SkipToken
}
# Execute query
$Result = Search-AzGraph @Params
if ($Result) {
$SubResults += $Result
Write-Host " [Batch Worker] Sub: $SubId - Page $PageCount - Retrieved $($Result.Count) resources" -ForegroundColor Yellow
}
$SkipToken = $Result.SkipToken
} while ($SkipToken)
Write-Host " [Batch Worker] β
Completed $SubId - Total: $($SubResults.Count) resources" -ForegroundColor Green
# Return results from this subscription
$SubResults
} -ThrottleLimit 5 # Process up to 5 subscriptions simultaneously
Write-Host "`n--- Batch Processing Finished ---"
Write-Host "Final total resource count: $($AllResults.Count)"
# Optional: Display sample results
if ($AllResults.Count -gt 0) {
Write-Host "`nFirst 5 resources:"
$AllResults | Select-Object -First 5 | Format-Table -AutoSize
}
| Technique | Use When... | Common Mistake | Actionable Advice |
| Skip Token | You must retrieve all data items, expecting more than 1,000 results. | Forgetting to check for the token; you only get partial data. | Always use a do-while loop to guarantee you get the complete set. |
| Batching | You need to run several separate queries (max 10 in ARG) efficiently. | Putting too many queries in the batch, causing the request to fail. | Group up to 10 logical queries or subscriptions into one fast request. |
By combining Skip Token for data completeness and Batching for efficiency, you can confidently query massive Azure estates without hitting limits or missing data.
These two techniques β when used together β turn Azure Resource Graph from a βgood toolβ into a scalable discovery engine for your entire cloud footprint.
Summary: Skip Token and Batching in Azure Resource Graph
Goal: Efficiently query massive Azure environments using PowerShell and Azure Resource Graph (ARG).
1. Skip Token (The Data Completeness Tool)
| Concept | What it Does | Why it Matters | PowerShell Use |
| Skip Token | A marker returned by Azure APIs when results hit the 1,000-item limit. It points to the next page of data. | Ensures you retrieve all records, avoiding incomplete data (pagination). | Use a do-while loop with the -SkipToken parameter in Search-AzGraph until the token is no longer returned. |
2. Batching (The Performance Booster)
| Concept | What it Does | Why it Matters | PowerShell Use |
| Batching | Processes multiple independent queries simultaneously using parallel execution. |
Drastically improves query speed by reducing overall execution time and helps avoid API throttling. | Use ForEach-Object -Parallel (PowerShell 7+) with -ThrottleLimit to control concurrent queries. For PowerShell 5.1, use Start-Job with background jobs. |
3. Best Practice: Combine Them
For maximum efficiency, combine Batching and Skip Token. Use batching to run queries across multiple subscriptions simultaneously and use the Skip Token logic within the loop to ensure every single subscription's data is fully paginated and retrieved.
Result: Fast, complete, and reliable data collection across your large Azure estate.