VMware Horizon View with Ansible and Powershell – Part 1
After battling Covid19 almost the whole of September I can say that I am back in business! In this post I want to show you how you can create a Horizon View Environment with Powershell and Ansible. This post will be one of three and will guide you in creating a simple and automated VMware Horizon View. The second post on VMware Horizon View will add some complexity, like automatically creating access groups in AD and add them as entitlement group to Horizon View. Because of housekeeping, which is necessary sometimes, post number three will be about deleting (parts of) the environment. To be clear, the environment built with the script is based on Full_Clone.
Roles
For the sake of space and time I am assuming that you have some knowledge an or at least some understanding about Ansible Roles. In Ansible, roles provide a framework to store scripts and then use them in automation. Roles are a part of Ansible’s standard directory structure, which means you do not have to take extra action to use them in a standard setup. A role is executed and called by a Playbook.
If necessary, please read: https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html
Setup used for Horizon View Environment
The roles I write are meant to be as independent as possible. This means they should not be dependent on ‘outside’ variables, like group_vars or vars in an inventory. Everybody should be able to download the role and build their own automation around it.
For this post the following setup of files and folders will be used:
- roles/role_horizon_view_create/tasks/main.yml
- this file holds the task(s) needed for the role
- roles/role_horizon_view_create/library/hv_desktoppool.ps1
- the custom powershell module for Horizon
- roles/role_horizon_view_create/defaults/main.yml
- holds variables to make role independent
- playbook-horizon-view-environment.yml
- to invoke the role and add some intelligence for running the automation
- hosts file
- with the horizon server
- vars/pools.yml
- holds the pools to create with some information
Because I would like to show you how you can actually automate stuff from the beginning, I will not talk about defaults/main.yml yet. It will make the role more independent though and I will come back on this in my next post.
Prerequisites
- ansible 2.8 or higher recommended (tested 2.9)
- windows jumphost with
- powercli 6.5 or higher
- VMware.HV.Helper installed
- Winrm / credssp enabled
- Horizon View 7.8 or higher (tested 7.8 / 7.12)
- One existing pool to use as template
Goal
Today I want to set up a small environment of three pools. To do this the script will use a template pool and read settings from the pool.yml file. The result should be idempotent.
- env_poolAA / 2 desktops / vdi_poolAA01 tm 02
- env_poolAB / 4 desktops / vdi_poolAA01 tm 04
- env_poolAC / 3 desktops / vdi_poolAA01 tm 03
Create the hv_desktoppool.ps1 module
The great thing about Ansible is that you can create your own modules with Powershell. I wrote a blogpost about it here https://www.codecrusaders.nl/vmware/powershell-to-write-ansible-modules-for-nsx-v/ . It is recommended to keep your Powershell modules compact and short for simplicity and clarity. That also makes sense in the way ansible uses modules.
#!powershell
# Copyright: Davy van de Laar
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#AnsibleRequires -CSharpUtil Ansible.Basic
#Requires -Module Ansible.ModuleUtils.Legacy
$ErrorActionPreference = "Stop"
# input parameters
$params = Parse-Args $args -supports_check_mode $true
$hvserver_input = Get-AnsibleParam -obj $params -name "hv_server" -type "str" -failifempty $true
$hvusername = Get-AnsibleParam -obj $params -name "hv_username" -type "str" -failifempty $true
$hvpassword = Get-AnsibleParam -obj $params -name "hv_password" -type "str" -failifempty $true
$hvuserdomain = Get-AnsibleParam -obj $params -name "hv_domain" -type "str" -failifempty $true
$hvtemplatepool = Get-AnsibleParam -obj $params -name "hv_template_poolname" -type "str" -failifempty $true
$poollist = Get-AnsibleParam -obj $params -name "hv_pool_list" -type "list" -element "str" -failifempty $true
$result = @{
changed = $false
exists = @()
create = @()
entitled = @()
entitle_exists =@()
}
Import-Module VMware.VimAutomation.HorizonView
Get-Module -ListAvailable 'VMware.Hv.Helper' | Import-Module
# make connection with connection server
try{
$hvserver = Connect-HVServer $hvserver_input -User $hvusername"@"$hvuserdomain -Password $hvpassword
$Global:services = $hvserver.ExtensionData
$hvserver
} catch {Fail-Json -obj $result "check credentials en pre-requisites"}
foreach ($pool in $poollist) {
$poolcheck = Get-HVPool -Poolname $pool.name
if ( $poolcheck -like '*No Pool Found with given search parameters*' ){
try {
Get-HVPool -Poolname $hvtemplatepool | New-HVPool -Poolname $pool.name -NamingPattern $pool.vdi
Start-Sleep -Seconds 5
Set-HVPool -PoolName $pool.name -Key automatedDesktopData.vmNamingSettings.patternNamingSettings.maxNumberOfMachines -Value $pool.poolsize
$result.create += $pool.name + " wordt gemaakt"
$result.changed = $true
} catch {Fail-Json -obj $result "onbekende fout bij pool creatie"}
}
elseif ( $poolcheck.Base.Name -eq $pool.name){
$result.exists += $poolcheck.Base.Name + " bestaat al"
}
}
Exit-Json -obj $result
This file should exist as hv_desktoppool.ps in roles/role_horizon_view_create/library
Line 1: indicate Powershell scripting
Line 6 and 7: requirements for Ansible to handle Powershell
Line 9: let the script stop on error, which makes sense in a module
Line 11 and 18: Declare Powershell variables and link them to variables you want to use for your tasks in Ansible
Line 18: $poollist holds a list of pools you want to create, the number of desktops per pool with the numbering pattern. The intelligence for this will come from the playbook or the defaults/main.yml. Keep in mind though that in Ansible hv_pool_list, actually is a list with all the necessary items. If not, the script will fail.
Line 20-21: The $result variable which holds the changed:$false setting. This should be false on default and become $true when something actually changes
Line 22-25: I like to keep some extra logging on thing that happen, stored as array in $result
Line 29-30: Import the VMware modules needed
Line 33-37: Try to connect to the Horizon View Server
With a Jumphost this seems to take up some time
Line 40: start for each loop
Line 41: check if pool already exists
Line 42-49: If pool does not exist it will be created from the template pool with given specs. $result – changed:$true will be set and logging written to $result
Line 51-52: If pool exists nothing happened. Logging will be written to $result
Line 55: Exit module and display $result
The setup of pools.yml
The pools.yml is a dictionary file that holds the Horizon pool information. It is not an Ansible default, it is just something I created. Why not use the inventory hosts file? Well, Horizon View environments tend to be volatile in nature and not very static. Therefor a hosts file, in my opinion, is not the best place to store Horizon View Environments. The data in pools.yml is not strictly limited to Horizon pools. This is my setup:
- In a dictionary the first entry is the name of the dictionary, here: environments
- Then the first key follows, in the example ‘poolAA’
- The key has several values
- ipnumber (not used for Horizon View)
- vdi_pool_custom_size
- active: true or false
The value for active indicates if a key should be included or skipped for the play.
environment:
'poolAA':
ipnumber:
- 10.55.55.55
vdi_pool_custom_size: 2
active: true
'poolAB':
ipnumber:
- 10.55.55.55
vdi_pool_custom_size: 4
active: true
'poolAC':
ipnumber:
- 10.55.55.55
vdi_pool_custom_size: 3
active: true
This file should exists as pools.yml in the vars folder
Create the taskfile for creating desktops
To use the newly created module, just call the module like a regular Ansible module and use it in the roles/tasks/main.yml file. Be aware that hv_pool_list requires a list with at least the following items:
- name: name of pool
- vdi: desktop prefix with naming pattern
- poolsize: size of the pool
In my example today the intelligence for hv_pool_list comes from the playbook. The playbook creates a list of items with the desired data from the pools.yml. This will make the pools.yml your point of truth. In an independent role I would put the framework for hv_pool_list in the roles/defaults/main.yml and the user of the role would have to build its own automation.
---
- name: create desktop pool
create_hv_desktoppool:
hv_server: "{{ your_connection_server }}"
hv_password: "{{ your_admin_password }}"
hv_username: "{{ your_admin_user }}"
hv_domain: "{{ your_horizon_domain }}"
hv_pool_list: "{{ environment_list }}"
hv_template_poolname: "{{ your_template_poolname }}"
when: environment_list|list
register: results
delegate_to: "{{ win_powershell_jumphost }}"
vars:
ansible_connection: winrm
ansible_winrm_transport: credssp
ansible_winrm_server_cert_validation: ignore
ansible_user: "{{ credssp_ansible_user }}"
ansible_password: "{{ credssp_ansible_password }}"
- name: debug results for logging
debug:
var: results
vars:
ansible_connection: winrm
ansible_winrm_transport: credssp
ansible_winrm_server_cert_validation: ignore
ansible_user: "{{ your_credssp_user }}"
ansible_password: "{{ your_credssp_password }}"
- name: show message if nothing to do
debug:
msg: 'lege lijst van environment'
when: not environment_list|list
This file should exist as main.yml in roles/role_horizon_view_create/tasks
Create the playbook-horizon-view-environment.yml
The playbook is used to invoke the role with tasks/main.yml file we just created. To keep things as clean as possible in the role I like to put some intelligence in my playbooks. For example, when creating a list from the pools.yml. My goal is to use the ‘key’ as unique identifier wherever possible and add additional information when necessary. This can be static pre- and postfixes, but also values belonging to the key from the dictionary.
---
# file: playbook-horizon-view-environment.yml
- name: Provision horizon desktoppools
max_fail_percentage: 0
hosts: all
connection: local
gather_facts: false
vars_files: "vars/pools.yml"
pre_tasks:
- name: create empty list environment_list
set_fact:
environment_list: []
- name: set scope to all environment
set_fact:
environment_list: "{{ environment_list | default([]) + \
[ { 'name': 'env_' + item.key, \
'vdi': 'vdi' + item.key + 'w{n:fixed=2}', \
'poolsize': item.value.vdi_pool_custom_size } ] }}"
with_dict: "{{ environment }}"
when: item.value.active
- debug:
var: environment_list
roles:
- role_horizon_view_create
Line 9: include pools.yml
Line 11: indicate tasks to run before starting role
Line 12 – 14: make sure of empty list
Line 16 – 18: set fact for environment_list, make ready to create list from environment
Line 19: name of pool to create – prefix ‘env_’
I want all my environments to start with ‘env_’
Line 20: naming of vdi’s with pattern.
I want all my vdi’s to start with vdi and have a pattern of 2 decimal digits to number the vdi’s
Line 21: number of desktops to deploy
Line 22: loop through the dict environment. (which is declared in pools.yml)
Line 23: Only make a list of items that have the setting active:true. False will be skipped
The hosts file
Last but not least, the hosts file. I like to point it to my vCenter. There are other ways to achieve the same result, for instance the use of a ini file. In the end it is entirely up to you!
---
all:
children:
codecrusaders:
children:
vcenter_cc:
hosts:
10.11.12.13:
Run the playbook !
Run the playbook with the following line:
$ ansible-playbook -i hosts playbook-horizon-view-environment.yml --limit vcenter_cc
Results
Here are some expected results to expect. I will show you the expected result after the first run. The second picture is to show idempotency. Keep in mind that after the first run you will only see one changed task. This is because only one task provisions the three pools.
Conclusion
This was a pretty long and extensive post. I wanted to show you some Horizon View automation and how you can use Ansible roles to your advantage.
The code can be found on my github:
Please speak out if you have any questions or remarks. Soon, Part 2 of this post will follow. Thanks for reading.
[…] little while ago I wrote part 1 of VMware Horizon View with Ansible and Powershell. The blog was about setting up a simple and […]
[…] to setup Horizon View Connection Server with Ansible. In my previous Horizon and Ansible posts I assumed the connection server was already there. What if it isn’t though and you need […]