Azure Orphaned Resources: Find and Delete to Save Cost

Many of you might have questions like, "What are orphaned resources in Azure Cloud?" To make it clear, orphaned resources in Azure refer to unused resources in Azure Subscription(s) that are still incurring costs, even though they are not being used or utilized for business needs. By finding and deleting such orphaned Azure resources, you will help in cost-saving for the project.

Note: Before taking action on any of the Azure orphaned resources, it's important to check if any other resources are dependent on them. Always remember to rectify them before deleting. Once deleted, you cannot revert back the changes. Ensure that no other active resources depend on the identified orphaned resources before deletion.

Are you paying for unused Azure resources? Find out how to identify and delete them with this guide. We'll show you how to use the Azure portal, KQL, and Azure PowerShell to find orphaned resources and how to delete them safely.

How to Find Orphan Resources in Azure?

These are the different ways to find orphaned resources in Azure:

  1. Azure Portal (Straightforward Method): for manually finding the azure orphan resources. 
  2. Azure Resource Graph KQL Query: to search for orphaned resources efficiently and export the detailed list of identified orphaned resources and communicate any planned deletions to relevant stakeholders.
  3. Azure PowerShell Script (Automation): for identification and deletion of orphaned resources.

In this article, I will be using the first two methods to find orphaned resources in Azure and the third method to automation using PowerShell or Azure CLI to streamline the identification and deletion of orphaned resources.

Let's dive into the topics with a step-by-step solution guide.

Find Azure Orphaned Disks from Azure Portal:

Step 1: Log into the Azure Portal >> Open Disks from the Azure global search.

Step 2: Now, add a filter and select the following, then click on Apply.

  • Filter - “Disk state”
  • Operator - “Equals”
  • Value - “Unattached”

Reference Sample Output:

Find Orphan Disks in Azure Portal

Find Azure Orphaned Disks using KQL Query:

To find orphaned disks using KQL (Kusto Query Language) in Azure Resource Graph Explorer, you can use following KQL query:

Resources
| where type has 'microsoft.compute/disks'
| extend diskState = tostring(properties.diskState) 
| where  diskState == 'Unattached' or managedBy == ''
Resources 
| where type has 'microsoft.compute/disks' 
| extend diskState = tostring(properties.diskState) 
| where  diskState == 'Unattached' or managedBy == '' 
| project name, diskState, managedBy, subscriptionId, resourceGroup, location, tags

The query will return a list of managed disks that are not attached to any virtual machines.

Tip: In Azure Resource Graph Explorer, you can run the KQL query by pressing "Shift+Enter" on your keyboard.

Delete Azure Orphaned Disks using PowerShell Script:

$SubscriptionName = ""
Set-AzContext -Subscription $SubscriptionName

$Disk = Get-AzDisk
$Orphan = $Disk | Select-Object -Property Name,SubscriptionName,ResourceGroupName,Type,DiskSizeGB,DiskState

$state_reserved = $Orphan | Where-Object -Property DiskState -eq “Reserved”
$state_unattached = $Orphan | Where-Object -Property DiskState -eq “Unattached”
$State_attached = $Orphan | Where-Object -Property DiskState -eq “attached”

#Unattached State Disk
Foreach ($disks in $state_unattached){
    $ResourceGroup=$disks.ResourceGroupName
    $DiskName=$disks.Name
    $DiskState=$disks.DiskState
    Write-Host "Disk Name       : $DiskName"
    Write-Host "ResourceGoupName: $ResourceGroup"
    Write-Host "State           : $DiskState"
    Write-Host ""
    Write-Host "Deleting unattached Managed Disk:$DiskName"
    $disks | Remove-AzDisk -Force
    Write-Host "Managed Disk: $DiskName Deleted!"
}

Find Azure Orphaned NICs from Azure Portal:

Step 1: Log into the Azure Portal >> Open Network interfaces from the Azure global search.

Step 2: Now, add a filter and select the following, then click on Apply.

  • Filter - “Attached to”
  • Operator - “Equals”
  • Value - “-”

Reference Sample Output:

Find Orphan NICs in Azure Portal

Find Azure Orphaned NICs using KQL Query:

To find Azure orphaned Network Interface Cards (NICs) using KQL (Kusto Query Language) in Azure Resource Graph Explorer, you can use the following KQL query:

Resources 
| where type has 'microsoft.network/networkinterfaces' 
| where '{nicWithPrivateEndpoints}' !has id 
| where properties !has 'virtualmachine'
Resources
| where type has 'microsoft.network/networkinterfaces' 
| where '{nicWithPrivateEndpoints}' !has id 
| where properties !has 'virtualmachine'
| project name, resourceGroup, subscriptionId, location, tags

Delete Orphaned NICs in Azure using PowerShell Script:

$SubscriptionName = "_add_subscription_name"
Set-AzContext -Subscription $SubscriptionName

$NICs = Get-AzNetworkInterface 
$Orphan = $Nics | Select-Object Name, ResourceGroupName, VirtualMachine | Where-Object { $_.VirtualMachine -eq $null }

Foreach ($nic in $Orphan){
    $VMName=$nic.virtualmachine
    $NICName=$nic.Name
    $ResourceGroup=$nic.ResourceGroupName
    Write-Host "NIC Name        : $NICName"
    Write-Host "Resource Group  : $ResourceGroup"
    Write-Host "Deleting NIC: $NICName"
    $nic | Remove-AzNetworkInterface -Force
    Write-Host "NIC: $NICName Deleted Successfully"
    Write-Host ""
}

Find Orphaned NSGs from Azure Portal:

Step 1: Log into the Azure Portal >> Open Network security groups from the Azure global search.

Step 2: Now, add a filter and select the following, then click on Apply.
  • Filter - “Associated with”
  • Operator - “Equals”
  • Value - “0 subnet, 0 network interface”

Reference Sample Output:

Find Orphan NSGs in Azure Portal

Find Azure Orphan NSGs using KQL Query:

Use the following KQL (Kusto Query Language) resource graph query to find the orphaned nsgs in Azure:

Resources 
| where type =~ 'microsoft.network/networksecuritygroups' 
and isnull(properties.networkInterfaces) 
and isnull(properties.subnets)
Resources
| where type =~ 'microsoft.network/networksecuritygroups' 
and isnull(properties.networkInterfaces) 
and isnull(properties.subnets)
| project name, resourceGroup, subscriptionId, location, tags

Delete Azure Orphaned NSGs using PowerShell:

$SubscriptionName = "_add_subscription_name"
Set-AzContext -Subscription $SubscriptionName

$NSGs = Get-AzNetworkSecurityGroup
$Orphan = $NSGs | Select-Object name,resourcegroupname | Where-Object { $_.NetworkInterfaces.count -eq 0 }

Foreach ($nsg in $Orphan){
	$ResourceGroup=$nsg.resourcegroupname
	$NSGName=$nsg.name
	Write-Host "NSG Name        : $NSGName"
	Write-Host "ResourceGoupName: $ResourceGroup"
	Write-Host "Deleting NSG: $NSGName "
	$nsg | Remove-AzDisk -Force
	Write-Host "NSG: $NSGName Successfully Deleted"
}

Find Orphaned Public IP Addresses in Azure Portal:

Step 1: Login into Azure Portal >> Open Public IP addresses from azure global search.

Step 2: Now Add a filter and select the following and then click on Apply.

  • Filter - “Associated to”
  • Operator - “Equals”
  • Value - “-"

Reference Sample Output:

Find Orphan Public IP Addresses in Azure Portal

Find Azure Orphan Public IP Addresses using KQL Query:

To find orphaned Public IP Addresses(PIPs) using KQL (Kusto Query Language) in Azure Resource Graph Explorer, you can use the following KQL query:

resources
| where type =~ 'microsoft.network/publicipaddresses'
| extend IpConfig = properties.ipConfiguration.id
| where isempty(IpConfig)

Find Orphaned Azure Availability Sets using KQL Query:

To find orphaned Azure Availability Sets using KQL (Kusto Query Language) in Azure Resource Graph Explorer, you can use the following KQL query:

resources
| where type =~ 'microsoft.compute/availabilitysets'
| extend VirtualMachines = array_length(properties.virtualMachines)
| where VirtualMachines == 0

Find Orphaned Resource Group in Azure using KQL Query:

ResourceContainers
| where type == 'microsoft.resources/subscriptions/resourcegroups'
| extend rgAndSub = strcat(resourceGroup, '--', subscriptionId)
| join kind=leftouter (
    Resources
    | extend rgAndSub = strcat(resourceGroup, '--', subscriptionId)
    | summarize count() by rgAndSub
) on rgAndSub
| where isnull(count_)
| extend SubscriptionName=case(
subscriptionId =~ 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx','Connectivity',
subscriptionId =~ 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx','Identity',
subscriptionId =~ 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx','Management',
subscriptionId)
| project ResourceGroup=resourceGroup, SubscriptionName, Location=location, Tags=tostring(tags)

Find Orphaned Azure App Service Plans using KQL Query:

resources
| where type =~ "microsoft.web/serverfarms"
| where properties.numberOfSites == 0
| extend Details = pack_all()
| extend SubscriptionName=case(
subscriptionId =~ 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx','Connectivity',
subscriptionId =~ 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx','Identity',
subscriptionId =~ 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx','Management',
subscriptionId)
| project ResourceName=name, ResourceGroupName=resourceGroup, Location=location, SubscriptionName, Tags=tostring(tags)

Find Orphaned Azure Load Balancers using KQL Query:

resources
| where type == 'microsoft.network/loadbalancers'
| where properties.backendAddressPools == "[]"
| extend Details = pack_all()
| extend SubscriptionName=case(
subscriptionId =~ 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx','Connectivity',
subscriptionId =~ 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx','Identity',
subscriptionId =~ 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx','Management',
subscriptionId)
| project ResourceName=name, ResourceGroupName=resourceGroup, Location=location, SubscriptionName, Tags=tostring(tags)

Find Orphaned Azure Route Tables using KQL Query:

resources
| where type == 'microsoft.network/routetables'
| where isnull(properties.subnets)
| extend Details = pack_all()
| extend SubscriptionName=case(
subscriptionId =~ 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx','Connectivity',
subscriptionId =~ 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx','Identity',
subscriptionId =~ 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx','Management',
subscriptionId)
| project ResourceName=name, ResourceGroupName=resourceGroup, Location=location, SubscriptionName, Tags=tostring(tags)

Find Orphaned Azure Traffic Manager Profiles using KQL Query:

resources
| where type == "microsoft.network/trafficmanagerprofiles"
| where properties.endpoints == "[]"
| extend Details = pack_all()
| extend SubscriptionName=case(
subscriptionId =~ 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx','Connectivity',
subscriptionId =~ 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx','Identity',
subscriptionId =~ 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx','Management',
subscriptionId)
| project ResourceName=name, ResourceGroupName=resourceGroup, Location=location, SubscriptionName, Tags=tostring(tags)

Find Orphaned Front Door WAF Policy using KQL Query:

resources
| where type == "microsoft.network/frontdoorwebapplicationfirewallpolicies"
| where properties.frontendEndpointLinks== "[]" and properties.securityPolicyLinks == "[]"
| extend Details = pack_all()
| extend SubscriptionName=case(
subscriptionId =~ 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx','Connectivity',
subscriptionId =~ 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx','Identity',
subscriptionId =~ 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx','Management',
subscriptionId)
| project ResourceName=name, ResourceGroupName=resourceGroup, Location=location, SubscriptionName, Sku=sku.name, Tags=tostring(tags)

By following this checklist, organizations can systematically identify, evaluate, and clean up orphaned resources in their Azure environment, helping to optimize costs and improve overall cloud management efficiency.

Reads more:

👉What is Kusto Query Language?

👉Azure Resource Graph Query Examples