Press "Enter" to skip to content

Getting VM Tags across vCenters using PowerShell & REST APIs

Joshua Stenhouse 0

Update 01/23/19: Due to scale issues with the VMware REST API call for listing all VMs (it returns an error at 3000+VMs), I rearchitected the script to get each VM name by id. This could be further optimized, but it works and it’s still pretty fast.

I was recently doing some work with a large health insurance provider on the east coast and an interesting requirement came up that needed some scripting wizardry. They have 15+ vCenters and needed to see all the VM tags assigned across the estate, but they had no internet access to install PowerCLI from the PS Gallery, and neither could they install anything in-guest. Even if we could get PowerCLI running from an offline module my experience tells me it doesn’t handle multiple vCenters well, cycling through them is slow, and the PowerCLI Tag functions are even slower. The solution? Go straight to the REST APIs!

As all the vCenters were a minimum of vSphere 6.5 so this allowed me to bypass PowerCLI altogether and go straight to the REST APIs. The bad news is that VMware created a rather complicated set of APIs for deciphering tags, as it’s not just 1 API call, you have to make multiple:

# To authenticate: 
https://hchldrvcenter.lab.local/rest/com/vmware/cis/session
# Get a list of VMs (to translate the VM IDs on assigned tags):
https://hchldrvcenter.lab.local/rest/vcenter/vm
# Get tag categories:
https://hchldrvcenter.lab.local/rest/com/vmware/cis/tagging/category
# Get the name of each tag category (yeah, they really made it hard!):
https://hchldrvcenter.lab.local/rest/com/vmware/cis/tagging/category/id:urn%3Avmomi%3AInventoryServiceCategory%3A4cf3f53d-0958-47e3-9b61-3bfdf9e54f24%3AGLOBAL
# Get every tag:
https://hchldrvcenter.lab.local/rest/com/vmware/cis/tagging/tag
# Get the name of the tag and its category ID:
https://hchldrvcenter.lab.local/rest/com/vmware/cis/tagging/tag/id:urn%3Avmomi%3AInventoryServiceTag%3A51a2f0ae-0828-4448-b6ad-5a2a7bfc805f%3AGLOBAL
# Finally, with a JSON body containing the tag ID we POST to the below to get all the objects its assigned to:
https://hchldrvcenter.lab.local/rest/com/vmware/cis/tagging/tag-association?~action=list-attached-objects-on-tags

Good job that was easy huh? Not! The good news is that I’ve done the leg work for you in putting this altogether to work across multiple vCenters using PowerShell 5.1 or 6.x. Click on the required link below to download an example:

Or you can copy and paste the example from the below, starting with the PowerShell 5.1 example:

################################################
# Configure the variables below for the vCenter
################################################
$vCenters = "hchlv2vcenter.lab.local","hchldrvcenter.lab.local"
# Prompting for credentials
$vCenterCredentials = Get-Credential -Message "Enter vCenter login credentials"
$vCenterUser = $vCenterCredentials.UserName
$vCenterPassword = $vCenterCredentials.GetNetworkCredential().Password
#######################################################################
# Nothing to configure below this line - Starting the main function
#######################################################################
################################################
# Adding certificate exception to prevent API errors
################################################
add-type @"
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
    public class TrustAllCertsPolicy : ICertificatePolicy {
        public bool CheckValidationResult(
            ServicePoint srvPoint, X509Certificate certificate,
            WebRequest request, int certificateProblem) {
            return true;
        }
    }
"@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
################################################
# Building AssignedTagArray object here, as this script supports multiple vCenters
################################################
$AssignedTagArray = @()
################################################
# For Each vCenter authenticating and building list of assigned tags
################################################
ForEach ($vCenter in $vCenters)
{
################################################
# Building vCenter API string & invoking REST API
################################################
$vCenterBaseAuthURL = "https://" + $vCenter + "/rest/com/vmware/cis/"
$vCenterBaseURL = "https://" + $vCenter + "/rest/vcenter/"
$vCenterSessionURL = $vCenterBaseAuthURL + "session"
$Header = @{"Authorization" = "Basic "+[System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($vCenterUser+":"+$vCenterPassword))}
$Type = "application/json"
# Authenticating with API
Try 
{
$vCenterSessionResponse = Invoke-RestMethod -Uri $vCenterSessionURL -Headers $Header -Method POST -ContentType $Type
}
Catch 
{
$_.Exception.ToString()
$error[0] | Format-List -Force
}
# Extracting the session ID from the response
$vCenterSessionHeader = @{'vmware-api-session-id' = $vCenterSessionResponse.value}
###############################################
# Getting list of Tag Categories
###############################################
$TagCategoriesURL = $vCenterBaseAuthURL+"tagging/category"
Try 
{
$TagCategoriesJSON = Invoke-RestMethod -Method Get -Uri $TagCategoriesURL -TimeoutSec 100 -Headers $vCenterSessionHeader -ContentType $Type
$TagCategories = $TagCategoriesJSON.value
}
Catch 
{
$_.Exception.ToString()
$error[0] | Format-List -Force
}
###############################################
# Getting Tag Category names
###############################################
$TagCategoryArray = @()
ForEach ($TagCategoryID in $TagCategories)
{
# Replacing : with %3
$TagCategoryIDURL = $TagCategoryID -replace ":","%3A"
# Getting Category info
$TagCategoryURL = $vCenterBaseAuthURL+"tagging/category/id:"+$TagCategoryIDURL
Try 
{
$TagCategoryJSON = Invoke-RestMethod -Method Get -Uri $TagCategoryURL -TimeoutSec 100 -Headers $vCenterSessionHeader -ContentType $Type
$TagCategory = $TagCategoryJSON.value
}
Catch 
{
$_.Exception.ToString()
$error[0] | Format-List -Force
}
# Getting Tag category name
$TagCategoryName = $TagCategory.name
# Adding to table/array
$TagCategoryArrayLine = New-Object PSObject
$TagCategoryArrayLine | Add-Member -MemberType NoteProperty -Name "Name" -Value "$TagCategoryName"
$TagCategoryArrayLine | Add-Member -MemberType NoteProperty -Name "ID" -Value "$TagCategoryID"
$TagCategoryArray += $TagCategoryArrayLine
}
###############################################
# Getting list of Tags
###############################################
$TagsURL = $vCenterBaseAuthURL+"tagging/tag"
Try 
{
$TagsJSON = Invoke-RestMethod -Method Get -Uri $TagsURL -TimeoutSec 100 -Headers $vCenterSessionHeader -ContentType $Type
$Tags = $TagsJSON.value
}
Catch 
{
$_.Exception.ToString()
$error[0] | Format-List -Force
}
###############################################
# Getting Tags
###############################################
$TagArray = @()
ForEach ($TagID in $Tags)
{
# Replacing : with %3
$TagIDURL = $TagID -replace ":","%3A"
# Getting tag info
$TagInfoURL = $vCenterBaseAuthURL+"tagging/tag/id:"+$TagIDURL
Try 
{
$TagJSON = Invoke-RestMethod -Method Get -Uri $TagInfoURL -TimeoutSec 100 -Headers $vCenterSessionHeader -ContentType $Type
$TagInfo = $TagJSON.value
}
Catch 
{
$_.Exception.ToString()
$error[0] | Format-List -Force
}
# Getting Tag category name and category ID
$TagName = $TagInfo.name
$TagCategoryID = $TagInfo.category_id
# Getting name of tag category from $TagCategoryArray
$TagCategoryName = $TagCategoryArray | Where-Object {$_.ID -eq $TagCategoryID} | Select -ExpandProperty Name
# Adding to table/array
$TagArrayLine = New-Object PSObject
$TagArrayLine | Add-Member -MemberType NoteProperty -Name "Name" -Value "$TagName"
$TagArrayLine | Add-Member -MemberType NoteProperty -Name "ID" -Value "$TagID"
$TagArrayLine | Add-Member -MemberType NoteProperty -Name "Category" -Value "$TagCategoryName"
$TagArrayLine | Add-Member -MemberType NoteProperty -Name "CategoryID" -Value "$TagCategoryID"
$TagArray += $TagArrayLine
}
###############################################
# For Each Tag Getting Assigned Objects
###############################################
ForEach ($Tag in $TagArray)
{
# Assigning Tag ID
$TagID = $Tag.id
$TagName = $Tag.Name
$TagCategoryName = $Tag.Category
$TagCategoryID = $Tag.CategoryID
# Building JSON
$TagJSON =
"{
  ""tag_ids"": [
  ""$TagID""
  ]
}"
# Getting tag assignment info
$TagURL = $vCenterBaseAuthURL+"tagging/tag-association?~action=list-attached-objects-on-tags"
Try 
{
$TagJSONResponse = Invoke-RestMethod -Method POST -Body $TagJSON -Uri $TagURL -TimeoutSec 100 -Headers $vCenterSessionHeader -ContentType $Type
$TagDetailedInfo = $TagJSONResponse.value
}
Catch 
{
$_.Exception.ToString()
$error[0] | Format-List -Force
}
# Selecting object info
$TagObjects = $TagDetailedInfo.object_ids
###############################################
# For Each Assigned object getting VM name and adding to array
###############################################
ForEach ($TagObject in $TagObjects)
{
$TagObjectID = $TagObject.id
$TagObjectType = $TagObject.type
# Getting VM Name direct from rest API (switched to individual API call as getting all VMs fails at scale)
IF ($TagObjectType -eq "VirtualMachine")
{
$VMInfoURL = $vCenterBaseURL+"vm?filter.vms="+$TagObjectID
Try 
{
$VMInfoJSON = Invoke-RestMethod -Method Get -Uri $VMInfoURL -TimeoutSec 100 -Headers $vCenterSessionHeader -ContentType $Type
$VMInfo = $VMInfoJSON.value
}
Catch 
{
$_.Exception.ToString()
$error[0] | Format-List -Force
}
# Setting VM Name
$TagObjectVMName = $VMInfo.name
}
# Adding to table/array
$AssignedTagArrayLine = New-Object PSObject
$AssignedTagArrayLine | Add-Member -MemberType NoteProperty -Name "vCenter" -Value "$vCenter"
$AssignedTagArrayLine | Add-Member -MemberType NoteProperty -Name "Category" -Value "$TagCategoryName"
$AssignedTagArrayLine | Add-Member -MemberType NoteProperty -Name "Tag" -Value "$TagName"
$AssignedTagArrayLine | Add-Member -MemberType NoteProperty -Name "VM" -Value "$TagObjectVMName"
$AssignedTagArrayLine | Add-Member -MemberType NoteProperty -Name "VMID" -Value "$TagObjectID"
$AssignedTagArrayLine | Add-Member -MemberType NoteProperty -Name "Type" -Value "$TagObjectType"
$AssignedTagArrayLine | Add-Member -MemberType NoteProperty -Name "TagID" -Value "$TagID"
$AssignedTagArrayLine | Add-Member -MemberType NoteProperty -Name "CategoryID" -Value "$TagCategoryID"
$AssignedTagArray += $AssignedTagArrayLine
}
# End of for each tag below
}
# End of for each tag above
################################################
# End of for each vCenter below
################################################
}
# End of for each vCenter above
################################################
# Showing output to user
################################################
$AssignedTagArray | Where-Object {$_.Type -eq "VirtualMachine"} | Sort-Object vCenter,Category,Tag,VM | Format-Table -AutoSize
###############################################
# End of script
###############################################

Here is the same script but for PowerShell 6.x:

################################################
# Configure the variables below for the vCenter
################################################
$vCenters = "hchlv2vcenter.lab.local","hchldrvcenter.lab.local"
# Prompting for credentials
$vCenterCredentials = Get-Credential -Message "Enter vCenter login credentials"
$vCenterUser = $vCenterCredentials.UserName
$vCenterPassword = $vCenterCredentials.GetNetworkCredential().Password
#######################################################################
# Nothing to configure below this line - Starting the main function 
#######################################################################
# Building AssignedTagArray object
################################################
$AssignedTagArray = @()
################################################
# For Each vCenter authenticating and building list of assigned tags
################################################
ForEach ($vCenter in $vCenters)
{
################################################
# Building vCenter API string & invoking REST API
################################################
$vCenterBaseAuthURL = "https://" + $vCenter + "/rest/com/vmware/cis/"
$vCenterBaseURL = "https://" + $vCenter + "/rest/vcenter/"
$vCenterSessionURL = $vCenterBaseAuthURL + "session"
$Header = @{"Authorization" = "Basic "+[System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($vCenterUser+":"+$vCenterPassword))}
$Type = "application/json"
# Authenticating with API
Try 
{
$vCenterSessionResponse = Invoke-RestMethod  -Method POST -Uri $vCenterSessionURL -Headers $Header -ContentType $Type -SkipCertificateCheck
}
Catch 
{
$_.Exception.ToString()
$error[0] | Format-List -Force
}
# Extracting the session ID from the response
$vCenterSessionHeader = @{'vmware-api-session-id' = $vCenterSessionResponse.value}
###############################################
# Getting list of VMs
###############################################
$VMListURL = $vCenterBaseURL+"vm"
Try 
{
$VMListJSON = Invoke-RestMethod -Method Get -Uri $VMListURL -TimeoutSec 100 -Headers $vCenterSessionHeader -ContentType $Type -SkipCertificateCheck
$VMList = $VMListJSON.value
}
Catch 
{
$_.Exception.ToString()
$error[0] | Format-List -Force
}
###############################################
# Getting list of Tag Categories
###############################################
$TagCategoriesURL = $vCenterBaseAuthURL+"tagging/category"
Try 
{
$TagCategoriesJSON = Invoke-RestMethod -Method Get -Uri $TagCategoriesURL -TimeoutSec 100 -Headers $vCenterSessionHeader -ContentType $Type -SkipCertificateCheck
$TagCategories = $TagCategoriesJSON.value
}
Catch 
{
$_.Exception.ToString()
$error[0] | Format-List -Force
}
###############################################
# Getting Tag Category names
###############################################
$TagCategoryArray = @()
ForEach ($TagCategoryID in $TagCategories)
{
# Replacing : with %3
$TagCategoryIDURL = $TagCategoryID -replace ":","%3A"
# Getting Category info
$TagCategoryURL = $vCenterBaseAuthURL+"tagging/category/id:"+$TagCategoryIDURL
Try 
{
$TagCategoryJSON = Invoke-RestMethod -Method Get -Uri $TagCategoryURL -TimeoutSec 100 -Headers $vCenterSessionHeader -ContentType $Type -SkipCertificateCheck
$TagCategory = $TagCategoryJSON.value
}
Catch 
{
$_.Exception.ToString()
$error[0] | Format-List -Force
}
# Getting Tag category name
$TagCategoryName = $TagCategory.name
# Adding to table/array
$TagCategoryArrayLine = New-Object PSObject
$TagCategoryArrayLine | Add-Member -MemberType NoteProperty -Name "Name" -Value "$TagCategoryName"
$TagCategoryArrayLine | Add-Member -MemberType NoteProperty -Name "ID" -Value "$TagCategoryID"
$TagCategoryArray += $TagCategoryArrayLine
}
###############################################
# Getting list of Tags
###############################################
$TagsURL = $vCenterBaseAuthURL+"tagging/tag"
Try 
{
$TagsJSON = Invoke-RestMethod -Method Get -Uri $TagsURL -TimeoutSec 100 -Headers $vCenterSessionHeader -ContentType $Type -SkipCertificateCheck
$Tags = $TagsJSON.value
}
Catch 
{
$_.Exception.ToString()
$error[0] | Format-List -Force
}
###############################################
# Getting Tags
###############################################
$TagArray = @()
ForEach ($TagID in $Tags)
{
# Replacing : with %3
$TagIDURL = $TagID -replace ":","%3A"
# Getting tag info
$TagInfoURL = $vCenterBaseAuthURL+"tagging/tag/id:"+$TagIDURL
Try 
{
$TagJSON = Invoke-RestMethod -Method Get -Uri $TagInfoURL -TimeoutSec 100 -Headers $vCenterSessionHeader -ContentType $Type -SkipCertificateCheck
$TagInfo = $TagJSON.value
}
Catch 
{
$_.Exception.ToString()
$error[0] | Format-List -Force
}
# Getting Tag category name and category ID
$TagName = $TagInfo.name
$TagCategoryID = $TagInfo.category_id
# Getting name of tag category from $TagCategoryArray
$TagCategoryName = $TagCategoryArray | Where-Object {$_.ID -eq $TagCategoryID} | Select -ExpandProperty Name
# Adding to table/array
$TagArrayLine = New-Object PSObject
$TagArrayLine | Add-Member -MemberType NoteProperty -Name "Name" -Value "$TagName"
$TagArrayLine | Add-Member -MemberType NoteProperty -Name "ID" -Value "$TagID"
$TagArrayLine | Add-Member -MemberType NoteProperty -Name "Category" -Value "$TagCategoryName"
$TagArrayLine | Add-Member -MemberType NoteProperty -Name "CategoryID" -Value "$TagCategoryID"
$TagArray += $TagArrayLine
}
###############################################
# For Each Tag Getting Assigned Objects
###############################################
ForEach ($Tag in $TagArray)
{
# Assigning Tag ID
$TagID = $Tag.id
$TagName = $Tag.Name
$TagCategoryName = $Tag.Category
$TagCategoryID = $Tag.CategoryID
# Building JSON
$TagJSON =
"{
  ""tag_ids"": [
  ""$TagID""
  ]
}"
# Getting tag assignment info
$TagURL = $vCenterBaseAuthURL+"tagging/tag-association?~action=list-attached-objects-on-tags"
Try 
{
$TagJSONResponse = Invoke-RestMethod -Method POST -Body $TagJSON -Uri $TagURL -TimeoutSec 100 -Headers $vCenterSessionHeader -ContentType $Type -SkipCertificateCheck
$TagDetailedInfo = $TagJSONResponse.value
}
Catch 
{
$_.Exception.ToString()
$error[0] | Format-List -Force
}
# Selecting object info
$TagObjects = $TagDetailedInfo.object_ids
# For Each object getting VM name and adding to array
ForEach ($TagObject in $TagObjects)
{
$TagObjectID = $TagObject.id
$TagObjectType = $TagObject.type
# Getting ID name
$TagObjectVMName = $VMList | Where-Object {$_.vm -eq $TagObjectID} | Select -ExpandProperty Name
# Adding to table/array
$AssignedTagArrayLine = New-Object PSObject
$AssignedTagArrayLine | Add-Member -MemberType NoteProperty -Name "vCenter" -Value "$vCenter"
$AssignedTagArrayLine | Add-Member -MemberType NoteProperty -Name "Category" -Value "$TagCategoryName"
$AssignedTagArrayLine | Add-Member -MemberType NoteProperty -Name "Tag" -Value "$TagName"
$AssignedTagArrayLine | Add-Member -MemberType NoteProperty -Name "VM" -Value "$TagObjectVMName"
$AssignedTagArrayLine | Add-Member -MemberType NoteProperty -Name "VMID" -Value "$TagObjectID"
$AssignedTagArrayLine | Add-Member -MemberType NoteProperty -Name "Type" -Value "$TagObjectType"
$AssignedTagArrayLine | Add-Member -MemberType NoteProperty -Name "TagID" -Value "$TagID"
$AssignedTagArrayLine | Add-Member -MemberType NoteProperty -Name "CategoryID" -Value "$TagCategoryID"
$AssignedTagArray += $AssignedTagArrayLine
}
# End of for each tag below
}
# End of for each tag above
################################################
# End of for each vCenter below
################################################
}
# End of for each vCenter above
################################################
# Showing output to user
################################################
$AssignedTagArray | Where-Object {$_.Type -eq "VirtualMachine"} | Sort-Object vCenter,Category,Tag,VM | Format-Table -AutoSize
###############################################
# End of script
###############################################

I hope you found this useful. Happy scripting,

@JoshuaStenhouse

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Discover more from Virtually Sober

Subscribe now to keep reading and get access to the full archive.

Continue reading