VMware Horizon View with Ansible and Powershell – Part 1

logo for ansible vmware

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.

First run
First run of the script
Second run
pools created
example of vdi’s

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:

https://github.com/davyvandelaar/codecrusaders/tree/master/VMware-Horizon-View-with-Ansible-and-Powershell

Please speak out if you have any questions or remarks. Soon, Part 2 of this post will follow. Thanks for reading.

Subscribe
Notify of
guest

2 Comments
Oldest
Newest
Inline Feedbacks
View all comments
trackback
VMware Horizon View with Ansible and Powershell – Part 2 - codecrusaders
3 years ago

[…] little while ago I wrote part 1 of VMware Horizon View with Ansible and Powershell. The blog was about setting up a simple and […]

trackback
Horizon View Connection Server with Ansible - codecrusaders
2 years ago

[…] 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 […]