I’ve spent enough hours working with VMware support, API folks, and Tagging team where I just need to share this for everyone. The more voices going to VMware about Tagging and vSphere/vRops performance, the faster a solution will be created.

The following Script is extremely rough. You can Edit the connection strings to be more secure with a $cred = get-credential etc. I made it extremely simple so each step can be validated within logs for performance testing.

The Script will :
1 – Connect to vcenter using VIServer and CISServer methods.
2 – Create ten Tag Categories
3 – Create 500 Random String tags within those tag categories
4 – Create a very simply VM template
5 – Clone that template across your test hosts.
6 – Finally, randomly assign a single tag from each category to every VM.

#######Connect to vCenter
connect-viserver “vcenter” -username “administrator@vsphere.local” -pass “**”
connect-cisserver “vcenter” -username “administrator@vsphere.local” -pass “**”
####### Create some tag categories:
new-TagCategory -name “cat1” -entityType “VMHost”,”VirtualMachine” -confirm:$false
new-TagCategory -name “cat2” -entityType “VMHost”,”VirtualMachine” -confirm:$false
new-TagCategory -name “cat3” -entityType “VMHost”,”VirtualMachine” -confirm:$false
new-TagCategory -name “cat4” -entityType “VMHost”,”VirtualMachine” -confirm:$false
new-TagCategory -name “cat5” -entityType “VMHost”,”VirtualMachine” -confirm:$false
new-TagCategory -name “cat6” -entityType “VMHost”,”VirtualMachine” -confirm:$false
new-TagCategory -name “cat7” -entityType “VMHost”,”VirtualMachine” -confirm:$false
new-TagCategory -name “cat8” -entityType “VMHost”,”VirtualMachine” -confirm:$false
new-TagCategory -name “cat9” -entityType “VMHost”,”VirtualMachine” -confirm:$false
new-TagCategory -name “cat10” -entityType “VMHost”,”VirtualMachine” -confirm:$false
####### Create 500 tags per category — total of 5,000 Tags
$allCateMethod = Get-CisService com.vmware.cis.tagging.category
$allcate = $allcateMethod.list()
$cates = @()
foreach ($cate in $allcate) {
$cates += $allCateMethod.Get($cate)
$cates = $cates | where {$_.name -match “cat”}

$alltagMethod = Get-CisService com.vmware.cis.tagging.tag
foreach($a in $cates){
$x = 0

while($x -lt 500){
$spec = $alltagMethod.Help.create.create_spec
$spec.name = (-join ((65..90) + (97..122) | Get-Random -Count 30 | % {[char]$_}))
$spec.description = “”
$spec.category_id = $a.id.value
#######Create your VM Template: – Basic VM then delete the harddisk to reduce need for Datastore space.
$ahost = get-vmhost “vmhost1”
$adatastore = $ahost | get-datastore | sort freespaceGB -desc | select -f 1

new-vm -name “zTagTesterTemplate” -vmhost $ahost -datastore $adatastore -memoryGB “0.004” -confirm:$false -runasync
$avm = get-vm “zTagTesterTemplate”
$avm | get-harddisk| remove-harddisk -DeletePermanently -confirm:$false
$avm | set-vm -totemplate -confirm:$false
$thetemplate = get-template -name “zTagTesterTemplate”

#######Create a bunch of VMs… Thread this to as many hosts as you want… Going Serial here because I dont know your lab…
####### uncomment the new-vm command below to select the best for your lab…
####### I suggest using four to six ESXi hosts because of vSphere config limitation of VMs per host…
####### edit the MaxCount to match your host count… four hosts – 4,000 VMs… six hosts, 6,000 VMs…
####### 1000 is configured below if you are testing with one host….
####### I tested with 5,600 VMs to exaggerate the impact/results within vRops. “Bigger impact on the charts”
####### Only used if you want to create more than 1000 VMs and want to thread them across more hosts.
#######$hostlist = get-cluster | get-vmhost
####### Vars taken from above… edit if you want to change it here….
#######$ahost = get-vmhost “vmhost1”
#######$adatastore = $ahost | get-datastore | sort freespaceGB -desc | select -f 1
#######$thetemplate = get-template -name “zTagTesterTemplate”

$count = 0
$maxcount = 1000
$vmnamePrefix = “zTagTester”
while ($count -lt $maxcount){
$vmname = $vmnamePrefix + $count
#######New-vm -Name $vmname -VMhost ($hostlist | get-random) -Template $thetemplate -Datastore (get-datastore | sort freespaceGB -desc | select -f 1) -runAsync
New-vm -Name $vmname -VMhost $ahost -Template $thetemplate -Datastore $adatastore -runAsync
$count = $count + 1

#######Assign Tags to your VMs… I suggest looking at vRops at this time and watch as these commands run.
####### I’m populating the tag vars first because the tagging API could stop responding later for a slower get and set call.

$tag1 = get-tagcategory | where {$_.name -eq “cat1”} | get-tag
$tag2 = get-tagcategory | where {$_.name -eq “cat2”} | get-tag
$tag3 = get-tagcategory | where {$_.name -eq “cat3”} | get-tag
$tag4 = get-tagcategory | where {$_.name -eq “cat4”} | get-tag
$tag5 = get-tagcategory | where {$_.name -eq “cat5”} | get-tag
$tag6 = get-tagcategory | where {$_.name -eq “cat6”} | get-tag
$tag7 = get-tagcategory | where {$_.name -eq “cat7”} | get-tag
$tag8 = get-tagcategory | where {$_.name -eq “cat8”} | get-tag
$tag9 = get-tagcategory | where {$_.name -eq “cat9”} | get-tag
$tag10 = get-tagcategory | where {$_.name -eq “cat10”} | get-tag

$allvms = get-vm | where {$_.name -match “ztagtester”}

####### using counter to show where you are in the assignment.
####### I Thread this into different powershell windows because the API is a bit slow using New-tagassignment. (could try cisserver assignment methods in your lab)
$count = 0
foreach($avm in $allvms){
$avm | new-tagassignment -tag ($tag1|get-random)
$avm | new-tagassignment -tag ($tag2|get-random)
$avm | new-tagassignment -tag ($tag3|get-random)
$avm | new-tagassignment -tag ($tag4|get-random)
$avm | new-tagassignment -tag ($tag5|get-random)
$avm | new-tagassignment -tag ($tag6|get-random)
$avm | new-tagassignment -tag ($tag7|get-random)
$avm | new-tagassignment -tag ($tag8|get-random)
$avm | new-tagassignment -tag ($tag9|get-random)
$avm | new-tagassignment -tag ($tag10|get-random)

####### Would be nice if new-tagassignment accepted an array of tags…
####### get a single VM and check if it has ten tags assigned….
get-vm ($allvms | select -f 1) | get-tagassignment

#######END – Check vrops collection cycle.

For vSphere tagging, most PowerCLI users go for the “Get-Tag”,”Get-TagCategory”, or “Get-TagAssignment” methods.
Depending on the size of your vCenter, inventory count, but more importantly, Tag count, the CisService methods may provide better performance for vSphere Tagging.

To access these APIs, you will need to log in to vcenter with the following string.  (Adjust vcenter/user/pass as needed)
connect-cisserver “vcenter.pcli.me” -username “administrator@vsphere.local” -pass “mypassword”

To start, each method has documentation.  I will say, it’s not extremely easy to read, but its better than leaving us stranded.
I will show you how to pull the command documentation for Tag Categories and you should be able to translate this for other methods.
Pasting the following two lines will show you the “Operations” you can complete within the “tagging.category.”
$allCateMethod = Get-CisService com.vmware.cis.tagging.category

From here, you can expand the help.  Lets say you want to create a new Tag Category.  You add on “create” to the help command and it will show you what you need to complete the command.

Below, I will post the most common methods I use.

# Create a new Tagging Category: (I included all possible options for the specs after the comment hash)
$allCateMethod = Get-CisService com.vmware.cis.tagging.category
$spec = $allCateMethod.Help.create.create_spec
$spec.name = “myFirstCategory”
$spec.description = “”
$spec.cardinality = “MULTIPLE” # “SINGLE” # yes it needs to be all caps….
$spec.associable_types = “virtualmachine”,”vmhost” # Cluster, Datacenter, Datastore, DatastoreCluster, DistributedPortGroup, DistributedSwitch, Folder, ResourcePool, VApp, VirtualPortGroup

# Get all Tag Categories:
$allCateMethod = Get-CisService com.vmware.cis.tagging.category
$allcate = $allcateMethod.list()
$cates = @()
foreach ($cate in $allcate) {
$cates += $allCateMethod.Get($cate)

# Create a new Tag:  (a Tag needs a category so be sure to create one first)
$alltagMethod = Get-CisService com.vmware.cis.tagging.tag
$spec = $alltagMethod.Help.create.create_spec
$spec.name = “myFirstTag”
$spec.description = “”
$spec.category_id = $cates | where {$_.name -eq “myFirstCategory”} | %{$_.id.value}

# Get all Tags:
$alltagMethod = Get-CisService com.vmware.cis.tagging.tag
$alltag = $alltagMethod.list()
$tags = @()
foreach ($tag in $alltag) {
$tags += $alltagMethod.Get($tag)

# From here, you can now Attach, Detach, or Query tags to objects.   To access those objects (like a Virtual Machine) you will need to connect to vcenter with our VIServer connection.
connect-viserver “vcenter.pcli.me” -username “administrator@vsphere.local” -pass “mypassword”

Now we can pull in object IDs and match them with Tag IDs.
# Attach a Tag to a VirtualMachine:
$mytag = $tags | where {$_.name -eq “myFirstTag”}
$vm = Get-VM -Name “myFirstVM”
$tagAssign = Get-CisService -Name com.vmware.cis.tagging.tag_association
$spec = $tagAssign.Help.attach.object_id.Create()
$spec.type = $vm.ExtensionData.MoRef.Type
$spec.id = $vm.ExtensionData.MoRef.Value
$tagAssign.attach($mytag.id.Value, $spec)

# Then to check the Association:
$tagAsso = Get-CisService -Name com.vmware.cis.tagging.tag_association

If you dig deep into the documentation of these methods, you will find ways to Add, Delete, Create, and even Batch assign tags.  I gave you the starting point here.

One extra for the road:
# Quick method to delete a Tag Category:   (Quick note…. Deleting a category WILL delete all of the tags under it and the API call will not prompt you about it.)
$mycateID = $cates |where {$_.name -eq “myFirstCategory”} |%{$_.id.value}
$allCateMethod = Get-CisService com.vmware.cis.tagging.category

Let me know if you have any questions @vmnick0 on twitter.

The folks on the VMware API team along with some Community Support ( <a href=”https://twitter.com/butch7903″ target=”_blank”>@butch7903</a> – Russell Hamker)
have created an amazing backup script for your vCenter Appliance.  Give it a once over.

Some quick notes:
– Only works on vSphere 6.5 and higher.
– It requires PowerCLI version 6.5.3 or greater.
– It needs the Powershell PSFTP or WinSCP Powershell module if you want to use FTP or WinSCP to copy the backup from vCenter to a storage location.
* (Optional) – I deployed a Photon OS VM and I use that as my SCP target to save my backups.

If you want to save all of the Command Line GUI in the 772 Line file, all you need are these lines to complete the backup.
Import-Module -Name VMware.VimAutomation.core
Import-Module -Name WinSCP
connect-cisserver “vcenter01” -username “administrator@vsphere.local” -pass “myPass”
$BackupAPI = Get-CisService com.vmware.appliance.recovery.backup.job
$CreateSpec = $BackupAPI.Help.create.piece.Create()
$CreateSpec.parts = @(“common”,”seat”)
$CreateSpec.backup_password = “”
$CreateSpec.location_type = “SCP”
$CreateSpec.location = “”
$CreateSpec.location_user = “root” #username of your SCP location
$CreateSpec.location_password = New-Object –TypeName System.Management.Automation.PSCredential –ArgumentList “backup location username”, (ConvertTo-SecureString –String “backup location password” –AsPlainText -Force)
$CreateSpec.comment = $Comment
$BackupJob = $BackupAPI.create($CreateSpec)

Then you can use the following command to check on its status:
$BackupJob | select id, progress, state

Here is a quick one-liner that will list your VM names and their Environment Tag.
– The assumption here is you use vCenter Tags, you created a tag category for Environment, created some tag types (like Production, Dev, etc) under that category, and assigned those Tags to your VMs.

$Thecategory = “Environment”
$thecluster = “TheCoolKidsCluster”
get-cluster $thecluster | Get-VM | Select Name,@{N=”Tag”;E={((Get-TagAssignment -category $Thecategory -Entity $_ | select -ExpandProperty Tag).Name -join “,”)}}

#The output can be sent to csv with  “|export-csv c:\temp\export.csv”
#The output looks like this:
VM1 Production
VM2 Development
VM3 Production…..
… etc

# Remember – you can change the get-cluster part to get-folder, get-datastore, get-datastorecluster, even a get-VDPortgroup!



Quick One-liner to look for any VM in your entire vcenter that has the VMware tools installer mounted.

get-view -viewtype virtualmachine -property ‘name’ -Filter @{‘Runtime.ToolsInstallerMounted’=’True’}


#Here is the same command but with the additional text to unmount the installer if any are found.

get-view -viewtype virtualmachine -property ‘name’ -Filter @{‘Runtime.ToolsInstallerMounted’=’True’} | foreach{$_.UnmountToolsInstaller()}




Here is a quick line of code you can throw in to your existing scripts if you want to do some validation based on the type of vcenter.
As more companies are moving to the vCenter appliance, MS Windows issues go away.   Until that time, it’s good to alert on disk space and CPU usage for the Windows vCenter.

The quick one-liner to check the vCenter OS:
if(($global:DefaultVIServer | %{$_.extensiondata.content.about.ostype}) -match “win”){ $windows}else{$Linux}

Here is a Windows vCenter “CPU and Disk space” check script:

$vcenter = “vcenter.pcli.me”
$localWindowsAccountUser = “user001”
$localWindowsAccountPass = “test”
## if you want to use a domain account then edit the “$pcstring” var below

Connect-VIServer $vcenter

if(($global:DefaultVIServer | %{$_.extensiondata.content.about.ostype}) -match “win”){
$pcstring = $vcenter+”\”+$localWindowsAccountUser
$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $pcstring,(ConvertTo-SecureString -String $localWindowsAccountPass -AsPlainText -Force)
$temp = get-WmiObject win32_logicaldisk -Computername $vcenter -credential $Credential
foreach($b in ($temp | where {$_.drivetype -eq “3”})){
$reportrow = “” | select VMname, DriveLetter, VolumeName,CapacityGB, FreespaceGB,percentFree
$reportrow.vmname = $vcenter
$reportrow.driveletter = $b.DeviceID
$reportrow.VolumeName = $b.VolumeName
$reportrow.capacityGB = “{0:N3}” -f ($b.Size / 1073741824)
$reportrow.freespaceGB = “{0:N3}” -f ($b.FreeSpace / 1073741824)
$reportrow.percentfree = [System.Math]::floor(($b.FreeSpace / $b.Size)*100)
$report += $reportrow
$cpucheck = get-WmiObject win32_processor  -Computername $vcenter -credential $Credential | Measure-Object -property LoadPercentage -Average | Select Average
if(([int]($cpucheck.average)) -ge 90){
$smtpServer = “smtp.pcli.me”
$msg = new-object Net.Mail.MailMessage
$smtp = new-object Net.Mail.SmtpClient($smtpServer)
$msg.From = “Script@pcli.me”
$sub = “vCenter HIGH CPU usage found for “+$vcenter
$msg.subject = $sub
$msg.Body = “”
Disconnect-VIServer -Server $global:DefaultVIServers -Force -confirm:$False

This Script will clone an existing vDS and its port groups.  **with new UUIDs**
This is great if you get a corrupted vDS and need to migrate away from it.
Or, if you want to recreate a new vDS in the image of an old one without using the export/import method.

** quick notes:
– This creates a new distributed switch and port groups with new IDs.  This isn’t good to use as an Export/Import when migrating hosts and VMs to a new vCenter.
– This script does not keep special Security Policies but its very easy to add it to the script. I left all of the vars complete so you can pull and assign the extension data for additional needs.
– This code pulls an old vDS and its port groups, then recreates a new one using a “-2” at the end of the names.  At the very end of the script, it then removes the “-2” from the names.   Keep this in mind if you have “-2” in the name of your vDS or port groups.  If you do use this “-2” string in your existing names, change it in the script to something like “QQQQ” so you can remove the trailing text at the end without impacting your existing names.

## Set your old vDS name and paste the rest.

Add-PSSnapin VMware.*
Connect-VIServer vcenter.pcli.me
$oldVDSName = “dvs-MyvDS”

#Grab the old vDS info
$oldvDS = get-VDSwitch $oldVDSName

#Create the new one and rename its uplink
$newvDS = new-vdswitch -name ($oldvDS.name+”-2″) -location $oldvDS.datacenter.name -NumUplinkPorts $oldvDS.NumUplinkPorts -mtu $oldvDS.Mtu
$oldUpLink = $oldvDS | get-vdportgroup | where {$_.IsUplink -eq “True”}
$newvDS | get-vdportgroup | where {$_.IsUplink -eq “True”}  | set-vdportgroup -name ($oldUpLink.name+”-2″) -vlantrunkRange $oldUpLink.VlanConfiguration

#Pull the old vDS port groups and recreate them on the new vDS
$Portgroups = $oldvDS | get-vdportgroup | ?{$_.IsUplink -ne “True”}
foreach($port in $portgroups){
$Pname = $port.name + “-2”
$PVlanid = $port.VlanConfiguration.vlanid
$pNumPorts = $port.numports
$newvDS | New-VDPortgroup -Name $Pname -Vlanid $PVlanid -numports $pNumPorts
$newvDS | New-VDPortgroup -Name $Pname -numports $pNumPorts

#Here you can migrate ESXi Host links and VMs from your old vDS to your new vDS.
#If you want to migrate to the new vDS, you can get some code help here : http://www.pcli.me/?p=64
#If you copied the vDS to the same vCenter, keep in mind that vDS names and vDS Port group names are vcenter unique.
#You will need to delete your old vDS or rename it to remove the “-2” from the new one.

#Remove the old vDS if you no longer need it – Do this Manual so you can verify that everything has migrated…
#$oldvDS | remove-vdswitch -confirm:$False

#rename the new vDS and its port groups to match the orginal.
$newvDS | set-vdSwitch -name ($newvDS.name -replace “-2″,””)
$Portgroups = $newvDS | get-vdportgroup

foreach($port in $portgroups){
$port | set-vdportgroup -name ($port.name -replace “-2″,””)


#Here is some extra code to set the Teaming Load Balancing policy for all port groups on your new vDS. This is configured to set all port groups to “Route based on Physical NIC load”
#Change the port group load policy to Load based
$newvDSPortgroups = get-VDSwitch $newVDS | get-vdportgroup
Function Set-VDPortGroupTeamingPolicy {
param (

Process {
$spec = New-Object VMware.Vim.DVPortgroupConfigSpec
$spec.configVersion = $VDPortgroup.ExtensionData.Config.ConfigVersion
$spec.defaultPortConfig = New-Object VMware.Vim.VMwareDVSPortSetting
$spec.defaultPortConfig.uplinkTeamingPolicy = New-Object VMware.Vim.VmwareUplinkPortTeamingPolicy
$spec.defaultPortConfig.uplinkTeamingPolicy.inherited = $false
$spec.defaultPortConfig.uplinkTeamingPolicy.policy = New-Object VMware.Vim.StringPolicy
$spec.defaultPortConfig.uplinkTeamingPolicy.policy.inherited = $false
$spec.defaultPortConfig.uplinkTeamingPolicy.policy.value = “loadbalance_loadbased”

foreach($port in $newvDSPortgroups){
$port| Set-VDPortgroupTeamingPolicy


#Upgrading a cluster from 5.1 to 5.5 or even updating past the Heart Bleed and need a quick double check that all of the host got the new build?
#I throw this at the entire vcenter but you can narrow it down to per datacenter or cluster if you like…

### Here is the quick (cluster only) code…

$Report = @()
get-cluster myCluster | get-vmhost | %{
$vmhost = Get-View $_.ID
$ReportRow = “” | Select-Object Hostname, build
$ReportRow.Hostname = $vmhost.Name
$ReportRow.Build = $vmhost.summary.config.product.build
$Report += $ReportRow
$report | FT


### If you want to be more fancy, here is a multi vCenter scan… (check ALL the hosts!)

Add-PSSnapin VMware.VimAutomation.Core

$vcenters = @()
$vcenters += “vcenter1”
$vcenters += “vcenter2”
$vcenters += “vcenter3”

$Report = @()
foreach($vcenter in $vcenters){
Connect-VIServer $vcenter

$clusters = get-cluster
foreach($a in $clusters){
$a | get-vmhost | %{
$vmhost = Get-View $_.ID
$ReportRow = “” | Select-Object vcenter,cluster,Hostname, build
$ReportRow.vcenter = $vcenter
$ReportRow.cluster = $a.name
$ReportRow.Hostname = $vmhost.Name
$ReportRow.Build = $vmhost.summary.config.product.build
$Report += $ReportRow
Disconnect-viserver -confirm:$False
$report | FT

#Looks like playing with vFlash can cause some vCenter Webclient issues….
#KB = http://kb.vmware.com/kb/2072392

#If you want to “remove” all vflash from your hosts so you can get things working again, you can use PowerCLI.
#1 – Download the “Extensions” for vflash (and vsan) here
– – https://labs.vmware.com/flings/powercli-extensions
#2 – Place that folder into your powershell > Modules directory.
– – The most common places is : C:\Windows\System32\WindowsPowerShell\v1.0\Modules
#3 – Import the Extensions : Import-Module VMware.VimAutomation.Extensions
#4 – Verify that the Extensions loaded : get-command -Module VMware.VimAutomation.Extensions
– – Output should show ten new command-lets
#5 – Review your current vFlash config :
– – $hosts = get-cluster pcli.me | Get-VMHost
– – Get-VMHostVFlashConfiguration -VMHost $hosts
– – Output will look close to this:
– – – Name CapacityGB SwapCacheReservationGB Extents
– – – esx1.pcli.me 731 0 {eui.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX:P1}
– – – esx2.pcli.me 731 0 {eui.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX:P1}

#6 – Remove the config with this command (you can make it smaller but this command worked best for me)
– – get-vmhost esx1.pcli.me| get-VMHostVFlashConfiguration | Set-VMHostVFlashConfiguration -RemoveVFlashResource
– – get-vmhost esx2.pcli.me| get-VMHostVFlashConfiguration | Set-VMHostVFlashConfiguration -RemoveVFlashResource

#7 – Then to verify that things are removed, throw another Get-VMHostVFlashConfiguration -VMHost $hosts