PowerShell: E-mail Server Load and Load Rules XenApp 7.x

drevilps
NOTE: I WROTE THIS SCRIPT FOR A XENAPP 7.6 ENVIRONMENT WITH POWERSHELL 4.0 AND XenApp 7.6 XENAPP CMDLETS.

Intro

Citrix has utilized load balancing in XenApp for years. This feature allows you to set parameters in a load evaluator, apply it to a server, and have Citrix manage how users are assigned to servers. Despite the numerous tools available on the market that report on your XenApp farm utilization, Citrix user load balancing remains the easiest to implement (and cheapest).

Find Server Load in Director
In Director, to find the Server Load Evaluator values, click on the “Filters” button.

director1

Then select Machines -> All Machines.

director2

Now click the Server OS Machines tab.

director3

Here, you will find the “Load Evaluator Index” column. This column displays a percentage from 0% to 100% to show how loaded (or unloaded) your server is.

director4

If you hover over a percentage, a floating window will display the details that make up the index percentage. While this is a more visual way to find this information than in the past (“qfarm /load” anyone), there is no straightforward way to get this detail in a single report for individual servers. There is an Load Evaluator Index trend report, but it summaries information for the whole Delivery Group and not single servers.

director5

Thankfully, with Citrix PowerShell commands, we can regularly report on individual server load (including the load evaluator rules) and e-mail that information.

The Script

<#
.SYNOPSIS
Gathers server load, assigned LE, and active and disconnected sessions and emails a HTLM formatted report.
.DESCRIPTION
Gathers server load, assigned LE, and active and disconnected sessions and emails a HTLM formatted report. It is recommended that this script be run as a Citrix admin. In addition, the Citrix Powershell modules should be installed .PARAMETER DeliveryController Required parameter. Which Citrix Delivery Controller (farm) to publish applicaiton with .EXAMPLE PS C:\PSScript &amp;gt; .\get-ctxLoadAndLE.ps1

 Will use all default values.
 Will query servers in the default Farm and create an HTA file and optionally email the report.
.EXAMPLE
 PS C:\PSScript &amp;gt; .\get-ctxLoadAndLE.ps1 -DeliveryController YOURDDC.DOMAIN.LOCAL 

 Will use YOURDDC.DOMAIN.LOCAL for the delivery controller address.
 Will query servers in the YOURDDC.DOMAIN.LOCAL Farm and create an HTA file and optionally email the report.
.OUTPUTS
 An HTA file is created and used for the report email. The HTA file is saved to the $TEMP environment variable
.NOTES
 NAME: get-ctxLoadAndLE.ps1
 VERSION: 2.00
 CHANGE LOG - Version - When - What - Who
 1.00 - 01/11/2012 -Initial script - Alain Assaf
 1.01 - 01/18/2012 - Changed way I get user sessions because it was timing out - Alain Assaf
 1.02 - 02/20/2012 - Added sendTo variable to add mulitple receipients - Alain Assaf
 1.03 - 03/05/2012 - Added lines to include LE Rules - Alain Assaf
 1.04 - 04/26/2012 - Added Test-Port function from Aaron Wurthmann (aaron (AT) wurthmann (DOT) com) - Alain Assaf
 2.00 - 09/15/2016 - Rewritten for XenApp 7.x. Removed test-port function - Alain Assaf
 AUTHOR: Alain Assaf
 LASTEDIT: September 19, 2016
.LINK
 http://www.linkedin.com/in/alainassaf/
 http://wagthereal.com
 http://powershell.com/cs/blogs/ebook/archive/2008/10/23/chapter-4-arrays-and-hashtables.aspx
 http://technet.microsoft.com/en-us/library/ff730946.aspx
 http://technet.microsoft.com/en-us/library/ff730936.aspx
 http://stackoverflow.com/questions/6588265/jquery-select-first-and-second-td
#>

Param(
 [parameter(Position = 0, Mandatory=$False )]
 [ValidateNotNullOrEmpty()]
 $DeliveryController="YOURDDC.DOMAIN.LOCAL" # Change to hardcode a default value for your Delivery Controller
 )

#Constants
$datetime = get-date -format "MM-dd-yyyy_HH-mm"
$Domain=".domain.local" # Change to match your companies Active Directory Domain
$ScriptRunner = (gci env:username).value
$PSSnapins = ("*citrix*")
$compname = (gci env:COMPUTERNAME).value
$ErrorActionPreference= 'silentlycontinue'

#Assign e-mail(s) to $sendto variable and SMTP server to $SMTPsrv
$sendto = "SOMEONE@YOURDOMAIN.com"
$SMTPsrv = 127.0.0.1

### START FUNCTION: get-mymodule #####################################################
Function Get-MyModule {
 Param([string]$name)
 if(-not(Get-Module -name $name)) {
 if(Get-Module -ListAvailable | Where-Object { $_.name -like $name }) {
 Import-Module -Name $name
 $true
 } #end if module available then import
 else { $false } #module not available
 } # end if not module
 else { $true } #module already loaded
}
### END FUNCTION: get-mymodule #####################################################

### START FUNCTION: get-mysnapin #####################################################
Function Get-MySnapin {
 Param([string]$name)
 if(-not(Get-PSSnapin -name $name)) {
 if(Get-PSSnapin -Registered | Where-Object { $_.name -like $name }) {
 add-PSSnapin -Name $name
 $true
 } #end if module available then import
 else { $false } #snapin not available
 } # end if not snapin
 else { $true } #snapin already loaded
}
### END FUNCTION: get-mysnapin #####################################################

#Initialize array
$finalout = @()

#Import Module(s) and Snapin(s)
foreach ($module in $PSModules) {
 if (!(get-mymodule $module)) {
 write-verbose "$module PowerShell Cmdlet not available."
 write-verbose "Please run this script from a system with the $module PowerShell Cmdlets installed."
 exit
 }
}
foreach ($snapin in $PSSnapins) {
 if (!(get-MySnapin $snapin)) {
 write-verbose "$snapin PowerShell Cmdlet not available."
 write-verbose "Please run this script from a system with the $snapin PowerShell Cmdlets installed."
 exit
 }
}

#Get server list
$ctxservers = Get-BrokerSharedDesktop -AdminAddress $DeliveryController | select -Property DNSName,RegistrationState

#Get user sessions
$ctxsrvSessions = Get-BrokerSession -AdminAddress $DeliveryController | select -Property UserFullName,UserName,HostedMachineName,Protocol,SessionState | where {($_.SessionState -eq 'Active' -or $_.SessionState -eq 'Disconnected')}

#Create a new object array with all the data we need. You will need to change the column names to reflect your evaluator rules.
foreach ($srv in $ctxservers) {
 if ($srv.RegistrationState -eq "Registered") {
 $ctxsrv = $srv.DNSName
 $srvload = Get-BrokerMachine -dnsname $ctxsrv | select -Property dnsname,RegistrationState,loadindex,loadindexes
 $srvactive = @($ctxsrvSessions | where {$_.HostedMachineName -eq $srv -and $_.SessionState -eq 'Active'}).count
 $srvdisconn = @($ctxsrvSessions | where {$_.HostedMachineName -eq $srv -and $_.SessionState -eq 'Disconnected'}).count
 $objctxsrv = new-object System.Object
 $objctxsrv | Add-Member -type NoteProperty -name ServerName -value $srv.DNSName.TrimEnd($Domain)
 $objctxsrv | Add-Member -type NoteProperty -name Status -value "Running"
 $objctxsrv | Add-Member -type NoteProperty -name Load -value $srvload.loadindex.ToString()
 $objctxsrv | Add-Member -type NoteProperty -name Active -value $srvactive.ToString()
 $objctxsrv | Add-Member -type NoteProperty -name Disconnected -value $srvdisconn.ToString()
 $objctxsrv | Add-Member -type NoteProperty -name 'CPU Usage' -value $srvload.LoadIndexes[0].Split(":")[1]
 $objctxsrv | Add-Member -type NoteProperty -name 'Memory Usage' -value $srvload.LoadIndexes[1].Split(":")[1]
 $objctxsrv | Add-Member -type NoteProperty -name 'Session Count' -value $srvload.LoadIndexes[2].Split(":")[1]
 $finalout += $objctxsrv
 } else {
 $objctxsrv = new-object System.Object
 $objctxsrv | Add-Member -type NoteProperty -name ServerName -value $srv.DNSName.TrimEnd($Domain)
 $objctxsrv | Add-Member -type NoteProperty -name Status -value "OFFLINE"
 $objctxsrv | Add-Member -type NoteProperty -name Load -value "0"
 $objctxsrv | Add-Member -type NoteProperty -name Active -value "0"
 $objctxsrv | Add-Member -type NoteProperty -name Disconnected -value "0"
 $objctxsrv | Add-Member -type NoteProperty -name 'CPU Usage' -value "0"
 $objctxsrv | Add-Member -type NoteProperty -name 'Memory Usage' -value "0"
 $objctxsrv | Add-Member -type NoteProperty -name 'Session Count' -value "0"
 $finalout += $objctxsrv
 }
 }

#Create HTML Header
$head = '
&amp;lt;img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cstyle%3E%0AMETA%7Bhttp-equiv%3Arefresh%20content%3A30%3B%7D%0ABODY%7Bfont-family%3AVerdana%3B%7D%0ATABLE%7Bborder-width%3A%201px%3Bborder-style%3A%20solid%3Bborder-color%3A%20black%3Bborder-collapse%3A%20collapse%3B%7D%0ATH%7Bfont-size%3A12px%3B%20border-width%3A%201px%3Bpadding%3A%202px%3Bborder-style%3A%20solid%3Bborder-color%3A%20black%3Bbackground-color%3APaleTurquoise%7D%0ATD%7Bfont-size%3A12px%3B%20border-width%3A%201px%3Bpadding%3A%202px%3Bborder-style%3A%20solid%3Bborder-color%3A%20black%3Bbackground-color%3AGhostWhite%7D%0A%3C%2Fstyle%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="&amp;amp;lt;style&amp;amp;gt;" title="&amp;amp;lt;style&amp;amp;gt;" /&amp;gt;

&amp;lt;img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20type%3D%22text%2Fjavascript%22%20src%3D%22http%3A%2F%2Fajax.googleapis.com%2Fajax%2Flibs%2Fjquery%2F1.3.2%2Fjquery.min.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="&amp;amp;lt;script&amp;amp;gt;" title="&amp;amp;lt;script&amp;amp;gt;" /&amp;gt;
&amp;lt;img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20type%3D%22text%2Fjavascript%22%3E%0A%24(function()%7B%0Avar%20linhas%20%3D%20%24(%22table%20tr%22)%3B%0A%24(linhas).each(function()%7B%0Avar%20Valor%20%3D%20%24(this).find(%22td%3Anth-child(2)%22).html()%3B%0Aif(Valor%20%3D%3D%20%22OFFLINE%22)%7B%0A%20%24(this).find(%22td%22).css(%22background-color%22%2C%22LightCoral%22)%3B%0A%7Delse%20if(Valor%20%3D%3D%20%22Running%22)%7B%0A%20%24(this).find(%22td%22).css(%22background-color%22%2C%22LightGreen%22)%3B%0A%7D%0A%7D)%3B%0A%7D)%3B%0A%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="&amp;amp;lt;script&amp;amp;gt;" title="&amp;amp;lt;script&amp;amp;gt;" /&amp;gt;
'
#$header = "
&amp;lt;H5&amp;gt;XenApp DASHBOARD&amp;lt;/H5&amp;gt;

"
$title = "XenApp DASHBOARD"
#$body = $finalout | ConvertTo-Html -head $head -body $header -title $title
#$body = $finalout | ConvertTo-Html -head $head -title $title

#Create mail parameters
$messageParameters = @{
 Subject = "Report: Server Load and Assigned LE for XenApp Farm"
 Body = $finalout | ConvertTo-Html -head $head -title $title | out-string
 From = "Admin-Citrixtask@DOMAIN.COM"
 To = $sendto
 SmtpServer = "$SMTPsrv"
}

#Send e-mail with Server load data
Send-MailMessage @messageParameters -BodyAsHtml

#Uncomment to output the results in an HTA file to view in a browser
#$finalout | ConvertTo-Html -head $head -title $title | out-file $env:temp\report.hta
#ii $env:temp\report.hta

Example report

Currently the script records the following:

  • Server Name
  • Status
  • Server Load
  • Active Sessions
  • Disconnected Sessions
  • Load Evaluator Rules (you will have to modify the above script depending on which LE rules are using)
    • CPU Usage
    • Memory Usage
    • Session Count (not a count of sessions, but the LE number that contributes to the load)

director6

Explore more (these articles refer to older versions of XenApp, but they are still relevant)

Load Manager Values Explained

Load Manager Rules Explained

Troubleshooting Load Balancing Issues

Conclusion

As always, I welcome all comments and questions.

Thanks,
Alain

Advertisements