Azure Unused Resources Cleanup Script
Automated Python script to identify and clean up unused Azure resources including unattached disks, idle VMs, and orphaned NICs to reduce cloud waste.
๐ธ Stop Paying for Unused Resources
Cloud waste is one of the biggest challenges in FinOps. This Python automation script scans your Azure subscriptions to find resources that are costing you money but providing no value.
Identify and clean up unused Azure resources to reduce your monthly cloud bill. This playbook helps you find unattached disks, stopped VMs, orphaned network interfaces, and more.
๐ What We Find
Unattached Managed Disks
Disks not attached to any VM, often left behind after VM deletion
Stopped VMs
VMs deallocated for more than 30 days but still incurring storage costs
Orphaned NICs
Network interfaces not attached to any VMs
Old Snapshots
Snapshots older than 90 days that may no longer be needed
๐ฐ Potential Savings
Organizations typically find $5,000-$50,000 in annual savings from unused resources on their first cleanup sweep. Regular automation keeps waste under control.
๐ Prerequisites
- โขPython 3.8 or higher
- โขAzure CLI (
az) installed and configured - โขAzure SDK for Python:
pip install azure-identity azure-mgmt-compute azure-mgmt-network - โขContributor or Reader access to target subscriptions
๐ Quick Start
# Install required Python packages pip install azure-identity azure-mgmt-compute azure-mgmt-network azure-mgmt-resource # Login to Azure az login # Set your subscription (optional) az account set --subscription "your-subscription-id"
โ๏ธ The Complete Script
This script uses the Azure Python SDK to scan your subscriptions and identify unused resources. It generates a detailed CSV report with cost estimates for each unused resource.
#!/usr/bin/env python3
"""
Azure Unused Resources Finder
Identifies unused Azure resources across subscriptions
"""
from azure.identity import DefaultAzureCredential
from azure.mgmt.compute import ComputeManagementClient
from azure.mgmt.network import NetworkManagementClient
from azure.mgmt.resource import SubscriptionClient
from datetime import datetime, timedelta
import csv
def get_subscriptions(credential):
"""Get all Azure subscriptions"""
subscription_client = SubscriptionClient(credential)
return [sub.subscription_id for sub in subscription_client.subscriptions.list()]
def find_unattached_disks(compute_client):
"""Find all unattached managed disks"""
unattached = []
for disk in compute_client.disks.list():
if disk.disk_state == 'Unattached':
cost_estimate = disk.disk_size_gb * 0.05 # Rough estimate: $0.05/GB/month
unattached.append({
'type': 'Disk',
'name': disk.name,
'resource_group': disk.id.split('/')[4],
'size_gb': disk.disk_size_gb,
'estimated_monthly_cost': f'${cost_estimate:.2f}',
'location': disk.location
})
return unattached
def find_orphaned_nics(network_client):
"""Find network interfaces not attached to VMs"""
orphaned = []
for nic in network_client.network_interfaces.list_all():
if not nic.virtual_machine:
orphaned.append({
'type': 'NIC',
'name': nic.name,
'resource_group': nic.id.split('/')[4],
'location': nic.location,
'estimated_monthly_cost': '$5.00'
})
return orphaned
def find_unused_public_ips(network_client):
"""Find public IPs not associated with any resource"""
unused = []
for ip in network_client.public_ip_addresses.list_all():
if not ip.ip_configuration:
unused.append({
'type': 'PublicIP',
'name': ip.name,
'resource_group': ip.id.split('/')[4],
'ip_address': ip.ip_address or 'Not assigned',
'estimated_monthly_cost': '$3.50',
'location': ip.location
})
return unused
def main():
print("๐ Azure Unused Resources Finder")
print("=" * 50)
credential = DefaultAzureCredential()
subscriptions = get_subscriptions(credential)
all_unused = []
total_estimated_cost = 0
for sub_id in subscriptions:
print(f"\n๐ Scanning subscription: {sub_id}")
compute_client = ComputeManagementClient(credential, sub_id)
network_client = NetworkManagementClient(credential, sub_id)
# Find unused resources
disks = find_unattached_disks(compute_client)
nics = find_orphaned_nics(network_client)
ips = find_unused_public_ips(network_client)
all_unused.extend(disks + nics + ips)
print(f" Found: {len(disks)} disks, {len(nics)} NICs, {len(ips)} IPs")
# Write to CSV
if all_unused:
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
filename = f'azure_unused_resources_{timestamp}.csv'
with open(filename, 'w', newline='') as f:
writer = csv.DictWriter(f, fieldnames=all_unused[0].keys())
writer.writeheader()
writer.writerows(all_unused)
print(f"\nโ
Report saved: {filename}")
print(f"๐ Total unused resources: {len(all_unused)}")
else:
print("\nโจ No unused resources found!")
if __name__ == "__main__":
main()๐ How to Use
- 1.Run the script:
python azure_cleanup_finder.py - 2.Review the CSV report that gets generated with all findings
- 3.Verify resources before deletion - some may be intentionally unattached
- 4.Clean up confirmed waste using the Azure Portal or CLI commands below
๐งน Cleanup Commands
Once you've identified resources to delete, use these Azure CLI commands:
# Delete an unattached disk az disk delete --name <disk-name> --resource-group <rg-name> --yes # Delete an orphaned NIC az network nic delete --name <nic-name> --resource-group <rg-name> # Delete an unused public IP az network public-ip delete --name <ip-name> --resource-group <rg-name> # Delete old snapshots az snapshot delete --name <snapshot-name> --resource-group <rg-name>
โ ๏ธ Safety First
Always review resources in the CSV report before deletion. Verify with your team that resources are truly unused. Some unattached disks may be intentionally kept for backup or restore purposes.
๐ธ Real-World Savings Example
โ FinOps Best Practice
Run this script monthly as part of your FinOps review process. Track savings over time and celebrate wins with your engineering teams to build a cost-conscious culture.