[HorizonAPI] Getting started with the Horizon REST api

Until now all of my blogging about the Horizon api’s was about consuming the SOAP api using PowerCLI. Since a couple of releases Horizon also has a REST api and since 7.12 we are also able to change some settings using that. So now it’s time for me to dive into the Horizon REST api’s. I will consume them using Powershell since I am the most comfortable using that but you can use whatever method you prefer..

The REST api is just like the soap api documented at the VMware{CODE} api explorer.

First of all we need to create an accesstoken, we can do this by using some code that I simply stole from Andrew Morgan because why would I re-invent the wheel? From his git repository I grabbed three basic functions: get-HRHeader, Open-HRConnection and close-hrconnection. there’s also a refresh-hrconnection but I won’t need that for now.

function Get-HRHeader(){
    param($accessToken)
    return @{
        'Authorization' = 'Bearer ' + $($accessToken.access_token)
        'Content-Type' = "application/json"
    }
}

function Open-HRConnection(){
    param(
        [string] $username,
        [string] $password,
        [string] $domain,
        [string] $url
    )

    $Credentials = New-Object psobject -Property @{
        username = $username
        password = $password
        domain = $domain
    }

    return invoke-restmethod -Method Post -uri "$url/rest/login" -ContentType "application/json" -Body ($Credentials | ConvertTo-Json)
}

function Close-HRConnection(){
    param(
        $accessToken,
        $url
    )
    return Invoke-RestMethod -Method post -uri "$url/rest/logout" -ContentType "application/json" -Body ($accessToken | ConvertTo-Json)
}
$accessToken = Open-HRConnection -username $username -password $password -domain $Domain -url $url

But we can’t do anything with only these functions, somehow we also need to supply username and password

$url = read-host -prompt "Connection server url"
$username = read-host -prompt "Username"
$password = read-host -prompt "Password" -AsSecureString
$Domain = read-host -Prompt "Domain"

$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password)
$UnsecurePassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)

(I am grabbing it from the command line here but when I run the scripts I have my creds hardcoded to make my life for the duration of this blog post a bit easier)

Next up is actually getting some data. The first thing that I wil do is show the connection servers. This can be done with the following API call. The part after -uri “$url/rest/ is what you can find int he api explorer. The method is the method also shown in the api explorer.

Invoke-RestMethod -Method Get -uri "$url/rest/monitor/connection-servers" -ContentType "application/json" -Headers (Get-HRHeader -accessToken $accessToken)

and the result:

Since one of the few things that you can already change using the rest api’s are the general settings I will take those as the next example

Invoke-RestMethod -Method Get -uri "$url/rest/config/v1/settings" -ContentType "application/json" -Headers (Get-HRHeader -accessToken $accessToken)

This works but I can’t say that it’s really usable. Now this is not the first time I do something with REST api’s (haven’t done it a lot though to be honest) so I know this can easily be converted to json to make it visible. What I will do is that I put it in a variable first.

$settings=Invoke-RestMethod -Method Get -uri "$url/rest/config/v1/settings" -ContentType "application/json" -Headers (Get-HRHeader -accessToken $accessToken)
$settings | ConvertTo-Json

Now this DOES look usable! Let’s take a look what is under general_settings

$settings.general_settings

Let’s say I want to change the forced logoff message

$settings.general_settings.forced_logoff_message="Get lost, the Bastard Operator From Hell is here."

Now my variable has the change but I need to send this to the server. This can be done using a put method and the settings variable has to be added as json. The second line is to pull the new settings from my connection server showing it directly in a json format.

 

Invoke-RestMethod -Method Put -uri "$url/rest/config/v1/settings" -ContentType "application/json" -Headers (Get-HRHeader -accessToken $accessToken) -body ($settings | ConvertTo-Json)
Invoke-RestMethod -Method Get -uri "$url/rest/config/v1/settings" -ContentType "application/json" -Headers (Get-HRHeader -accessToken $accessToken) | ConvertTo-Json

and in the admin interface:

That’s it for my 1ste blog post about the horizon REST api’s hopefully it’s useful! Below is an example of the script that I used.

$url = read-host -prompt "Connection server url" 
$username = read-host -prompt "Username" 
$password = read-host -prompt "Password" -AsSecureString 
$Domain = read-host -Prompt "Domain" 

#$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password) 
#$UnsecurePassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)

function Get-HRHeader(){
    param($accessToken)
    return @{
        'Authorization' = 'Bearer ' + $($accessToken.access_token)
        'Content-Type' = "application/json"
    }
}
function Open-HRConnection(){
    param(
        [string] $username,
        [string] $password,
        [string] $domain,
        [string] $url
    )

    $Credentials = New-Object psobject -Property @{
        username = $username
        password = $password
        domain = $domain
    }

    return invoke-restmethod -Method Post -uri "$url/rest/login" -ContentType "application/json" -Body ($Credentials | ConvertTo-Json)
}

function Close-HRConnection(){
    param(
        $accessToken,
        $url
    )
    return Invoke-RestMethod -Method post -uri "$url/rest/logout" -ContentType "application/json" -Body ($accessToken | ConvertTo-Json)
}

$accessToken = Open-HRConnection -username $username -password $password -domain $Domain -url $url

Invoke-RestMethod -Method Get -uri "$url/rest/monitor/connection-servers" -ContentType "application/json" -Headers (Get-HRHeader -accessToken $accessToken)

[Update 23-04-2020]Adding vCenter server to Horizon View using the api’s

Update

I don’t know since what version but somewhere this script stopped working because VMware change some things. In 7.8 there was a change about the thumbprint algorithm to DER_BASE64_PEM so it might have started there. Another change is that in the sslcertthumbprint field they stopped using the thumbprint but actually add the entire certificate.

What is needed to fix this?

Replace:

$spec.CertificateOverride=($services.Certificate.Certificate_Validate($spec.serverspec)).thumbprint

with

$spec.CertificateOverride.SslCertThumbprint=($services.Certificate.Certificate_Validate($spec.serverspec)).certificate
$spec.CertificateOverride.sslCertThumbprintAlgorithm = "DER_BASE64_PEM"

and you should be good. I have already updated the version of the script below.

A big thank you to Mark Brookfield for asking me about this

/update

Yesterday Sean Massey (https://thevirtualhorizon.com/) asked me if it was possible to add a vCenter server + some other things to Horizon View using the api’s. With a quick look at the api explorer I confirmed this should be possible. The other things he asked I will put in a separate blogpost.

It looks like a simple matter of building the spec and I should be good. In the end it turned out to be a bit more work then expected. Some items are not required according to the api explorer but should at least be called in the spec (set them to something empty) while others can safely be left away. The automatic generated ssl certs in my lab also turned out to be a pita. First I copied them from a current spec and later I downloaded the certificate on the Connection server itself and read that cert. Andrew Morgan (http://andrewmorgan.ie/)from VMware helped me out with this by showing their internal script that they use. It turned out that except for the SSL certs I was on the right path. As usual I will add this functionality to the vmware.hv.helper but since that might take a while I decided to create a useful script

$hvServer = $global:DefaultHVServers[0]
$services=  $hvServer.ExtensionData

# Create required objects

$spec=new-object VMware.Hv.VirtualCenterSpec
$spec.serverspec=new-object vmware.hv.serverspec
$spec.viewComposerData=new-object VMware.Hv.virtualcenterViewComposerData

$spec.Certificateoverride=new-object vmware.hv.CertificateThumbprint
$spec.limits=new-object VMware.Hv.VirtualCenterConcurrentOperationLimits
$spec.storageAcceleratorData=new-object VMware.Hv.virtualcenterStorageAcceleratorData

# vCenter Server specs

$spec.ServerSpec.servername="pod2vcr1.loft.lab"        # Required, fqdn for the vCenter server
$spec.ServerSpec.port=443                                 # Required
$spec.ServerSpec.usessl=$true                             # Required
$spec.ServerSpec.username="[email protected]"   # Required [email protected]
$vcpassword=read-host "vCenter User password?" -assecurestring
$temppw = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($vcPassword)
$PlainvcPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($temppw)
$vcencPassword = New-Object VMware.Hv.SecureString
$enc = [system.Text.Encoding]::UTF8
$vcencPassword.Utf8String = $enc.GetBytes($PlainvcPassword)
$spec.ServerSpec.password=$vcencPassword
$spec.ServerSpec.servertype="VIRTUAL_CENTER"

# Description & Displayname, neither is required to be set

#$spec.description="description"              # Not Required
#$spec.displayname="virtualcenterdisplayname" # Not Required
$spec.CertificateOverride=($services.Certificate.Certificate_Validate($spec.serverspec)).thumbprint
$spec.CertificateOverride.SslCertThumbprint=($services.Certificate.Certificate_Validate($spec.serverspec)).certificate
$spec.CertificateOverride.sslCertThumbprintAlgorithm = "DER_BASE64_PEM"


# Limits
# Only change when you want to change the default values. It is required to set these in the spec

$spec.limits.vcProvisioningLimit=20
$spec.Limits.VcPowerOperationsLimit=50
$spec.limits.ViewComposerProvisioningLimit=12
$spec.Limits.ViewComposerMaintenanceLimit=20
$spec.Limits.InstantCloneEngineProvisioningLimit=20

# Storage Accelerator data

$spec.StorageAcceleratorData.enabled=$false
#$spec.StorageAcceleratorData.DefaultCacheSizeMB=1024   # Not Required

# Cmposer
# most can be left empty but they need to be set otherwise you'll get a xml error

$spec.ViewComposerData.viewcomposertype="STANDALONE"  # DISABLED for none, LOCAL_TO_VC for installed with the vcenter and STANDALONE for s standalone composer


if ($spec.ViewComposerData.viewcomposertype -ne "DISABLED"){
    $spec.ViewComposerData.ServerSpec=new-object vmware.hv.serverspec
    $spec.ViewComposerData.CertificateOverride=new-object VMware.Hv.CertificateThumbprint
    $cmppassword=read-host "Composer user password?" -assecurestring
    $temppw = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($cmpPassword)
    $PlaincmpPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($temppw)
    $cmpencPassword = New-Object VMware.Hv.SecureString
    $enc = [system.Text.Encoding]::UTF8
    $cmpencPassword.Utf8String = $enc.GetBytes($PlaincmpPassword)
    $spec.ViewComposerData.ServerSpec.password=$cmpencPassword
    $spec.ViewComposerData.ServerSpec.servername="pod2cmp1.loft.lab"
    $spec.ViewComposerData.ServerSpec.port=18443
    $spec.ViewComposerData.ServerSpec.usessl=$true
    $spec.ViewComposerData.ServerSpec.username="[email protected]"
    $spec.ViewComposerData.ServerSpec.servertype="VIEW_COMPOSER"

    $spec.ViewComposerData.CertificateOverride=($services.Certificate.Certificate_Validate($spec.ViewComposerData.ServerSpec)).thumbprint
    $spec.ViewComposerData.CertificateOverride.sslCertThumbprint = ($services.Certificate.Certificate_Validate($spec.ViewComposerData.ServerSpec)).certificate
    $spec.ViewComposerData.CertificateOverride.sslCertThumbprintAlgorithm = "DER_BASE64_PEM"
}


# Disk reclamation, this is required to be set to either $false or $true
$spec.SeSparseReclamationEnabled=$false 

# This will create the connection
$services.VirtualCenter.VirtualCenter_Create($spec)

 

Looking at the output it will only ask for the vCenter user’s password and if a Composer server is set for that user’s password.

 

[HorizonAPI] Configuring the Horizon event database in code

Last week Mark Brookfield asked the question if it is possible to configure the event database in code. My answer was that I thought it should be possible until Stephen Jesse pointed me to the the vmware.hv.helper where there is the set-hveventdatabase cmdlet for this. When looking at the code I noticed something familiar:

.NOTES
Author                      : Wouter Kursten
Author email                : [email protected]
Version                     : 1.0

===Tested Against Environment====
Horizon View Server Version : 7.4
PowerCLI Version            : PowerCLI 10
PowerShell Version          : 5.0

So that’s why I knew it was possible! A good reason to create a quick blogpost though. Mark made a nice script for himself with variables and all those fancy things but I just want to quickly show how you can do it.

$hvedbpw=read-host -AsSecureString
$temppw=[System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($hvedbpw)
$PlainevdbPassword=[System.Runtime.InteropServices.Marshal]::PtrToStringAuto($temppw)
$dbupassword=New-Object VMware.Hv.SecureString
$enc=[system.Text.Encoding]::UTF8
$dbupassword.Utf8String=$enc.GetBytes($PlainevdbPassword)
$eventservice=new-object vmware.hv.eventdatabaseservice
$eventservicehelper=$eventservice.getEventDatabaseInfoHelper()
$eventsettings=new-object VMware.Hv.EventDatabaseEventSettings
$eventdatabase=new-object VMware.Hv.EventDatabaseSettings
$eventsettings.ShowEventsForTime="TWO_WEEKS"
$eventsettings.ClassifyEventsAsNewForDays=2
$eventdatabase.Server="labsql01.magneet.lab"
$eventdatabase.type="SQLSERVER"
$eventdatabase.port=1433
$eventdatabase.name="pod1_events"
$eventdatabase.username="sa_view"
$eventdatabase.password=$dbupassword
$eventservicehelper.setDatabase($eventdatabase)
$eventservicehelper.setsettings($eventsettings)
$eventservice.update($hvservice,$eventservicehelper)

The first three line make it possible to not use a plaintext password. If you don’t care about that you can remove those and declare something for $plainevdbpassword.

For the $eventsettings.ShowEventsForTime for time there are several options (same as in the gui) these are:

ONE_WEEK,TWO_WEEKS,THREE_WEEKS,ONE_MONTH,TWO_MONTHS,THREE_MONTHS,SIX_MONTHS
Yes, they are all in capitals!

To show how this works I will first clear the current database.

$hvservice.EventDatabase.EventDatabase_Clear()
$hvservice.EventDatabase.EventDatabase_Get()

Yes this is one of those exceptions where a service_get doesn’t need an id.

Now I run the script with a new _get to show the results.

If you are interested in the details:

[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!

[HorizonAPI] Changing the amount of desktops or RDS hosts in a pool/farm

Sometimes there is a need to change the amount of desktops/rds hosts in a pool/farm. Since doing this in the GUI sucks (although that seems to have gotten slightly better with 7.11) I prefer to do it using the API’s. Let’s start with a Desktop pool.

The easiest way to change pool settings is to use the helper function of a service. After connecting to the connection server we first need to query for the ID of the desktoppool that we need to change.

[VMware.Hv.QueryServiceService]$queryService = New-Object VMware.Hv.QueryServiceService
[VMware.Hv.QueryDefinition]$defn = New-Object VMware.Hv.QueryDefinition
$defn.queryEntityType = 'DesktopSummaryView'
$defn.Filter = New-Object VMware.Hv.QueryFilterEquals -property @{'memberName'='desktopSummaryData.name'; 'value' = "Pod01_Pool01"}
[array]$queryResults= ($queryService.queryService_create($HVservice, $defn)).results
$hvpoolid=$queryResults.id

To actually change the pool it’s the best to use the helper function of a service so we first put the desktopservice into an object

$desktopservice=new-object vmware.hv.DesktopService

The next step is to read the current settings into another object.

$desktophelper=$desktopservice.read($HVservice, $HVPoolID)

If you want to see what’s in here we’ll just do this

$desktophelper | get-member

With the get helper method’s it’s possible to get things while you can change them with their set counterpart. Don’t forget to use brackets when you want to go deeper.

$desktophelper.getAutomatedDesktopDataHelper() | get-member

And we can go on and on with this but I happen to already have found where the amount of desktops is listed.

$desktophelper.getAutomatedDesktopDataHelper().getVmNamingSettingsHelper().getPatternNamingSettingsHelper() | get-member

Let’s take a look at the getMaxNumberOfMachines method.

$desktophelper.getAutomatedDesktopDataHelper().getVmNamingSettingsHelper().getPatternNamingSettingsHelper().getMaxNumberOfMachines()

And we can actually use this with setMaxNumberOfMachines

$desktophelper.getAutomatedDesktopDataHelper().getVmNamingSettingsHelper().getPatternNamingSettingsHelper().setMaxNumberOfMachines(10)

But nothing has changed yet (and yes I am lazy so I will show it using the vmware.hv.helper module.

(get-hvpool -PoolName pod01_pool01).automateddesktopdata.VmNamingSettings.PatternNamingSettings

To apply the change to 10 vm’s we need to apply the helper using the update method

$desktopservice.update($hvservice, $desktophelper)

And when we check this with get-hvpool.

And we can do almost the same for RDS farms just a few details that are different in the naming of various objects.

[VMware.Hv.QueryServiceService]$queryService = New-Object VMware.Hv.QueryServiceService
[VMware.Hv.QueryDefinition]$defn = New-Object VMware.Hv.QueryDefinition
$defn.queryEntityType = 'FarmSummaryView'
$defn.Filter = New-Object VMware.Hv.QueryFilterEquals -property @{'memberName'='data.name'; 'value' = "pod1_rds_IC"}
[array]$queryResults= ($queryService.queryService_create($HVservice, $defn)).results
$hvfarmid=($queryResults).id
(Get-HVFarm -FarmName pod1_rds_ic).automatedfarmdata.RdsServerNamingSettings.PatternNamingSettings
[VMware.Hv.FarmService]$farmservice=new-object vmware.hv.FarmService
$farmhelper=$farmservice.read($HVservice, $HVFarmID)
$farmhelper.getAutomatedFarmDataHelper().getRdsServerNamingSettingsHelper().getPatternNamingSettingsHelper().setMaxNumberOfRDSServers(3)
$farmservice.update($HVservice, $farmhelper)

 

New Horizon API explorer posted (new queries!)

While watching the VMworld US 2019 video of Sean Massey presenting about getting started with the Horizon API’s I decided to check if the API explorer has been updated. To my surprise it was and it is good to see that several queries have been added besides the method’s that I previously found.

The new queries that I found are:

  • DesktopAssignmentView
    • Description:
      • Desktop id + Desktop assignment data which will include desktop pool information, operation system, global entitlement.
  • DesktopHealthInfo
    • Description:
      • Desktop health Information. This data will be populated only for the desktops which support application remoting.
  • GlobalEntitlementSummaryView
    • Description:
      • Summary information about Global Entitlements.
  • MachineSummaryView
    • Description:
      • This View includes summary data of all entities related to this Machine

So the DesktopAssignmentView seems to give a lot of similar data to what the DesktopSummaryData query already gives. They both give global entitlement data plus user assignment data. The big difference is that it gives way more detailed information about the desktop pool itself like vGPU settings. The names should have been DesktopInfo in my opinion.

Desktophealthinfo is created for the new Windows 10 App remoting and the monitoring for that. The globalentitlementsummaryview and machinesummaryview are linked to GlobalEntitlementInfo and MachineDetailsView from which they give a subset of data.

I hope to have a new blog post soon with more detailed information of what the new method’s and queries will bring but I wanted to update you with the fact that the api explorer has been updated as soon as possible.

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

[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
[email protected]($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)
[email protected]()


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.