To find empty Azure resource groups, compare the resource groups in Azure Resource Graph with the resources indexed for each subscription. Resource Graph can omit some resource types and its index can lag, so verify a group in Azure before treating it as empty or deleting it.
| Method | Best fit |
|---|---|
| PowerShell | A repeatable, sortable CSV across selected subscriptions. |
| Admin centre | A quick review in Azure Resource Graph Explorer without installing tools. |
| Altivane | Recommended for recurring reviews across readable subscriptions, with report files to keep. |
Before you start
Use an Azure account with read access to the subscriptions you want to check. The built-in Reader role is sufficient for Resource Graph queries; assign it at the subscription or a parent scope. The PowerShell example uses Az.Accounts and Az.ResourceGraph. Microsoft's Resource Graph prerequisites do not list a separate product licence.
The query counts entries in Resource Graph's Resources table for each group in ResourceContainers. It can only count resources the index contains. Azure documents Resource Graph as an asynchronous, near real-time index, and some resource types may not be represented in its data. The script scopes the query to enabled subscriptions the account can read and paginates beyond the first 1,000 results. It reads Azure and exports a CSV without changing resources. Its finally block disconnects the account and clears its local Azure contexts, so you may need to sign in again to use them. Check a reported group and its contents directly in Azure before any cleanup.
Option 1
Find empty resource groups with PowerShell
Install the modules once and connect to Azure. The query starts with resource group records, joins the indexed resources by subscription and group name, and keeps groups with a count of zero.
Install-Module -Name Az.Accounts, Az.ResourceGraph -Repository PSGallery -Scope CurrentUser$ErrorActionPreference = 'Stop'
Connect-AzAccount -ErrorAction Stop
try {
$subscriptionIds = @(
Get-AzSubscription |
Where-Object State -eq 'Enabled' |
Select-Object -ExpandProperty Id
)
if ($subscriptionIds.Count -eq 0) {
Write-Output 'No enabled subscriptions are available to this account.'
return
}
$query = @(
'ResourceContainers'
'| where type =~ "microsoft.resources/subscriptions/resourcegroups"'
'| project id, name, subscriptionId, location, key = tolower(name)'
'| join kind=leftouter ('
' Resources'
' | summarize itemCount = count() by subscriptionId,'
' key = tolower(resourceGroup)'
') on subscriptionId, key'
'| extend resourceCount = coalesce(itemCount, long(0))'
'| where resourceCount == 0'
'| project id, name, subscriptionId, location, resourceCount'
'| order by id asc'
) -join ' '
function Get-AzGraphRows {
param([string]$Query, [string[]]$SubscriptionIds)
$results = [System.Collections.Generic.List[object]]::new()
$skipToken = $null
do {
$arguments = @{
Query = $Query
Subscription = $SubscriptionIds
First = 1000
}
if ($skipToken) { $arguments.SkipToken = $skipToken }
$page = Search-AzGraph @arguments
foreach ($record in $page.Data) { $results.Add($record) }
$skipToken = $page.SkipToken
} while ($skipToken)
return $results.ToArray()
}
$rows = @(Get-AzGraphRows -Query $query -SubscriptionIds $subscriptionIds)
if ($rows.Count -eq 0) {
Write-Output 'No empty resource groups were found in the indexed resources.'
} else {
$rows | Select-Object name, subscriptionId, location, resourceCount, id |
Export-Csv -Path './empty-resource-groups.csv' -NoTypeInformation -Encoding utf8
Write-Output "$($rows.Count) group rows saved to empty-resource-groups.csv."
}
} finally {
Disconnect-AzAccount | Out-Null
}The export includes each group's name, subscription, location, indexed resource count and full resource ID. An empty result produces a message, not an empty CSV. The count is specifically a count of resources represented in Resource Graph; it is not a guarantee that every Azure service has no item in that group.
To narrow the search, keep only the subscription IDs you want in $subscriptionIds. The query keeps id in the output because Azure's continuation token requires it for reliable pagination.
Option 2
Review empty groups in Azure Resource Graph Explorer
- Sign in to the Azure portal with read access to the subscriptions.
- Search for resource graph > choose Resource Graph Explorer.
- To change its scope, choose Directory, then choose the directory, management group or subscription to query.
- Paste this query in Query 1 and choose Run query:
ResourceContainers | where type =~ 'microsoft.resources/subscriptions/resourcegroups' | project id, name, subscriptionId, location, key = tolower(name) | join kind=leftouter ( Resources | summarize itemCount = count() by subscriptionId, key = tolower(resourceGroup) ) on subscriptionId, key | extend resourceCount = coalesce(itemCount, long(0)) | where resourceCount == 0 | project id, name, subscriptionId, location, resourceCount | order by id asc - Review the candidate groups in Results.
Explorer suits an occasional query when you want to inspect the result interactively. Use PowerShell when you need a repeatable CSV or automatic pagination. A zero count is a lead for verification, not authorisation to delete a group.
Option 3 ยท Recommended for recurring reviews
Review empty resource groups with Altivane
- Open Altivane and choose the tenant.
- Run the Azure Unused Resources report.
- Open the Excel workbook and read Empty resource groups for the group name, subscription and resource ID.
Altivane reads every subscription it can in 1 run and writes an Excel workbook to keep. If a subscription cannot be read, the report marks the result partial instead of presenting it as empty. Its table counts resources Azure Resource Graph lists, so it can miss some types. Verify each candidate against Azure's current group contents before taking action.
Contact usCommon questions
Can I delete every group in the result?
No. Resource Graph may not list every resource type, and the group may hold resources you intend to keep. Check the live group contents, dependencies, ownership and retention requirements before deleting anything.
Why does an empty result still need a check?
The query counts only the resources represented in the Resource Graph index, which is updated asynchronously. A recent deployment or deletion may not have appeared yet. For a critical decision, verify the group's current contents with Azure Resource Manager.
Where can I find other Azure cleanup candidates?
The same report includes disks with no virtual machine and public IP addresses with no association. See how to find unused public IP addresses in Azure.
Microsoft references
Technical references checked on 7 October 2026. The example scripts have been reviewed and syntax checked; they have not been run against a live tenant.
- Install-Module cmdlet reference
- Connect-AzAccount cmdlet reference
- Get-AzSubscription cmdlet reference
- Disconnect-AzAccount cmdlet reference
- Run a Resource Graph query using Azure PowerShell
- Search-AzGraph cmdlet reference
- Run a Resource Graph query in the Azure portal
- Azure Resource Graph query language and tables
- Azure Resource Graph overview
- Manage resource groups in the Azure portal
- Azure built-in roles: Reader