[HorizonAPI] Working with UAG’s

Something that was added in the last few versions of the Horizon API is the option to handle UAG’s. Since I had to add an uag to my lab for another project I decided to find out what api calls are possible. First I’ll check what services there are.

$hvservice | Select-Object gateway*

I will ignore the GatewayAccessUserOrGroup since that was already in there so we are left with Gateway and GatewayHealth. Let’s see what methods are available under Gateway.

$hvservice.Gateway | gm

I Gateway_Get and Gateway_List will show the same information as always but with _Get you will need a gateway ID and it only shows the information about one gateway. WIth _List you will get the information about all registered gateways.

$hvservice.Gateway.Gateway_List()
$gw=$hvservice.Gateway.Gateway_List() | select-object -First 1
$hvservice.Gateway.Gateway_Get($gw.id)

Let’s see what’s in that GeneralData (Spoiler: not a lot!)

$gwdata=$hvservice.Gateway.Gateway_Get($gw.id)
$gwdata.GeneralData

To remove a gateway we use Gateway_Unregister with the gatewayid

$hvservice.Gateway.Gateway_Unregister($gw.id)

Now i need to register the Gateway again let’s see what we need for that.

$hvservice.Gateway.Gateway_Register

So we need an object of the type VMware.Hv.GatewaySpec. Let’s define that and see what it looks like.

$gwspec=New-Object VMware.Hv.GatewaySpec
$gwspec

So we only need the GatewayName, please use the exact name that was used to configure the UAG otherwise it can be added but it won’t be showing any data.

$gwspec.GatewayName="pod1uag1"

Now to register the UAG

$hvservice.Gateway.Gateway_Register($gwspec)

So with this we did everything we could with the Gateway service. Next is the GatewayHealth service.

$hvservice.GatewayHealth | Get-Member

as usual there’s only a get and a list so let’s see what data is in there.

$hvservice.GatewayHealth.GatewayHealth_List()
($hvservice.GatewayHealth.GatewayHealth_List()).ConnectionData

Sadly nothing more than the admin interface gives us but enough to build an health check like I did for the vCheck already (that can be found here)

For the type there are several options and those can be found in the API Explorer.

VALUE DESCRIPTION
“AP” AP type is for UAG.
“F5” F5 type is for F5 server.
“SG” SG type is for Security Server.
“SG-cohosted” SG-cohosted type is for Cohosted CS as gateway.
“Unknown” Unknown type is for unrecognized gateway type.

I was told by a VMware employee that SG-cohosted is fancy wording for a connection server.

And that’s everything we can do with UAG’s using the Horizon API’s!

[Horizon API] Discovering pods and sites

When working with a Cloud Pod Architecture with the Horizon API’s we always have to make our scripts so that we connect to each pod separately. What if there is a way to discover the other available pods in a site or other site’s and connect to those? I already spent a couple of posts on working with pods and site’s. In this post I will be mainly using the get and list commands to get the information we need. First of all it’s the easiest to have the credentials saved somewhere because we will be disconnecting and connecting from and to pods. More on that can be found in this post.

To start we need to find what pod we’re currently connected to, with the following command we can list all pods:

$hvservice.Pod.Pod_List()

You see I have two pods: Cluster-Pod2CBR1 and Cluster-POD1CBR1, both have a property called localpod that provides the locality information we need. What we can’t see is if both pods belong to the same site. This can be done by comparing the VMware.Hv.Siteid object but I would prefer to do that from the site side because we might have several pods inside a site and it might become messy that way. The better was is to use that siteid to get all the information from the site.

$localpod=$hvservice.Pod.Pod_List() | where-object {$_.LocalPod -eq $True}
$localpod

And use the site id to grab the localsite.

$localsite=$hvservice.Site.Site_Get($localpod.site)
$localsite
($localsite).pods

The pods object is an array with all the pods within that site, I have added my second pod to this site to show this. Now I am going to select a connection server from each pod, if you want to connect to all the pods regardless the sites you can use the results from pod_list() to create the same output that we get by using this:

$sitepods=foreach ($sitepod in ($localsite.pods)){$hvservice.Pod.Pod_Get($sitepod)}
$sitepods

we still don’t have the name for the connection servers but those are part of the endpoints. We do this by getting the first podendpoint from all the pods within the site.

$podendpoints=foreach ($sitepod in $sitepods){$hvservice.PodEndpoint.PodEndpoint_Get((($sitepod).endpoints | select-object -first 1))}
$podendpoints

Now we’re getting somewhere, we just can’t connect to the serveraddress directly so we need to strip the things from the url’s

$connectionservers=$Podendpoints.serveraddress.replace("https://","").replace(":8472/","")
$connectionservers

Now we have a list of a connection servers from each pod inside site 1. If we would have used the pod_list() as source we would have ended up with one connection server from all pods within the CPA. The only thing we need to do now is to disconnect and do a foreach with whatever we want to do against the connectionservers.

foreach ($connectionserver in $connectionservers){
    Write-Output "This is connectionserver $connectionserver"
    $hvserver=connect-hvserver -Server $connectionserver -cred $cred
    $hvserver.ExtensionData.ConnectionServerHealth.ConnectionServerHealth_List()
    disconnect-hvserver $hvserver -confirm:$false
}

My VMworld EU 2019 presentations

It’s already the week after and I am looking back at a very good VMworld last week in Barcelona. In the end I was at a podium for none less than four times and wanted to share the decks or videos with you when available. For the vExpert daily there is no deck (duh) and for the EUC Beer and tapas community event there is no video. I also had to remove most of the slides because the fling hasn’t been published yet, you can expect a blogpost when it’s been published because it’s going to be awesome!

vExpert Daily: Video | Deck

vBrownbag: tools for Horizon Helpdesk: Video | Deck

EUC Beer and Tapas top 5 flings for Horizon: Video | Deck

VMware{Code}-Horizon API 101: Video | Deck

Updates to the Horizon API’s in PowerCLI 11.4

So today PowerCLI 11.4 was released with the following updates:

  • Add support for Horizon View 7.9
  • Added new cmdlets to the Storage module
  • Updated Storage module cmdlets
  • Updated HCX module cmdlets

As usual we need to wait for API explorer to be updated before we get the exact changes to the api’s but I already grabbed s short list by comparing the methods. Later I will create a more elaborate blog post about the changes if I have an overview. What I do see are some new additions that might be added to the vCheck for Horizon.

Also: even though the updates are for Horizon 7.9 there’s a good chance that a lot of this also works for previous versions, the examples below where done with 7.8.

  • Datacenter
  • DesktopHealth
  • Gateway
  • GatewayHealth
  • MessageClient
  • Monitoring
  • PersistentDiskQueryService
  • Privilege
  • SecondaryCredentials
  • SessionStatistics
  • StorageAccelerator
  • UsageStatistics
  • Validator
  • VirtualCenterStatistics

Sadly it’s late so I can only show a couple of examples:

$services.Privilege.Privilege_ListSelectablePrivileges()

 

$services.SessionStatistics.SessionStatistics_GetLocalSessionStatistics()

Finally we can reset the usage counters as well now

And some statistics from vCenter

($services.VirtualCenterStatistics.VirtualCenterStatistics_listSummaryStatistics())
($services.VirtualCenterStatistics.VirtualCenterStatistics_listSummaryStatistics()).DataStoreSummaryStatistics

Generating a clean Host Profile using PowerCLI

First of all: I love Host Profiles! But they’re easy to mess up as well, leave something selected related to hardware and an update in ESXi, vib’s or even a firmware update might break it. For a customer where we are going to do the entire vSphere build from scratch I got the idea to generate an empty Host Profile and extend that one using scripting. At first I though this would be an easy thing but it definitely isn’t, a reply from PowerCLI guru Luc Dekens at the VMware{Code} forums set me on the right path to do so. Luc’s remark that editing Host Profiles might take some reverse engineering for the lack of documentation is a huge understatement. It has cost me many many hours to build the script below.

I strongly recommend having the reference host as clean as possible.

These are the steps the script takes

  1. connect to vCenter
  2. extract a new Host Profile
  3. Gets the new Host Profile
  4. Copies all members of the new Host Profile to an object that can be edited
  5. Sets everything that I could find in my environment to false
  6. Updates the Host Profile with the edited object

Required parameters

  • vCenter
    • Your vCenter host
  • Referencehost
    • the name of the host in vCenter
  • Hostprofilename
    • Name for the Host Profile

There are also a couple of optional parameters:

  • dnshost
    • It’s mandatory to have a DNS set in the defaulttcpipstack. With this parameter you can change this.
  • domainname
    • Like DNS it’s mandatory to have a domainname set in the defaulttcpipstack. With this parameter you can change this
  • Cleanup
    • This one defaults to false but can be set to true. It will remove all NFS Datastores, vmkernel ports, portgroups, device aliases and direct i/o profiles.
    • Use this one with care, if you apply it to a host it will most probably remove all networking details for that host making it unusable.

This is how a manual extracted Host Profile looks

This is how a Host Profile looks after using my script without the cleanup option, everything is deselected but the device aliases for example are kept.

.\create_clean_hostprofile.ps1 -vcenter vCenter -Hostprofilename demo_no_cleanup -referencehost hostname

And this is how it looks with the cleanup used.

.\create_clean_hostprofile.ps1 -vcenter vCenter -Hostprofilename demo_no_cleanup -referencehost hostname -cleanup $true

The script itself can be found on Github as well:

#-------------------------------------------------
# Generates a clean Host Profile
#
# Build using PowerCLI 11
#
# Version 1.0
# 17-08-2019
# Created by: Wouter Kursten
# Website: https://www.retouw.nl
#
#-------------------------------------------------

param(
[Parameter(Mandatory=$true)][String]$Hostprofilename,
[Parameter(Mandatory=$true)][String]$vcenter,
[Parameter(Mandatory=$true)][String]$referencehost,
[Parameter()][String]$dnshost,
[Parameter()][String]$domainname,
[Parameter()][bool]$Cleanup = $false
)

# I grabbed this function somewhere from an example by Luc Dekens
function Copy-Property ($From, $To, $PropertyName ="*"){
    foreach ($p in Get-Member -In $From -MemberType Property -Name $propertyName){
        trap {
            Add-Member -In $To -MemberType NoteProperty -Name $p.Name -Value $From.$($p.Name) -Force
            continue
        }
    $To.$($P.Name) = $From.$($P.Name)
    }
}

#connect to the vCenter
connect-viserver $vcenter

# This deletes any existing Host Profile with the same name as we're using in this script
get-vmhostprofile -name $Hostprofilename  -ErrorAction SilentlyContinue | Remove-VMHostProfile -Confirm:$false

# This creates a new Host Profile from the referencehost
new-vmhostprofile -name $Hostprofilename -referencehost $referencehost

# Retrieves the newly created Host Profile
$hp = Get-VMHostProfile -Name $Hostprofilename

# Creates the spec where the cleanup is done
$spec = New-Object VMware.Vim.HostProfileCompleteConfigSpec

# Copies all properties of the new Host Profile to the spec
Copy-Property -From $hp.ExtensionData.Config -To $spec

# This removes everything that could be specific to the referencehost
if ($cleanup -eq $true){
    $spec.ApplyProfile.Network.Vswitch=$null
    $spec.ApplyProfile.Network.VMportgroup=$null
    $spec.ApplyProfile.Network.HostPortGroup=$null
    $spec.ApplyProfile.Network.pnic=$null
    $spec.ApplyProfile.Storage.NasStorage=$null
    ($spec.ApplyProfile.Property | where-object {$_.PropertyName -like "*DeviceAlias*"}).profile=$null
    ($spec.ApplyProfile.Property | where-object {$_.PropertyName -like "*PCI*"}).profile.property.profile=$null
}

# From here it's just disabling of items except for:
# -items under storage> PSA Configuration (profiles are removed)
# -Properties of the fixed DNS config (set to the default values from this scripts parameters)
$spec.ApplyProfile.Datetime.Enabled=$False
$spec.ApplyProfile.Authentication.Enabled=$False
$spec.ApplyProfile.Authentication.ActiveDirectory.Enabled=$False

foreach ($o in $spec.applyprofile.Option){
    if ($o.Enabled){
        $o.Enabled=$False
    }
}

foreach ($p in $spec.ApplyProfile.Property.Profile){
    if ($p.Enabled){
        $p.Enabled=$False
    }
    foreach ($pa in $p.Property.Profile){
            if ($pa.Enabled){
                $pa.Enabled=$False
                }
        foreach ($paa in $pa.Property.Profile){
                if ($paa.Enabled){
                    $paa.Enabled=$False
                }
        }
    }
}

foreach ($s in $spec.ApplyProfile.Storage.Nasstorage){
    if ($s.Enabled){
        $s.Enabled=$False
    }
    foreach ($sa in $s){
        if ($sa.Enabled){
            $sa.Enabled=$False
        }
    }
}

foreach ($s in $spec.ApplyProfile.Storage.Property.Profile){
    if ($s.Enabled){
        $s.Enabled=$False
    }

    if ($s.ProfileTypeName -eq "psa_psaProfile_PluggableStorageArchitectureProfile" -AND $cleanup -eq $true){
        foreach ($sa in $s.property){
            if ($sa.propertyname -like "*psa_psaProfile_PsaDevice*"){
                $sa.profile=@()
            }
        }
    }
    foreach ($sa in $s.Property.Profile){
        if ($sa.Enabled){
            $sa.Enabled=$False
            }
        foreach ($saa in $sa.Property.Profile){
            if ($saa.Enabled){
                $saa.Enabled=$False
            }
        }
    }
}

foreach ($f in $spec.ApplyProfile.Firewall.ruleset){
    if ($f.Enabled){
        $f.Enabled=$False
    }
}

foreach ($n in $spec.ApplyProfile.Network.vswitch){
    if ($n.Enabled){
        $n.Enabled=$False
    }
    foreach ($na in $n){
        if ($na.Enabled){
            $na.Enabled=$False
        }
        foreach ($naa in $na.link){
            if ($naa.enabled -eq $True){
                $naa.Enabled=$False
            }
        }
        foreach ($naa in $na.NumPorts){
            if ($naa.enabled -eq $True){
                $naa.Enabled=$False
            }
        }
        foreach ($naa in $na.NetworkPolicy){
            if ($naa.enabled -eq $True){
                $naa.Enabled=$False
            }
        }
    }
}

foreach ($n in $spec.ApplyProfile.Network.pnic){
    if ($n.Enabled){
        $n.Enabled=$False
    }
    foreach ($na in $n){
        if ($na.Enabled){
            $na.Enabled=$False
        }
    }
}

foreach ($n in $spec.ApplyProfile.Network.VmPortGroup){
    if ($n.Enabled){
        $n.Enabled=$False
    }
    foreach ($na in $n){
        if ($na.Enabled){
            $na.Enabled=$False
        }
        foreach ($naa in $na.Vlan){
            if ($naa.enabled -eq $True){
                $naa.Enabled=$False
            }
        }
        foreach ($naa in $na.Vswitch){
            if ($naa.enabled -eq $True){
                $naa.Enabled=$False
            }
        }
        foreach ($naa in $na.NetworkPolicy){
            if ($naa.enabled -eq $True){
                $naa.Enabled=$False
            }
        }
    }
}

foreach ($n in $spec.ApplyProfile.Network.HostPortGroup){
    if ($n.Enabled){
        $n.Enabled=$False
    }
    foreach ($na in $n){
        if ($na.Enabled){
            $na.Enabled=$False
        }
        foreach ($naa in $na.IpConfig){
            if ($naa.enabled -eq $True){
                $naa.Enabled=$False
            }
        }
        foreach ($naa in $na.Vlan){
            if ($naa.enabled -eq $True){
                $naa.Enabled=$False
            }
        }
        foreach ($naa in $na.Vswitch){
            if ($naa.enabled -eq $True){
                $naa.Enabled=$False
            }
        }
        foreach ($naa in $na.NetworkPolicy){
            if ($naa.enabled -eq $True){
                $naa.Enabled=$False
            }
        }
    }
}

foreach ($n in $spec.ApplyProfile.Network.Property.Profile){
    if ($n.Enabled){
        $n.Enabled=$False
    }
    foreach ($na in $n.Property.Profile){
        if ($na.Enabled){
            $na.Enabled=$False
            }
        foreach ($np in $na.policy.policyoption){
            if ($np.id -eq "FixedDnsConfig"){
                foreach ($npp in $np.parameter){
                    if ($dnshost){
                        if ($npp.key -eq "address") {
                            [string[]]$dnsarray=@($dnshost)
                            $npp.value=$dnsarray
                        }
                    }
                    if ($domainname){
                        if ($npp.key -eq "domainName"){
                            $npp.value=$domainname
                        }
                    }
                }
            }
        }
        foreach ($naa in $na.Property.Profile){
            if ($naa.Enabled){
                $naa.Enabled=$False
            }
            foreach ($naaa in $naa.Property.Profile){
                if ($naaa.Enabled){
                    $naaa.Enabled=$False
                }
            }
        }
    }
}


(Get-VMHostProfile $Hostprofilename).ExtensionData.Updatehostprofile($spec)
disconnect-viserver $vcenter -confirm:$False

And yes that’s a lot of foreach’s.

 

 

The VMware Labs flings monthly for July 2019

So I am in the middle of my summer holiday but stil it’s time for this monthly overview. I see two new flings with the Virtual Machine Compute Optimizer and Machine Learning on VMware Cloud Foundation tools. No less than six have received updates: vSphere Mobile Client, Desktop Watermark, HCIBench, Horizon Toolbox, Horizon Helpdesk Utility and Horizon Session recording. I already blogged about the updates to the Horizon Helpdesk Utility over here.

New Releases

[sta_anchor id=”vmlp” /]

Machine Learning on VMware Cloud Foundation

Want to do things with big data? This Fling might be able to help you with that on VCF, It could do with a proper logo though.

This Fling provides a platform for Data Scientists to quickly setup a virtualized cloud infrastructure to conduct data science experiments:

  • Virtualized environment based on VMware cloud and Kubernetes
  • Currently support CPU only (but will support GPU in future)
  • Based on Open Source Kubeflow, Horovod

Provides a set of example Notebooks and libraries for common data science tasks, including:

  • Data collection and cleaning (extract data from various sources, and describe the data semantics using metadata)
  • Data cleansing and transformation (clean up collected data and transform them from its raw form to a structured form more suitable for analytic processing)
  • Model training (develop predictive and optimization machine learning models)
  • Model serving (deploy model into a run time environment where online request will be served)

[sta_anchor id=”vmco” /]

Virtual Machine Compute Optimizer

I personally wouldn’t call the Virtual Machine Computer Optimizer a fling since it’s a script but it’s here.

The Virtual Machine Computer Optimizer (VMCO) is a Powershell script that uses the PowerCLI module to capture information about the hosts and VMS running in your vSphere environment, and reports back on whether the VMs are configured optimally based on the Host CPU and memory. It will flag a VM as “YES” if it is optimized and “NO” if it is not. For non-optimized VMs, a recommendation is made that will keep the same number of vCPUs currently configured, with the optimal number of virtual cores and sockets.

Note that the VMCO will not analyze whether your VMs are configured with the correct number of vCPUs based on the VM’s workload. A more in-depth analysis tool such as VMware vRealize Operations Manager can make right-sizing determinations based on workload and actual performance.

Update flings

[sta_anchor id=”hsr” /]

Horizon Session Recording

The Horizon Session Recording fling gives the Horizon admin a tool to record sessions for troubleshooting reasons for example.

Changelog

Version 1.2.2

  • Added support for horizon 7.8 and above
  • Added support for recording based on group memberships
  • Many bug fixes in agent
  • Bug fixes in server

[sta_anchor id=”hhu” /]

Horizon Helpdesk Utility

Like I said I already blogged about the changes in the Horizon Helpdesk Utility but here’s the changelog, just to be complete.

Changelog

Version 1.4.0.1

  • No longer requires a helpdesk license! Yay!
  • Added the ability to interact with vCenter machines
  • Added the ability to open vCenter VM consoles
  • Added the ability to perform bulk machine actions
  • Added the ability to perform refresh / recompose tasks directly from helpdesk.
  • Fixed performance issues with multiple windows open (see single instance).
  • Fixed a crash when logon durations could not be accessed.
  • Added polling to allow logon durations to be received if notavailable when the session page is requested.
  • Fixed a crash in the ending of processes.
  • Fixed a metric ton of bugs with delegated administration.
  • Fixed a memory leak in the tray icon menu, of all places.
  • Removed the logon page graphic as it was to much of a pain to change it’s colour when changing themes
  • Fixed some layout issues when changing themes.
  • Removed empty sites from the viewon the change pod tray menu.
  • Added preliminary support for Horizon 7.9.

[sta_anchor id=”horizontoolbox” /]

Horizon Toolbox

The Horizon Toolbox is another usefull utility for the Horizon admin that doesn’t have access to the enterprise add-ons.

Changelog

July 12, 2019, 7.8.1

  • Added support for Horizon View 7.5, 7.6, 7.7, 7.8
  • Fixed some issues

[sta_anchor id=”hcibench” /]

HCIBench

We have seen this one quite a lot already, if you need to benchmark your HCI than the HCIBench might be your tool. Good chance though that it’s better optimized for VSAN than others.

Changelog

Version 2.2.1

  • Fixed docker volume moving issue
  • MD5 checksum of HCIBench_2.2.1.ova: 1a39c9df7d1485bc06332ae0b9d92ca7

Version 2.2

  • Moved docker volume to sdb to avoid blowing up OS disk
  • Added Fio spreadsheet generator
  • Added DRS warning checkup
  • Enhanced Grafana to keep all the historical data
  • Added DNS exception handler
  • Fixed RAM and PCPU reporting issue
  • Fixed Vdbench spreadsheet not reporting issue
  • MD5 checksum of HCIBench_2.2.ova: bb2a77dcf2ecc23b1ec2c30aee9945ec

[sta_anchor id=”desktopwatermark” /]

Desktop Watermark

I personally haven’t really used the Desktop Watermark fling yet but I guess it could be useful for others.

Changelog

v1.0 – Build 20190724-signed

  • Added a new attribute %DATETIME% to show hour and minute info on screen.

[sta_anchor id=”vsphereclient” /]

vSphere Mobile Client

The vSphere Mobile Client fling is still a work in progress but functionality keeps being added. Very useful for most VI admins.

Changelog

Version 1.2.0

New features:

  • Focused inventory (bookmark a VM and then enter focused mode by clicking the bullseye button in the header)
  • vCenter dashboard now has host and virtual machine aggregates
  • Swiping the VM card displays a screenshot, clicking on it displays an even larger image

Bug Fixes

  • Removed option to delete virtual machines
  • Improvements to the login page
  • Improvements to the events and alarms page

 

 

 

 

[API]How to successfully logoff users in Horizon

One of the things that annoy me about the Horizon admin interface is the fact that if you give a session the logoff command that this only works if the user is active aka when the desktop is not locked. With the api’s though (and Andrew implemented this in the helpdesk fling) it is possible to force a logoff. Let’s look at the available method’s first.

So we have a logoff and logoffForced. But there are also the logoffsessions and LofoffSessionsForced, I guess those let you logoff multiple sessions. this is what the extensiondata says about them.

So for the singular method’s we need a single id and for the sessions we need an array of ids. At first I will use get-hvglobalsession (yes, this works against sessions in other pod’s in a cloud pod architecture as well!) to get the id’s to show how it works. I have 5 sessions running from my desktop

$services1.Session.Session_Logoff((get-hvglobalsession | select -first 1).id)

Damn locked, let’s force this bastard from his desktop.

$services1.Session.Session_LogoffForced((get-hvglobalsession | select -first 1).id)

Aaaand it’s gone

And to show that it works I had to make sure the first session wasn’t locked.

And now the big bang fuck all of you!

$services1.Session.Session_LogoffSessionsForced((Get-HVGlobalSession).id)

As you can see one of my users was a but slow in logging off (nested esxi with only a couple vcpu’s for that one) I have also created a script that asks for the user whom you want to logoff and which session you want to logoff in case they have multiple. It’s not the cleanest code that I have written but it works 🙂

$hvserver1=connect-hvserver servername -user user -domain domain -password passwords
$Services1= $hvServer1.ExtensionData

$username= Read-Host "Which user do you want to logoff? (no wildcards needed, part of the name is enough)"

$queryService = New-Object VMware.Hv.QueryServiceService
$userdefn = New-Object VMware.Hv.QueryDefinition
$userdefn.queryEntityType = 'ADUserOrGroupSummaryView'
$userfilter1= New-Object VMware.Hv.QueryFilterContains
$userfilter1.membername='base.name'
$userfilter1.value=$username
$userfilter2= New-Object VMware.Hv.QueryFilterEquals
$userfilter2.membername='base.group'
$userfilter2.value=$False
$userfilter=new-object vmware.hv.QueryFilterAnd
$userfilter.filters=@($userfilter1, $userfilter2)
$userdefn.filter=$userfilter
$users=($queryService.QueryService_Create($Services1, $userdefn)).results

$menu = @{}
for ($i=1;$i -le $users.count; $i++){ 
    Write-Host "$i. $($users[$i-1].base.name)" 
    $menu.Add($i,($users[$i-1].id))
}
[int]$ans = read-host "Please select the correct user"
$user=$menu.Item($ans)

$GlobalSessionQueryService = new-object VMware.Hv.GlobalSessionQueryServiceService
$sessionfilterspec=new-object vmware.hv.GlobalSessionQueryServiceQuerySpec
$sessionfilterspec.user=$user
$sessions=($GlobalSessionQueryService.GlobalSessionQueryService_QueryWithSpec($services1, $sessionfilterspec)).results

$menu = @{}
for ($i=1;$i -le $sessions.count; $i++){ 
    Write-Host "$i. $($sessions[$i-1].namesdata.basenames.MachineOrRDSServerName)" 
    $menu.Add($i,($sessions[$i-1].id))
}
[int]$ans = read-host "Please select the correct VDI Desktop"
$session=$menu.Item($ans)

$Services1.Session.Session_Logoffforced($session)
$queryService.QueryService_DeleteAll($services1)

This script forces the logoff for the sessions since I haven’t been able yet to find where the desktop status (locked or not) is visible.

[API’s] Getting session counts (incl performance comparison)

One of my customers asked the question if it is possible to get a quick sessioncount for a script that they can run very often for a correct logging of license usage. While this could easily be done by grabbing all the sessions I thought this could be a slow process. I remembered though that the first release of the vmware.hv.helper module had a function called get-podsessions that only returned a sessioncount. I decided to see what was used for this. By going back in time at github I found that the GlobalSessionQueryService was still used but with the GlobalSessionQueryService_GetCountWithSpec method. It needs the service and a spec of the type VMware.Hv.GlobalSessionQueryServiceCountSpec.

the spec itself can hold one of the many options to get a count for

As you can see there is a globalentitlement property that needs to be set using the id so let’s grab that one first.

$queryService = New-Object VMware.Hv.QueryServiceService
$defn = New-Object VMware.Hv.QueryDefinition
$defn.queryEntityType = 'GlobalEntitlementSummaryView'
$globalentitlements = ($queryService.QueryService_Create($Services1, $defn)).results

I will use the first globalentitlement to grab the sessioncount

$globalentitlement=$globalentitlements | select -first 1
$globalsessionqueryservice_helper = New-Object VMware.Hv.GlobalSessionQueryServiceService  
$count_spec = New-Object VMware.Hv.GlobalSessionQueryServiceCountSpec  
$count_spec.globalentitlement=$globalentitlement.id
$sessioncountperglobalentitlements=$globalsessionqueryservice_helper.GlobalSessionQueryService_GetCountWithSpec($services1,$count_spec)

As you can see we actually get a count per pod so to get all the counts from all pods from all globalentitlements I have created a script with a couple foreach’s.

$hvserver1=connect-hvserver SERVERNAME
$services1=$hvserver1.extensiondata
$queryService = New-Object VMware.Hv.QueryServiceService
$defn = New-Object VMware.Hv.QueryDefinition
$defn.queryEntityType = 'GlobalEntitlementSummaryView'
$globalentitlements = ($queryService.QueryService_Create($Services1, $defn)).results
$queryservice.QueryService_DeleteAll($services1)
$sessioncount=@()


foreach ($globalentitlement in $globalentitlements){
  $globalsessionqueryservice_helper = New-Object VMware.Hv.GlobalSessionQueryServiceService  
  $count_spec = New-Object VMware.Hv.GlobalSessionQueryServiceCountSpec  
  $count_spec.globalentitlement=$globalentitlement.id
  $sessioncountperglobalentitlements=$globalsessionqueryservice_helper.GlobalSessionQueryService_GetCountWithSpec($services1,$count_spec)
  foreach ($sessioncountperglobalentitlement in $sessioncountperglobalentitlements){
    $pod=$services1.pod.pod_get($sessioncountperglobalentitlement.id)
    $sessioncount+= New-Object PSObject -Property @{
      "Global_Entitlement_Name" = $globalentitlement.base.displayname;
      "Pod_Name"=$pod.displayname
      "Pod_Sessioncount" = ($sessioncountperglobalentitlement | select-object -expandproperty count);
      "Site_Name"= ($services1.site.site_get($pod.site)).base.Displayname;
    }
  }
}
 return $sessioncount | select-object Global_Entitlement_Name,Pod_Name,Site_Name,Pod_Sessioncount

The W10_MGMT global entitlement only has a pool in pod1 so even though the pod doesn’t have a pool inside the global entitlement it will still return a sessioncount.

Performance

I also decided to time it but in my small environment it took 3 seconds and 3 of those where for connecting to the connection server. If I removed the connecting part it was 0.7 seconds.

Measure-Command {D:\scripts\dev\session_count.ps1}

Back at the customer I decided to compare this against dumping all global sessions, this will give some better data since it has a couple more sessions in it (around 3500 at the moment of testing)

The script I used for getting all global sessions is the code that I used for the get-hvglobalsession in the vmware.hv.helper module

$query_service_helper = New-Object VMware.Hv.GlobalSessionQueryServiceService
$query=new-object vmware.hv.GlobalSessionQueryServiceQuerySpec

$SessionList = @()
foreach ($pod in $services1.Pod.Pod_List()) {
  $query.pod=$pod.id
  $queryResults = $query_service_helper.GlobalSessionQueryService_QueryWithSpec($services1, $query)
  $GetNext = $false
  do {
    if ($GetNext) { $queryResults = $query_service_helper.GlobalSessionQueryService_GetNext($services1, $queryResults.id) }
    $SessionList += $queryResults.results
    $GetNext = $true
  } while ($queryResults.remainingCount -gt 0)
    $query_service_helper.GlobalSessionQueryService_Delete($services1, $queryresults.id)

}
return $sessionlist

Screenshots from the timing:

so the getcountwithspec method is about 2.5 seconds faster but the data in the globalsession is way more extensive and usable for all kinds of management overviews.

[API]Resetting Desktops

This is the first post in a series of shorts that I will be posting about various methods that you can use with the VMware Horizon API’s. This time it will be about resetting desktops. When looking at the API Explorer you’ll see that there are two ways do do this from the machine service.

So the first is for a single VDI desktop and the latter for multiple.

First we need to get a list of vm’s I will be using the machines in pod1pool02 as victims for this post.

$queryservice=new-object VMware.Hv.QueryServiceService
$defn=New-Object VMware.Hv.QueryDefinition
$defn.QueryEntityType="MachineNamesView"
$filter=new-object VMware.Hv.QueryFilterContains
$filter.MemberName='base.name'
$filter.Value="Pod1Pool2"
$defn.filter=$filter
$results=($queryservice.QueryService_Query($services1, $defn)).results

with this result:

From this we’ll make a variable with all of them and one with a single one

$singlevm=$results | select-object -first 1
$multiplevms=$results

Before I will reset the single VM I will show the state of all the vm’s.

($queryservice.QueryService_Query($services1, $defn)).results.base.basicstate

And now let’s reset the vm.

$services1.machine.machine_reset($singlevm.id)

Since this is an instant clone you’ll see provisioned and not reset. Now let’s reset the rest as well.

$services1.Machine.Machine_ResetMachines($multiplevms.id)

And this method will work for all managed vdi desktops full, linked or instant clones.

Applying Golden Images for VDI & RDS cloned pools using the Horizon View API’s

Recently I came up with the idea to create a script to apply new Golden Images against the various types of desktop pools and farms that we have in Horizon View. This was something that I thought was not available from the vmware.hv.helper module but after some research I did find that it it available from the module by using start-hvpool and start-hvfarm. No those are not the best names for the functions in my opinion. This wouldn’t stop me for creating this post though on how to apply the images using api’s only since the module uses mapentries and I still hate those. I will cover full clones and defining a new image without recomposing in a next post since that requires updating the pools.

Let’s take a look at the api explorer on what is needed to recompose or push an image.

 

The DesktopPushImageSpec for instant clones has a comparable setup with some nuance differences.

For RDS farms the linked clones spec is equal to the desktop spec but for instant clones there’s a rather big difference but I will cover that later on.

So the common steps for most types of applying the golden image are:

  • Selecting the Desktop Pool or RDS Farm
  • getting the id for the vcenter or datacenter where the parent VM lives
  • getting the id of the Parent VM
  • getting the id of the snapshot to use
  • getting the id’s of the machines in the desktop pool (Linked Clones only)
  • Select date & time for the recompose or imagepush (if required)
  • combine the above info into a spec to recompose or imagepush
  • Apply the recompose or ImagePush

Each step uses information from the step above it.

Selecting the Desktop Pool or RDS Farm

This can be done using queries. For desktops we user the desktopsummaryview definition and for farms farmsummaryview.

$poolname="pod02_LC"
$queryservice=New-Object VMware.Hv.QueryServiceService
$defn=new-object VMware.Hv.QueryDefinition
$defn.QueryEntityType='DesktopSummaryView'
$defn.Filter= New-Object VMware.Hv.QueryFilterEquals -property @{'MemberName'='desktopSummaryData.name'; 'value'=$poolname}
$desktoppool=($queryservice.QueryService_Create($services1, $defn)).results

And for a farm

$farmname="rds_IC"
$queryservice=New-Object VMware.Hv.QueryServiceService
$defn=new-object VMware.Hv.QueryDefinition
$defn.QueryEntityType='FarmSummaryView'
$defn.Filter= New-Object VMware.Hv.QueryFilterEquals -property @{'MemberName'='data.name'; 'value'=$farmname}
$farm=($queryservice.QueryService_Create($services1, $defn)).results

getting the id for the vcenter or datacenter where the parent VM lives

For desktops this is a property of the $desktoppool object we have now

$desktopppoolvcenterid=$desktoppool.desktopsummarydata.VirtualCenter

For automated farms we need a small extra step since it is not property for the summary data we we can get it by doing a farm_get with the id we received from the query

$farmvcenterid=($services1.Farm.Farm_Get($farm.id)).automatedfarmdata.VirtualCenter

getting the id of the Parent VM

Using the vcenterid as done below we are able to list all vm’s in the vCenter that might be a Golden Image using

$services1.BaseImageVm.BaseImageVm_List($desktopppoolvcenterid)

You might be able to see it but this gives a list of all VM’s in the vCenter, sadly there is no query for this yet even though that would be really useful. If you know the exact name you can select on that but if you look at the IncompatibleReasons property there’s info to filter (if you want to create a menu for example)

$baseimagevmlist=$services1.BaseImageVm.BaseImageVm_List($desktopppoolvcenterid)
$baseimagevmlist.IncompatibleReasons

InUseByDesktop is a usable one for instantclones. I don’t know why InUseByLinkedCloneDesktop doesn’t give any true values even though I have one pool with linked clones, viewcomposerreplica does work. I have filtered this on some of the more obvious ones and end up with both my golden images for Windows 7 & Server 2016

$baseimagevmlist |where {$_.IncompatibleReasons.InUseByDesktop -eq $false -and $_.IncompatibleReasons.InstantInternal -eq $false -and $_.IncompatibleReasons.ViewComposerReplica -eq $false}

I will do it easy and select on the name for now

$Desktopbaseimagevm=$baseimagevmlist | where {$_.name -eq "GI_H72"}
$farmbaseimagevm=$baseimagevmlist | where {$_.name -eq "rds_template"}

getting the id of the snapshot to use

With the baseimagevmid we can utilize the baseimagesnapshot method to get the id for the snapshot.

$desktopsnapshotlist=$services1.BaseImageSnapshot.BaseImageSnapshot_List($Desktopbaseimagevm.id)
$farmsnapshotlist=$services1.BaseImageSnapshot.BaseImageSnapshot_List($farmbaseimagevm.id)

In this there is also an IncompatibleReasons property but that doesn’t give a lot of information so we’ll need to filter on name.

$desktopLCsnapshot=$desktopsnapshotlist | where-object {$_.name -eq "gi_linked"}
$desktopICsnapshot=$desktopsnapshotlist | where-object {$_.name -eq "snap_gi"}
$farmsnapshot=$farmsnapshotlist | where-object {$_.name -eq "gi_rds_2016"}

getting the id’s of the machines in the desktop pool

As you’ll see later in the spec there’s a requirement to list the machine id’s for the pool if you want to do a recompose. These can be grabbed by doing a query

$queryService = New-Object VMware.Hv.QueryServiceService
$defn = New-Object VMware.Hv.QueryDefinition
$defn.queryEntityType = 'MachineSummaryView'
$defn.filter=New-Object VMware.Hv.QueryFilterEquals -property @{'memberName'='base.desktop'; 'value'=$desktoppool.id}
$QueryResults=$queryService.Queryservice_create($Services1, $defn)
$desktopmachinelist=$queryresults.results

For Linked Clone RDS farms you ned to use the QueryEntityType of RDSServerSummaryView but since I don’t have those in my lab I can only show the theory

$queryService = New-Object VMware.Hv.QueryServiceService
$defn = New-Object VMware.Hv.QueryDefinition
$defn.queryEntityType = 'RDSServerSummaryView'
$defn.filter=New-Object VMware.Hv.QueryFilterEquals -property @{'memberName'='base.desktop'; 'value'=$farm.id}
$QueryResults=$queryService.Queryservice_create($Services1, $defn)
$farmmachinelist=$queryresults.results

Settings date and time for the action

It’s not required to set a date and but is very usable if you want to schedule an action. Please be aware that this is based on us format for day and time so mm-dd-yyyy otherwise I would have scheduled it for august.

$datetime=[DateTime]"02-08-2019 10:00:00AM"

The [DateTime] converts the string that follows it to a variable of the type day and time

combine the above info into a spec to recompose or imagepush.

To build the spec we first need to declare a new object with new-object vmware.hv.DesktopRecomposeSpec please be aware that for this name you need to look at the data object in the API explorer and not the class.

$desktoprecomposespec=new-object vmware.hv.DesktopRecomposeSpec
$desktoprecomposespec.ParentVm=$desktopbaseimagevm.id
$desktoprecomposespec.Snapshot=$desktopLCsnapshot.id
$desktoprecomposespec.StartTime=$datetime
$desktoprecomposespec.LogoffSetting="WAIT_FOR_LOGOFF"
$desktoprecomposespec.StopOnFirstError=$true
$desktoprecomposespec.Machines=$desktopmachinelist.id

For the instant clone image push there’s an extra layer required for the settings

$desktopimagepushspec=new-object VMware.Hv.DesktopPushImageSpec
$desktopimagepushspec.settings=new-object vmware.hv.DesktopPushImageSettings
$desktopimagepushspec.ParentVm=$desktopbaseimagevm.id
$desktopimagepushspec.snapshot=$desktopICsnapshot.id
$desktopimagepushspec.settings.StartTime=$datetime
$desktopimagepushspec.settings.LogoffSetting="WAIT_FOR_LOGOFF"
$desktopimagepushspec.settings.StopOnFirstError=$true

The recompose for a linked clone rds farm is similar to the desktop linked clone.

$farmrecomposespec=new-object vmware.hv.farmRecomposeSpec
$farmrecomposespec.ParentVm=$farmbaseimagevm.id
$farmrecomposespec.Snapshot=$farmLCsnapshot.id
$farmrecomposespec.StartTime=$datetime
$farmrecomposespec.LogoffSetting="WAIT_FOR_LOGOFF"
$farmrecomposespec.StopOnFirstError=$true
$farmrecomposespec.Machines=$farmmachinelist.id

For RDS instant clone farms the pushing of a new image is part of the maintenance schedule that can be done immediate or recurring. I will do the recurring option for now since rds hosts needs to be refreshed every once in a while anyway. There’s some options inside the settings that are explained in the api explorer.

$farmmaintenancespec=new-object vmware.hv.FarmMaintenanceSpec
$farmmaintenancespec.recurringMaintenanceSettings=new-object vmware.hv.FarmRecurringMaintenanceSettings
$farmmaintenancespec.imageMaintenanceSettings=new-object vmware.hv.FarmImageMaintenanceSettings
$farmmaintenancespec.maintenanceMode="RECURRING"
$farmmaintenancespec.scheduledTime=$datetime
$farmmaintenancespec.logoffsetting="WAIT_FOR_LOGOFF"
$farmmaintenancespec.stopOnFirstError=$true
$farmmaintenancespec.recurringMaintenanceSettings.startTime="23:00"
$farmmaintenancespec.recurringMaintenanceSettings.maintenancePeriod="WEEKLY"
$farmmaintenancespec.recurringMaintenanceSettings.startInt=1
$farmmaintenancespec.ImageMaintenanceSettings.parentVm=$farmbaseimagevm.id
$farmmaintenancespec.ImageMaintenanceSettings.snapshot=$farmsnapshot.id

Apply the recompose or ImagePush

This is the easiest part of the spec’s have been build properly.

Please note that the variables for the pools I use are a bit different to show the linked and instant clone pools

$services1.Desktop.Desktop_Recompose($linkedclonepool.id,  $desktoprecomposespec)
$services1.Desktop.Desktop_SchedulePushImage($instantclonepool.id, $desktopimagepushspec)
$services1.farm.Farm_ScheduleMaintenance($farm.id, $farmmaintenancespec)

No visible feedback but it’s visible from the admin console (sadly not all tasks can be gotten from the api’s yet 🙁 )

That’s it for now but expect future posts about full clones, setting a default image for linked clones without recompose and maybe a complete script that does it all for you.