×
Search results provided by Azure Search - read how I built it in this post.
Max Melcher

5 minute read

In this article I explain how to use Azure Logic App to monitor unattached disks in Azure. This is a very common scenario in Azure, where you have a lot of VMs and you want to make sure that you don’t have any unattached disks. This can happen if you delete a VM and forget to delete the disk, ‘I will delete this later…’. Can also be adjusted for other resources like NICs, NSGs, etc.

Introduction

When you run Azure at scale and lots of users provision VMs in Azure, there will be a lot of disks. It is very easy to forget to delete a disk when you delete a VM. This can lead to a lot of unattached disks in your subscription. This is not only a waste of money.

Recently Azure introduced a new feature to also delete Disks when the VM is deleted, but many people don’t know about this feature or the feature is not enabled when you provision a VM with infrastructure as code.

Therefore a small monitoring script comes in handy - and there are multiple ways to do this:

  • Going to the Disks blade in the Azure Portal and check if there are any unattached disks. But this is not very efficient and you have to do this manually.
  • Using Azure Policy to monitor unattached disks - this is the best approach to centralize this governance aspect, but central IT most of the times cannot decide if a disk should be deleted or not. Therefore this is not a good approach for all scenarios.
  • Using Azure Logic App to monitor unattached disks - this is a good approach to monitor unattached disks and send an alert to the owner of the disk. This is a good approach for most scenarios.

But as the title of this post says, I will show you how to use Azure Logic App to monitor unattached disks.

Logic App

The following picture shows how the Logic App looks like:

The high level steps are:

  • Trigger the Logic App with a recurring schedule, e.g. once a month
  • Get all disks in a subscription
  • Filter out all disks that are attached to a VM
  • Send an email with a HTML table (not in the screenshot)

At first I wanted to get all the disks in one go, unfortunately the ‘List resources by subscription’ does not return the ARM property that shows if a disk is attached to a VM or not. Therefore I had to get all the disks in a subscription and then get the details of each disk. This is not very efficient, but it works.

The example here only monitors one subscription - but you can also adjust the Logic App to monitor multiple subscriptions.

The for each loop on every disk resource then fetches the disk details and checks if the disk is attached to a VM. If the disk is not attached to a VM, the disk details are added to a list. This list is then used to create the HTML table in the email with the action ‘Create HTML table’.

The tricky part for me was to get the correct filters. For ‘List resources by subscription’ I use the filter:

resourceType eq 'Microsoft.Compute/disks'

Then in the for each loop iterating over the ‘value’ of the ‘List resources by subscription’ I use the following parameters:

  • Resource Group: split(item().id,’/’)[4]
  • Provider: Microsoft.Compute
  • Short Resource Id: disks/[Name]

And lastly the json with all the details and parameters of tfe logic app. If you copy that to the ‘Code view’ of the Logic App, you can adjust the parameters and configure the email notification (needs a little back and forth because of the missing Azure parameter).

Hope it helps,
Max

{
    "definition": {
        "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
        "actions": {
            "Create_HTML_table": {
                "inputs": {
                    "format": "HTML",
                    "from": "@variables('unattachedDisks')"
                },
                "runAfter": {
                    "For_each": [
                        "Succeeded"
                    ]
                },
                "type": "Table"
            },
            "For_each": {
                "actions": {
                    "Condition": {
                        "actions": {
                            "Append_to_array_variable": {
                                "inputs": {
                                    "name": "unattachedDisks",
                                    "value": "@body('Read_a_resource')"
                                },
                                "runAfter": {},
                                "type": "AppendToArrayVariable"
                            }
                        },
                        "expression": {
                            "and": [
                                {
                                    "equals": [
                                        "@body('Parse_JSON')?['diskState']",
                                        "Unattached"
                                    ]
                                }
                            ]
                        },
                        "runAfter": {
                            "Parse_JSON": [
                                "Succeeded"
                            ]
                        },
                        "type": "If"
                    },
                    "Parse_JSON": {
                        "inputs": {
                            "content": "@body('Read_a_resource')?['properties']",
                            "schema": {
                                "properties": {
                                    "creationData": {
                                        "properties": {
                                            "createOption": {
                                                "type": "string"
                                            },
                                            "imageReference": {
                                                "properties": {
                                                    "id": {
                                                        "type": "string"
                                                    }
                                                },
                                                "type": "object"
                                            }
                                        },
                                        "type": "object"
                                    },
                                    "diskIOPSReadWrite": {
                                        "type": "integer"
                                    },
                                    "diskMBpsReadWrite": {
                                        "type": "integer"
                                    },
                                    "diskSizeBytes": {
                                        "type": "integer"
                                    },
                                    "diskSizeGB": {
                                        "type": "integer"
                                    },
                                    "diskState": {
                                        "type": "string"
                                    },
                                    "encryption": {
                                        "properties": {
                                            "type": {
                                                "type": "string"
                                            }
                                        },
                                        "type": "object"
                                    },
                                    "hyperVGeneration": {
                                        "type": "string"
                                    },
                                    "networkAccessPolicy": {
                                        "type": "string"
                                    },
                                    "osType": {
                                        "type": "string"
                                    },
                                    "provisioningState": {
                                        "type": "string"
                                    },
                                    "publicNetworkAccess": {
                                        "type": "string"
                                    },
                                    "supportedCapabilities": {
                                        "properties": {
                                            "acceleratedNetwork": {
                                                "type": "boolean"
                                            },
                                            "architecture": {
                                                "type": "string"
                                            }
                                        },
                                        "type": "object"
                                    },
                                    "supportsHibernation": {
                                        "type": "boolean"
                                    },
                                    "tier": {
                                        "type": "string"
                                    },
                                    "timeCreated": {
                                        "type": "string"
                                    },
                                    "uniqueId": {
                                        "type": "string"
                                    }
                                },
                                "type": "object"
                            }
                        },
                        "runAfter": {
                            "Read_a_resource": [
                                "Succeeded"
                            ]
                        },
                        "type": "ParseJson"
                    },
                    "Read_a_resource": {
                        "inputs": {
                            "host": {
                                "connection": {
                                    "name": "@parameters('$connections')['arm']['connectionId']"
                                }
                            },
                            "method": "get",
                            "path": "/subscriptions/@{encodeURIComponent('36d3ff36-dc30-4224-9970-6c24b9043705')}/resourcegroups/@{encodeURIComponent(split(item().id,'/')[4])}/providers/@{encodeURIComponent('Microsoft.Compute')}/@{encodeURIComponent('disks/',items('For_each')?['name'])}",
                            "queries": {
                                "x-ms-api-version": "2021-12-01"
                            }
                        },
                        "runAfter": {},
                        "type": "ApiConnection"
                    }
                },
                "foreach": "@body('List_resources_by_subscription')?['value']",
                "runAfter": {
                    "Initialize_variable": [
                        "Succeeded"
                    ]
                },
                "type": "Foreach"
            },
            "Initialize_variable": {
                "inputs": {
                    "variables": [
                        {
                            "name": "unattachedDisks",
                            "type": "array"
                        }
                    ]
                },
                "runAfter": {
                    "List_resources_by_subscription": [
                        "Succeeded"
                    ]
                },
                "type": "InitializeVariable"
            },
            "List_resources_by_subscription": {
                "inputs": {
                    "host": {
                        "connection": {
                            "name": "@parameters('$connections')['arm']['connectionId']"
                        }
                    },
                    "method": "get",
                    "path": "/subscriptions/@{encodeURIComponent('36d3ff36-dc30-4224-9970-6c24b9043705')}/resources",
                    "queries": {
                        "$expand": "resourceGroup",
                        "$filter": "resourceType eq 'Microsoft.Compute/disks'",
                        "x-ms-api-version": "2016-06-01"
                    }
                },
                "runAfter": {},
                "type": "ApiConnection"
            }
        },
        "contentVersion": "1.0.0.0",
        "outputs": {},
        "parameters": {
            "$connections": {
                "defaultValue": {},
                "type": "Object"
            }
        },
        "triggers": {
            "Recurrence": {
                "evaluatedRecurrence": {
                    "frequency": "Day",
                    "interval": 1
                },
                "recurrence": {
                    "frequency": "Day",
                    "interval": 1
                },
                "type": "Recurrence"
            }
        }
    },
    "parameters": {
        "$connections": {
            "value": {
                "arm": {
                    "connectionId": "/subscriptions/36d3ff36-dc30-4224-9970-6c24b9043705/resourceGroups/mamelch_group/providers/Microsoft.Web/connections/arm",
                    "connectionName": "arm",
                    "id": "/subscriptions/36d3ff36-dc30-4224-9970-6c24b9043705/providers/Microsoft.Web/locations/westeurope/managedApis/arm"
                }
            }
        }
    }
}
comments powered by Disqus