ManageTagsOnDisk.ps1


<#PSScriptInfo
 
.VERSION 1.0
 
.GUID 39ac4585-7daf-4509-bfc0-089ec25db7fe
 
.AUTHOR AlexG
 
.COMPANYNAME GooberWorks
 
.COPYRIGHT GooberWorks (c) 2020
 
.TAGS Azure AzureRM Automation TAGS Tags Disk replace remove delete copy add tags Unattached ManageTagsOn
 
.LICENSEURI
 
.PROJECTURI
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
 
  1.0 - 2/13/2020
  Original Release
 
.PRIVATEDATA
 
#>


<#
 
.DESCRIPTION
    This is a Powershell script to help manage TAGS on Azure Disk Resource
    While it is very easy to add and change tags using console, it is not so easy to delete a specific tag
    from a resource. The console also limits the number of resources you can update to 100. So if you need
    to change tags on more than 100 VMs, then you'll need to rinse and repeat as often as neccessary
 
    This script will allow you to add, replace or remove a specific tag from any Disk that matches a search criteria
    using parameters -ManageThisTag or ManageThisTag and ManageThisTagValue. It will also allow you to Copy tags
    from the Virtual Machine that owns the Disk. The Copy function of the script will overwrite the existing
    Tags on the Disk with those from the VM. If you wanted to keep some of the Tags on the Disk that are not on
    the Virtual Machine, then you can you Merge function. The Merge Function will combine Tags from the VM and Disk.
    For those tags that exist on both VM and Disk, you can specify which of the two values will be kept by using
    the switches -vmwins or -diskwins
 
    While running the -merge process, the script will identify and Disk Resource that is not Managed, not attached, to any
    Virtual Machine. You may want to carefully review that list to see if you are spending on Disk Storage for
    the VMs you have deleted a while back.
 
    This script is part of a set of ManageTagsOn scripts. Please look for
    ManageTagsOnVM
    ManageTagsOnDisk
    ManageTagsOnNetworkCard
    ManageTagsOnPublicIP
    ManageTagsOnNetworkSecurityGroup
    ManageTagsOnAvailabilitySet
    ManageTagsOnRouteTable
    ManageTagsOnAutomationRunbooks
 
.SYNOPSIS
    Add, Remove, Replace, Merge or Copy Tags on Disk Resource in Azure
 
.DESCRIPTION
    This script will allow you to add, replace or remove a specific tag from any Disk that matches a search criteria
    using parameters -ManageThisTag or ManageThisTag and ManageThisTagValue. It will also allow you to Copy tags
    from the Virtual Machine that owns the Disk. The Copy function of the script will overwrite the existing
    Tags on the Disk with those from the VM. If you wanted to keep some of the Tags on the Disk that are not on
    the Virtual Machine, then you can you Merge function. The Merge Function will combine Tags from the VM and Disk.
    For those tags that exist on both VM and Disk, you can specify which of the two values will be kept by using
    the switches -vmwins or -diskwins.
 
 
.PARAMETER ManageThisTag
    The name of the TAG to be use as a search criteria. Filters only those Disk Resources where this tag exists.
 
.PARAMETER ManageThisTagValue
    The value of the TAG specified by ManageThisTag. This further filters only those Disk Resources where this tag exists
    and it value matches ManageThisTagValue.
 
.PARAMETER FromGroup
    The name of the Resource Group to limit search scope. Filters only those Disk Resource in the specific Resource Group.
 
.PARAMETER addtag
    The -addtag switch works together with NewTagKey and NewTagValue parameters. This will make the script add a new
    tag NewTagKey with value of NewTagValue to the Disk Resource that match the search criteria
 
.PARAMETER replacetag
    The -replacetag switch will tell the script to delete the tag specified by ManageThisTag, no matter of its value is
    and replace it with a new tag NewTagKey with value of NewTagValue
 
.PARAMETER replacetagvalue
    The -replacetagvalue switch will tell the script to delete the tag specified by ManageThisTag, only if its value
    matches ManageThisTagValue and replace it with a new tag NewTagKey with value of NewTagValue
 
.PARAMETER removetag
    The -removetag parameter will tell the script to delete the tag specified by ManageThisTag, no matter of its value is.
 
.PARAMETER removetagvalue
    The -removetagvalue parameter will tell the script to delete the tag specified by ManageThisTag,
    only if its value matches ManageThisTagValue
 
.PARAMETER merge
    The -merge switch works together with -vmwins, -diskwins and -replace switches. The Merge Function will combine
    Tags from the VM and Disk. For those tags that exist on both VM and Disk, you can specify which of the two values
    will be kept by using the switches -vmwins or -diskwins. The -replace switch will make the script perform what
    amounts to a copy of the tags from the Virtual Machine to the Disk Resources it "owns"
 
.PARAMETER WhatIf
    Safe run of the script without any changes to preview changes that would be made.
 
.EXAMPLE
    ManageTagsOnDisk.ps1 -ManageThisTag "Environment" -removetag -FromGroup "test-vm-servers-rg"
 
.EXAMPLE
    ManageTagsOnDisk.ps1 -ManageThisTag "Environment" -ManageThisTagValue "Development" -replacetagvalue -NewTagKey "Environment" -NewTagValue "Test"
 
.EXAMPLE
    ManageTagsOnDisk.ps1 -merge -vmwins -FromGroup "test-vm-servers-rg"
 
.EXAMPLE
    ManageTagsOnDisk.ps1 -merge -replace
 
.NOTES
    AUTHOR: Alexander Goldberg
    LASTEDIT: Feb 12, 2020
    WEBSITE: www.alexgoldberg.com
#>


param(
    [string]$ManageThisTag,
    [string]$ManageThisTagValue,
    [string]$NewTagKey,
    [string]$NewTagValue,
    [string]$FromGroup,
    [switch]$addtag,
    [switch]$replacetag,
    [switch]$replacetagvalue,
    [switch]$removetag,
    [switch]$removetagvalue,
    [switch]$merge,
    [switch]$diskwins,
    [switch]$vmwins,
    [switch]$replace,
    [switch]$WhatIf
)


function Merge-HashTable {
    param(
        [hashtable] $default, # Your original set
        [hashtable] $uppend # The set you want to update/append to the original set
    )

    # Clone for idempotence
    $default1 = $default.Clone();

    # We need to remove any key-value pairs in $default1 that we will
    # be replacing with key-value pairs from $uppend
    foreach ($key in $uppend.Keys) {
        if ($default1.ContainsKey($key)) {
            $default1.Remove($key);
        }
    }

    # Union both sets
    return $default1 + $uppend;
}


if ( 1 -ne $replacetag.IsPresent + $replacetagvalue.IsPresent + $removetag.IsPresent + $removetagvalue.IsPresent + $addtag.IsPresent + $merge.IsPresent) {
    'Usage: ManageTagsOnDisk -ManageThisTag [-ManageThisTagValue] [-FromGroup] { -addtag | -replacetag | -replacetagvalue | -removetag | -removetagvalue | -merge } [-diskwins | -vmwins | -replace] [-WhatIf]'
    exit 1
}

if ($merge.IsPresent) {
    if ( 1 -ne $diskwins.IsPresent + $vmwins.IsPresent + $replace.IsPresent) {
        'When merging tags, specify who wins if tags exists on both VM and Disk. Usage: -merge { -diskwins | -vmwins | -replace}'
        exit 1
    }
}

# Initilize an Array to hold unattached disks

$UnattachedDisks = New-Object 'System.Collections.Generic.Dictionary[String,String]'

if ($FromGroup.Length -eq 0) {
    # Getting the list of all Disks in the subscription.
    $Resources = Get-AzureRmDisk
}
else {
    # Getting the list of Disks based on the resource group.
    $Resources = Get-AzureRmDisk -ResourceGroupName $FromGroup
}

# Details of the tag to remove are stored in the $TagToManage variable.
$TagToManage = @{Key="$ManageThisTag";Value="$ManageThisTagValue"}

foreach ($Resource in $Resources)
{
    Write-Output ('processing: ' + $Resource.Name)
    # Initialize UpdateTags to false
    $UpdateTags = $false

    # Getting the list of all the tags for the disk.
    $ResourceTags = $Resource.tags


    if ($addtag) {
        if ($null -ne $ResourceTags -and $ResourceTags.ContainsKey($TagToManage.Key)) {
            Write-Output ("Tag " + $TagToManage.Key + " exists in the " + $Resource.Name + ". The new tag will be Added here")
            # Adding new tag to the Resource because it has the TagToManage.Key
            $Resource.Tags.Add($NewTagKey, $NewTagValue)

            # In addition to the new tag key and value, you can also hardcode any additonal tag value key pairs here
            # I know, it's not pretty, but it works if you want to add multiple tags
            #$Resource.Tags.Add("tag name","tag value") # Adding the tag to update with new value
            #$Resource.Tags.Add("tag name","tag value") # Adding the tag to update with new value

            #set UpdateTags to true
            $UpdateTags = $true
        }
    }

    # The replacetag parameter will tell the script to delete the specified tag, no matter of its value is
    # and replace it with a new tag and value
    if ($replacetag) {
        if ($null -ne $ResourceTags -and $ResourceTags.ContainsKey($TagToManage.Key)) {
            Write-Output ("Tag " + $TagToManage.Key + " exists in the " + $Resource.Name + " and will be Replaced")
            $Resource.Tags.Remove($TagToManage.Key)
            $Resource.Tags.Add($NewTagKey, $NewTagValue)

            #set UpdateTags to true
            $UpdateTags = $true
        }
    }

    # The replacetagvalue parameter will tell the script to delete the specified tag, only if its value matches ManageThisTagValue
    # and replace it with a new tag and value
    if ($replacetagvalue) {
        if ($null -ne $ResourceTags -and $ResourceTags.ContainsKey($TagToManage.Key) -and ($ResourceTags.($TagToManage.Key) -eq $TagToManage.Value)) {
            Write-Output ("Tag " + $TagToManage.Key + " with value of " + $TagToManage.Value + " exists in the " + $Resource.Name + " and will be Replaced")
            $Resource.Tags.Remove($TagToManage.Key)
            $Resource.Tags.Add($NewTagKey, $NewTagValue)

            #set UpdateTags to true
            $UpdateTags = $true
        }
    }

    # The removetag parameter will tell the script to delete the specified tag, no matter of its value is
    if ($removetag) {
        If ($Resourcetag.Key -eq $TagToManage.Key) {
            Write-Output ("Tag " + $TagToManage.Key + " exists in the " + $Resource.Name + " and will be Removed")
            $Resource.Tags.Remove($TagToManage.Key)
            $UpdateTags = $true
        }
    }

    # The removetagvalue parameter will tell the script to delete the specified tag, only if its value matches ManageThisTagValue
    if ($removetagvalue) {
        if ($null -ne $ResourceTags -and $ResourceTags.ContainsKey($TagToManage.Key) -and ($ResourceTags.($TagToManage.Key) -eq $TagToManage.Value)) {
            Write-Output ("Tag " + $TagToManage.Key + " with value of " + $TagToManage.Value + " exists in the " + $Resource.Name + " and will be Removed")
            $Resource.Tags.Remove($TagToManage.Key)

            #set UpdateTags to true
            $UpdateTags = $true
        }
    }

    if ($merge) {
        # Before we go after the ManagerVM let's find out if the disk is attached to a VM
        # Because if Disk is not Managed, we can't copy the tags from VM
        if ($null -eq $Resource.ManagedBy) {
            # No ManagerVM found, Add to unattached disk hashtable to report at the end of the script run
            $UnattachedDisks.Add($Resource.Name, $Resource.ResourceGroupName)
        } else {
            # Get id of the Virtual Machine that manages the disk. Property: ManagedBy
            # Get tags from the Manager VM

            $ManagerVM = Get-AzureRmResource -ResourceId $Resource.ManagedBy
            $ManagerVMTags = $ManagerVM.Tags

            # If ManagerVMTags does not have tags then there is nothing to merge, so skip it
            if ($null -ne $ManagerVMTags) {
                Write-Output ("Merging Tags from " + $ManagerVM.Name + " with tags on Disk " + $Resource.Name)
                if ($diskwins) {
                    $NewTags = Merge-HashTable $ManagerVMTags $ResourceTags
                }
                if ($vmwins) {
                    $NewTags = Merge-HashTable $ResourceTags $ManagerVMTags
                }

                if ($replace) {
                    $NewTags = $ManagerVMTags
                }

                # The merged HashTable uses object dictionary, we need to use String dictionary.
                # So we loop through the NewTags and copy all the values to the $ReplacementTags variable
                $ReplacementTags = New-Object 'System.Collections.Generic.Dictionary[String,String]'

                foreach ($MergedTagValue in $NewTags.GetEnumerator()) {
                      $ReplacementTags.add($MergedTagValue.Key,$MergedTagValue.Value) # Setting newtags as string hash table
                }
                $Resource.Tags = $ReplacementTags
                #set UpdateTags to true
                $UpdateTags = $true
            }
        }
    }
    if ($UpdateTags) {
        Write-Output ("Updating Tags on: " + $Resource.Name)
         $Resource | Update-AzureRmDisk -WhatIf:$WhatIf
        Write-Output ($Resource.Tags | Out-String)
    }
}

if ($UnattachedDisks.Count -gt 0) {
    Write-Warning ($UnattachedDisks | Out-String)
}