The other day I was looking at my virtual machines’ VMware Tools status, and sadly, many of them are out of date. It seems like I spend a lot of my time on host patching, but the Tools update seems to fall by the wayside. Perhaps it’s the disruption to production that rebooting causes (and the related change records and change management meetings), or maybe it’s the sheer number of VMs that I have to manage. I was curious what other folks’ infrastructure looked like, so I posed the question on Twitter: “#VMware admins, how good are you at keeping the Tools up to date?”
I only got a handful of responses, but they varied from “fanatical”, to “so so”. Tim Oudin said that he keeps his virtual machines up to date by setting “Check and upgrade Tools during power cycling” under a VM’s Options > VMware Tools. That got me thinking, “How can I enable that setting on hundreds of VMs?”
The answer is (of course) PowerCLI. Since there’s no cmdlet that will do this, we have to utilize our good friend, the vSphere API. The data object that we’re interested in is VirtualMachineConfigSpec. While doing the necessary research for this article, I ran into a very interesting property, changeVersion. This property is useful because it guards against updates that have happened between when the VM’s configInfo is read and when it is applied. We don’t need to increment or change the value of the changeVersion property, we just need to supply the current changeVersion.
Enabling VMware Tools upgrade at power cycle:
[powershell]
$vm = Get-VM -Name "virtualmachine"
$spec = New-Object VMware.Vim.VirtualMachineConfigSpec
$spec.changeVersion = $vm.ExtensionData.Config.ChangeVersion
$spec.tools = New-Object VMware.Vim.ToolsConfigInfo
$spec.tools.toolsUpgradePolicy = "upgradeAtPowerCycle"
$_this = Get-View -Id $vm.Id
$_this.ReconfigVM_Task($spec)
[/powershell]
Enabling a VM to sync its time with the host:
[powershell]
$vm = Get-VM -Name "virtualmachine"
$spec = New-Object VMware.Vim.VirtualMachineConfigSpec
$spec.changeVersion = $vm.ExtensionData.Config.ChangeVersion
$spec.tools = New-Object VMware.Vim.ToolsConfigInfo
$spec.tools.syncTimeWithHost = $true
$_this = Get-View -Id $vm.Id
$_this.ReconfigVM_Task($spec)
[/powershell]
Here are the snippets you’ll need in case you want to undo these settings.
Disabling VMware Tools upgrade:
[powershell]
$spec.tools.toolsUpgradePolicy = "manual"
[/powershell]
Disabling VM time sync:
[powershell]
$spec.tools.syncTimeWithHost = $false
[/powershell]
Andrew Fidel says
Just thought I’d add my extension here in case other people come looking, this does machines in a specific cluster and only sets the flag if it’s a Windows box (according to the documentation it only works for Windows guests anyways but I figure better safe than sorry)
$machines = get-cluster “” | get-vm
foreach ($vm in $machines){
if ( $vm.guest.OSFullName -like “*Windows*)
{
$spec = New-Object VMware.Vim.VirtualMachineConfigSpec
$spec.changeVersion = $vm.ExtensionData.Config.ChangeVersion
$spec.tools = New-Object VMware.Vim.ToolsConfigInfo
$spec.tools.toolsUpgradePolicy = “upgradeAtPowerCycle”
$_this = Get-View -Id $vm.Id
$_this.ReconfigVM_Task($spec)
}
}
Chris B says
Does anyone have any suggestions on outputting this to a text file? We’d like to be able to review and if any fail, then be able to identify those machines.
Chris B says
Forgot to include, I tried enabling transcription, but it only echos the task number.
Optimally, we’d like to see each machine by name with a complete or fail.
Shail says
Does this require a reboot of the VM to disable vmware time sync?
Forbes Guthrie says
Another twist to this. I had a long text file of VM names that I needed to disable the VMware tools upgrade, so I did this:
$a = Get-Content "c:\Temp\vm_list.txt"
foreach ($i in $a){
$vm = Get-VM -Name $i | % {Get-View $_.ID}
$vmConfigSpec = New-Object VMware.Vim.VirtualMachineConfigSpec
$vmConfigSpec.Tools = New-Object VMware.Vim.ToolsConfigInfo
$vmConfigSpec.Tools.ToolsUpgradePolicy = "manual"
$vm.ReconfigVM($vmConfigSpec)
}
Damian Karlson says
Cool, thanks Forbes!
Rob Collins says
Looks like a great script! But I get the following error run I run it in vSphere PowerCLI. Any idea what I’m doing wrong? I’ve tried it both with and without Forbes’ extra line.
Get-View : Cannot validate argument on parameter ‘Id’. The argument is null or
empty. Supply an argument that is not null or empty and then try the command ag
ain.
At C:\scripts\Enable VMTools Upgrade at Power Cycle.ps1:8 char:22
+ $_this = Get-View -Id <<<< $vm.Id
+ CategoryInfo : InvalidData: (:) [Get-View], ParameterBindingVal
idationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,VMware.VimAutom
ation.Commands.DotNetInterop.GetVIView
You cannot call a method on a null-valued expression.
At C:\scripts\Enable VMTools Upgrade at Power Cycle.ps1:9 char:23
+ $_this.ReconfigVM_Task <<<< ($spec)
+ CategoryInfo : InvalidOperation: (ReconfigVM_Task:String) [], R
untimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
Rob Collins says
Ignore me. On Line 1 I used $v instead of $vm. I did this because I was trying to work out how to accomodate Forbes’ extra line. Do I just stick it in on Line 2? It’s the $v that confuses me, it’s not defined anywhere!
Rob Collins says
Argh! Now the script looks like it will work, but I’m getting the message “Cannot complete operation due to concurrent modification by another operation” listed in vSphere Client. Any ideas?
Damian Karlson says
Hi, Rob, thanks for commenting! Would you mind pasting the entire PowerCLI script, so I can take a look?
Lewis says
I had this same issue, turned out that someone else was doing modifications at the same time 🙁
Ted says
Damian, forgive my ignorance. I have no experience with using the PowerCLI. Is there a simple way to create and run the scripts above for us “non-programmer” types? 🙂
Damian Karlson says
Thanks for commenting, Ted. Yes, there are more accessible, or more user-friendly tools out there. You might want to check out PowerGui and the VMware Community PowerPack (available from the same site). The Community PowerPack uses PowerCLI to easily accomplish common administrative processes in VMware. Of course, you’ll still need to download and install VMware PowerCLI on the same computer that you’re going to install PowerGui & and the PowerPack.
PowerGui is a great place to start learning PowerShell’s syntax, and getting familiarized with PowerCLI. That’s probably the best resource that I can think of. I hope I’ve answered your question, if not please let me know. 🙂
Travis says
This may be helpful… I used the above…and added a check to see if the flag was set on .config.tools.toolsUpgradePolicy… Worked for me!
Get-VM | Get-View | ForEach-Object{
Write-Output $_.name
if ($_.config.tools.toolsUpgradePolicy -ne “upgradeAtPowerCycle”){
$vm = Get-VM -Name $_.name
$spec = New-Object VMware.Vim.VirtualMachineConfigSpec
$spec.changeVersion = $vm.ExtensionData.Config.ChangeVersion
$spec.tools = New-Object VMware.Vim.ToolsConfigInfo
$spec.tools.toolsUpgradePolicy = “upgradeAtPowerCycle”
$_this = Get-View -Id $vm.Id
$_this.ReconfigVM_Task($spec)
Write-Output “Completed”
}
}
Damian Karlson says
Thanks, Travis!
Forbes Guthrie says
I ran something similar to your first script a couple of weeks ago across an vSphere instance. I also added in the following line:
$vm = $v | Get-View | Where-Object {$_.Guest.GuestFamily -eq ‘windowsGuest’}
to make sure the Linux/Solaris VMs were not changed.
Damian Karlson says
Great idea, Forbes! Thanks for commenting. 🙂
Doug Crist says
Another Powershell newbie here. Forbes, any chance I could see your entire script, or at least the first few lines? With the ‘Where’ clause, do I then still need the ForEach after it?
Thanks,
Doug
Forbes Guthrie says
Hi Doug,
This is what I did:
Foreach ($v in (get-vm)) {
$vm = $v | Get-View | Where-Object {$_.Guest.GuestFamily -eq 'windowsGuest'}
$vmConfigSpec = New-Object VMware.Vim.VirtualMachineConfigSpec
$vmConfigSpec.Tools = New-Object VMware.Vim.ToolsConfigInfo
$vmConfigSpec.Tools.ToolsUpgradePolicy = "UpgradeAtPowerCycle"
$vm.ReconfigVM($vmConfigSpec)
}
Alex says
Hi all, I’m trying to implement this however first specifying a list of machines that need to be changed,
Using the following
$a = Get-Content “c:\vmlist.csv”
foreach ($i in $a){
$vm = Get-VM -Name $i | % {Get-View $_.ID}
$vmConfigSpec = New-Object VMware.Vim.VirtualMachineConfigSpec
$vmConfigSpec.Tools = New-Object VMware.Vim.ToolsConfigInfo
$vmConfigSpec.Tools.ToolsUpgradePolicy = “upgradeAtPowerCycle”
$vm.ReconfigVM($vmConfigSpec)
Write-Output “Completed”
}
This is fine and changes the majority of the machines however im getting 25% of the machines that cant be found and then get the following error, any ideas?
Get-VM : 18/02/2013 16:16:11 Get-VM VM with name ‘Name’ was not found, using the specified filter(s).
At line:3 char:13
+ $vm = Get-VM <<<< -Name $i | % {Get-View $_.ID}
+ CategoryInfo : ObjectNotFound: (:) [Get-VM], VimException
+ FullyQualifiedErrorId : Core_OutputHelper_WriteNotFoundError,VMware.VimAutomation.ViCore.Cmdlets.Commands.GetVM
You cannot call a method on a null-valued expression.
At line:7 char:15
+ $vm.ReconfigVM <<<< ($vmConfigSpec)
+ CategoryInfo : InvalidOperation: (ReconfigVM:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
Get-VM : 18/02/2013 16:16:28 Get-VM VM with name 'VMNAME**(Changed) ' was not found, using the specified filter(s).
At line:3 char:13
+ $vm = Get-VM <<<< -Name $i | % {Get-View $_.ID}
+ CategoryInfo : ObjectNotFound: (:) [Get-VM], VimException
+ FullyQualifiedErrorId : Core_OutputHelper_WriteNotFoundError,VMware.VimAutomation.ViCore.Cmdlets.Commands.GetVM
You cannot call a method on a null-valued expression.
At line:7 char:15
+ $vm.ReconfigVM <<<< ($vmConfigSpec)
+ CategoryInfo : InvalidOperation: (ReconfigVM:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
Get-VM : 18/02/2013 16:16:28 Get-VM VM with name 'VMNAME**(Changed) ' was not found, using the specified filter(s).
At line:3 char:13
+ $vm = Get-VM <<<< -Name $i | % {Get-View $_.ID}
+ CategoryInfo : ObjectNotFound: (:) [Get-VM], VimException
+ FullyQualifiedErrorId : Core_OutputHelper_WriteNotFoundError,VMware.VimAutomation.ViCore.Cmdlets.Commands.GetVM
You cannot call a method on a null-valued expression.
At line:7 char:15
+ $vm.ReconfigVM <<<< ($vmConfigSpec)
+ CategoryInfo : InvalidOperation: (ReconfigVM:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
Get-VM : 18/02/2013 16:16:28 Get-VM VM with name 'VMNAME**(Changed) ' was not found, using the specified filter(s).
At line:3 char:13
+ $vm = Get-VM <<<< -Name $i | % {Get-View $_.ID}
+ CategoryInfo : ObjectNotFound: (:) [Get-VM], VimException
+ FullyQualifiedErrorId : Core_OutputHelper_WriteNotFoundError,VMware.VimAutomation.ViCore.Cmdlets.Commands.GetVM
You cannot call a method on a null-valued expression.
At line:7 char:15
+ $vm.ReconfigVM <<<< ($vmConfigSpec)
+ CategoryInfo : InvalidOperation: (ReconfigVM:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
Get-VM : 18/02/2013 16:16:29 Get-VM VM with name 'VMNAME**(Changed) ' was not found, using the specified filter(s).
At line:3 char:13
+ $vm = Get-VM <<<< -Name $i | % {Get-View $_.ID}
+ CategoryInfo : ObjectNotFound: (:) [Get-VM], VimException
+ FullyQualifiedErrorId : Core_OutputHelper_WriteNotFoundError,VMware.VimAutomation.ViCore.Cmdlets.Commands.GetVM
You cannot call a method on a null-valued expression.
At line:7 char:15
+ $vm.ReconfigVM <<<< ($vmConfigSpec)
+ CategoryInfo : InvalidOperation: (ReconfigVM:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
Get-VM : 18/02/2013 16:16:52 Get-VM VM with name 'VMNAME**(Changedforsecurityreasons) ' was not found, using the specified filter(s).
At line:3 char:13
+ $vm = Get-VM <<<< -Name $i | % {Get-View $_.ID}
+ CategoryInfo : ObjectNotFound: (:) [Get-VM], VimException
+ FullyQualifiedErrorId : Core_OutputHelper_WriteNotFoundError,VMware.VimAutomation.ViCore.Cmdlets.Commands.GetVM
You cannot call a method on a null-valued expression.
At line:7 char:15
+ $vm.ReconfigVM <<<< ($vmConfigSpec)
+ CategoryInfo : InvalidOperation: (ReconfigVM:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
Thanks in advance
Alex
Alex says
Cancel my last, for some reason there were spaces at the end of the vm names once these were removed the script ran fine,
Cheers
Alex
Damian Karlson says
Alex, glad you got it sorted. I was also going to suggest using PowerShell’s Import-CSV. http://technet.microsoft.com/en-us/library/ee176874.aspx