PowerCLI Script to Horizon Desktop Pool machine counts & provisioning type

A long time ago in a galaxy far way I used to be a freelancer for ControlUp creating Script Actions and that actually helped me in securing a job with this great company. One of the first SBA’s that I made was one to change the amount of machines in a desktop pool. Recently one of our customers asked if it was possible to also control the minimum amount and powered on machines. Today I have updated this sba and it will be published shortly (if it hasn’t been published when you read this hit me up for a preview sba xml file). I took it a step further though and added the option to change the provisioning type. With a small security piece in place to prevent you from accidentally changing the type. Besides this being published as an sba I have also published a script that can be used from any computer using PowerCLI.

To be clear: this script uses PowerCLI with the SOAP api’s so it should work with almost all Horizon Versions since 7.5. If I find the time I will create a REST version but that will only work with Horizon 8 2111 and above.

The parameters:

  • Credentials : This optional parameter needs a credential object from get-credential. If you don’t supply it you will get a popup for credentials
  • HVDesktopPoolname: Required parameter with the name of the Desktop Pool to change
  • HVConnectionServerFQDN: Required parameter with the FQDN for a connection server to connect to
  • Provisioningtype: Optional Parameter if you want to change the provisioning type. Has to be either UP_FRONT or ON_DEMAND
  • ChangeProvisioningtype: optional parameter that needs either $true or $false and defaults to $false if not provided. The script will error if you set this to false while the provisionintype is different from the current one.
  • maxNumberOfMachines: required parameter with the maximum amount of machines
  • minNumberOfMachines: required parameter when using ON_DEMAND as provisioning type for the minimum amount of machines. Validation is done later in the script so it will not ask for an amount if not provided.
  • numberOfSpareMachines: required parameter when using ON_DEMAND as provisioning type for the minimum amount of powered on machines. Validation is done later in the script so it will not ask for an amount if not provided.

Usage:

Set-Desktoppoolmachinecountandtype.ps1 -Credentials $creds  -HVDesktopPoolname Pod01-Pool02 -HVConnectionServerFQDN pod1cbr1.loft.lab -Provisioningtype ON_DEMAND -maxNumberOfMachines 10 -minNumberOfMachines 3 -ChangeProvisioningtype $true -numberOfSpareMachines 4

or

Set-Desktoppoolmachinecountandtype.ps1 -Credentials $creds  -HVDesktopPoolname Pod01-Pool02 -HVConnectionServerFQDN pod1cbr1.loft.lab -Provisioningtype UP_FRONT -maxNumberOfMachines 10 -ChangeProvisioningtype $false

there’s an option to add -verbose for a bit more visibility, I will use this in my screenshots:

Changing the count for an pool that provisions all desktops up front

Changing the count & type but not setting the changeprovisioningtype to $true

Corrected changeprovisioningtype

As usual the script is available on Github or down below

<#
    .SYNOPSIS
    Changes the amount of Desktops in a Horizon Desktop Pool

    .DESCRIPTION
    This script changes the amount of Desktops in a Horizon Desktop Pool.

    .PARAMETER Credential
    Mandatory: No
    Type: PSCredential
    Object with credentials for the connection server with domain\username and password. If not supplied the script will ask for user and password.

    .PARAMETER HVDesktopPoolname
    Name of the Desktop Pool to update

    .PARAMETER Provisioningtype
    Use ON_DEMAND to provision all desktops up front (will ignore minNumberOfMachines and numberOfSpareMachines

    .PARAMETER ChangeProvisioningtype
    User either True or False to enable or disable the changing of the provisioning type

    .PARAMETER maxNumberOfMachines
    Maximum number of desktops in the pool

    .PARAMETER minNumberOfMachines
    Minimum number of desktops in the pool

    .PARAMETER numberOfSpareMachines
    Minimum number of powered on desktops in the pool

    .PARAMETER HVConnectionServerFQDN
    FQDN for a connectionserver in the pod the pool belongs to.

    .EXAMPLE
    Set-Desktoppoolmachinecountandtype.ps1 -Credentials $creds  -HVDesktopPoolname Pod01-Pool02 -HVConnectionServerFQDN pod1cbr1.loft.lab -Provisioningtype ON_DEMAND -maxNumberOfMachines 10 -minNumberOfMachines 3 -ChangeProvisioningtype $true -numberOfSpareMachines 4
    
    .EXAMPLE
    Set-Desktoppoolmachinecountandtype.ps1 -Credentials $creds  -HVDesktopPoolname Pod01-Pool02 -HVConnectionServerFQDN pod1cbr1.loft.lab -Provisioningtype UP_FRONT -maxNumberOfMachines 10 -ChangeProvisioningtype $false

    .EXAMPLE
    Set-Desktoppoolmachinecountandtype.ps1 -Credentials $creds  -HVDesktopPoolname Pod01-Pool02 -HVConnectionServerFQDN pod1cbr1.loft.lab -maxNumberOfMachines 10

    .NOTES
    This script requires VMWare PowerCLI to be installed on the machine running the script.
    PowerCLI can be installed through PowerShell (PowerShell version 5 or higher required) by running the command 'Install-Module VMWare.PowerCLI -Force -AllowCLobber -Scope AllUsers' Or by using the 'Install VMware PowerCLI' script.
    Credentials can be set using the 'Prepare machine for Horizon View scripts' script.

    Modification history:   12/12/2019 - Wouter Kursten - First version
                            26/03/2022 - Wouter Kursten - Added options for on demand provisioning

    .LINK
    https://code.vmware.com/web/tool/11.3.0/vmware-powercli


    .COMPONENT
    VMWare PowerCLI

#>

[CmdletBinding()]
Param
(
    [Parameter(Mandatory=$false,
    HelpMessage='Credential object as domain\username with password' )]
    [PSCredential] $Credentials,

    [Parameter(
        Mandatory=$true,
        HelpMessage='Name of the Desktop Pool'
    )]
    [ValidateNotNullOrEmpty()]
    [string] $HVDesktopPoolname,

    [Parameter(
        Mandatory=$true,
        HelpMessage='FQDN for the connection server'
    )]
    [ValidateNotNullOrEmpty()]
    [string] $HVConnectionServerFQDN,

    [Parameter(
        Mandatory=$false,
        HelpMessage='Provisioning type'
    )]
    [ValidateSet("UP_FRONT","ON_DEMAND")]
    [string] $Provisioningtype,

    [Parameter(
        Mandatory=$false,
        HelpMessage='Change Provisioning type?'
    )]
    [ValidateSet("True","False")]
    [bool] $ChangeProvisioningtype = $false,

    [Parameter(
        Mandatory=$true,
        HelpMessage='Maximum number of machines in the desktop.'
    )]
    [ValidateNotNullOrEmpty()]
    [int] $maxNumberOfMachines,

    [Parameter(
        Mandatory=$false,
        ParameterSetName = 'ondemand',
        HelpMessage='The minimum number of machines to have provisioned if on demand provisioning is selected. Will be ignored if provisioningtype is set to UP_FRONT.'
    )]
    [ValidateNotNullOrEmpty()]
    [int] $minNumberOfMachines,

    [Parameter(
        Mandatory=$false,
        ParameterSetName = 'ondemand',
        HelpMessage='Number of spare powered on machines. Will be ignored if provisioningtype is set to UP_FRONT.'
    )]
    [ValidateNotNullOrEmpty()]
    [int] $numberOfSpareMachines
)

$ErrorActionPreference = 'Stop'

function Load-VMWareModules {
    <# Imports VMware modules
    NOTES:
    - The required modules to be loaded are passed as an array.
    - In versions of PowerCLI below 6.5 some of the modules can't be imported (below version 6 it is Snapins only) using so Add-PSSnapin is used (which automatically loads all VMWare modules)
    #>

    param (
        [parameter(Mandatory = $true,
            HelpMessage = "The VMware module to be loaded. Can be single or multiple values (as array).")]
        [array]$Components
    )

    # Try Import-Module for each passed component, try Add-PSSnapin if this fails (only if -Prefix was not specified)
    # Import each module, if Import-Module fails try Add-PSSnapin
    foreach ($component in $Components) {
        try {
            $null = Import-Module -Name VMware.$component
        }
        catch {
            try {
                $null = Add-PSSnapin -Name VMware
            }
            catch {
                write-error 'The required VMWare modules were not found as modules or snapins. Please check the .NOTES and .COMPONENTS sections in the Comments of this script for details.'
                exit
            }
        }
    }
}

function Connect-HorizonConnectionServer {
    param (
        [parameter(Mandatory = $true,
            HelpMessage = "The FQDN of the Horizon View Connection server. IP address may be used.")]
        [string]$HVConnectionServerFQDN,
        [parameter(Mandatory = $true,
            HelpMessage = "The PSCredential object used for authentication.")]
        [PSCredential]$Credential
    )
    # Try to connect to the Connection server
    try {
        Connect-HVServer -Server $HVConnectionServerFQDN -Credential $Credential
    }
    catch {
        write-error "There was a problem connecting to the Horizon View Connection server: $_."
        exit
    }
}

function Disconnect-HorizonConnectionServer {
    param (
        [parameter(Mandatory = $true,
            HelpMessage = "The Horizon View Connection server object.")]
        [VMware.VimAutomation.HorizonView.Impl.V1.ViewObjectImpl]$HVConnectionServer
    )
    # Try to connect from the connection server
    try {
        Disconnect-HVServer -Server $HVConnectionServer -Confirm:$false
    }
    catch {
        write-error  "There was a problem disconnecting from the Horizon View Connection server: $_"
        exit
    }
}

function Get-HVDesktopPool {
    param (
        [parameter(Mandatory = $true,
        HelpMessage = "Name of the Desktop Pool.")]
        [string]$HVPoolName,
        [parameter(Mandatory = $true,
        HelpMessage = "The Horizon View Connection server object.")]
        [VMware.VimAutomation.HorizonView.Impl.V1.ViewObjectImpl]$HVConnectionServer
    )
    # Try to get the Desktop pools in this pod
    try {
        # create the service object first
        [VMware.Hv.QueryServiceService]$queryService = New-Object VMware.Hv.QueryServiceService
        # Create the object with the definiton of what to query
        [VMware.Hv.QueryDefinition]$defn = New-Object VMware.Hv.QueryDefinition
        # entity type to query
        $defn.queryEntityType = 'DesktopSummaryView'
        # Filter oud rds desktop pools since they don't contain machines
        $defn.Filter = New-Object VMware.Hv.QueryFilterEquals -property @{'memberName'='desktopSummaryData.displayName'; 'value' = "$HVPoolname"}
        # Perform the actual query
        [array]$queryResults= ($queryService.queryService_create($HVConnectionServer.extensionData, $defn)).results
        # Remove the query
        $queryService.QueryService_DeleteAll($HVConnectionServer.extensionData)
        # Return the results
        if (!$queryResults){
            write-error  "Can't find $HVPoolName, exiting"
            exit
        }
        elseif (($queryResults).desktopsummarydata.type -eq "MANUAL"){
            write-output  "This a manual Horizon View Desktop Pool, cannot change the amount of desktops"
            exit
        }
        elseif (($queryResults).desktopsummarydata.source -eq "VIRTUAL_CENTER"){
            write-output  "This a Full Clone Horizon View Desktop Pool, if the amount of desktops has been reduced the extra systems need to be removed manually"
            return $queryResults
        }
        else {
            return $queryResults
        }
    }
    catch {
        write-error  "There was a problem retreiving the Horizon View Desktop Pool: $_"
        exit
    }
}

function get-hvpoolspec{
    param (
        [parameter(Mandatory = $true,
            HelpMessage = "ID of the Desktop Pool.")]
        [VMware.Hv.DesktopId]$HVPoolID,
        [parameter(Mandatory = $true,
            HelpMessage = "The Horizon View Connection server object.")]
        [VMware.VimAutomation.HorizonView.Impl.V1.ViewObjectImpl]$HVConnectionServer
    )
    try {
        $HVConnectionServer.ExtensionData.Desktop.Desktop_Get($HVPoolID)
    }
    catch {
        write-error "There was a problem retreiving the desktop pool details: $_"
        exit
    }
}

function Set-HVPool {
    param (
        [parameter(Mandatory = $true,
            HelpMessage = "ID of the Desktop Pool.")]
        [VMware.Hv.DesktopId]$HVPoolID,
        [parameter(Mandatory = $true,
        HelpMessage = "Provisioning type UP_FRONT or ON_DEMAND")]
        [ValidateSet("UP_FRONT","ON_DEMAND")]
        [string] $Provisioningtype,
        [parameter(Mandatory = $true,
            HelpMessage = "Desired amount of desktops in the pool.")]
        [int]$maxNumberOfMachines,
        [parameter(Mandatory = $false,
        HelpMessage = "Desired amount of spare desktops in the pool.")]
        [int]$numberOfSpareMachines,
        [parameter(Mandatory = $false,
        HelpMessage = "Desired minimum amount of desktops in the pool.")]
        [int]$minNumberOfMachines,
        [parameter(Mandatory = $true,
            HelpMessage = "The Horizon View Connection server object.")]
        [VMware.VimAutomation.HorizonView.Impl.V1.ViewObjectImpl]$HVConnectionServer
    )
    if($Provisioningtype -eq "UP_FRONT"){
        try {
            # First define the Service we need
            [VMware.Hv.DesktopService]$desktopservice=new-object vmware.hv.DesktopService
            # Fill the helper for this service with the application information
            $desktophelper=$desktopservice.read($HVConnectionServer.extensionData, $HVPoolID)
            # Change the state of the application in the helper
            $desktophelper.getAutomatedDesktopDataHelper().getVmNamingSettingsHelper().getPatternNamingSettingsHelper().setMaxNumberOfMachines($maxNumberOfMachines)
            $desktophelper.getAutomatedDesktopDataHelper().getVmNamingSettingsHelper().getPatternNamingSettingsHelper().setProvisioningTime("UP_FRONT")
            # Apply the helper to the actual object
            $desktopservice.update($HVConnectionServer.extensionData, $desktophelper)
        }
        catch {
            write-error "There was a problem changing the desktop count: $_"
            exit
        }
    }
    else{
        try {
            # First define the Service we need
            [VMware.Hv.DesktopService]$desktopservice=new-object vmware.hv.DesktopService
            # Fill the helper for this service with the application information
            $desktophelper=$desktopservice.read($HVConnectionServer.extensionData, $HVPoolID)
            # Change the state of the application in the helper
            $desktophelper.getAutomatedDesktopDataHelper().getVmNamingSettingsHelper().getPatternNamingSettingsHelper().setminNumberOfMachines($minNumberOfMachines)
            $desktophelper.getAutomatedDesktopDataHelper().getVmNamingSettingsHelper().getPatternNamingSettingsHelper().setMaxNumberOfMachines($maxNumberOfMachines)
            $desktophelper.getAutomatedDesktopDataHelper().getVmNamingSettingsHelper().getPatternNamingSettingsHelper().setnumberOfSpareMachines($numberOfSpareMachines)
            $desktophelper.getAutomatedDesktopDataHelper().getVmNamingSettingsHelper().getPatternNamingSettingsHelper().setProvisioningTime("ON_DEMAND")
            # Apply the helper to the actual object
            $desktopservice.update($HVConnectionServer.extensionData, $desktophelper)
        }
        catch {
            write-error "There was a problem changing the desktop count: $_"
            exit
        }
    }
}

write-verbose "Script will change this Desktop Pool: $HVDesktopPoolName"
write-verbose "Script will connect to this Connection Server: $HVConnectionServerFQDN "
if($Provisioningtype){
    write-verbose "Provisioningtype was set to $Provisioningtype"
}
else{
    write-verbose "No ProvisioningType was provided"
}

write-verbose "ChangeProvisioningtype was set to $ChangeProvisioningtype"
write-verbose "New Maximum Desktop Count is $maxNumberOfMachines "
if($minNumberOfMachines){
    write-verbose "minNumberOfMachines was set to $minNumberOfMachines"
}
else{
    write-verbose "No minNumberOfMachines was provided"
}

if($numberOfSpareMachines){
    write-verbose "numberOfSpareMachines was set to $numberOfSpareMachines"
}
else{
    write-verbose "No numberOfSpareMachines was provided"
}

if($Credentials){
    $creds = $credentials
}
else{
    $creds = get-credential
}



# Connect to the Horizon View Connection Server

[VMware.VimAutomation.HorizonView.Impl.V1.ViewObjectImpl]$objHVConnectionServer = Connect-HorizonConnectionServer -HVConnectionServerFQDN $HVConnectionServerFQDN -Credential $creds

# Retreive the desktop pool
$HVPool=Get-HVDesktopPool -HVPoolName $HVDesktopPoolname -HVConnectionServer $objHVConnectionServer
write-verbose  "Retreived information about $HVDesktopPoolname"

# But we only need the ID
$HVPoolID=($HVPool).id

# Retreive the pool spec
$hvpoolspec=Get-HVPoolSpec -HVConnectionServer $objHVConnectionServer -HVPoolID $HVPoolID
$ProvisioningTime=($hvpoolspec).AutomatedDesktopData.VmNamingSettings.PatternNamingSettings.ProvisioningTime
write-verbose "Current provisioningtype = $ProvisioningTime"
write-verbose "Checking if provisioningtype matches the current setting and if I am allowed to change it."
if($Provisioningtype){
    if($ProvisioningTime -ne $provisioningtype -and $changeprovisioningtype -eq $False){
        write-error "Provisioningtype of $provisioningtype does not match the current provisioningtype. Set changeprovisioningtype to True to change the provisioningtype"
        exit
    }
    elseif($ProvisioningTime -ne $provisioningtype -and $changeprovisioningtype -eq $true){
        $Provisioningtype=$Provisioningtype.toupper()
        write-verbose "Changing Provisioningtype to $Provisioningtype"
    }
}
else{
    $Provisioningtype = $ProvisioningTime
}

if($Provisioningtype -eq "ON_DEMAND"){
    write-verbose "Checking if numberOfSpareMachines or minNumberOfMachines is missing"
    if(!$minNumberOfMachines -or !$numberOfSpareMachines){
        write-error "numberOfSpareMachines and minNumberOfMachines are required when using provisioningtype: $provisioningtype"
        exit
    }
}

# We cannot change manual pools so we give a warning about this and exit the script.
if ($hvpoolspec.Type -eq "MANUAL"){
    write-error "Could not execute, this a manual Horizon View Desktop Pool, cannot change the amount of desktops."
    exit
}

# When not all vm's are provisioned up front the max amount of machines can't be lower that the minimum amount or the number of spare machines.
if ($Provisioningtype -eq "ON_DEMAND"){
    if ($numberOfSpareMachines -ge $maxNumberOfMachines -or $minNumberOfMachines -ge $maxNumberOfMachines){
        write-error "Could not execute, the number of desktops cannot be smaller than the minimum amount of desktops or the number of spare desktops"
        exit
    }
}

# Change the desktop count in the pool

if($Provisioningtype -eq "UP_FRONT"){
    write-verbose "Provisioningtype is $Provisioningtype so ignoring minNumberOfMachines and numberOfSpareMachines if they have been added."
    write-verbose  "Trying to change $HVDesktopPoolname to $maxNumberOfMachines desktops."
    Set-HVPool -HVConnectionServer $objHVConnectionServer -HVPoolID $HVPoolID -maxNumberOfMachines $maxNumberOfMachines -Provisioningtype $Provisioningtype
    write-output  "Changed $HVDesktopPoolname to $maxNumberOfMachines desktops all provisioned up front."
}
else{
    write-verbose "Provisioningtype is $Provisioningtype so using minNumberOfMachines and numberOfSpareMachines."
    write-verbose  "Trying to change $HVDesktopPoolname to $maxNumberOfMachines desktops with a minimum of $minNumberOfMachines machines and $numberOfSpareMachines spares."
    Set-HVPool -HVConnectionServer $objHVConnectionServer -HVPoolID $HVPoolID -maxNumberOfMachines $maxNumberOfMachines -Provisioningtype $Provisioningtype -minNumberOfMachines $minNumberOfMachines -numberOfSpareMachines $numberOfSpareMachines
    write-output  "Changed $HVDesktopPoolname to $maxNumberOfMachines desktops with a minimum of $minNumberOfMachines machines and $numberOfSpareMachines spares."
}

# Disconnect from the connection server
Disconnect-HorizonConnectionServer -HVConnectionServer $objHVConnectionServer

 

Bookmark the permalink.

Comments are closed.