It has been noted that when a VM is migrated from one vCenter to another using cross-vCenter vMotion, the tags on the VM do not get migrated with it even if the corresponding tags & categories are present in the target vCenter. I've built a pair of scripts that we're using to remediate that oversight.
The first script copies the tags & categories from one vCenter to another. The second script is used to migrate tag assignments as part of the vMotion process.
An important distinction between the two is that you can perform the category/tag copy at any time: it isn't a full CRUD implementation, just making copies from a source to the destination. The migrate script must be run before (and during) a cross-vCenter migration because it needs to capture the tagging details on a VM before it migrates, or that information is lost forever. So the procedure would be as follows:
- Run Copy-vcTags.ps1 to make sure all the destination system tags are current
- Run Migrate-vcTags.ps1 to get a snapshot of the tag assignments on the source system before the migration is performed. Allow it to pause at the "reprocess" prompt; do no close it out.
- Perform cross-vCenter migrations.
- Select 'Y' or otherwise allow Migrate-vcTags.ps1 to reprocess any time a cross-vCenter migration completes for a VM.
- Stop Migrate-vcTags.ps1 after all migrations for the session have completed and have been reprocessed.
Copy-vcTags.ps1
#Requires -Modules VMware.VimAutomation.Core
Write-Host 'Loading VCF.PowerCLI...'
$ErrorActionPreference = 'Stop'
function Read-Param {
<#
generic function to grab input from the user, providing for defaults if there's no entry provided
#>
param(
$prompt,
$default,
[switch] $AsSecureString
)
# Get the input
if(-not $AsSecureString) {
$value = Read-Host ("$prompt [$default]" -f $default)
if ($value) { return $value }
else { return $default }
}
else { # Special handling for password entry
$value = Read-Host ("$prompt [$default]" -f $default) -MaskInput
if ($value) { #user entered something
return ConvertTo-SecureString -AsPlainText -Force $value
}
if ($default) { #user didn't enter anything, and default is non-null
return ConvertTo-SecureString -AsPlainText -Force $default
}
return ''
}
}
# Get source vCenter details
$SrcVcsa = Read-Param 'Enter *SOURCE* vCenter IP or FQDN' 'appvcb22.infra.local'
$SrcUsr = Read-Param 'Enter *SOURCE* username' 'kcjkmadm@infra.local'
$SrcPwd = Read-Param 'Enter *SOURCE* password' '' -AsSecureString
if (-not $SrcPwd) { throw 'Password is NULL.' }
$srcCred = New-Object System.Management.Automation.PSCredential($SrcUsr,$SrcPwd)
if (-not $srcCred) { throw 'Source credential is NULL.' }
# Connect to the source vCenter
Write-Host "Connecting to $SrcVcsa..."
$hSrcVcsa = Connect-VIServer -Server $SrcVcsa -Credential $SrcCred
if (-not $hSrcVcsa) { throw 'Cannot connect to source vCenter.' }
# Get destination vCenter details. Supply source input as default
$DstVcsa = Read-Param 'Enter *DESTINATION* vCenter IP or FQDN' 'le-w01-vc01.infra.local'
$DstUsr = Read-Param 'Enter *DESTINATION* username' $SrcUsr
$DstPwd = Read-Param 'Enter *DESTINATION* password' '' -AsSecureString
if (-not $DstPwd) { $DstPwd = $SrcPwd }
$DstCred = New-Object System.Management.Automation.PSCredential($DstUsr,$DstPwd)
if (-not $DstCred) { throw 'Destination credential is NULL.' }
# Connect to the destination vCenter
Write-Host "Connecting to $DstVcsa..."
$hDstVcsa = Connect-VIServer -Server $DstVcsa -Credential $DstCred
if (-not $hDstVcsa) { throw 'Cannot connect to destination vCenter.' }
<#
Loop through the tags on the source.
For each tag, see if it exists on the destination; if it does, continue.
Collect them all into an object and dump them into a CSV for consideration.
#>
$SrcTags = Get-Tag -Server $hSrcVcsa
foreach ($SrcTag in $SrcTags) {
# Check to see if the tag already exists
try {
$DstTag = Get-Tag -Server $hDstVcsa -Category $SrcTag.Category.Name -Name $SrcTag.Name
}
catch {
#Tag doesn't exist, so away we go...
# Note: need a category to exist before a tag can be created
# BUT it's possible that the category already exists even if the tag doesn't
try {
$DstCat = Get-TagCategory -Server $hDstVcsa -Name $SrcTag.Category.Name
}
catch { #Category doesn't exist, so create it as a copy of the source category
try {
$SrcCat = $SrcTag.Category
$DstCat = New-TagCategory -Server $hDstVcsa -Name $SrcCat.Name -Cardinality $SrcCat.Cardinality -Description $SrcCat.Description -EntityType $SrcCat.EntityType
Write-Host ("Creating tag category [{0}]" -f $DstCat.Name)
}
catch {
Write-Host 'Yikes! Couldn''t create the new category'
}
}
# At this point, we have a category but no tag. Let's do that now...
try {
$DstTag = New-Tag -Server $hDstVcsa -Name $SrcTag.Name -Description $SrcTag.Description -Category $DstCat
}
catch {
Write-Host 'Yikes! Couldn''t create the new tag'
}
Write-Host ("Creating tag [{0}] under category [{1}]" -f $DstTag.Name, $DstCat.Name)
}
}