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"
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"
*For Public IP *
resources
| where type == "microsoft.network/publicipaddresses"
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)
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"
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
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
resources
| where type == "microsoft.compute/disks"
| where properties.diskState == 'Unattached'
| project id, subscriptionId,resourceGroup, resourceName = name, type, resourceValue = properties.diskSizeBytes
resources
| where type == "microsoft.web/serverfarms"
| where properties.kind <> "functionapp"
| where properties.numberOfSites == 0
| project id, subscriptionId,resourceGroup, resourceName = name, resourceValue = sku.tier
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)
Top comments (2)
Why it says
The requested path does not exist
?I tried with both options
You can also add orphaned Nic: