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

Director Under the Hood: New Users

Intro

Director is Citrix’s new metrics and monitoring dashboard. The interface is modern and the emphasis is on real-time information about your users. It consolidates information about your environment and makes it easy to differentiate between applications and desktops. If your only experience has been with EdgeSight in the past then you’ll see Director as a breath of fresh air.

There’s a lot of good views and data in the new Citrix Director and the “one pane of glass” view of your environment is pursued by all 3rd party monitoring, reporting, and alerting vendors. Unfortunately, it’s not easy to get all the same data I’ve gathered in past from the Director database. In this post we’ll look at tracking new users connecting to your Citrix environment.

For information on the database schema…read my previous article on Director.

New Users

I collect lots of metrics to report on my environment. One of the ones I track is the number of new users that connect to my Citrix environment. I view this metric as speaking to the overall adoption rate of my Citrix platform as well as a leading indicator for growth. Can we find this info in the Director Trends dashboard?

The short answer is no. The long answer is noooooooooooooooooooooooooooooo. In fact, it is not possbile to track this in EdgeSight. In a previous job, we worked around this by adding a USER table to the Edgesight database and then ran a query to compare the unique users who logged in that past month against the USER table. Who ever did not show up in the USER table was considered new.

SELECT distinct [user]
FROM vw_ctrx_archive_server_start_perf AS ESdata
WHERE [user]  'UNKNOWN'
and convert(varchar(10),time_stamp,111) between '2016/05/01'
and '2016/05/31'
and (NOT EXISTS
(SELECT distinct userid
FROM userarchive
WHERE (userarchive.userid = ESData.[user]))) order by [user]

The above query gets all the unqiue users who logged in between May 1st and May 31st (using the Edgesight view: vw_ctrx_archive_server_start_perf). It then compares this list against the userarchive table that we created to store the username and some other data about our users. Thus we got  a count of new users to our Citrix environment. Once we completed our monthly reporting, we added these new users to the userarchive table.

You say, “That’s great Alain. Wow! How the heck do I do this in Director?”

I say…

“SQL To the Rescue!”

For this query I’m using only one table:

MonitorData.User (Table)
image

I select the month and year and then count the usernames for that month and year. The great thing about this table is that it only creates a new row the first time a user connects to the system automatically. So, the following query will give you a easy way to see the new users who connected to your Citrix envrionment.

SELECT convert(char(9),datename(month,CreatedDate)) + ' '
+ convert(char(4),datepart(year,CreatedDate)) as 'Month',
count (Username) as 'New Users'
FROM MonitorData.[User]
GROUP BY convert(char(9),datename(month,CreatedDate)) + ' '
+ convert(char(4),datepart(year,CreatedDate))

MonitorData.User_query

In conclusion

I hope this encourages you to take a look under the hood of Director to see what you can get out of it. The database infrastructure is much, much simpler than EdgeSight and should provide a lot of good detail.

Thanks,
Alain

Director Under the Hood: Total Sessions and Unique Users Per Day

Intro

Director is Citrix’s new metrics and monitoring dashboard. The interface is modern and the emphasis is on real-time information about your users. It consolidates information about your environment and makes it easy to differentiate between applications and desktops. If your only experience has been with EdgeSight in the past then you’ll see Director as a breath of fresh air.

There’s a lot of good views and data in the new Citrix Director and the “one pane of glass” view of your environment is pursued by all 3rd party monitoring, reporting, and alerting vendors. Unfortunately, it’s not easy to get all the same data I’ve gathered in past from the Director database. In this post we’ll look at a query to show you the total sessions and unique users per day.

This is it…really?

The tables that make up the Director Database

The views that make up the Director Database

image image

After years of pouring through and querying EdgeSight’s tables and views, I first thought that something must be wrong. This can’t be all there is to the Director database, but that’s all there is.  Before we dive into SQL, let’s see what we can find using the Director GUI. I like to collect lots of metrics when I report on my environment. The 3 main session metrics I track are concurrent user per day, unique users per day and total sessions per day. Can we find this info in the Director Trends dashboard?

I set the Time period to Last Month and then set to custom ending to 10/1/2015. This should give me data for September 2015. Here’s what we get:

image

NOTE: For these examples, I’m looking at all delivery groups. You can limit your view by delivery group if you wanted to track metrics for different groups of users.

As you can see, we get a pretty graph, but we have to export the data to Excel to get precise detail:

image

What this doesn’t show us it how many sessions and unique users there are per day. The only way to get this using the Director interface is to click on a point on the graph to see the session details. This will only work for more recent time period.

image

SQL To the Rescue

For this query I’m using the following tables/views:

MonitorData.SessionV1 (View) MonitorData.Connection (Table) MonitorData.User (Table)
image image image

I’m linking the SessionV1 and Connection SessionKey columns together and the User.id and SessionV1.userid columns together. This ensures that I’m grouping the same sessions and users together (users can have more than one session). Then I group by the LogOnStartDate and count the distinct sessionkeys and distinct userids. This gives me the total sessions and unique users per day.
This query will pull all available data and total the sessions and unique users per day.

select convert(varchar(10),LogOnStartDate,111) as 'Date', count (distinct MonitorData.SessionV1.sessionKey) as 'Total Sessions', count (distinct MonitorData.SessionV1.Userid) as 'Unique Users'
from MonitorData.SessionV1,MonitorData.Connection,MonitorData.[User]
where FailureDate is NULL and MonitorData.SessionV1.SessionKey = MonitorData.Connection.SessionKey
and MonitorData.[User].Id = MonitorData.SessionV1.userid
group by convert(varchar(10),LogOnStartDate,111)
order by convert(varchar(10),LogOnStartDate,111)

image
The following query is similar, but it just pulls data for the current month.

DECLARE @mydate DATETIME
Set @mydate = GETDATE()
select convert(varchar(10),LogOnStartDate,111) as 'Date', count (distinct MonitorData.SessionV1.sessionKey) as 'Total Sessions', count (distinct MonitorData.SessionV1.Userid) as 'Unique Users'
from MonitorData.SessionV1,MonitorData.Connection,MonitorData.[User]
where FailureDate is NULL and MonitorData.SessionV1.SessionKey = MonitorData.Connection.SessionKey
and MonitorData.[User].Id = MonitorData.SessionV1.userid
and convert(varchar(10),LogOnStartDate,111) between CONVERT(VARCHAR(25),DATEADD(dd,-(DAY(@mydate)-1),@mydate),111)
and CONVERT(VARCHAR(25),DATEADD(dd,-(DAY(DATEADD(mm,1,@mydate))),DATEADD(mm,1,@mydate)),111)
group by convert(varchar(10),LogOnStartDate,111)
order by convert(varchar(10),LogOnStartDate,111)

image

This query groups by the current month, so you can get the total unique sessions and users for the current month:

DECLARE @mydate DATETIME
Set @mydate = GETDATE()
select convert(char(9),datename(month,LogOnStartDate)) + ' ' + convert(char(4),datepart(year,LogonStartDate)) as 'Month',
count (distinct MonitorData.SessionV1.sessionKey) as 'Total Sessions',
count (distinct MonitorData.SessionV1.Userid) as 'Unique Users'
from MonitorData.SessionV1,MonitorData.Connection,MonitorData.[User]
where FailureDate is NULL
and MonitorData.SessionV1.SessionKey = MonitorData.Connection.SessionKey
and MonitorData.[User].Id = MonitorData.SessionV1.userid
and convert(varchar(25),LogOnStartDate,107) between CONVERT(VARCHAR(25),DATEADD(dd,-(DAY(@mydate)-1),@mydate),107)
and CONVERT(VARCHAR(25),DATEADD(dd,-(DAY(DATEADD(mm,1,@mydate))),DATEADD(mm,1,@mydate)),107)
group by convert(char(9),datename(month,LogOnStartDate)) + ' ' + convert(char(4),datepart(year,LogonStartDate))

image

This query is similar to above, but takes all the available data and groups it by month:

select convert(char(9),datename(month,LogOnStartDate)) + ' ' + convert(char(4),datepart(year,LogonStartDate)) as 'Month',
count (distinct MonitorData.SessionV1.sessionKey) as 'Total Sessions',
count (distinct MonitorData.SessionV1.Userid) as 'Unique Users'
from MonitorData.SessionV1,MonitorData.Connection,MonitorData.[User]
where FailureDate is NULL
and MonitorData.SessionV1.SessionKey = MonitorData.Connection.SessionKey
and MonitorData.[User].Id = MonitorData.SessionV1.userid
group by convert(char(9),datename(month,LogOnStartDate)) + ' ' + convert(char(4),datepart(year,LogonStartDate))

image

In conclusion

I hope this encourages you to take a look under the hood of Director to see what you can get out of it. The database infrastructure is much, much simpler than EdgeSight and should provide a lot of good detail.

Thanks,
Alain

%d bloggers like this: