One of the most common uses of the Microsoft Azure cloud for administrators is virtual machines (VMs). It's easy to create Azure VMs, but it's not quite as intuitive to remove one.

Sure, you can go to the Azure Portal, find your VM and remove it easy enough. You could also use the Remove-AzVM PowerShell command in the Azure PowerShell module to quickly remove a VM but there's a lot more to that VM than just the VM itself.

When that VM is created, you could have potentially created many other resources along with it that should be cleaned up as well. Resources like the below can all be associated with a VM that needs to be removed.

  • Boot diagnostics storage containers
  • Network interfaces
  • Public IP addresses
  • OS disk and status storage blobs
  • Data disks

In this article, we'll cover how to build a PowerShell function called Remove-AzrVirtualMachine that will not only remove the Azure VM but also anything directly associated with it.

Prerequisites

To remove an Azure VM and cleanup other related resources with PowerShell, you're going to need a couple of things.

  • The Azure PowerShell Module?-?This PowerShell module can be downloaded and installed via the PowerShell Gallery by running Install-Module Az.
  • Authenticated to an Azure subscription?-?You'll need to be authenticated to the Azure subscription your VM is located in. To do this, run Connect-AzAccount.

To verify, you can then run Get-AzVm -Name <VMName> -ResourceGroupName <VMResourceGroupName> to check it returns the VM. If no error is returned, you should be good to go.

Getting the Azure VM Object

To make things easier in this script, we'll first get the VM object that contains all of the necessary information we're looking for. To do that, we use the Get-AzVm command. For this article, I'll be working with a VM called WINSRV19 in the MyTestVMs resource group.

$vm = Get-AzVm -Name WINSRV19 -ResourceGroupName MyTestVMs

You should now have the VM object stored in the $vm variable allowing you to read various objects from the variable as you progress through the script.

Removing the Boot Diagnostics Storage Container

When creating an Azure VM, you always have the option of creating a boot diagnostics container. This is useful to troubleshooting VM boot issues but doesn't get removed when a VM is deleted. Let's remedy that.

To remove the boot diagnostics container, you first need to figure out the name of the storage account the container resides on. To find this storage account, you'll have to do some parsing of the storageUri property that's buried in the DiagnosticsProfile object on the VM.

To save you some time, here's some regex to parse out that name.

$diagSa = [regex]::match($vm.DiagnosticsProfile.bootDiagnostics.storageUri, '^http[s]?://(.+?)\\.').groups[1].value

Next, you'll need to find the name of the boot diagnostics storage container. To find that, you'll first need to find the VM ID using the Get-AzResource command. Here is some PowerShell to get that done for you.

if ($vm.Name.Length -gt 9) {
    $i = 9
} else {
    $i = $vm.Name.Length - 1
}

$azResourceParams = @{
    'ResourceName' = WINSRV
    'ResourceType' = 'Microsoft.Compute/virtualMachines'
    'ResourceGroupName' = MyTestVMs
}

$vmResource = Get-AzResource @azResourceParams
$vmId = $vmResource.Properties.VmId
$diagContainerName = ('bootdiagnostics-{0}-{1}' -f $vm.Name.ToLower().Substring(0, $i), $vmId)

Next, you'll need the name of the resource group the boot diagnostics container is a part of.

$diagSaRg = (Get-AzStorageAccount | where { $_.StorageAccountName -eq $diagSa }).ResourceGroupName

Finally, you can now remove the storage container with the Remove-AzStorageContainer command.

$saParams = @{
    'ResourceGroupName' = $diagSaRg
    'Name' = $diagSa
}

Get-AzStorageAccount @saParams | Get-AzStorageContainer | where { $_.Name-eq $diagContainerName } | Remove-AzStorageContainer -Force

One resource down! Let's keep going.

Removing the Azure VM

Next, we'll remove the VM itself. Since you've already captured the VM in the $vm variable, you can pass that object to the Remove-AzVm command and be done with it.

$null = $vm | Remove-AzVM -Force

Removing Network Interfaces and Public IP Addresses

Next up, are one or more network interfaces (NICs) that were once attached to that VM. Using the NetworkInterfaces property on the VM object, we can loop through each ID, remove the NIC with the Remove-AzNetworkInterface command.

While we're in this loop, we can also check the IpConfiguration property on each NIC to discover if that NIC has a public IP address associated with it. If so, we can remove that with the Remove-AzPublicIpAddress command.

Below you can see, I'm looking for all NICs, removing each one, checking for public IPs on each NIC and if found, parsing the PublicIpAddress property's ID to get the name of the public IP address resource and removing it.

foreach($nicUri in $vm.NetworkProfile.NetworkInterfaces.Id) {
    $nic = Get-AzNetworkInterface -ResourceGroupName $vm.ResourceGroupName -Name $nicUri.Split('/')[-1]
    Remove-AzNetworkInterface -Name $nic.Name -ResourceGroupName $vm.ResourceGroupName -Force

    foreach($ipConfig in $nic.IpConfigurations) {
        if($ipConfig.PublicIpAddress -ne $null) {
            Remove-AzPublicIpAddress -ResourceGroupName $vm.ResourceGroupName -Name $ipConfig.PublicIpAddress.Id.Split('/')[-1] -Force
        }
    }
}

Removing the OS Disk

The OS disk is a storage blob you can remove with the Remove-AzStorageBlob command but first, like the other steps, you'll have to do some searching to find the correct parameter values to pass to it.

In the below example PowerShell code snippet, I'm first finding the name of the OS disk storage container. Once I find that I'm then using that along with the storage account the OS disk storage container resides on to pass to Remove-AzStorageBlob to get the OS disk removed.

$osDiskUri = $vm.StorageProfile.OSDisk.Vhd.Uri
$osDiskContainerName = $osDiskUri.Split('/')[-2]
$osDiskStorageAcct = Get-AzStorageAccount | where { $_.StorageAccountName -eq $osDiskUri.Split('/')[2].Split('.')[0] }
$osDiskStorageAcct | Remove-AzStorageBlob -Container $osDiskContainerName -Blob $osDiskUri.Split('/')[-1]

Removing the OS Disk Status Blob

To clean up the OS disk status blog, I need to find the storage container the OS disk is in, assume the blog itself ends in status and then pass that to Remove-AzStorageBlob to obliterate that blob.

$osDiskStorageAcct | Get-AzStorageBlob -Container $osDiskContainerName -Blob "$($vm.Name)*.status" | Remove-AzStorageBlob

Removing Attached Data Disks

Finally, some VMs may have data disks attached to them that you'd rather not use elsewhere. Not a problem. We can remove those too!

First, you'll read the Uri property embedded deep in the StorageProfile object on the original VM object you retrieved. Since there can be more than one data disk with a URI, you'll loop over each of these URIs find the storage account each one is on.

Once you've discovered the storage account of each one, you'll use that and parse the storage URI to form the blob name to pass to the Remove-AzStorageBlob command, as shown below.

if ($vm.DataDiskNames.Count -gt 0) {
    foreach ($uri in $vm.StorageProfile.DataDisks.Vhd.Uri) {
        $dataDiskStorageAcct = Get-AzStorageAccount -Name $uri.Split('/')[2].Split('.')[0]
        $dataDiskStorageAcct | Remove-AzStorageBlob -Container $uri.Split('/')[-2] -Blob $uri.Split('/')[-1]
    }
}

Summary

We've covered quite a bit in this article. Following best PowerShell coding practices, you could quickly build a tool from this code. Lucky for you, I've already done it for you! If you'd like to see an example of how this code could be implemented, check out the Remove-AzrVirtualMachine function.

Join the Jar Tippers on Patreon

It takes a lot of time to write detailed blog posts like this one. In a single-income family, this blog is one way I depend on to keep the lights on. I'd be eternally grateful if you could become a Patreon patron today!

Become a Patron!