PowerShell is a very powerful scripting language that can help automate many aspects of maintaining and deploying a XenDesktop environment. To share some of my experiences creating PowerShell scripts, I’ve created this blog series that will give guidance and ideas for creating your own scripts for automating XenDesktop deployments. As part of the blog series, I will discuss the automation techniques I’ve employed to help streamline the installation and configuration of XenDesktop. This first post will cover creating why you should automate using PowerShell, creating functions, and how to use the functions in remote sessions.

Part 1: PowerShell Pre-Reqs, Functions inside Remote Sessions
Part 2:  Create a Sysprepped Template & Create a Group of VMs With 1 Click
Part 3: Set a Static IP & Join Machines to the Domain
Part 4: Remotely Install XenDesktop Software
Part 5: Remotely Add StoreFront & Delivery Controllers

Why Automate

First off, why should you use Powershell rather than doing certain tasks manually? While automation might seem faster, time must be spent upfront developing the scripts. It is very easy to say it takes a few clicks to install and configure the software and that automating the same task would actually take longer. While this is true is some instances, there are certain individuals such as Citrix consultants and engineers who are constantly building new XenDesktop environments. Anything that can help speed up the process and reduce errors is extremely helpful.

Imagine if you are a consultant building XenDesktop from scratch at customers every week or you are an engineer who is regularly building and tearing down lab environments. Really anyone who is consistently doing the same tasks over and over again can benefit from using PowerShell to automate. For Citrix administrators who are maintaining a XenDesktop environment across many machines, PowerShell can help with tasks like dynamically growing resources such as StoreFront servers or Delivery Controllers.

PowerShell Pre-Requistes: Setting the Execution Policy

Executing PowerShell scripts for the first time can be frustrating if you don’t properly set the Executing Policy.The execution policy can be set via GPO or set locally on the machine. Below are two examples how you can set the execution policy to Bypass which will allow any PowerShell script to run without any prompts:

  • powershell.exe -ExecutionPolicy ByPass -file .\myscript.ps1
  • set-executionpolicy -executionPolicy Bypass -force

Creating Functions

As the tasks that need to be automated are defined, it will become clear what parts of code should be placed inside a function. Placing code into a function will allow it to be reused through the script without having to type the same code each time. This makes the script much clearer and streamlined. For example, the function below allows me at add a user to the Local Administrators group.

function local_admin($a, $b) {
    Try {
        ([adsi]"WinNT://localhost/Administrators,group").Add("WinNT://$a/$b,user")
    } Catch{
        Write-Host "Unable to add $b in Domain $a - Error: $($_.exception.Message.Trim())" -F Red
    }
}

Instead of inserting the code above at each instance where I need add a local administrator, I just reference the function.  Along with referencing the function, two variables must be specified: the username to be added to the local administrators group and the domain the user is located in. Example: local_admin mydomain svc_xendesktop

PowerShell Remoting

When designing the framework for automation, there are a few options for running PowerShell scripts on remote machines on a local area network. Most PowerShell scripts are designed to run the local machine which means if you would like to have a low touch deployment, you need to first copy the scripts there somehow and execute them. To overcome this, PowerShell Remoting should be used when executing code on a remote computer. PowerShell Remoting allows for a PowerShell session to be opened on a remote computer without having to connect to the computer via RDP or the console.

Before PowerShell Remoting can be used, it must be enabled and configured appropriately. The following commands run from PowerShell allow you to start using PowerShell Remoting very easily since they disable HTTPs and allow for basic authentication. They are not best practice when in a high security environment. Be sure to set  the appropriate firewall rules (the default HTTP port of 5985 is used).

  • winrm quickconfig -q
  • winrm quickconfig -transport:http
  • winrm set winrm/config/client ‘@{TrustedHosts=”*”}
  • winrm set winrm/config @{MaxTimeoutms=”1800000″}
  • winrm set winrm/config/winrs @{MaxMemoryPerShellMB=”300″}
  • winrm/config/service @{AllowUnencrypted=”true”}
  • winrm set winrm/config/service/auth @{Basic=”true”}

Now on to the code examples..  If the user “svc_xendesktop” in the domain “MyDomain” needed to be added to the local administrators group on “Server1”, the code would look something like this:

param(
[Parameter(Mandatory=$true)][string]$domain,
[Parameter(Mandatory=$true)][string]$username
)

# Set Remote Computer Credentials
$pass = ConvertTo-SecureString -AsPlainText "MyPassword" -Force
$Credentials = New-Object System.Management.Automation.PSCredential -ArgumentList "MyUsername",$pass

function local_admin($a, $b) {
    Try {
        ([adsi]"WinNT://localhost/Administrators,group").Add("WinNT://$a/$b,user")
    } Catch{
        Write-Host "Unable to add $b in Domain $a - Error: $($_.exception.Message.Trim())" -F Red
    }
}

Invoke-Command -ComputerName Server1 -Credential $Credentials -ScriptBlock {

    #Add svc_xendesktop to Local Administrators
    local_admin $using:domain $using:username
}

Actually, that code wouldn’t work as you would expect! This error would appear: “The term ‘local_admin’ is not recognized as the name of a cmdlet, function, script file, or operable program.”


I’ll explain why the error occurs soon, but first let’s break down the code a bit further to understand what’s going on.

  1. Define Parameters: First, we define the parameters which are the variables that can be inputted to the script. In this case, the $domain and $account variables are defined as Mandatory parameters. This means values for each must be defined when the script is run. Example: .\myscript.ps1 -domain MyDomain -username svc_xendesktop
  2. Create Local Function: Next, the local_admin function I used as an example earlier is created.
  3. Remote Credentials Defined: The credentials that will be used when connecting to “Server1” are set by creating a PSCredential Object. This allows the Invoke-Command to utilize a specific username/password combination when connecting to the remote host.
  4. Create Remote PowerShell Session: The “Invoke-Command” opens a remote PowerShell session on the computer specified and executes the code contains in the ScriptBlock between the two braces. In this example, the code that is being run is the local_admin function.
  5. Reference Local Variables: There are a few ways to pass the $domain & $account variables inside the remote session. The way I’ve chosen is the $using:LocalVariable method. This feature is available starting with PowerShell 3.0. So all the local variables that need to be passed to the remote session begin with $using then a semicolon and the variable. Example: $using:domain

If you were to execute the code above as shown, it would fail. The reason is that PowerShell by default cannot pass locally defined functions to remote sessions. The code run inside the ScriptBlock braces is executed on the “Server1” computer and cannot see the local_admin function. Thankfully, there a few methods to bypass this limitation.

Example 1: Create Dynamic ScriptBlock

$SB = [ScriptBlock]::Create(@"

## Define Functions
function local_admin {$Function:local_admin}

## Add Local Administrator Users
local_admin $domain $username
"@)

Invoke-Command -ComputerName Server1 -ScriptBlock $SB

In this example, the ScriptBlock is dynamically created before the remote session is initiated. Any local functions such as “local_admin” that need to be included must be explicitly defined. While this method does not require that much extra effort, this isn’t the most graceful way. Along with having to define each function you wish to use, all the code is wrapped in double quotes which makes most text editors with syntax highlighting label the entire code as a string. This can make managing large of code placed inside the ScriptBock irritating to maintain.

Example 2: Functions Redefined Inside ScriptBlock

$SB = {
#Define Function
[void](New-Item -Path $args[0].PSPath -Value $args[0].Definition);

# Call the function
local_admin $using:domain $using:username;
}

Invoke-Command -ComputerName Server1 -ScriptBlock $SB -ArgumentList (Get-Item -Path function:local_admin)

This example is even more complex than the first since the local_admin function must be passed as a Argument to the Invoke-Command and then redefined in the ScriptBlock. As in Example 1, the $using method is utilized to get the value of the locally defined variables. While a bit more complex, I like this option a bit more since the content inside the ScriptBlock is placed inside braces and not double quotes.

Example 3: Invoke-Command Replacement Function (The Option I Chose!)

Although the other options utilize native PowerShell methods to include local functions, I decided to use a custom script called Invoke-CommandAST.ps1. It basically improves the original Invoke-Command to allow local functions to be called from inside a remote session. All you have to do is download the file from GitHub and include the file in your PowerShell script. It’s a very seamless option that makes PowerShell Remoting much easier. The example below will now allow the local_admin function to be utilized in the remote session.

param(
[Parameter(Mandatory=$true)][string]$domain,
[Parameter(Mandatory=$true)][string]$username
)
# Include Invoke-CommandAST File
. "\Invoke-CommandAST.ps1"
function local_admin($a, $b) {
    # The code used earlier
}
Invoke-Command -ComputerName Server1 -ScriptBlock {

    #Add svc_xendesktop to Local Administrators
    local_admin $using:domain $using:username
}

All that changed from the first example was that the Invoke-CommandAST.ps1 file was included the script by “dot sourcing” the file which means a dot is typed followed by the path to the file. In this case, the file is located in the same folder so I just typed “\Invoke-CommandAST.ps1”. Now the local_admin function is able to be used in the remote session on Server1.

In Part2, I will go deeper into PowerShell Remoting, explaining some of its limitations when used it with XenDesktop and more code examples. Stay Tuned!


Roger LaMarca – Senior Consultant
Worldwide Consulting
Virtual Desktop Handbook
Project Accelerator