NonAttachedUnmanagedDisk.psm1

#requires -Version 5
#requires -Modules @{ ModuleName='Microsoft.PowerShell.Utility'; ModuleVersion='3.1.0.0' }
#requires -Modules @{ ModuleName='AzureRM.Profile'; ModuleVersion='2.6.0' }
#requires -Modules @{ ModuleName='AzureRM.Compute'; ModuleVersion='2.7.0' }
#requires -Modules @{ ModuleName='Azure.Storage'; ModuleVersion='2.6.0' }
#requires -Modules @{ ModuleName='AzureRM.Storage'; ModuleVersion='2.6.0' }

<#
.SYNOPSIS
Get the unmanaged disks (VHDs/Blobs) that non-attached to any virtual machines from the entire subscription.
 
.DESCRIPTION
Get the unmanaged disks (VHDs/Blobs) that non-attached to any virtual machines from the entire subscription.
 
.PARAMETER ExcludeResourceGroup
This cmdlet is ignore the resource groups that provided by this parameter. This parameter is optional.
 
.EXAMPLE
    Get-AzureUtilNonAttachedUnmanagedDisk -Verbose
 
In this example, it is to get the all non-attached unmanaged disks (VHDs/Blobs) in the current subscription.
 
.EXAMPLE
    Get-AzureUtilNonAttachedUnmanagedDisk -ExcludeResourceGroup 'TemplateStore-RG','securitydata' -Verbose
 
In this example, it is to get the all non-attached unmanaged disks (VHDs/Blobs) in the current subscription except the storage accounts in the "TemplateStore-RG" and "securitydata" resource groups.
 
.EXAMPLE
    PS C:\>$disks = Get-AzureUtilNonAttachedUnmanagedDisk -ExcludeResourceGroup 'securitydata'
    PS C:\>$disks | Format-Table -Property @{ Label = 'Resource Group'; Expression = { $_.ResourceGroupName } },
                                           @{ Label = 'Storage Account'; Expression = { $_.StorageAccountName } },
                                           @{ Label = 'Location'; Expression = { $_.Location } },
                                           @{ Label = 'SKU'; Alignment = 'Left'; Expression = { $_.Sku.Name } },
                                           @{ Label = 'Container'; Expression = { $_.ContainerName } },
                                           @{ Label = 'VHD/Blob'; Expression = { $_.Name } },
                                           @{ Label = 'Size (GB)'; Expression = { [int] ($_.Length / 1GB) } },
                                           'LastModified'
 
    Resource Group Storage Account Location SKU Container VHD/Blob Size (GB) LastModified
    -------------- --------------- -------- --- --------- -------- --------- ------------
    ProjectA-RG vm1sa1055 japaneast StandardLRS vhd datadisk1.vhd 127 5/6/2017 11:05:14 AM +00:00
    ProjectB-RG vm2sa1310 japaneast StandardLRS vhd osdisk.vhd 127 5/5/2017 2:22:10 PM +00:00
    Test-RG premsa japaneast PremiumLRS vhd osdisk.vhd 127 5/5/2017 3:52:45 PM +00:00
 
In this example, it is to get the all non-attached unmanaged disks (VHDs/Blobs) in the current subscription except the storage accounts in the "securitydata" resource group. The results is formatted as table style in this example.
 
.EXAMPLE
    Get-AzureUtilNonAttachedUnmanagedDisk -ExcludeResourceGroup 'securitydata' -Verbose | Remove-AzureStorageBlob -Verbose
 
In this example, it is to remove the all non-attached unmanaged disks (VHDs/Blobs) in the current subscription except the storage accounts in the "securitydata" resource group.
 
.LINK
PowerShell Gallery: https://www.powershellgallery.com/packages/AzureUtil/
 
.LINK
GitHub: https://github.com/tksh164/AzureUtil-PowerShellModule
 
.LINK
Get-AzureUtilNonAttachedManagedDisk
 
.LINK
Get-AzureUtilEmptyResourceGroup
#>

function Get-AzureUtilNonAttachedUnmanagedDisk
{
    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param (
        [Parameter(Mandatory = $false)][ValidateNotNullOrEmpty()]
        [string[]] $ExcludeResourceGroup
    )

    # Login check.
    try { [void] (Get-AzureRMContext -ErrorAction Stop) } catch { throw }

    # Get the all attached VHD URIs from the VM configurations.
    Write-Verbose -Message ('Get all attached VHD URI...')
    $attachedVhdUris = GetAttachedVhdUri -ExcludeResourceGroup $ExcludeResourceGroup
    Write-Verbose -Message ('Found the {0} attached VHD in the current subscription.' -f $attachedVhdUris.Count)

    #
    # Enumerate the VHD blobs by scanning of all storage accounts.
    #

    Write-Verbose -Message ('Scanning all storage accounts.')

    Get-AzureRmStorageAccount |
        ForEach-Object -Process {

            # Exclude the non target resource groups.
            if ($ExcludeResourceGroup -notcontains $_.ResourceGroupName)
            {
                $storageAccount = $_
                $resourceGroupName = $storageAccount.ResourceGroupName
                $storageAccountName = $storageAccount.StorageAccountName
                Write-Verbose -Message ('Scanning SA:{0} in RG:{1}' -f $storageAccountName,$resourceGroupName)

                $storageAccountKey = (Get-AzureRmStorageAccountKey -ResourceGroupName $resourceGroupName -Name $storageAccountName).Value | Select-Object -First 1
                $context = New-AzureStorageContext -StorageAccountName $storageAccountName -StorageAccountKey $storageAccountKey
                $nonAttachedVhdCount = 0

                # Get all containers in the storage account.
                Get-AzureStorageContainer -Context $context |
                    ForEach-Object -Process {
        
                        $containerName = $_.Name

                        # Get all blobs in the container.
                        Get-AzureStorageBlob -Context $context -Container $containerName |
                            ForEach-Object -Process {

                                $blobUri = $_.ICloudBlob.Uri.AbsoluteUri

                                # Verify that it is a non-attached VHD.
                                if ($blobUri.EndsWith('.vhd') -and ($attachedVhdUris -notcontains $blobUri))
                                {
                                    [PSCustomObject] @{
                                        ResourceGroupName  = $resourceGroupName
                                        StorageAccountName = $storageAccountName
                                        Location           = $storageAccount.Location
                                        Sku                = $storageAccount.Sku
                                        ContainerName      = $containerName
                                        Name               = $_.Name
                                        ICloudBlob         = $_.ICloudBlob
                                        BlobType           = $_.BlobType
                                        Length             = $_.Length
                                        ContentType        = $_.ContentType
                                        LastModified       = $_.LastModified
                                        SnapshotTime       = $_.SnapshotTime
                                        ContinuationToken  = $_.ContinuationToken
                                        Context            = $_.Context
                                        StorageAccount     = $storageAccount
                                    }

                                    $nonAttachedVhdCount++
                                }
                            }
                    }

                Write-Verbose -Message ('Found {0} the non-attached VHD in SA:{1} in RG:{2}' -f $nonAttachedVhdCount,$storageAccountName,$resourceGroupName)
            }
        }
}

function GetAttachedVhdUri
{
    [CmdletBinding()]
    [OutputType([string])]
    param (
        [Parameter(Mandatory = $false)]
        [string[]] $ExcludeResourceGroup
    )

    Get-AzureRmVM |
        ForEach-Object -Process {

            # Exclude the non target resource groups.
            if ($ExcludeResourceGroup -notcontains $_.ResourceGroupName)
            {
                $storageProfile = $_.StorageProfile

                # Attached VHD URI as OS disk.
                if ($storageProfile.OsDisk.Vhd -ne $null) { $storageProfile.OsDisk.Vhd.Uri }

                # All attached VHD URIs as data disks.
                if ($storageProfile.DataDisks.Vhd -ne $null) { $storageProfile.DataDisks.Vhd.Uri }
            }
        }
}