Pages

Friday, February 02, 2018

Horizon View Automation with PowerShell and VMware Orchestrator


For a customer PoT, I had the challenge to automate the rollout of a new Horizon View environment with an initial set of functionality like adding a vCenter, and provision an initial Pool and additional tasks like:
·      Scale-Up: Change the Pool size
·      Scale-Out: Add an additional vCenter and create a new Pool on the second vCenter
I want to share the scripts I’ve created to do so, but with the note, that they are not bullet proof and in an absolute PoT character J

For the installation, the following binaries have to be stored in the C:\TEMP directory of the VM where you want to install Horizon:
·      VMware-viewconnectionserver-x86_64-7.3.2-7161118.exe
·      VMware-PowerCLI-6.5.0-4624819.exe
·      VMware.Hv.Helper.zip (download from here: https://github.com/vmware/PowerCLI-Example-Scripts)

The first thing we need to do, is to enable interactive sessions on the VM, unfortunately the PowerCLI installation will only work interactively because we cannot get rid of an annoying window which opens during installation. So run the following script on the VM to set the VMware Tools service to allow interactive sessions:
REG ADD HKLM\SYSTEM\CurrentControlSet\Control\Windows /v NoInteractiveServices /t REG_DWORD /d 0x0 /f
SC CONFIG VMTools type=own type=interact

Now we need to restart the VMware Tools or what I did, restart the whole server and wait until the VMware Tools are up and running again.

Next step is to run the installation of Horizon View. We run now the installation of our two binaries with the following script interactively (you need to replace the admin SID with an SID valid for your environment. I’ve used an domain account that’s why I specified the admin SID here):
Start-Process C:\TEMP\VMware-viewconnectionserver-x86_64-7.3.2-7161118.exe -ArgumentList '/s /v"/qn VDM_SERVER_INSTANCE_TYPE=1 FWCHOICE=2 VDM_INITIAL_ADMIN_SID=S-1-5-xx-xxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxx VDM_SERVER_RECOVERY_PWD=vmware"' -PassThru -Wait
Start-Sleep -Seconds 5
Start-Process C:\TEMP\VMware-PowerCLI-6.5.0-4624819.exe -ArgumentList '/s /qn /w /V"/qr /L*v PowerCLI.log"' -PassThru -Wait
cd "C:\Program Files\VMware\VMware View\Server\extras\PowerShell"
.\add-snapin.ps1

The next step is to install the VMware HV Helper PowerShell module. Therefore we run the following script (from now on we don’t need to run our scripts interactively any more):
Set-ExecutionPolicy unrestricted -force

Add-Type -AssemblyName System.IO.Compression.FileSystem

function Unzip
{
    param([string]$zipfile, [string]$outpath)

    [System.IO.Compression.ZipFile]::ExtractToDirectory($zipfile, $outpath)
}
Unzip "C:\TEMP\VMware.Hv.Helper.zip" "C:\Program Files (x86)\VMware\Infrastructure\PowerCLI\Modules\"
dir "C:\Program Files (x86)\VMware\Infrastructure\PowerCLI\Modules\VMware.Hv.Helper\" | Unblock-File

Again we need to restart the VM, because otherwise when starting PowerShell we cannot access the fresh installed PowerShell modules.

Now we have an installed Horizon View environment, but nothing configured so far. We start now with the configuration. In my case I’ve started adding an AD user group which will get entitled to the new Pool we create. I’ve used the build-in vRO workflow: “Create a user group in an organizational unit”. Next step was to entitle the AD administrative account locally on the VM, so we need to run this bash script to entitle the user to the local Administrators:
NET LOCALGROUP Administrators hv-admin@domain.com /ADD

Next step is to install the Horizon View license. Therefore we run the following script:
Import-Module VMware.VimAutomation.HorizonView
Import-Module VMware.VimAutomation.Core
Add-PSSnapin VMware.View.Broker
Connect-HVServer -server localhost -user hv-admin -password Password -domain domain.com
Set-License -key "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX"
Disconnect-HVServer -server localhost -Force -Confirm:$false

Now we go and install the vCenter certificate to the VM, so we can add the vCenter to Horizon View using the Horizon View Administrator user account. To do so, run the following script (I’m sure there is a better way to do so but I was not sure where to import the certificate so I was importing it in nearly every certificate store J):
$secpasswd = ConvertTo-SecureString "Password" -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential ("hv-admin@domain.com", $secpasswd)
Invoke-Command -Computer $env:COMPUTERNAME -ScriptBlock {

$url = "https://<URL>"
[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
[System.Uri] $u = New-Object System.Uri($url)
[Net.ServicePoint] $sp = [Net.ServicePointManager]::FindServicePoint($u);

[System.Guid] $groupName = [System.Guid]::NewGuid()

[Net.HttpWebRequest] $req = [Net.WebRequest]::create($url)
$req.Method = "GET"
$req.Timeout = 600000 # = 10 minutes
$req.ConnectionGroupName = $groupName

[Net.HttpWebResponse] $result = $req.GetResponse()

$sp.CloseConnectionGroup($groupName)

$outfilename = "C:\TEMP\Export.cer"

[System.Byte[]] $data = $sp.Certificate.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert)
[System.IO.File]::WriteAllBytes($outfilename, $data)
Write-Host $outfilename

Import-Certificate -FilePath "C:\TEMP\Export.cer" -CertStoreLocation Cert:\LocalMachine\Root
Import-Certificate -FilePath "C:\TEMP\Export.cer" -CertStoreLocation Cert:\LocalMachine\CA
Import-Certificate -FilePath "C:\TEMP\Export.cer" -CertStoreLocation Cert:\LocalMachine\My
Import-Certificate -FilePath "C:\TEMP\Export.cer" -CertStoreLocation Cert:\LocalMachine\AuthRoot
Import-Certificate -FilePath "C:\TEMP\Export.cer" -CertStoreLocation Cert:\LocalMachine\TrustedDevices
Import-Certificate -FilePath "C:\TEMP\Export.cer" -CertStoreLocation Cert:\CurrentUser\Trust
Import-Certificate -FilePath "C:\TEMP\Export.cer" -CertStoreLocation Cert:\CurrentUser\CA
Import-Certificate -FilePath "C:\TEMP\Export.cer" -CertStoreLocation Cert:\CurrentUser\AuthRoot
Import-Certificate -FilePath "C:\TEMP\Export.cer" -CertStoreLocation Cert:\CurrentUser\Myn
} -Credential $cred


Now we can add the vCenter to our Horizon View environment using the following script, again executed using the HV administrator account:
Import-Module VMware.VimAutomation.HorizonView
Import-Module VMware.VimAutomation.Core
Import-Module VMware.Hv.Helper
Add-PSSnapin VMware.View.Broker

$hvServer = Connect-HVServer -server 'localhost' -user 'hv-admin' -password 'Password' -domain 'domain.com'
$Global:hvServices = $hvServer.ExtensionData

$vcService = New-Object VMware.Hv.VirtualCenterService
$certService = New-Object VMware.Hv.CertificateService
$vcSpecHelper = $vcService.getVirtualCenterSpecHelper()

$vcPassword = New-Object VMware.Hv.SecureString
$enc = [system.Text.Encoding]::UTF8
$vcPassword.Utf8String = $enc.GetBytes('Password')

$serverSpec = $vcSpecHelper.getDataObject().serverSpec
$serverSpec.serverName = '<URL>'
$serverSpec.port = 443
$serverSpec.useSSL = $true
$serverSpec.userName = 'administrator@vsphere.local'
$serverSpec.password = $vcPassword
$serverSpec.serverType = $certService.getServerSpecHelper().SERVER_TYPE_VIRTUAL_CENTER

$certData = $certService.Certificate_Validate($hvServices, $serverSpec)
$certificateOverride = New-Object VMware.Hv.CertificateThumbprint
$certificateOverride.sslCertThumbprint = $certData.thumbprint.sslCertThumbprint
$certificateOverride.sslCertThumbprintAlgorithm = $certData.thumbprint.sslCertThumbprintAlgorithm

$vcSpecHelper.getDataObject().CertificateOverride = $certificateOverride

$vcId = $vcService.VirtualCenter_Create($hvServices, $vcSpecHelper.getDataObject())

Disconnect-HVServer -server localhost -Force -Confirm:$false

Now we have our vCenter added to Horizon View, we can create a Pool, here a script to create an instant-clone Pool:
Import-Module VMware.VimAutomation.HorizonView
Import-Module VMware.VimAutomation.Core
Import-Module VMware.Hv.Helper
Add-PSSnapin VMware.View.Broker

$hvServer = Connect-HVServer -server 'localhost' -user 'hv-admin' -password 'Password' -domain 'domain.com'
$Global:hvServices = $hvServer.ExtensionData

New-HVPool -InstantClone -PoolName '<poolName>' -PoolDisplayName '<poolDisplayName>' -Description '"<poolDescription>"' -UserAssignment FLOATING -ParentVM '<ParentVMName>' -SnapshotVM '<SnapshotName>' -VmFolder 'InstantClones' -HostOrCluster '<ClusterName>' -NamingMethod PATTERN -NamingPattern "<poolName>-{n:fixed=3}" -Usevsan $True -Datastores 'vsanDatastore' -NetBiosName '<Domain>' -EnableProvisioning $TRUE -StopOnProvisioningError $TRUE -MaximumCount 5 -SpareCount 2 -MinimumCount 2 -ProvisioningTime ON_DEMAND -AdContainer 'OU=Clients' -DomainAdmin hv-admin -Vcenter <vCenter> -ResourcePool ‘<ResourcePoolName>’

Set-HVPool -PoolName <poolName> -enableHTMLAccess $true

Disconnect-HVServer -server localhost -Force -Confirm:$false

The last missing piece is to entitle our created AD user group to access the Pool:
Import-Module VMware.VimAutomation.HorizonView
Import-Module VMware.VimAutomation.Core
Import-Module VMware.Hv.Helper
Add-PSSnapin VMware.View.Broker

$hvServer = Connect-HVServer -server 'localhost' -user 'hv-admin' -password 'Password' -domain 'domain.com'
$Global:hvServices = $hvServer.ExtensionData

$queryService = New-Object VMware.Hv.QueryServiceService
$defn = New-Object VMware.Hv.QueryDefinition
$defn.queryEntityType = 'ADUserOrGroupSummaryView'
$queryResults = $queryService.QueryService_Create($hvServices, $defn)

[VMware.Hv.ADUserOrGroupSummaryView]$adUserOrGroupSummaryView
try {
   while ($queryResults.results -ne $null) {
      foreach ($result in $queryResults.Results) {
         [VMware.Hv.ADUserOrGroupSummaryView]$adUserOrGroupSummaryViewResult = $result
         if ($adUserOrGroupSummaryViewResult.Base.Name -eq "<poolName>_User") {
                     $adUserOrGroupSummaryView = $adUserOrGroupSummaryViewResult
                     break;
               }
      }

      # Fetch next page
      if ($queryResults.id -eq $null) {
         break;
      }
      $queryResults = $queryService.QueryService_GetNext($hvServices, $queryResults.id)
   }
} finally {
   if ($queryResults.id -ne $null) {
      $queryService.QueryService_Delete($hvServices, $queryResults.id)
   }
}

$hvPool = Get-HVPool -PoolName "<poolName>"

$userEntitlementBase = New-Object -Type VMware.Hv.UserEntitlementBase
$userEntitlementBase.UserOrGroup = $adUserOrGroupSummaryView.Id
$userEntitlementBase.Resource = $hvPool.Id

$userEntitlementService = New-Object -TypeName VMware.Hv.UserEntitlementService
$userEntitlementService.UserEntitlement_Create($hvServices, $userEntitlementBase)

Disconnect-HVServer -server localhost -Force -Confirm:$false

That’s it. Now we have a running and configured Horizon View environment with an initial Pool and an entitled AD user group to access the Pool.

To scale a Pool, you can use the following two scripts. The first, to get the current values of the pool:
Import-Module VMware.VimAutomation.HorizonView
Import-Module VMware.VimAutomation.Core
Import-Module VMware.Hv.Helper
Add-PSSnapin VMware.View.Broker

$hvServer = Connect-HVServer -server 'localhost' -user 'hv-admin' -password 'Password' -domain 'domain.com'
$Global:hvServices = $hvServer.ExtensionData

$HVPool = Get-HVPool -PoolName <poolName>
$HVPool.AutomatedDesktopData.VmNamingSettings.PatternNamingSettings

Disconnect-HVServer -server localhost -Force -Confirm:$false
And the following javascript part to parse the output:
var maxNumberOfMachines = scriptOutputTextOut.match(/MaxNumberOfMachines\s*:\s*[0-9]+/).shift().match(/[0-9]+/).shift();
var numberOfSpareMachines = scriptOutputTextOut.match(/NumberOfSpareMachines\s*:\s*[0-9]+/).shift().match(/[0-9]+/).shift();
var minNumberOfMachines = scriptOutputTextOut.match(/MinNumberOfMachines\s*:\s*[0-9]+/).shift().match(/[0-9]+/).shift();

And second, to set the provisioning values on the Pool:
Import-Module VMware.VimAutomation.HorizonView
Import-Module VMware.VimAutomation.Core
Import-Module VMware.Hv.Helper
Add-PSSnapin VMware.View.Broker

$hvServer = Connect-HVServer -server 'localhost' -user 'hv-admin' -password 'Password' -domain 'domain.com'
$Global:hvServices = $hvServer.ExtensionData

Set-HVPool -PoolName <poolName> -key 'automatedDesktopData.vmNamingSettings.patternNamingSettings.maxNumberOfMachines' -value <maxNumberOfMachines>
Set-HVPool -PoolName <poolName> -key 'automatedDesktopData.vmNamingSettings.patternNamingSettings.numberOfSpareMachines' -value <numberOfSpareMachines>
Set-HVPool -PoolName <poolName> -key 'automatedDesktopData.vmNamingSettings.patternNamingSettings.minNumberOfMachines' -value <minNumberOfMachines>

Disconnect-HVServer -server localhost -Force -Confirm:$false

To scale out the Horizon View environment to use a second vCenter and create a second Pool. All scripts are already provided, you just need to combine them.

Have fun with Horizon View automation. For me it was pain in the a** to get so far ;-)