I'm trying to get write a script that will allow someone to select a VM folder and then it will scan the VM's in that folder to see if they need an upgrade or not. Then a window would come up with a list of the VM's that need to be upgraded. The user could then select the ones to upgrade and tools would be upgraded (w/o reboot). I've tried a few different things, even with a csv file, but it seems whenever I kind of get it to work it only takes one VM and upgrades it. I'm looking at the csv file and I see multiple machines in there. Oh I'm also trying to ignore powered off machines. Here's what I have for my code:
$vms = Get-Folder $myfolder | Get-VM
foreach ($vm in $vms) {
if ($vm.ExtensionData.Guest.ToolsVersionStatus2 -eq "guestToolsSupportedOld") {
$selectedvms = Get-VM -Name $vm.Name
$selectedvms | select Name | Export-CSV -Path .\toolsneedupgrade.csv -NoTypeInformation -UseCulture -Append
Write-Host "VM-->",$vm.Name, "needs to have tools upgraded"
}
}
Import-Csv -Path .\toolsneedupgrade.csv -UseCulture | Foreach-Object -Process { Get-VM -Name $_.Name
#Get-VM -Name $vm | #Out-GridView -Title 'Select the VMs you wish to upgrade tools on' -OutPutMode Multiple
}
foreach ($vm in $selectedvms) {
if ($vm.Guest.State -eq "Running")
{
Write-Host "VM -->",$vm.Name, "is powered on so tools will be updated"
Get-VM -Name $vm.Name | Update-Tools -NoReboot
}
elseif ($vm.Powerstate -eq "PoweredOff")
{
Write-Host "VM-->",$vm.Name, "is powered off, so ignoring"
}
And when I run it everything seems ok until I select the folder. It then outputs this:
VM--> VM1 needs to have tools upgraded
VM--> VM2 needs to have tools upgraded
Name PowerState Num CPUs MemoryGB
---- ---------- -------- --------
VM1 PoweredOff 4 24.000
VM2 PoweredOn 2 8.000
VM3 PoweredOn 2 8.000
VM1 PoweredOff 4 24.000
VM2 PoweredOn 2 8.000
VM --> VM2 is powered on so tools will be updated
Update-Tools : 12/6/2018 3:40:27 PM Update-Tools Operation is not valid due to the current state of the object.
At C:\Users\emcclure\Desktop\GenScripts\GenUpgradeVMTools.ps1:67 char:25
+ Get-VM -Name $vm.Name | Update-Tools -NoReboot
+ ~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Update-Tools], VimException
+ FullyQualifiedErrorId : Core_BaseCmdlet_UnknownError,VMware.VimAutomation.ViCore.Cmdlets.Commands.UpdateVmTools
It does wind up updating the VM tools on the machine, but doesn't get to the part where it sees the VM is powered off and moves on in the script. I'm sure it's something simple I'm missing, but I'm not sure what.
Ok, let's see how this version fulfills your requirements.
$vcName = Read-Host -Prompt 'Enter the vCenter name'
Connect-VIServer -Server $vcName
while($true){
$endAnswer = 'D'
while($endAnswer -ne 'Q'){
if($endAnswer -eq 'D'){
$dc = Get-Datacenter | Select -ExpandProperty Name
if($dc.count -gt 1){
$dc = $dc | Out-GridView -OutputMode Single -Title 'Select one datacenter'
}
$endAnswer = 'F'
}
if($endAnswer -eq 'F'){
$folder = Get-Folder -Location $dc -Type VM | Select -ExpandProperty Name
if($folder.count -gt 1){
$folder = $folder | Out-GridView -OutputMode Multiple -Title 'Select one or more folders'
}
$endAnswer = 'V'
}
if($endAnswer -eq 'V'){
$vms = Get-VM -Location $folder |
where {$_.ExtensionData.Guest.ToolsVersionStatus2 -eq "guestToolsSupportedOld"} |
Select -ExpandProperty Name
if($vms.Count -eq 0){
Write-Host "No VMs found that require a VMware Tools update"
break
}
if($vms.count -gt 1){
$vms = $vms | Out-GridView -OutputMode Multiple -Title 'Select one or more folders'
}
}
if($vms.count -gt 0){
foreach($vm in Get-VM -Name $vms){
if($vm.PowerState -eq 'PoweredOn'){
if($vm.Guest.State -eq 'Running'){
Write-Host "VMware Tools on VM $($vm.Name) will be updated"
Get-VM -Name $vm.Name | Update-Tools -NoReboot
}
else{
Write-Host "VMware Tools are not running on VM $($vm.Name)"
}
}
else{
Write-Host "VM $($vm.Name) is not powered on"
}
}
}
else{
Write-Host "No VMs selected"
}
write-host "Please select an option"
Write-Host "D - Go back to the datacenter selection"
Write-Host "F - Go back to the folder selection"
Write-Host "V - Go back to the VM selection"
Write-Host "Q - Exit the script"
$endAnswer = ''
while('D','F','V','Q' -notcontains $endAnswer){
$endAnswer = (Read-Host -Prompt 'Your answer').ToUpper()
}
}
Disconnect-VIServer -Server $vcName -Confirm:$false
break
}
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
In the first ForEach loop you are overwriting the content of the $selectedVM variable for each VM.
After the ForEach loop yoru CSV will contain all the candidate VMs (due to the Append switch), but the $selectedVM variable will only contain the last selected VM.
You could do something like this
$vms = Get-Folder 'vm' | Get-VM
$selectedVM = Get-Folder -Name $myFolder | Get-VM |
where{$_.ExtensionData.Guest.ToolsVersionStatus2 -eq "guestToolsSupportedOld"}
$selectedVM | Select -Property Name |
Export-CSV -Path .\toolsneedupgrade.csv -NoTypeInformation -UseCulture -Append
Import-Csv -Path .\toolsneedupgrade.csv -UseCulture | Foreach-Object -Process {
Get-VM -Name $_.Name
#Get-VM -Name $vm |
#Out-GridView -Title 'Select the VMs you wish to upgrade tools on' -OutPutMode Multiple
}
foreach ($vm in $selectedvms) {
if ($vm.Guest.State -eq "Running")
{
Write-Host "VM -->",$vm.Name, "is powered on so tools will be updated"
Get-VM -Name $vm.Name | Update-Tools -NoReboot
}
elseif ($vm.Powerstate -eq "PoweredOff")
{
Write-Host "VM-->",$vm.Name, "is powered off, so ignoring"
}
}
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
Hi LucD,
So I made those changes, but no improvement. In fact it's not even trying to update the machines. I removed the $vms = Get-Folder line as it seems like it's redundant for the line below it. I also changed the foreach ($vm in $selectedvms) since there's no $selectedvms anywhere and it still doesn't work. What I'd really like is to create a pop up using the Out-GridView to see the list of machines so the user can update whichever ones they want. Below is what I currently have for the code:
$selectedVM = Get-Folder -Name $myfolder | Get-VM | where {$_.ExtensionData.Guest.ToolsVersionStatus2 -eq "guestToolsSupportedOld"}
$selectedVM | select -Property Name | Export-CSV -Path .\toolsneedupgrade.csv -NoTypeInformation -UseCulture -Append
Import-Csv -Path .\toolsneedupgrade.csv -UseCulture | Foreach-Object -Process { Get-VM -Name $_.Name
}
foreach ($vm in $selectedvms) {
if ($vm.Guest.State -eq "Running")
{
Write-Host "VM -->",$vm.Name, "is powered on so tools will be updated"
Get-VM -Name $vm.Name | Update-Tools -NoReboot
}
elseif ($vm.Powerstate -eq "PoweredOff")
{
Write-Host "VM-->",$vm.Name, "is powered off, so ignoring"
}
The way it is now I can select a folder and it will list me the VM's, what their name, powerstate, number of cpu's and memory are and that's it. Doesn't go past that to try and do anything and I can verify in vCenter that there are a couple VM's that need to be upgraded with tools.
Ok, try this version.
The $selectedVM now contains the VMs that the user selected via Out-GridView
I left out the Export-Csv and Import-Csv since it really does not play any role in the script's logic.
If you want an external file to record which VMs are handled, you can add the Export-Csv again.
$myfolder = 'MyFolder'
$selectedVM = Get-Folder -Name $myfolder | Get-VM |
where {$_.ExtensionData.Guest.ToolsVersionStatus2 -eq "guestToolsSupportedOld"} |
Out-GridView -OutputMode Multiple
foreach ($vm in $selectedVM) {
if ($vm.Guest.State -eq "Running")
{
Write-Host "VM --> $($vm.Name) is powered on so tools will be updated"
Update-Tools -VM $vm -NoReboot
}
elseif ($vm.Powerstate -eq "PoweredOff")
{
Write-Host "VM--> $($vm.Name) is powered off, so ignoring"
}
}
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
Hi LucD,
So now with those changes it will only update one VM. It will show that a VM is powered on and needs to be updated, but that's it. It doesn't go thru the whole list of VM's I select and it doesn't show anything in regards to the VM's that are powered off. When I had the csv file it would show a list of the VM's that were on or off, so I'm not sure what's going on here. Do I need something else in the if statement part?
I added some debugging statements in the script.
Can you try with this version?
$myfolder = 'MyFolder'
$selectedVM = Get-Folder -Name $myfolder | Get-VM |
where {$_.ExtensionData.Guest.ToolsVersionStatus2 -eq "guestToolsSupportedOld"} |
Out-GridView -OutputMode Multiple
Write-Host "Selected VM: $($selectedVM.Name -join '|')"
foreach ($vm in $selectedVM) {
Write-Host "Looking at $($vm.Name)"
Write-Host "`tGuest state $($vm.Guest.State)"
Write-Host "`tPower state $($vm.PowerState)"
if ($vm.Guest.State -eq "Running")
{
Write-Host "VM --> $($vm.Name) is powered on so tools will be updated"
Update-Tools -VM $vm -NoReboot
}
elseif ($vm.Powerstate -eq "PoweredOff")
{
Write-Host "VM--> $($vm.Name) is powered off, so ignoring"
}
}
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
Hi LucD,
With the code below I get this:
Selected VM: VMToolUpg1|VMToolUpg2|VMToolUpg3|VMToolUpg4|VMToolUpg5
Looking at VMToolUpg1
Guest state Running
Power state PoweredOn
VM --> VMToolUpg1 is powered on so tools will be updated
Then it takes me to the loop at the end of the script. All 5 of those selected need to be upgraded. 2 of them are not powered on and I would expect the script to tell me that so it ignores them, but it never gets there and never starts on a 2nd VM.
Are you running this from a .ps1 file?
If yes, can you attach the script.
If not, how are you running the script?
Can you share a screenshot?
Some extra debug lines
$myfolder = 'MyFolder'
$selectedVM = Get-Folder -Name $myfolder | Get-VM |
where {$_.ExtensionData.Guest.ToolsVersionStatus2 -eq "guestToolsSupportedOld"} |
Out-GridView -OutputMode Multiple
Write-Host "Selected VM count : $($selectedVM.Count)"
Write-Host "Selected VM: $($selectedVM.Name -join '|')"
Write-Host "Before loop - selected VM count : $($selectedVM.Count)"
foreach ($vm in $selectedVM) {
Write-Host "Inside loop - selected VM count : $($selectedVM.Count)"
Write-Host "Looking at $($vm.Name)"
Write-Host "`tGuest state $($vm.Guest.State)"
Write-Host "`tPower state $($vm.PowerState)"
if ($vm.Guest.State -eq "Running")
{
Write-Host "VM --> $($vm.Name) is powered on so tools will be updated"
Update-Tools -VM $vm -NoReboot
Write-Host "After Update-Tools"
Write-Host "`tVM $($vm.Name)"
}
elseif ($vm.Powerstate -eq "PoweredOff")
{
Write-Host "VM--> $($vm.Name) is powered off, so ignoring"
Write-Host "VM powered off"
Write-Host "`tVM $($vm.Name)"
}
}
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
Yes running it as a .ps1 file. I just ran it with the changes you had and it didn't even attempt to update the VM's. Just gave me this:
Selected VM count : 5
Selected VM: VMToolUpg4|VMToolUpg1|VMToolUpg5|VMToolUpg2|VMToolUpg3
Before loop - selected VM count : 5
Inside loop - selected VM count : 5
Looking at VMToolUpg4
Guest state NotRunning
Power state PoweredOff
VM--> VMToolUpg4 is powered off, so ignoring
VM powered off
VM VMToolUpg4
File is attached with the most recent changes.
The problem is in your handling of the Continue or Exit block.
This simple example should demonstrate (just runs 4 times through the loop and asks the Continue/Exit question)
1..4 | %{
Write-Host "Loop $_"
$continue = New-Object System.Management.Automation.Host.ChoiceDescription '&continue', 'Continue getting tools info?'
$exit = New-Object System.Management.Automation.Host.ChoiceDescription '&exit', 'Exit the script'
$options = [System.Management.Automation.Host.ChoiceDescription[]]($continue, $exit)
$title = 'Continue or exit script'
$message = 'Do you want to continue or exit the script? Exiting script will disconnect you from all hosts/vCenters'
$result = $host.ui.PromptForChoice($title, $message, $options, 0)
if ($result -ne "0"){
break
}
}
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
Hi LucD,
So I've had the loop like that for a while in my scripts and afaik it's never been a problem for me with the main part of any script. I made the changes as shown below and when it gets to the part of choosing continue or exit it ends the script once I choose continue.
Ok so for the moment I have removed the loop just to figure things out. I made a couple changes to the code which are below:
$dc = Get-Datacenter
if ($dc.Count -gt 1) {
Write-Host "I found $($dc.Count) datacenters"
Write-Host "Choose a datacenter"
1..($dc.Count) | %{
Write-Host "$_ - $($dc[$_ - 1].Name)"
}
$answer = 0
while (1..($dc.Count) -notcontains $answer){
$answer = Read-Host -Prompt "Select a datacenter (1-$($dc.Count))"
}
}
Get-Datacenter -Name $dc[$answer - 1].Name | Get-Folder -Type "VM" | sort Name | Format-Table
$myfolder = Read-Host "Select a folder"
$selectedVM = Get-Folder -Name $myfolder | Get-VM | where {$_.ExtensionData.Guest.ToolsVersionStatus2 -eq "guestToolsSupportedOld"} | Out-GridView -Title 'Select VMs you wish to scan for tools updates' -OutputMode Multiple
$myvms = Get-VM -Name $selectedVM | Select -Property Name | sort Name
$selectedVMs = Get-VM -Name $myvms.Name | sort Name
foreach ($vm in $selectedVMs) {
if ($vm.Name -and ($vm.Guest.State -eq "Running"))
{
Write-Host "VM -->",$vm.Name, "is powered on so tools will be updated"
Get-VM -Name $vm.Name | Update-Tools -NoReboot
}
elseif ($vm.Powerstate -eq "PoweredOff")
{
Write-Host "VM-->",$vm.Name, "is powered off, so ignoring"
}
So now everything is working ok to a certain degree. It will update, or at least attempt to update whatever VM's I click. For example I have this output:
VM --> VMToolUpg4 is powered on so tools will be updated
VM --> VMToolUpg5 is powered on so tools will be updated
VM --> VMToolUpg6 is powered on so tools will be updated
VM --> VMToolUpg7 is powered on so tools will be updated
VM--> VMToolUpg8 is powered off, so ignoring
VM--> VMToolUpg9 is powered off, so ignoring
Which looks great. However it only really updates VMToolUpg4. It takes the longest, but when I look in vSphere I see that the tools show as upgraded, even though I didn't reboot the machine. When I look at all the others in vSphere it does the install/mount/upgrade of the tools rather quickly. Even if I reboot them they still show as not being upgraded.
I added the $vm.Name in the if section as I noticed it didn't want to get multiple VM's for whatever reason. No matter how many I selected it would only try to update one, but once I added that in it started to at least attempt to update them all.
So now I'm wondering why it's only updating the first one and not the rest. I'm also wondering why the loop is breaking things. I have this in a lot of other scripts and it hasn't caused a problem anywhere else.
I may have found what was blocking the ones from updating, but need to do a little more research. Will update this thread later.
Do you really call $commands inside $commands in the other scripts?
That is definitely bound to break things
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
So I happened to just run the Update-Tools command against a VM and got the vix error code 21009. I ran another script that I have that does this:
Get-VM -Name $VMname.Name | New-AdvancedSetting -Name "isolation.tools.guestInitiatedUpgrade.disable" -value "FALSE" -Force -Confirm:$false #Adds an entry to the vmx file to allow VM tools to upgrade
And I was able to update VM tools on those machines. So that fixes that problem.
So, what problems are still remaining?
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
Yes. I was digging around one day to find out how to loop a script so someone wouldn't have to do run the script over and over and came across how I have it setup. It's worked well for all of the scripts I've used it in so far without any issue, other than the one in this thread. I basically wanted a way to loop back to a certain point in each script, whether it was the datacenter part or something else so people could just do their thing. I'm always open to another way of doing things, so if there's a better way to loop each script and even get a loop to work right in this one then I'm game.
Ok, I can have another look.
But would you mind attaching the actual script as you are currently using?
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
Start with something like this
$dc = Get-Datacenter | Select -ExpandProperty Name
if($dc.count -gt 1){
$dc = $dc | Out-GridView -OutputMode Multiple -Title 'Select one or more datacenters'
}
$folder = Get-Datacenter -Name $dc | Get-Folder -Type VM | select -ExpandProperty Name
if($folder.Count -gt 1){
$folder = $folder | Out-GridView -OutputMode Multiple -Title 'Select one or more folders'
}
$vms = Get-Folder -Name $folder -Location $dc -Type VM | Get-VM |
where {$_.ExtensionData.Guest.ToolsVersionStatus2 -eq "guestToolsSupportedOld"} |
Select -ExpandProperty Name
if($vms.Count -eq 0){
Write-Host "No VMs found"
break
}
if($vms.Count -gt 1){
$vms = $vms | Out-GridView -OutputMode Multiple -Title 'Select one or more VMs'
}
foreach($vm in Get-VM -Name $vms){
Write-Host "Looking at $($vm.Name)"
if($vm.Guest.State -eq 'Running'){
Write-Host "VM --> $($vm.Name) is powered on so tools will be updated"
Get-VM -Name $vm.Name | Update-Tools -NoReboot
}
elseif($vm.PowerState -eq 'PoweredOff'){
Write-Host "VM--> $($vm.Name) is powered off, so ignoring"
}
$answer = ''
while('y','n' -notcontains $answer){
$answer = Read-Host -Prompt "Continue with the next VM? (y/n)"
}
if($answer -eq 'n'){
break
}
}
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference