Automation | Powershell scripts
Xenapp 6.5 Health check script
XenAppServerHealthCheck Report through Script
## XenAppServerHealthCheck
## The script checks the health of a XenApp 6.x farm and e-mails two reports. The full farm
## health report is attached to the e-mail and any errors encountered are in an html report
## embedded in the email body.
## This script checks the following:
## - Ping response
## - Logon enabled
## - Assigned Load Evaluator
## - Active sessions
## - ICA port response
## - CGP port response (session reliability)
## - WMI response (to check for WMI corruption)
## - Citrix IMA, Citrix XML, Citrix Print Manager and Spooler services
## - Server uptime (to ensure scheduled reboots are occurring)
## - Server folder path and worker group memberships are report for informational purposes
## - PVS vDisk and write cache file size
##
## Change history:
## 2.7 - Fixed bug with Check Services
## 2.6 - Added support for unique load evaluators and PVS vDisks per server silo (based on
## server folder path). This functionality is in the $allSiloInfo hash table data
## structure.
## 2.5 - Added support for Cache in device RAM overflow to disk PVS option, now queries running
## services once for each server speeding up the script.
## 2.4 - Changed default Load Evaluator to handle multiple values; updated Ping routine to be
## more robust.
##
## TO DO
## - Rewrite for XA/XD 7.x!!
## - Add unique user count
## - Move per environment variable data to an XML file
## - PVS server health check
## - Incorporate Medvac??
##
## You are free to use this script in your environment but please e-mail me any improvements.
################################################################################################
if ((Get-PSSnapin "Citrix.XenApp.Commands" -EA silentlycontinue) -eq $null) {
try { Add-PSSnapin Citrix.XenApp.Commands -ErrorAction Stop }
catch { write-error "Error loading XenApp Powershell snapin"; Return }
}
# Change the below variables to suit your environment
#==============================================================================================
# Default load evaluator assigned to servers. Can have multiple values in format "LE1", "LE2",
# if a match is made to ANY of the listed LEs SUCCESS is returned for the LE check.
$defaultLE = "Servers"
# Default PVS vDisk assigned to servers
$defaultVDisk = "XA6_Desktop_1"
# Relative path to the PVS vDisk write cache file
$PvsWriteCache = "d$\.vdiskcache"
$PvsWriteCache2 = "d$\vdiskdif.vhdx"
# Maximum size of the local PVS write cache file
$PvsWriteMaxSize = 8gb # size in GB
# Servers in the excluded folders will not be included in the health check
$excludedFolders = @("Servers/Test","Servers/Turned Off")
# We always schedule reboots on XenApp farms, usually on a weekly basis. Set the maxUpTimeDays
# variable to the maximum number of days a XenApp server should be up for.
$maxUpTimeDays = 7
# E-mail report details
$emailFrom = "XenAppFarmReport@acme.co.nz"
$emailTo = "citrix.admins@acme.co.nz"
$smtpServer = "mail.acme.co.nz"
$emailSubject = ("XenApp Farm Report - " + (Get-Date -format R))
# Silo info
$allSiloInfo = @{}
$siloInfo = @{}
# Uncomment the below to enable silo support
# Copy the below four line as many time as required - once for each silo to be supported.
#$siloInfo.name = "Servers/Production"
#$siloInfo.LE = "Productions Servers"
#$siloInfo.vDisk = "XA6_Desktop_1"
#$allSiloInfo.($siloInfo.name) = $siloInfo
# Only change this if you have changed the Session Reliability port from the default of 2598
$sessionReliabilityPort = "2598"
#==============================================================================================
$currentDir = Split-Path $MyInvocation.MyCommand.Path
$logfile = Join-Path $currentDir ("XenAppServerHealthCheck.log")
$resultsHTM = Join-Path $currentDir ("XenAppServerHealthCheckResults.htm")
$errorsHTM = Join-Path $currentDir ("XenAppServerHealthCheckErrors.htm")
$headerNames = "FolderPath", "WorkerGroups", "ActiveSessions", "ServerLoad", "Ping", "Logons", "LoadEvaluator", "ICAPort", "CGPPort", "IMA", "CitrixPrint", "WMI", "XML", "Spooler", "Uptime", "WriteCacheSize", "vDisk"
$headerWidths = "6", "6", "4", "4", "4", "6", "6", "4", "6", "4", "4", "4", "4", "4", "5", "4", "4"
#==============================================================================================
function LogMe() {
Param(
[parameter(Mandatory = $true, ValueFromPipeline = $true)] $logEntry,
[switch]$display,
[switch]$error,
[switch]$warning,
[switch]$progress
)
if ($error) {
$logEntry = "[ERROR] $logEntry" ; Write-Host "$logEntry" -Foregroundcolor Red}
elseif ($warning) {
Write-Warning "$logEntry" ; $logEntry = "[WARNING] $logEntry"}
elseif ($progress) {
Write-Host "$logEntry" -Foregroundcolor Green}
elseif ($display) {
Write-Host "$logEntry" }
#$logEntry = ((Get-Date -uformat "%D %T") + " - " + $logEntry)
$logEntry | Out-File $logFile -Append
}
#==============================================================================================
function Ping([string]$hostname, [int]$timeout = 1000, [int]$retries = 3) {
$result = $true
$ping = new-object System.Net.NetworkInformation.Ping #creates a ping object
$i = 0
do {
$i++
#write-host "Count: $i - Retries:$retries"
try {
#write-host "ping"
$result = $ping.send($hostname, $timeout).Status.ToString()
} catch {
#Write-Host "error"
continue
}
if ($result -eq "success") { return $true }
} until ($i -eq $retries)
return $false
}
#==============================================================================================
Function writeHtmlHeader
{
param($title, $fileName)
$date = ( Get-Date -format R)
$head = @"
<html>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=iso-8859-1'>
<title>$title</title>
<STYLE TYPE="text/css">
<!--
td {
font-family: Tahoma;
font-size: 11px;
border-top: 1px solid #999999;
border-right: 1px solid #999999;
border-bottom: 1px solid #999999;
border-left: 1px solid #999999;
padding-top: 0px;
padding-right: 0px;
padding-bottom: 0px;
padding-left: 0px;
overflow: hidden;
}
body {
margin-left: 5px;
margin-top: 5px;
margin-right: 0px;
margin-bottom: 10px;
table {
table-layout:fixed;
border: thin solid #000000;
}
-->
</style>
</head>
<body>
<table width='1200'>
<tr bgcolor='#CCCCCC'>
<td colspan='7' height='48' align='center' valign="middle">
<font face='tahoma' color='#003399' size='4'>
<!--<img src="http://servername/administration/icons/xenapp.png" height='42'/>-->
<strong>$title - $date</strong></font>
</td>
</tr>
</table>
<table width='1200'>
<tr bgcolor='#CCCCCC'>
<td width=50% height='48' align='center' valign="middle">
<font face='tahoma' color='#003399' size='4'>
<!--<img src="http://servername/administration/icons/active.png" height='32'/>-->
Active Sessions: $TotalActiveSessions</font>
<td width=50% height='48' align='center' valign="middle">
<font face='tahoma' color='#003399' size='4'>
<!--<img src="http://servername/administration/icons/disconnected.png" height='32'/>-->
Disconnected Sessions: $TotalDisconnectedSessions</font>
</td>
</tr>
</table>
"@
$head | Out-File $fileName
}
# ==============================================================================================
Function writeTableHeader
{
param($fileName)
$tableHeader = @"
<table width='1200'><tbody>
<tr bgcolor=#CCCCCC>
<td width='6%' align='center'><strong>ServerName</strong></td>
"@
$i = 0
while ($i -lt $headerNames.count) {
$headerName = $headerNames[$i]
$headerWidth = $headerWidths[$i]
$tableHeader += "<td width='" + $headerWidth + "%' align='center'><strong>$headerName</strong></td>"
$i++
}
$tableHeader += "</tr>"
$tableHeader | Out-File $fileName -append
}
# ==============================================================================================
Function writeData
{
param($data, $fileName)
$data.Keys | sort | foreach {
$tableEntry += "<tr>"
$computerName = $_
$tableEntry += ("<td bgcolor='#CCCCCC' align=center><font color='#003399'>$computerName</font></td>")
#$data.$_.Keys | foreach {
$headerNames | foreach {
#"$computerName : $_" | LogMe -display
try {
if ($data.$computerName.$_[0] -eq "SUCCESS") { $bgcolor = "#387C44"; $fontColor = "#FFFFFF" }
elseif ($data.$computerName.$_[0] -eq "WARNING") { $bgcolor = "#FF7700"; $fontColor = "#FFFFFF" }
elseif ($data.$computerName.$_[0] -eq "ERROR") { $bgcolor = "#FF0000"; $fontColor = "#FFFFFF" }
else { $bgcolor = "#CCCCCC"; $fontColor = "#003399" }
$testResult = $data.$computerName.$_[1]
}
catch {
$bgcolor = "#CCCCCC"; $fontColor = "#003399"
$testResult = ""
}
$tableEntry += ("<td bgcolor='" + $bgcolor + "' align=center><font color='" + $fontColor + "'>$testResult</font></td>")
}
$tableEntry += "</tr>"
}
$tableEntry | Out-File $fileName -append
}
# ==============================================================================================
Function writeHtmlFooter
{
param($fileName)
@"
</table>
<table width='1200'>
<tr bgcolor='#CCCCCC'>
<td colspan='7' height='25' align='left'>
<font face='courier' color='#003399' size='2'><strong>Default Load Evaluator = $DefaultLE</strong></font>
<tr bgcolor='#CCCCCC'>
<td colspan='7' height='25' align='left'>
<font face='courier' color='#003399' size='2'><strong>Default VDISK Image = $DefaultVDISK</strong></font>
</td>
</tr>
</table>
</body>
</html>
"@ | Out-File $FileName -append
}
Function Check-Port
{
param ([string]$hostname, [string]$port)
try {
#$socket = new-object System.Net.Sockets.TcpClient($ip, $_.IcaPortNumber) #creates a socket connection to see if the port is open
$socket = new-object System.Net.Sockets.TcpClient($hostname, $Port) #creates a socket connection to see if the port is open
} catch {
$socket = $null
"Socket connection failed" | LogMe -display -error
return $false
}
if($socket -ne $null) {
"Socket Connection Successful" | LogMe
if ($port -eq "1494") {
$stream = $socket.GetStream() #gets the output of the response
$buffer = new-object System.Byte[] 1024
$encoding = new-object System.Text.AsciiEncoding
Start-Sleep -Milliseconds 500 #records data for half a second
while($stream.DataAvailable)
{
$read = $stream.Read($buffer, 0, 1024)
$response = $encoding.GetString($buffer, 0, $read)
#Write-Host "Response: " + $response
if($response -like '*ICA*'){
"ICA protocol responded" | LogMe
return $true
}
}
"ICA did not response correctly" | LogMe -display -error
return $false
} else {
return $true
}
} else { "Socket connection failed" | LogMe -display -error; return $false }
}
# ==============================================================================================
# == MAIN SCRIPT ==
# ==============================================================================================
"Checking server health..." | LogMe -display
rm $logfile -force -EA SilentlyContinue
# Data structure overview:
# Individual tests added to the tests hash table with the test name as the key and a two item array as the value.
# The array is called a testResult array where the first array item is the Status and the second array
# item is the Result. Valid values for the Status are: SUCCESS, WARNING, ERROR and $NULL.
# Each server that is tested is added to the allResults hash table with the computer name as the key and
# the tests hash table as the value.
# The following example retrieves the Logons status for server NZCTX01:
# $allResults.NZCTX01.Logons[0]
$allResults = @{}
# Get session list once to use throughout the script
$sessions = Get-XASession
Get-XAServer | % {
$tests = @{}
$server = $_.ServerName
# Check to see if the server is in an excluded folder path
$folderPath = $_.FolderPath
#$excludedFolders | ? { $_ -like $folderPath } | { $server + " in excluded folder - skipping" | LogMe; return }
if ($excludedFolders -contains $_.FolderPath) { $server + " in excluded folder - skipping" | LogMe; return }
$server | LogMe -display -progress
if ($allSiloInfo.$folderPath) {
# Silo specific info is available
if (($allSiloInfo.$folderPath).LE) { $serverLE = ($allSiloInfo.$folderPath).LE }
if (($allSiloInfo.$folderPath).vDisk) { $serverVDisk = ($allSiloInfo.$folderPath).vDisk }
} else {
$serverLE = $defaultLE
$serverVDisk = $defaultVDisk
}
$tests.FolderPath = $null, $_.FolderPath
$tests.WorkerGroups = $null, (Get-XAWorkerGroup -ServerName $server | % {$_.WorkerGroupName})
if ($_.CitrixVersion -ge 6.5) { $minXA65 = $true }
else { $minXA65 = $false }
# Check server logons
if($_.LogOnsEnabled -eq $false){
"Logons are disabled on this server" | LogMe -display -warning
$tests.Logons = "WARNING", "Disabled"
} else {
$tests.Logons = "SUCCESS","Enabled"
}
# Report on active server sessions
$activeServerSessions = [array]($sessions | ? {$_.State -eq "Active" -and $_.Protocol -eq "Ica" -and $_.ServerName -match $server})
if ($activeServerSessions) { $totalActiveServerSessions = $activeServerSessions.count }
# the following line will return unique users rather than active sessions
#if ($activeServerSessions) { $totalActiveServerSessions = ($activeServerSessions | Group-Object -property AccountName).count }
else { $totalActiveServerSessions = 0 }
$tests.ActiveSessions = $null, $totalActiveServerSessions
# Check Load Evaluator
$assignedLE = (Get-XALoadEvaluator -ServerName $_.ServerName).LoadEvaluatorName
if ($serverLE -notcontains $assignedLE) {
"Non-default Load Evaluator assigned" | LogMe -display -warning
$tests.LoadEvaluator = "WARNING", $assignedLE
} else {
$tests.LoadEvaluator = "SUCCESS", $assignedLE
}
# Ping server
$result = Ping $server 100
if ($result -ne "SUCCESS") { $tests.Ping = "ERROR", $result }
else { $tests.Ping = "SUCCESS", $result
# Test ICA connectivity
if (Check-Port $server $_.IcaPortNumber) { $tests.ICAPort = "SUCCESS", "Success" }
else { $tests.ICAPort = "ERROR","No response" }
# Test Session Reliability port
if (Check-Port $server $sessionReliabilityPort) { $tests.CGPPort = "SUCCESS", "Success" }
else { $tests.CGPPort = "ERROR", "No response" }
# Check services
$services = Get-Service -Computer $Server
if (($services | ? {$_.Name -eq "IMAService"}).Status -Match "Running") {
"IMA service running..." | LogMe
$tests.IMA = "SUCCESS", "Success"
} else {
"IMA service stopped" | LogMe -display -error
$tests.IMA = "ERROR", "Error"
}
if (($services | ? {$_.Name -eq "Spooler"}).Status -Match "Running") {
"SPOOLER service running..." | LogMe
$tests.Spooler = "SUCCESS","Success"
} else {
"SPOOLER service stopped" | LogMe -display -error
$tests.Spooler = "ERROR","Error"
}
if (($services | ? {$_.Name -eq "cpsvc"}).Status -Match "Running") {
"Citrix Print Manager service running..." | LogMe
$tests.CitrixPrint = "SUCCESS","Success"
} else {
"Citrix Print Manager service stopped" | LogMe -display -error
$tests.CitrixPrint = "ERROR","Error"
}
if (($minXA65 -and $_.ElectionPreference -ne "WorkerMode") -or (!($minXA65))) {
if (($services | ? {$_.Name -eq "ctxhttp"}).Status -Match "Running") {
"XML service running..." | LogMe
$tests.XML = "SUCCESS","Success"
} else {
"XML service stopped" | LogMe -display -error
$tests.XML = "ERROR","Error"
}
} else { $tests.XML = "SUCCESS","N/A" }
# If the IMA service is running, check the server load
if ($tests.IMA[0] -eq "Success") {
try {
$CurrentServerLoad = Get-XAServerLoad -ServerName $server
#$CurrentServerLoad.GetType().Name|LogMe -display -warning
if( [int] $CurrentServerLoad.load -lt 7500) {
"Serverload is low" | LogMe
$tests.Serverload = "SUCCESS", ($CurrentServerload.load)
}
elseif([int] $CurrentServerLoad.load -lt 9000) {
"Serverload is Medium" | LogMe -display -warning
$tests.Serverload = "WARNING", ($CurrentServerload.load)
}
else {
"Serverload is High" | LogMe -display -error
$tests.Serverload = "ERROR", ($CurrentServerload.load)
}
}
catch {
"Error determining Serverload" | LogMe -display -error
$tests.Serverload = "ERROR", ($CurrentServerload.load)
}
$CurrentServerLoad = 0
}
# Test WMI
$tests.WMI = "ERROR","Error"
try { $wmi=Get-WmiObject -class Win32_OperatingSystem -computer $_.ServerName }
catch { $wmi = $null }
# Perform WMI related checks
if ($wmi -ne $null) {
$tests.WMI = "SUCCESS", "Success"
$LBTime=$wmi.ConvertToDateTime($wmi.Lastbootuptime)
[TimeSpan]$uptime=New-TimeSpan $LBTime $(get-date)
if ($uptime.days -gt $maxUpTimeDays){
"Server reboot warning, last reboot: {0:D}" -f $LBTime | LogMe -display -warning
$tests.Uptime = "WARNING", $uptime.days
} else {
$tests.Uptime = "SUCCESS", $uptime.days
}
} else { "WMI connection failed - check WMI for corruption" | LogMe -display -error }
################ PVS SECTION ###############
if (test-path \\$Server\c$\Personality.ini) {
$PvsWriteCacheUNC = Join-Path "\\$Server" $PvsWriteCache
$CacheDiskexists = Test-Path $PvsWriteCacheUNC
if ($CacheDiskexists -eq $False) {
$PvsWriteCacheUNC = Join-Path "\\$Server" $PvsWriteCache2
$CacheDiskexists = Test-Path $PvsWriteCacheUNC
}
if ($CacheDiskexists -eq $True)
{
$CacheDisk = [long] ((get-childitem $PvsWriteCacheUNC -force).length)
$CacheDiskGB = "{0:n2}GB" -f($CacheDisk / 1GB)
"PVS Cache file size: {0:n2}GB" -f($CacheDisk / 1GB) | LogMe
#"PVS Cache max size: {0:n2}GB" -f($PvsWriteMaxSize / 1GB) | LogMe -display
if($CacheDisk -lt ($PvsWriteMaxSize * 0.5))
{
"WriteCache file size is low" | LogMe
$tests.WriteCacheSize = "SUCCESS", $CacheDiskGB
}
elseif($CacheDisk -lt ($PvsWriteMaxSize * 0.8))
{
"WriteCache file size moderate" | LogMe -display -warning
$tests.WriteCacheSize = "WARNING", $CacheDiskGB
}
else
{
"WriteCache file size is high" | LogMe -display -error
$tests.WriteCacheSize = "ERORR", $CacheDiskGB
}
}
$Cachedisk = 0
$VDISKImage = get-content \\$Server\c$\Personality.ini | Select-String "Diskname" | Out-String | % { $_.substring(12)}
if($VDISKImage -Match $serverVDisk){
"Default vDisk detected" | LogMe
$tests.vDisk = "SUCCESS", $VDISKImage
} else {
"vDisk unknown" | LogMe -display -error
$tests.vDisk = "WARNING", $VDISKImage
}
}
else { $tests.WriteCacheSize = "SUCCESS", "N/A"; $tests.vDisk = "SUCCESS", "N/A" }
############## END PVS SECTION #############
}
$allResults.$server = $tests
}
# Get farm session info
$ActiveSessions = [array]($sessions | ? {$_.State -eq "Active" -and $_.Protocol -eq "Ica"})
$DisconnectedSessions = [array]($sessions | ? {$_.State -eq "Disconnected" -and $_.Protocol -eq "Ica"})
if ($ActiveSessions) { $TotalActiveSessions = $ActiveSessions.count }
# the following line will return unique users rather than active sessions
# if ($activeSessions) { $totalActiveSessions = ($activeSessions | Group-Object -property AccountName).count }
else { $TotalActiveSessions = 0 }
if ($DisconnectedSessions) { $TotalDisconnectedSessions = $DisconnectedSessions.count }
else { $TotalDisconnectedSessions = 0 }
"Total Active Sessions: $TotalActiveSessions" | LogMe -display
"Total Disconnected Sessions: $TotalDisconnectedSessions" | LogMe -display
# Write all results to an html file
Write-Host ("Saving results to html report: " + $resultsHTM)
writeHtmlHeader "XenApp Farm Report" $resultsHTM
writeTableHeader $resultsHTM
$allResults | sort-object -property FolderPath | % { writeData $allResults $resultsHTM }
writeHtmlFooter $resultsHTM
# Write only the errors to an html file
#$allErrors = $allResults | where-object { $_.Ping -ne "success" -or $_.Logons -ne "enabled" -or $_.LoadEvaluator -ne "default" -or $_.ICAPort -ne "success" -or $_.IMA -ne "success" -or $_.XML -ne "success" -or $_.WMI -ne "success" -or $_.Uptime -Like "NOT OK*" }
#$allResults | % { $_.Ping -ne "success" -or $_.Logons -ne "enabled" -or $_.LoadEvaluator -ne "default" -or $_.ICAPort -ne "success" -or $_.IMA -ne "success" -or $_.XML -ne "success" -or $_.WMI -ne "success" -or $_.Uptime -Like "NOT OK*" }
#Write-Host ("Saving errors to html report: " + $errorsHTM)
#writeHtmlHeader "XenApp Farm Report Errors" $errorsHTM
#writeTableHeader $errorsHTM
#$allErrors | sort-object -property FolderPath | % { writeData $allErrors $errorsHTM }
#writeHtmlFooter $errorsHTM
($mailMessageParameters = @{
From = $emailFrom
To = $emailTo
Subject = $emailSubject
SmtpServer = $smtpServer
Body = (gc $resultsHTM) | Out-String
Attachment = $resultsHTM
}
Send-MailMessage @mailMessageParameters -BodyAsHtml)
$mailMessageParameters = @{
from = “abc@co.com”
to = “xyz@coi.com”
Subject = “XenApp6 Health Check Report”
body = get-content “C:\Temp\XenApp 6.x\XenAppServerHealthCheckResults.htm” | Out-String
attachments = “C:\Temp\XenApp 6.x\XenAppServerHealthCheckResults.htm”
smtpserver = “10.0.0.1”
}
Send-MailMessage @mailMessageParameters -BodyAsHtml