Add Azure VM Tags w/PowerShell

Summary: PowerShell can easily update or add tags to all VMs within an Azure Subscription. The following will only add or update tags, not remove existing tags. This script requires a CSV input file with the following format:

VM Name,Tag:Value,Tag:Value,Tag:Value,...

This script will break if there are any commas or colons in the Tag Name or Tag Value since they are used to parse the input file. The script can be updated to adjust delimiter handling.

Set-VMTags.csv (example):

xbogus,BuiltBy:otto.helweg@cloudrobots.net,Application:Test,AppOwner:otto.helweg@cloudrobots.net,Account:123456
otto-test-linux,Owner:otto,needed-until-date:2020-12-31,environment:test
otto-test-linux-2,Owner:otto,needed-until-date:2020-12-31,environment:test
otto-test-win,Owner:otto,needed-until-date:2020-12-31,environment:test
otto-dev-win10,Owner:otto,needed-until-date:2021-12-31,environment:dev
Otto-MyWindows,Owner:otto,needed-until-date:2021-12-31,environment:dev

Tags are then applied to the VM instance, and the associated Disks and NICs (associated Public IPs (PIPs) are not included.

Set-VMTags.ps1 (example):

<#
.DESCRIPTION
    Set tags for all VMs in a subscription
.EXAMPLE
    PS >> .\Set-VMMTags.ps1
.NOTES
    AUTHORS: Otto Helweg
    LASTEDIT:September 2, 2020
    VERSION: 1.0.3
    POWERSHELL: Requires version 6
    Update Execution Policy and Modules:
        Set-ExecutionPolicy Bypass -Force
    Login to Azure first:
            Logout-AzAccount
            Login-AzAccount -Subscription "<Azure Subscription>"
            Select-AzSubscription -Subscription "<Azure Subscription>"
    Example:
        .\Set-VMTags.ps1 -Wait -inputFile "Set-VMTags.csv"
#>

param($inputFile)

if (!($inputFile)) {
    $inputFile = "Set-VMTags.csv"
}

$csvContent = Get-Content "./$inputFile"
$vmList = @{}
foreach ($item in $csvContent) {
    $tags = @{}
    $vmName,$vmTags = $item.Split(",")
    if ($vmName -and $vmTags) {
        $vmList[$vmName] = $vmTags
        foreach ($tag in $vmTags) {
            $tagData = $tag.Split(":")
            $tags[$tagData[0]] = $tagData[1]
        }

        $vmAzure = Get-AzVM -Name "$vmName"

        if ($vmAzure) {
            Write-Output "$vmName VM updating Tags"
            Update-AzTag -ResourceId $vmAzure.Id -Operation Merge -Tag $tags
            foreach ($nic in $vmAzure.NetworkProfile.NetworkInterfaces) {
                Write-Output "> $vmName NIC updating Tags"
                Update-AzTag -ResourceId $nic.Id -Operation Merge -Tag $tags
            }
            if ($vmAzure.StorageProfile.OsDisk.ManagedDisk.Id) {
                Write-Output "> $vmName Disk $($vmAzure.StorageProfile.OsDisk.Name) updating Tags"
                Update-AzTag -ResourceId $vmAzure.StorageProfile.OsDisk.ManagedDisk.Id -Operation Merge -Tag $tags
            }
            foreach ($disk in $vmAzure.StorageProfile.DataDisks) {
                Write-Output "> $vmName Disk $($disk.Name) updating Tags"
                $azResource = Get-AzResource -Name "$($disk.Name)"
                Update-AzTag -ResourceId $azResource.Id -Operation Merge -Tag $tags
            }

            if ($Args -contains "-Wait") {
                Read-Host "Press Enter to continue"
            }
        } else {
            Write-Output "$vmName VM not found"
        }
    } else {
        Write-Output "Malformed tags"
    }
}

Enjoy!