DEV Community

Olivier Miossec
Olivier Miossec

Posted on

Detecting unused Azure resources

If you run a large number of Azure Subscriptions where each team is responsible for their deployment, you will find, after a while, some orphan and unused objects left on subscriptions. Users created VM and forget to remove linked resources after the deletion. They may have created, also, an APP service plan that was left after all applications were deleted.
This can be challenging; these resources are still billed.
You can find 3 types of resources that can be left alone in subscriptions

  • Public IP
  • Disk
  • App Service Plan

How do detect them? When talking about more than one subscription the most effective tool is Azure Resource Graph. It will query Azure metadata instead of querying resources subscription per subscription.
So, let's see how to create a query for each of the resources

For disk

resources
| where type == "microsoft.compute/disks"
Enter fullscreen mode Exit fullscreen mode

But we need to have only disks not attached to a VM we will need to test the diskState properties and if the value is Unattached.

resources
| where type == "microsoft.compute/disks"
| where properties.diskState == "Unattached"
Enter fullscreen mode Exit fullscreen mode

*For Public IP *

resources
| where type == "microsoft.network/publicipaddresses"
Enter fullscreen mode Exit fullscreen mode

To have standalone Public IP, IP not attached to a resource you will need to test if there is no IPconfiguration ID

resources
| where type == "microsoft.network/publicipaddresses"
| where isnull(properties.ipConfiguration.id)
Enter fullscreen mode Exit fullscreen mode

For the App service plan
We need to find plans not used by Azure functions

resources
| where type == "microsoft.web/serverfarms"
| where properties.kind <> "functionapp"
Enter fullscreen mode Exit fullscreen mode

To get only App service without any attached website you need to look at the mumbeOfSites Properties.

resources
| where type == "microsoft.web/serverfarms"
| where properties.kind <> "functionapp" 
| where properties.numberOfSites == 0
Enter fullscreen mode Exit fullscreen mode

But we can create a single query to have all these objects at the same time.
For that, we need to standardize the output. We need to have the same col in the 3 queries: the ResourceID, the SubscriptionID, the Resource Group, and the name of the resource, we also need to have the SKU for public IP or App Service Plan or the disk size for the plan.

The new queries look like

resources
| where type == "microsoft.network/publicipaddresses"
| where isnull(properties.ipConfiguration.id)
| project id, subscriptionId, resourceGroup, resourceName = name, type, resourceValue = sku.name
Enter fullscreen mode Exit fullscreen mode
resources
| where type == "microsoft.compute/disks"
| where properties.diskState == 'Unattached'
| project id, subscriptionId,resourceGroup, resourceName = name, type, resourceValue = properties.diskSizeBytes
Enter fullscreen mode Exit fullscreen mode
resources
| where type == "microsoft.web/serverfarms"
| where properties.kind <> "functionapp" 
| where properties.numberOfSites == 0 
| project id, subscriptionId,resourceGroup, resourceName = name, resourceValue = sku.tier
Enter fullscreen mode Exit fullscreen mode

To create a unique query, the solution is to use the UNION, it combines two tables into a single result. You can combine up to 3 queries with the union operator.

resources
| where type == "microsoft.network/publicipaddresses"
| where isnull(properties.ipConfiguration.id)
| project id, subscriptionId, resourceGroup, resourceName = name, type, resourceValue = sku.name 
| union (resources
    | where type == "microsoft.compute/disks"
    | where properties.diskState == 'Unattached'
    | project id, subscriptionId,resourceGroup, resourceName = name, type, resourceValue = properties.diskSizeBytes)
|union (resources
    | where type == "microsoft.web/serverfarms"
    | where properties.kind <> "functionapp" 
    | where properties.numberOfSites == 0 
    | project id, subscriptionId,resourceGroup, resourceName = name, resourceValue = sku.tier)
Enter fullscreen mode Exit fullscreen mode

Top comments (2)

Collapse
 
francesco1119 profile image
Francesco Mantovani

Why it says The requested path does not exist?

I tried with both options
Image description

Collapse
 
yabrun profile image
yabrun

You can also add orphaned Nic:

resources
| where type == "microsoft.network/networkinterfaces"
| where (isnull(properties.virtualMachine.id) and isnull(properties.privateEndpoint.id))
Enter fullscreen mode Exit fullscreen mode