ProvisioningServices
Happy New Script – PVS 7.7 HealthCheck
We are close to the New Year 2016 and I want to wish you all the best for 2016!
I’m already happy now, because I can publish some hours after the PVS 7.7 release from Citrix this Script:
Today Citrix released XenApp/XenDesktop 7.7 and Citrix Provisioning Services 7.7. There are a bunch of new features, but for me the most important thing, beside of the Win10 support, is
…. drum roll ….
the new PVS PowerShell API. PoSh with PVS 7.6 and below was a pain … To know what else is new in PVS look here: PVS 7.7 New in this release
Now PVS comes with a real PoSh interface which gave more back than a bundle of strings. I had the chance to play around with PVS 7.7 Tech Preview and so here you have already the PVS HealthCheck Script for Version 7.7 in a complete new developed version:
If you have a feature request or a bug report please post it direct on GitHub.
This version doesn’t work with PVS 7.6 and below, if you have an older version than 7.7 take this script: Citrix PVS HealthCheck
Update 11.04.2016: Performance-Improvement, Change the order of the table, all the VDI status now at the end.
Update 09.05.2016: Bugfix & Input from Jay, get CPU/Memory/Disk usage of PVS.
Update 27.05.2016: Now my Script is on GitHub
I’m an absolute GitHub newbie … for this reason I leave it still also here as a download until I’m really comfortable with GitHub.
The code is on GitHub:
GeekOvation – PVS Script on Top3
My Citrix PVS Health Check PowerShell Script was nominated into the final round (Top3) of the Citrix GeekOvation Award. This means I have to present my “geeky solution” on Citrix Synergy 2015 in Orlando on the SYN501 Geek Speak Tonight Session. To be honest to speak in front of so much people is a new thing for me … and it makes me “a bit” twitchy …
To be nominated for this Award was not just a big surprise for me, I see this also as a honor and glory for my work. I would say it’s not so a big thing this script and it isn’t rocket science.
Dear GeekSpeak Tonight audience, please check out my PVS Health Check Script and provide me feedback if you want. Also have a look to my XenApp / XenDesktop Script.
At this place I want to say thank you to Jason Poyner who gave me the idea for such kind of Script with his XenApp6 HealthCheck and where I took some code lines for the HTML output. To Martin Hartmann who helped me by learning PowerShell and of course to Aaron Parker (http://stealthpuppy.com) who was the assigned CTP helping me polish off my presentation materials.
Update 14.5.2015:
I won – thank YOU
I won the competition and seems that I’m the not-yet-known Geek #1 this Year! Thanks you all! When I saw the work from to other two guys, I had the feeling to have no chance, not just because I’m the only not-native english speaker. David Ott with a great PowerShell script to modify the user profile and Shane O’Neil with the C-Sharp code to have a self healing VDI, GREAT!
Read this blog post from Shane and Paul, if you are interested on more PVS PowerShell stuff: http://blogs.citrix.com/2015/04/07/wait-you-mean-pvs-has-powershell and watch the recording of “SYN514: Turn XenApp and XenDesktop into capacity on demand with Provisioning Services automation”
I’m sure I also had luck to win, and now I have the chance to go to Las Vegas Citrix Synergy 2016. I also want to thank ControlUp for the AppleWatch they sponsored for the GeekOvation Winner.
About the code
Like already mentioned the idea about the health check with the HTML output comes from Jason Poyner. The main script with all the logics it’s from me. The hardest thing when you Script with PoSh on PVS that it doesn’t return proper PS Object, that mean’s you doing a lot of format-cut stuff to get what you want. If you want to know more about that read Paul’s blog section “PVS comes with a PowerShell add-in that is not an easy beast to tame.”
What’s next?
I’m currently working on a Script which shows PVS retries during a variable timeframe (day, week or month) and create a graphic output, in this way you can see at which time you maybe have a traffic jam on your network.
I also made a script to export/import target’s into PVS and another one for export and import in XenDesktop, if I have time I’ll polish and publish this here.
Citrix PVS HealthCheck
Problem: There is currently no free tool to check the health status of a Citrix Provisioning Services farm with all Citrix relevant parameters. There are monitoring tools which tell you if a server is up and running or if it’s not, but now tool who gives you a special overview for Citrix Provisioning Services according to how much targets on which server, how much communication retries on which target an so on.
Solution:
Since some year’s I’m using the XenApp Health Check from Jason Poyner (deptive.co.nz) to get daily a status of my environment(s). I really appreciate the work from Jason, if you don’t know it until now, you really need have a look on his blog and on the Script . The mentioned XenApp Farm Health Check Script gave me the inspiration to create a similar Health Check Script for my Citrix Provisioning Server farm. To be honest, and I’m happy that Jason doesn’t mind, that I have “recycled” some parts, or say the “framework” of his XenApp Farm Health Check script to build my Script.
The script I built can be scheduled with a Windows task which trigger a run once a day and send out a email with the attached HTML-Report:
I’ve written the Script to perform the checks in 3 Parts:
Target Devices:
- Ping
- Retry (Threshold configurable, default is 15)
- PVS Disk (Value which I read from PVS farm with get-mcli)
PVS Disk (Value which is written in the personality.ini on the Target)- PVS disk version (V1.2)
- The vDisk-Store *
- PVS Server
- WriteCache Size
* like you can read in my BlogPost “Achieve fastest Citrix Provisioning Target Device” I propose to have the vDisk Store local on each provisioning server. So I can check with that column if the Target is booting from the correct vDisk store. The Master Store is on CIFS and only used for updates of the Image.
vDisks:
- Replication state (PVS don’t replicate anything – but can check if the vDisks on all servers are equal)
- DeviceCount to see how many targets are currently reading from this Image
- Load Balancing algorithm
(Green if Best Effort because I want to have subnet affinity – feel free to change for your need. I have two data centers with two separated subnet’s – so I can guarantee that each target stream from his own data centre as long the server is available. Just in case of an outage of all PVS server in one DC it fail over to the other DC ) - WriteCache Type
- Name & Date of the vDisk
PVS Servers:
- Ping
- Active
- How many device are connected
Don’t forget to register the PVS SnapIn DLL e.g. with .Net Framework 2.0 with the command:
“C:\WINDOWS\Microsoft.NET\Framework64\v2.0.50727\InstallUtil.exe” “C:\Program Files\Citrix\Provisioning Services Console\McliPSSnapIn.dll”
Otherwise you get a blank output. If you have another .Net Framework version or path, please adjust the path in the command!
#============================================================================================== # Created on: 08.2014 Version: 1.63 # Created by: Sacha Thomet, blog.sachathomet.ch / sachathomet.ch # Filename: Citrix-PVS-Farm-Health-toHTML.ps1 # # Special Thanks to: # - Jason Poyner ... I've borrowed parts of the script and ideas to create this # PVS health check script. Check his excellent XenApp Health Check @ techblog.deptive.co.nz. # - Martin Hartmann to share his PowerShell KnowHow with me. # # Description: This script checks Citrix Provisioning Server, Farm, vDisk & Target devices. # # Prerequisite: Script must run on a PVS server, where MCLI snap-in is registered. # Register SnapIn with command: C:\WINDOWS\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe # "C:\Program Files\Citrix\Provisioning Services Console\McliPSSnapIn.dll" # # Call by : Scheduled Task, e.g. once a day # # Change Log: # V1.1: Consolidated code # V1.2: Add possibility to check only specified Versions # V1.3: New version after Citrix Synergy 2015 GeekOvation Award nomination. # With correction of typos and add some documentation lines. # V1.4: Possibility for multiple stores (Thanks to Kafedzhiev for the code) (08-2015) # V1.5: Show create date of vDisk, FileName and the count of the used vDisk (09-2015) # V1.6: Add RamCache used from each target, added code by Jonathan Pitre, # code from Matthew Nics http://mattnics.com/?p=414 (10-2015) # V1.61: Changed RamCache to general WriteCache, add possibility to get Size of Cache on HD # V1.62: Correction in Header to show correct farm name instead a "6", correction in # LoadBalancingAlgorithm, Error if a disk is assigned fix to a server. # V1.63: Check of Stream-, Soap-, and TFTP-Service (12-2015) # # # THIS SCRIPT IS FOR PVS 7.6 AND BELOW. ASK FOR BETA VERSION OF PVS 7.7 HEALTH CHECK SCRIPT # IF YOU ARE USING THE TECHPREVIEW OF PVS 7.7 WITH COMPLETE NEW POSH-IMPLEMENTATION # # #============================================================================================== if ((Get-PSSnapin "McliPSSnapIn" -EA silentlycontinue) -eq $null) { try { Add-PSSnapin McliPSSnapIn -ErrorAction Stop } catch { write-error "Error loading PVS McliPSSnapIn PowerShell snapin"; Return } } # Change the below variables to suit your environment #============================================================================================== # Target Device Health Check threshold: $retrythresholdWarning= "15" # define the Threshold from how many retries the color switch to red # Include for Device Collections, type "every" if you want to see every Collection # Example1: $Collections = @("XA65","XA5") # Example2: $Collections = @("every") $Collections = @("every") # Information about the site you want to check: $siteName="site" # site name on which the according Store is. # E-mail report details $emailFrom = "email@company.ch" $emailTo = "citrix@company.ch"#,"sacha.thomet@appcloud.ch" $smtpServer = "mailrelay.company.ch" $emailSubjectStart = "PVS Farm Report" $mailprio = "High" #============================================================================================== $currentDir = Split-Path $MyInvocation.MyCommand.Path $logfile = Join-Path $currentDir ("PVSHealthCheck.log") $resultsHTM = Join-Path $currentDir ("PVSFarmReport.htm") $errorsHTM = Join-Path $currentDir ("PVSHealthCheckErrors.htm") #Header for Table 1 "Target Device Checks" $TargetfirstheaderName = "TargetDeviceName" $TargetheaderNames = "CollectionName", "Ping", "Retry", "vDisk_PVS", "vDisk_Version", "WriteCache", "PVSServer" $TargetheaderWidths = "4", "4", "4", "4", "2" , "4", "4" $Targettablewidth = 1200 #Header for Table 2 "vDisk Checks" $vDiksFirstheaderName = "vDisk" $vDiskheaderNames = "Store", "vDiskFileName", "deviceCount", "CreateDate" , "ReplState", "LoadBalancingAlgorithm", "WriteCacheType" $vDiskheaderWidths = "4", "8", "2","4", "4", "4", "4" $vDisktablewidth = 1200 #Header for Table 3 "PV Server" $PVSfirstheaderName = "PVS Server" $PVSHeaderNames = "Ping", "Active", "deviceCount","SoapService","StreamService","TFTPService" $PVSheaderWidths = "4", "4", "4","4","4","4" $PVStablewidth = 600 #Header for Table 4 "Farm" $PVSFirstFarmheaderName = "FarmChecks" $PVSFarmHeaderNames = "Setting", "Value" $PVSFarmWidths = "4", "8", "8" $PVSFarmTablewidth = 400 #============================================================================================== #log function 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 = 200) { $ping = new-object System.Net.NetworkInformation.Ping #creates a ping object try { $result = $ping.send($hostname, $timeout).Status.ToString() } catch { $result = "Failure" } return $result } #============================================================================================== 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'> <strong>$title - $date</strong></font> </td> </tr> </table> "@ $head | Out-File $fileName } # ============================================================================================== Function writeTableHeader { param($fileName, $firstheaderName, $headerNames, $headerWidths, $tablewidth) $tableHeader = @" <table width='$tablewidth'> <tbody> <tr bgcolor=#CCCCCC> <td width='6%' align='center'><strong>$firstheaderName</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 writeTableFooter { param($fileName) "</table> "| Out-File $fileName -append } #============================================================================================== Function writeData { param($data, $fileName, $headerNames) $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='#000000' size='2'><strong>Retry Threshold =</strong></font><font color='#003399' face='courier' size='2'> $retrythresholdWarning <tr></font> <tr bgcolor='#CCCCCC'> </td> </tr> <tr bgcolor='#CCCCCC'> </tr> </table> </body> </html> "@ | Out-File $FileName -append } #============================================================================================== # == MAIN SCRIPT == #============================================================================================== rm $logfile -force -EA SilentlyContinue "Begin with Citrix Provisioning Services HealthCheck" | LogMe -display -progress " " | LogMe -display -progress # ======= PVS Target Device Check ======== "Check PVS Target Devices" | LogMe -display -progress " " | LogMe -display -progress $allResults = @{} $pvsdevices = mcli-get device -f deviceName | Select-String deviceName foreach($target in $pvsdevices) { $tests = @{} # Check to see if the server is in an excluded folder path $target | Select-String deviceName $_targetshort = $target -replace "deviceName: ","" $pvcollectionName = mcli-get deviceinfo -p devicename=$_targetshort | select-string collectionName $short_collectionName = $pvcollectionName.ToString().TrimStart("collectionName: ") #Only Check Servers in defined Collections: if ($Collections -contains $short_collectionName -Or $Collections -contains "every") { $target | Select-String deviceName $_targetshort = $target -replace "deviceName: ","" $_targetshort | LogMe -display -progress # Ping server $result = Ping $_targetshort 100 if ($result -ne "SUCCESS") { $tests.Ping = "ERROR", $result } else { $tests.Ping = "SUCCESS", $result } #CollectionName $pvcollectionName = mcli-get deviceinfo -p devicename=$_targetshort | select-string collectionName $short_collectionName = $pvcollectionName.ToString().TrimStart("collectionName: ") $tests.CollectionName = "NEUTRAL", "$short_collectionName" # Test Retries $devicestatus = mcli-get deviceinfo -p devicename=$_targetshort -f status $retrycount = $devicestatus[4].TrimStart("status: ") -as [int] if ($retrycount -lt $retrythresholdWarning) { $tests.Retry = "SUCCESS", "$retrycount Retry = OK" } else { $tests.Retry = "WARNING","$retrycount retries!" } #Check assigned Image $devicediskFileName = mcli-get deviceinfo -p devicename=$_targetshort | select-string diskFileName $short_devicediskFileName = $devicediskFileName.ToString().TrimStart("diskFileName: ") $tests.vDisk_PVS = "SUCCESS", "$short_devicediskFileName" #Check assigned Image Version $devicediskVersion = mcli-get deviceinfo -p devicename=$_targetshort | select-string diskVersion: $short_devicediskVersion = $devicediskVersion.ToString().TrimStart("diskVersion: ") $tests.vDisk_Version = "SUCCESS", "$short_devicediskVersion" #PVS-Server $PVSServername = mcli-get deviceinfo -p devicename=$_targetshort | select-string serverName $short_PVSServername = $PVSServername.ToString().TrimStart("serverName: ") $tests.PVSServer = "Neutral", "$short_PVSServername" ################ PVS WriteCache SECTION ############### if (test-path \\$_targetshort\c$\Personality.ini) { $wconhd = "" $wconhd = Get-Content \\$_targetshort\c$\Personality.ini | Where-Object {$_.Contains("WriteCacheType=4") } If ($wconhd -match "$WriteCacheType=4") {Write-Host Cache on HDD #WWC on HD is $wconhd # Relative path to the PVS vDisk write cache file $PvsWriteCache = "d$\.vdiskcache" # Size of the local PVS write cache drive $PvsWriteMaxSize = 10gb # size in GB $PvsWriteCacheUNC = Join-Path "\\$_targetshort" $PvsWriteCache $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.WriteCache = "SUCCESS", $CacheDiskGB } elseif($CacheDisk -lt ($PvsWriteMaxSize * 0.8)) { "WriteCache file size moderate" | LogMe -display -warning $tests.WriteCache = "WARNING", $CacheDiskGB } else { "WriteCache file size is high" | LogMe -display -error $tests.WriteCache = "ERORR", $CacheDiskGB } } $Cachedisk = 0 $VDISKImage = get-content \\$_targetshort\c$\Personality.ini | Select-String "Diskname" | Out-String | % { $_.substring(12)} if($VDISKImage -Match $DefaultVDISK){ "Default vDisk detected" | LogMe $tests.vDisk = "SUCCESS", $VDISKImage } else { "vDisk unknown" | LogMe -display -error $tests.vDisk = "SUCCESS", $VDISKImage } } else {Write-Host Cache on Ram #RAMCache #Get-RamCache from each target, code from Matthew Nics http://mattnics.com/?p=414 $RAMCache = [math]::truncate((Get-WmiObject Win32_PerfFormattedData_PerfOS_Memory -ComputerName $_targetshort).PoolNonPagedBytes /1MB) $tests.WriteCache = "Neutral", "$RamCache MB on Ram" } } else {Write-Host WriteCache not readable $tests.WriteCache = "Neutral", "Cache not readable" } ############## END PVS WriteCache SECTION ############# #Forward results to $allResult array which will be written in HTM-File $allResults.$_targetshort = $tests } } # ======= PVS vDisk Check #================================================================== "Check PVS vDisks" | LogMe -display -progress " " | LogMe -display -progress $storenames = mcli-get store | Select-string storename $vdiskResults = @{} foreach ($storenameA in $storenames) { $storename = $storenameA -replace "storename: ","" $storeid = mcli-get store -p storeName=$storename | Select-String storeId $storeid_short = $storeid -replace "storeId: ","" $alldisks = Mcli-Get disklocator -p siteName=$siteName, storeId=$storeid_short | Select-String diskLocatorName foreach($disk in $alldisks) { $disk1 = $disk | Select-String diskLocatorName $disklocator_short = $disk1 -replace "diskLocatorName: ","" foreach($diksloc in $disklocator_short) { $VDtests = @{} $DiskVersion = Mcli-Get DiskVersion -p diskLocatorName=$disklocator_short, siteName=$siteName, storeName=$storename $diskreplstatus = $DiskVersion | Select-String goodInventoryStatus $diskreplstatus_short = $diskreplstatus -replace "goodInventoryStatus: ","" $disklocator_short $diskreplstatus_short # vDiskFileName & createDate $pathA = mcli-get store -p storeName=$storename | Select-String path -casesensitive $path = $pathA -replace "path: ","" $diskfilenameA = Mcli-Get DiskVersion -p diskLocatorName=$disklocator_short, siteName=$siteName, storeName=$storename | Select-String diskFileName $diskfilename = $diskfilenameA -replace "diskFileName: "," " $createDateA = Mcli-Get DiskVersion -p diskLocatorName=$disklocator_short, siteName=$siteName, storeName=$storename | Select-String createDate $createDate = $createDateA -replace "createDate: "," " $VDtests.vDiskFileName = "OK", " $diskfilename" Write-Host ("Path is $path $disklocator_short $diskfilename") $VDtests.createDate = "OK", " $createDate" Write-Host ("Path is $path $disklocator_short $createDate") $vdiskResults.$disklocator_short = $VDtests #Check if correct replicated if($diskreplstatus_short -eq 1 ){ "$disklocator_short correct replicated" | LogMe $VDtests.ReplState = "SUCCESS", "Replication is OK" } else { "$disklocator_short not correct replicated " | LogMe -display -error $VDtests.ReplState = "ERROR", "Replication is NOT OK" } # Check deviceCount: $diskdevicecount = $DiskVersion | Select-String deviceCount $diskdevicecounts_short = $diskdevicecount -replace "deviceCount: "," " $VDtests.deviceCount = "OK", "$diskdevicecounts_short " #Label Storename $VDtests.Store = "OK", " $storename " Write-Host ("Store is $storename") $vdiskResults.$disklocator_short = $VDtests # Check for LB-Algorithm # ---------------------- # Feel free to change it to the the from you desired State (e.g.Exchange a SUCCESS with a WARNING) # In this default configuration "BestEffort" or "None" is desired and appears green on the output. # is desired) #ServeName must be empty! otherwise no LB is active! $LBnoServer = "" $LBnoServer_short = "" $LBnoServer = Mcli-Get disklocator -p siteName=$siteName, storeName=$storename, diskLocatorName=$disklocator_short | Select-String serverName $LBnoServer_short = $LBnoServer -replace "serverName: ","" Write-Host ("vDisk is fix assigned to $LBnoServer") #not assigned to a server if ($LBnoServer_short -eq "") { $LBAlgo = Mcli-Get disklocator -p siteName=$siteName, storeName=$storename | Select-String subnetAffinity $LBAlgo_short = $LBAlgo -replace "subnetAffinity: ","" #SubnetAffinity: 1=Best Effort, 2= fixed, 0=none if($LBAlgo_short -eq 1 ){ "LB-Algorythm is set to BestEffort" | LogMe $VDtests.LoadBalancingAlgorithm = "SUCCESS", "LB is set to BEST EFFORT"} elseif($LBAlgo_short -eq 2 ){ "LB-Algorythm is set to fixed" | LogMe $VDtests.LoadBalancingAlgorithm = "WARNING", "LB is set to FIXED"} elseif($LBAlgo_short -eq 0 ){ "LB-Algorythm is set to none" | LogMe $VDtests.LoadBalancingAlgorithm = "SUCCESS", "LB is set to NONE, least busy server is used"} } #Disk fix assigned to a server else { $VDtests.LoadBalancingAlgorithm = "ERROR", "vDisk is fix assigned to $LBnoServer, no LoadBalancing!"} } #Check for WriteCacheType # ----------------------- # Feel free to change it to the the from you desired State (e.g.Exchange a SUCCESS with a WARNING) # In this default configuration, only "Cache to Ram with overflow" and "Cache to Device Hard disk" is desired and appears green on the output. $WriteCacheType = Mcli-Get DiskInfo -p diskLocatorName=$disklocator_short, siteName=$siteName, storeName=$storename $WriteCacheType_short = $WriteCacheType -replace "WriteCacheType: ","" #$WriteCacheType 9=RamOfToHD 0=PrivateMode 4=DeviceHD 8=DeviceHDPersistent 3=DeviceRAM 1=PVSServer 7=ServerPersistent if($WriteCacheType_short -eq 9 ){ "WC is set to Cache to Device Ram with overflow to HD" | LogMe $VDtests.WriteCacheType = "SUCCESS", "WC Cache to Ram with overflow to HD"} elseif($WriteCacheType_short -eq 0 ){ "WC is not set because vDisk is in PrivateMode (R/W)" | LogMe $VDtests.WriteCacheType = "Error", "vDisk is in PrivateMode (R/W) "} elseif($WriteCacheType_short -eq 4 ){ "WC is set to Cache to Device Hard Disk" | LogMe $VDtests.WriteCacheType = "SUCCESS", "WC is set to Cache to Device Hard Disk"} elseif($WriteCacheType_short -eq 8 ){ "WC is set to Cache to Device Hard Disk Persistent" | LogMe $VDtests.WriteCacheType = "Error", "WC is set to Cache to Device Hard Disk Persistent"} elseif($WriteCacheType_short -eq 3 ){ "WC is set to Cache to Device Ram" | LogMe $VDtests.WriteCacheType = "WARNING", "WC is set to Cache to Device Ram"} elseif($WriteCacheType_short -eq 1 ){ "WC is set to Cache to PVS Server HD" | LogMe $VDtests.WriteCacheType = "Error", "WC is set to Cache to PVS Server HD"} elseif($WriteCacheType_short -eq 7 ){ "WC is set to Cache to PVS Server HD Persistent" | LogMe $VDtests.WriteCacheType = "Error", "WC is set to Cache to PVS Server HD Persistent"} } } # ======= PVS Server Check ================================================================== "Check PVS Servers" | LogMe -display -progress " " | LogMe -display -progress $PVSResults = @{} $allPVSServer = mcli-get server | Select-String serverName foreach($PVServerName in $allPVSServer) { $PVStests = @{} $PVServerName1 = $PVServerName | Select-String serverName $PVServerName_short = $PVServerName1 -replace "serverName: ","" $PVServerName_short # Ping server $result = Ping $PVServerName_short 100 if ($result -ne "SUCCESS") { $PVStests.Ping = "ERROR", $result } else { $PVStests.Ping = "SUCCESS", $result } #Check PVS Service Status $serverstatus = mcli-get ServerStatus -p serverName=$PVServerName_short -f status $actviestatus = $serverstatus[4].TrimStart("status: ") -as [int] if ($actviestatus -eq 1) { $PVStests.Active = "SUCCESS", "active" } else { $PVStests.Active = "Error","inactive" } # Check services if ((Get-Service -Name "soapserver" -ComputerName $PVServerName_short).Status -Match "Running") { "SoapService running..." | LogMe $PVStests.SoapService = "SUCCESS", "Success" } else { "SoapService service stopped" | LogMe -display -error $PVStests.SoapService = "ERROR", "Error" } if ((Get-Service -Name "StreamService" -ComputerName $PVServerName_short).Status -Match "Running") { "StreamService service running..." | LogMe $PVStests.StreamService = "SUCCESS","Success" } else { "StreamService service stopped" | LogMe -display -error $PVStests.StreamService = "ERROR","Error" } if ((Get-Service -Name "BNTFTP" -ComputerName $PVServerName_short).Status -Match "Running") { "TFTP service running..." | LogMe $PVStests.TFTPService = "SUCCESS","Success" } else { "TFTP service stopped" | LogMe -display -error $PVStests.TFTPService = "ERROR","Error" } #Check PVS deviceCount $serverdevicecount = mcli-get ServerStatus -p serverName=$PVServerName_short -f deviceCount $numberofdevices = $serverdevicecount[4].TrimStart("deviceCount: ") -as [int] if ($numberofdevices -gt 1) { $PVStests.deviceCount = "SUCCESS", " $numberofdevices active" } else { $PVStests.deviceCount = "WARNING","No devices on this server" } $PVSResults.$PVServerName_short = $PVStests } # ======= PVS Farm Check ==================================================================== "Read some PVS Farm Parameters" | LogMe -display -progress " " | LogMe -display -progress $PVSFarmResults = @{} $PVSfarms = mcli-get Farm #| Select-String FarmName $farmname = mcli-get Farm | Select-String FarmName $farmname_short = $farmname -replace "farmName: ","" $Nr=0 foreach($PVSFarm in $PVSfarms) { $PVSFarmtests = @{} # remove not needed record parts if ($PVSFarm -like '*description*'){continue;} if ($PVSFarm -like '*record*'){continue;} if ($PVSFarm -like '*failover*'){continue;} if ($PVSFarm -like '*executing*'){continue;} if ($PVSFarm -like '*defaultSiteName*'){continue;} if ($PVSFarm -like '*autoAddEnabled*'){continue;} if ($PVSFarm -like '*role*'){continue;} if ($PVSFarm -like '*audit*'){continue;} if ($PVSFarm -like '*defaultSiteId*'){continue;} if ($PVSFarm -like '*maxVersions*'){continue;} if ($PVSFarm -like '*databaseInstanceName*'){continue;} if ($PVSFarm -like '*farmId*'){continue;} if ($PVSFarm -like '*merge*'){continue;} if ($PVSFarm -like '*adGroups*'){continue;} if ($PVSFarm -ne '') { $Nr += 1 $arr = $PVSFarm -split ': ' $farmsetting = $arr[0] $PVSFarmtests.Setting = "NEUTRAL", "$farmsetting" $arr = $PVSFarm -split ': ' $farmsettingvalue = $arr[1] $PVSFarmtests.Value = "NEUTRAL", "$farmsettingvalue" $farmnr=$Nr $PVSFarmResults.$farmnr = $PVSFarmtests } } # ======= Write all results to an html file ================================================= Write-Host ("Saving results to html report: " + $resultsHTM) writeHtmlHeader "PVS Farm Report $farmname_short" $resultsHTM writeTableHeader $resultsHTM $TargetFirstheaderName $TargetheaderNames $TargetheaderWidths $TargetTablewidth $allResults | sort-object -property collectionName | % { writeData $allResults $resultsHTM $TargetheaderNames} writeTableFooter $resultsHTM writeTableHeader $resultsHTM $vDiksFirstheaderName $vDiskheaderNames $vDiskheaderWidths $vDisktablewidth $vdiskResults | sort-object -property ReplState | % { writeData $vdiskResults $resultsHTM $vDiskheaderNames } writeTableFooter $resultsHTM writeTableHeader $resultsHTM $PVSFirstheaderName $PVSheaderNames $PVSheaderWidths $PVStablewidth $PVSResults | sort-object -property PVServerName_short | % { writeData $PVSResults $resultsHTM $PVSheaderNames} writeTableFooter $resultsHTM writeTableHeader $resultsHTM $PVSFirstFarmheaderName $PVSFarmHeaderNames $PVSFarmWidths $PVSFarmTablewidth $PVSFarmResults | % { writeData $PVSFarmResults $resultsHTM $PVSFarmHeaderNames} writeTableFooter $resultsHTM writeHtmlFooter $resultsHTM #send email $emailSubject = ("$emailSubjectStart - $farmname_short - " + (Get-Date -format R)) $mailMessageParameters = @{ From = $emailFrom To = $emailTo Subject = $emailSubject SmtpServer = $smtpServer Body = (gc $resultsHTM) | Out-String Attachment = $resultsHTM } # Send mail if you wish Send-MailMessage @mailMessageParameters -BodyAsHtml -Priority $mailprio
or Download the file on GitHub: Citrix-PVS-Farm-Health-toHTML.ps1
Question or suggestion for improvement? Just contact me.
Update 12.11.14: Now in version 1.2 it’s possible to define the device collection which should be checked. An other additional feature is that the script shows the version of the vDisk.
Update 15.08.15: Now in version 1.4 it’s possible to have more than just one vDisk store.
Update 16.09.15: Now in version 1.5 the vDisk file name, it’s date and the count of the used disk will be showed.
Update 14.10.15: Version 1.61 New feature, PVS Cache (on disk or Ram) will be reported. / 20.10.2015 1.62: fixed some bugs (see comments)
Update 29.12.15: If you use PVS 7.7 or higher check out the new blog post with the 7.7 Script: http://blog.sachathomet.ch/happy-new-script-pvs-7-7-healthcheck/
Because of the Release of PVS with the new PoSh API the further development of this Script is discontinued. Sorry.
Achieve fastest Citrix Provisioning Target Device
Citrix Provisioning Services is in my point of view an incomparable way to deliver a Software image to a system. It’s not a kind of an known enterprise software deployment, rather it’s a way to stream a virtual disk during runtime to hardware. Beside of PVS, the acronym of Provisioning Services.
This article doesn’t explain what’s PVS and how it works. It only shows my personal best practice to have the fastest PVS target device. It’s maybe not the only one and right solution to work with PVS, e.g. for VDI other approaches would match better. In the last couple of years I was working on multiple projects to introduce PVS OS streaming for XenApp servers on physical hardware or virtualized servers. The following instruction shows my way to deliver the fastest PVS Target for read-only XenApp servers, or in the XenApp replacement this role is Xen Desktop 7.x App Edition called .
In this article I only mention performance relevant topics, I will not talk about HA, continuous availability or about PXE, TFTP and so on.
#1. Reduce reads from disk, make use of Standby Memory for vDisks
The best way to increase performance is to reduce work, in this first step it’s to reduce the hard disk read by reading virtual hard disk files (VHD’s). This can be achieved by make use of the Windows disk cache, this approach is possible on physical PVS servers and as well on virtualized PVS servers on any hypervisor. Using the Windows disk cache means that files which are read from disk, are cached in Standby memory of the Server.
(If you are using XenServer as hypervisor have a look to the IntelliCache feature, http://blog.citrix24.com/how-does-intellicache-work. )
To make use of the file cache you need:
- The vDisk on local drives, no CIFS share, no NFS share.
- A plenty of memory to the PVS.
Having the vDisk’s on local disks is not the common way to work with PVS, keep in mind that you have to synchronize the files on all servers in PVS farm.
You can check if the file cache is used properly by having a look in the resource manager. If you have nearly no free memory and have a lot of use standby memory it’s a good sign:
To have a deep dive look in the cache use RamMap and keep an eye to File Summary:
#2. Use Target Device Write Cache on Ram
The second second way to increase performance is to use fastest possible storage, hard disk drives are quite fast today, solid state disks are faster but Ram is still the fastest memory. Because we need on the Target device only the Write Cache it doesn’t matter that the Ram is a transient memory.
A short performance comparison:
With AS SSD Benchmark you can measure the speed of your disk.
Target Device with WriteCache on Ram:
Target Device with WriteCache on local disk:
( AS SSD Benchmark is Freeware and you can download and donate on: http://www.alex-is.de/PHP/fusion/downloads.php?cat_id=4&download_id=9 )
Keep in sight the Memory
When you working with Write Cache in the Target device Ram you must appreciate the dangers of it … If you are running out of WriteCache the Target Device, perhaps a XenApp server with 50 users on it, will reboot immediately .
(By the way … Citrix Provisioning Services 7.1 offers a great new feature, it’s the possibility to Cache to Ram with overflow to hard disk. Do not use it … it doesent work proper! Waiting for a hotfix.)
- Plan the amount of real used Memory:Think how much of the Ram you will use for WriteCache, give as much as possible and consider the real memory usage of a fully loaded server. e.g. if you have a XenApp server as Target device with 16GB of Memory you can allocate 6GB for WriteCache and have still 10GB Memory for the XenApp servers. If you have special memory hungry application on the server you need maybe an other segmentation. It doesn’t exists a recipe, have a look on your currently productive servers about the used memory.
- Reboot your Target device periodically to reset the used cache.
- Reduce usage of Write Cache by redirecting as much as possible to an other disk drive, of course this is only possible if you still have a local disk attached to the Target Device.Good candidates to redirect are:
- Printer Spooler
- Pagefile
- EventLog
- EdgeSight database
- Monitor your Write Cache, a way to do this is e.g. using this small Powershell script on the PVS server with a scheduled task:
(If you have PVS 7.1 with the newest Hotfixes I recommend to use WriteCache on Ram with overflow to hard disk)
#======================================================================== # Created on: 09.01.2014 # Created by: Sacha T. blog.sachathomet.ch # Filename: Citrix-PVS-Get-Targetdevice-WriteCacheSize.ps1 # # Descritpion: This script query the used write cache of the PVS farm # connected target devices and write an event log entry if # a target device exceed a definec % value of cache. # # Prerequisite: Script must run on a PVS server, where MCLI snap-in is registered. # # Register SnapIn with command: C:\WINDOWS\Microsoft.NET\Framework64 # \v2.0.50727\InstallUtil.exe 'C:\Program Files\Citrix\Provisioning S # ervices Console\McliPSSnapIn.dll' # # Call by : Scheduled Task all e.g. all 5 min. # #======================================================================== $thresholdInformation=55 # define the Threshold in % $thresholdWarning=70 # define the Threshold in % Add-PSSnapin -Name McliPSSnapIn -ErrorAction SilentlyContinue $pvsdevices = mcli-get device -f deviceName | Select-String deviceName foreach($target in $pvsdevices) { $target | Select-String deviceName $_targetshort = $target -replace "deviceName: ","" mcli-get deviceinfo -p devicename=$_targetshort -f status $devicestatus = mcli-get deviceinfo -p devicename=$_targetshort -f status if ($devicestatus[4].TrimStart("status: ") -replace ",","." -gt $thresholdWarning) {Write-Host "WARNING: The Write Cache of the PVS-Target device $_targetshort exceeded $thresholdWarning %! Intervention is needed" -foregroundcolor "red" #do here what you need, email, eventlog etc ... write-eventlog -logname Application -source StreamProcess -eventID 3001 -entrytype Warning -message "A PVS target device, $_targetshort, exceeded the threshold ($thresholdWarning %) !" -category 1 -rawdata 10,20 } elseif ($devicestatus[4].TrimStart("status: ") -replace ",","." -gt $thresholdInformation) {Write-Host "INFORMATION: The Write Cache of the PVS-Target device $_targetshort exceeded $thresholdInformation %! " -foregroundcolor "yellow" #do here what you need, email, eventlog etc ... write-eventlog -logname Application -source StreamProcess -eventID 3001 -entrytype Information -message "A PVS target device, $_targetshort, exceeded the threshold ($thresholdInformation %) !" -category 1 -rawdata 10,20 }
#3. Avoid if possible cross Data centre streaming
- If you have different subnet’s, make use of subnet affinity in the load balancing configuration on the vDisk.
- If you have one subnet over more data centres, create a store with the vDisk for each data centre and let only provide servers from the corresponding data centre this store. With this way you can still switch fast to a cross data centre configuration in a case of a PVS server outage in a data centre.
4#. Use all exiting best practice guides for tuning:
(for all what I’ve forget here … )
e.g. : http://blogs.citrix.com/2010/11/05/provisioning-services-and-cifs-stores-tuning-for-performance and http://support.citrix.com/article/CTX127549
ff
Issue by creating a PVS 7.1 farm
If you try to create a new Farm and the Provisioning Services Configuration Wizard stuck on a “Not responding” during the Database Server step maybe you have to many databases on the Server:
Workaround: Use the DbScript.exe to get the Database creation script and create the database with this script directly on the DB server: