sachathomet.ch

Application virtualization, IoT and Cloud Computing, Blog of Sacha Thomet

IoT, LaMetric and mouse issues …

In summer 2014 I became a backer for the Kickstarter project LaMetric. LaMetric was advertised as a smart ticker for life and business. To be honest the Internet of Things (IoT) topic was not so present for me in summer 2014 and I just decided to spend some money for a cool gadget.

Last week I finally received my LaMetric Time and today I need to say it’s one of the coolest gadget I bought in the last months. With the acquisition of Octoblu, what Citrix made early this year and promoted on Citrix Synergy (especially on the GeekSpeak Tonight …) I’m now more aware of IoT an it’s possibilities!

LaMetric is the perfect IoT output device!

First LaMetric app

The creation of apps is pretty easy! On the LaMetric developper portal you can create your push or poll apps. With push you can transmit data to the LaMetric servers in JSON code. W ith pull it’s also easy, you just need a webserver on which you have a file in the correct format.

On this way I created the EuroMillions app which display the current Jackpot. The data are fetched from www.euro-millions.com and formatted with a small and simple PHP script:


{
 "frames": [
 {
 "index": 0,
 "text": "<?php 
$host = "http://www.euro-millions.com"; 
$filestring = file_get_contents($host); 
$startpos = 0; 
// <div class="est-jackpot">&euro;30<span class="amount">Million</span></div>

while($pos = strpos($filestring, ">&euro;", $startpos)) 
{ 
 $string = substr($filestring, $pos, strpos($filestring, "<span class=", $pos + 1) - $pos); 


 //echo $string. " "; 
 echo str_replace(">&euro;","","$string");

 $startpos = $pos + 1; 
} 

?> Millions estimated in next Jackpot",
 "icon": "i616"
 }
 ]
}

Result:

LaMetric-EuroMillions

 

First IoT LaMetric Project

Problem

I’m living in an old farmhouse which is converted with some apartments, but it’s still a old house with a lot of wood. And the old problems … mouses. So I have now since some weeks 4 mouse traps installed, not the killing traps, only those which able me to take the mouse out of my apartment.

Problem: Every morning I need to check all mouse traps, because the traps are hidden positioned an annoying daily work. And with the risk that the mouse need to wait hours until I find them.

Concept

Now the concept of my first IoT project is to get alerted as soon a mouse get in a trap.

I want to solve this with:

  • a modified mouse trap with an attached window/door sensor, something like this http://store.wirelesstag.net/products/reed-kumosensor
  • an Octoblu workflow
  • and LaMetric as Output device.

The result must be this:

LaMetricMouse

Implementation

Need some hours … I have to learn how to handle  octoblu and learn a bit of JSON.

PoSh Script who alert me if I’m running out of pooled Desktops

Just a small one, last days I’ve created a small script who alert me if I’m running out of pooled Desktops.

If you run this script at regular intervals you will receive an email as soon you have less desktops free than you defined as threshold:

LowDesktopFree


#==============================================================================================
# Created on: 08.2015 Version: 0.2
# Created by: Sacha Thomet
# File name: Citrix-XenDesktop-Alert-low-free-desktops.ps1
#
# Description: Check for Free Desktops in DeliveryGroups
#
# Prerequisite: None
#
# Call by : Scheduled Task e.g every 10 minutes
#
# Changelog:
# V0.1 Initial Version, create report file from array FreeDesktopReport and attach this to the email.
# V0.2 Change from txt-file to formatted HTML-Mail
#
#==============================================================================================
if ((Get-PSSnapin "Citrix.Common.Commands" -EA silentlycontinue) -eq $null) {
try { Add-PSSnapin Citrix.* -ErrorAction Stop }
catch { write-error "Error Citrix.* Powershell snapin"; Return }
}
# Change the below variables to suit your environment
#==============================================================================================

# Variables what should be changed according your environment and wishes

$DeliveryGroups = @("Win7-Desktops","Win10-Desktops")
$minDesktops = 10
$directoraddress="http://citrixdirector.mycompany.ch"
$EnvironmentName="Production XenDesktop"

# E-mail report details
$emailFrom = "citrix@mycompany.ch"
$emailTo = "citrix@mycompany.ch"
$smtpServer = "mailrelay.mycompany.ch"

#=======DONT CHANGE BELOW HERE =======================================================================================

$mailbody = $mailbody + "<!DOCTYPE html>"
$mailbody = $mailbody + "<html>"

$mailbody = $mailbody + "<head>"
$mailbody = $mailbody + "<style>"
$mailbody = $mailbody + "BODY{background-color:#fbfbfb; font-family: Arial;}"
$mailbody = $mailbody + "TABLE{border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse; width:60%; }"
$mailbody = $mailbody + "TH{border-width: 1px;padding: 0px;border-style: solid;border-color: black; text-align:left;}"
$mailbody = $mailbody + "TD{border-width: 1px;padding: 0px;border-style: solid;border-color: black;}"
$mailbody = $mailbody + "</style>"
$mailbody = $mailbody + "</head>"

$mailbody = $mailbody + "<body>"
$mailbody = $mailbody + "This is the Low-Desktop-Alert for $EnvironmentName, if you receive this mail the value of free desktops is below the configured threshold of $minDesktops desktops! <br><br>"

$FreeDesktopReport = @()

foreach($dg in $DeliveryGroups)
{
$desktops = Get-BrokerDesktopGroup | where {$_.Name -eq $dg }
$CurrentDeliveryGroup = "" | Select-Object Name, Alert, DesktopsAvailable

# Write Array Values
$CurrentDeliveryGroup.Name = $dg

$CurrentDeliveryGroup.DesktopsAvailable = $desktops.DesktopsAvailable

if ($desktops.DesktopsAvailable -lt $minDesktops )
{
Write-Host "Number of free desktops to low for DeliveryGroup $dg, sending email"
# Add Line to Report
$CurrentDeliveryGroup.alert = "True"
}

$FreeDesktopReport += $CurrentDeliveryGroup
}

$mailbody += $FreeDesktopReport | ConvertTo-Html
$mailbody += "<br><br>Launch Citrix Studio or browse to <a href=$directoraddress>Citrix Director</a> see more information about the current Desktop usage<br>"

$mailbody = $mailbody + "<body>"
$mailbody = $mailbody + "</html>"

# If any record raises an alert, send an email.
if (($FreeDesktopReport | where {$_.alert -eq "True"}) -ne $null) {Send-MailMessage -to $emailTo -from $emailFrom -subject "********* Low free Desktop Alert for $EnvironmentName *********" -Body $mailbody -BodyAsHtml -SmtpServer $smtpServer }

Or download the Script here

GeekOvation – PVS Script on Top3

geek-zone-150x150My 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

geekovationI 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 X1 Prototype Mouse – hands-on

On Citrix Summit in Las Vegas back in January 2015 Citrix presented the X1 Prototype Mouse. This mouse is not just another mouse with a Citrix logo on it. It’s a mouse that works in Citrix Receiver with iOS-Devices! Yes it’s the truth, normal bluetooth mice doesn’t work with the Citrix receiver on iPad.

Even when I was not on Citrix Summit, with the social media channels I was pretty fast to get that Info and I was the 7th on the order form to request this cool gadget for a described use case.  I want to demonstrate this in my company because we are currently in a VDI initiative project with up to 2000 VDIs.

Today almost 4 months later I received my Citrix X1 Prototype Mouse and can start test and maybe show off in my company 🙂 To be honest, the design don’t give me the chance to show off …

Mouse MouseBottom
The X1 Mouse appears in a nasty 80ties style, the power switch on the bottom has three possible positions, up- and down for ON and OFF in the middle.

Connect the mouse to the iPhone? Really? 4,7 inch ? 

Not only the iPad is the only use case for the mouse, remember 2010 the Vision of the Nirvana Device and the Motorola NirvanaPhone . With the new version of the Citrix Receiver for iOS you can connect a Screen on your iPhone, a Bluetooth Keyboard, that mouse and you can work with the iPhone as “Thin Client” on you television connected with Airplay or on your 24″ Office Screen connected with a Lightning VGA adapter.

I made some quick hands-on test:

I’ve used the following infrastructure to test:

  • XenDesktop 5  on Windows 7 x64, Citrix Webinterface behind Netscaler Gateway.
  • XenDesktop 7.6 on Windows 7 x64, Citrix Webinterface behind Netscaler Gateway.
  • XenApp 7.6 Desktop on Windows 2008 R2, Citrix StoreFront 2.6 behind Netscaler Gateway.

For all tests I used my Apple iPad mini with the R1 Receiver which is mandatory necessary to use the X1 Prototype Mouse. I didn’t install anything special on XenDesktop or XenApp.

11210509_10205552449102835_7320258021443610820_n

iPad Mini, Belkin Keyboard, Citrix X1 Prototype Mouse

 

 

The next points where I mention what is ok and what needs improvement I will complement as soon as I have new points.

What works fine:

  • No issue to connect and use the mouse if you follow the instructions from Citrix.
  • I can use the X1 mouse inside my virtual desktop like a normal mouse, I can launch applications from start menu, change the active cells in MS Excel – most things I usually do with my mouse works.
  • I can configure in mouse pointer options a “mouse track” which also works fine.

What currently doesn’t work or need still improvement:

  • I tried to resize an Internet Explorer window and was irritated because the mouse pointer doesn’t change his  shape on the edge of the window to a “double-arrow”:
    mouseedge-expected

    Expected mouse pointer on window edge, with a classic mouse on my macbook

    mouseedge-UNexpected

    Unexpected mouse pointer on window edge, from iOS with the X1 Prototype mouse

     

  • Most mouse properties (Mouse speed, pointer scheme, etc.) has no effect.
  • I can see my mouse pointer in my R1 Receiver for iOS on the Webinterface but I cannot click anything. Possibly I need for this the X1 Web Receiver (StoreFront 2.7)  and not my legacy Webinterface …

 

Conclusion: For iOS the Citrix X1 Mouse can be a game changer, but to be honest that what is now possible with this mouse on iOS is already long time possible with an Android Tablet and a commercial of the shelf bluetooth mouse.

By the way, the X1 Mouse can also be used with other OS than iOS, so if you have an X1 Mouse but you decide that your iPad is to small to work you can use your “cool” mouse with a computer that supports Bluetooth 4.0 as a classic mouse.

 

Update 5.5.2015 06:10 GMT+1:

From now it’s possible to use the normal Recevier with the X1 mouse:

X1-Receiver-info X1-Receiver

Update 5.5.2015 22:20 GMT+1:

The final version of the X1 mouse will be launched on Citrix Synergy: http://blogs.citrix.com/2015/05/05/the-mouse-that-roared-for-business and what sounds exciting:
“…this is a unique Bluetooth Low Energy mouse (BTLE) with custom firmware that provides full-function mouse support to specific Citrix Mobile apps including Citrix Receiver, GoToMyPC, ShareConnect and WorkDesktop.”

Citrix Synergy First-Timer Preperation

This year I have the chance to join the Citrix Synergy in Orlando, FL, USA, THE Citrix conference.
For more Information: http://www.citrixsynergy.com

There is already a very good guide for the preparation of this conference from Neil Spellings, see the The ultimate Citrix Synergy survival guide – 2015 , that covers a lot of needed information.

But I add in this post some information for a first timer comes from overseas (Switzerland, Europe) like me.

Revision history
because this is a “living Blog-Post”:

  • 26.04.2015: Added “Join evening together Central & Eastern Europe Team”

Avoid the Jetlag when coming from overseas
My personal best method to avoid the Jetlag (Switzerland, Europe to Florida, US -6 hours):

  • I will arrive early, even Synergy starts on the 13th, I take already the plane on the 9th of May. That gives me some days to get in the correct day/night rhythm.
  • I stay waked up after arrival until my normal sleeping time, even if I’m very tired!
  • If I wake up in the middle of the first night because it’s morning in Europe (and in my day/night rhythm), I’ll take a soft Sleep Aid called MidNite, available in a lot of US Stores (e.g. CVS).
  • I get up after sunrise an go outside to the Sun to let my inner day/night rhythm know thats now day.

Stay online without expensive mobile roaming costs!
Instead of the Net10 card what is mentioned in the blog of Neil spellings I will buy a AT&T GoPhone Prepaid Card with e.g. 2GB for 25$ and I put it in my Huawei E5372. If you intend to use an other device or provider be aware of the used frequencies. This combination gives you a Mobile Hotspot which works in thr US with AT&T and Europe on 2G/3G and 4G mobile network.

Plan your sessions
You can plan the Sessions you want to attend on the Synergy Session Catalog Website . If you are already registered for Synergy you can login with your Synergy account subscribe to the sessions you like to attend (first come first served, no possibility to reserve the seats with exception of the 99$ Pre-event Training Workshop).  and  afterwards you can download a PDF with all your sessions or you can download the  Calendar-Files.

Plan Meetings!

  • After login to your Synergy Account you have the possibility for an Attendee Search and request meetings:
    synmeetings2
  • Try to meet people you known today just virtual from twitter or from a blog to get a face behind of a URL or a Username.

If you register for Citrix Synergy and like this post or this blog
Please use my referral code in the registration:

It’s my email address with MyPeer at the end. That gives me the chance to win Converse sneakers .

If you are from Central & Eastern Europe join the snacks, beer and networking event
Check your Mailbox, and the Spam-Folder about an invitation from Dirk Pfefferle, to a “evening together with Citrix Central & Eastern Europe Team”. I just found in my Spam folder an invitation for this snacks, beer and networking event.

Client to Server Content Redirection no more working because of License Error

Recently I faced a really strange error with a weird solution. Because I didn’t found this on the web I post this here, maybe it can help someone else too.

We had the issue that users cannot use anymore the Client to Server Content Redirection, means they can’t double-click anymore on *.vsd files to open the file with the XenApp server installed MS Visio.

After some minutes of investigation and checking the Logs on Client, XenApp server and so on we’ve finally checked the EventLog on the Webinterface server and found the following error:

Event 31007
 
eventlog-contentredirectionerror
Site path: C:\inetpub\wwwroot\Citrix\PNAgent. 

The Citrix servers are not licensed to support workspace control. This message was reported from the XML Service at address 
http://myserver.ch:8080/scripts/wpnbr.dll [com.citrix.xml.NFuseProtocol.RequestReconnectSessionData]. 
[Unique Log ID: a5e760c4] 

For specific information about this message, see the Web Interface documentation at 
http://support.citrix.com/proddocs/topic/web-interface-impington/wi-log-messages-event-ids-hardwick.html.

Now we remembered that we consolidated some Citrix license server’s and changed the license server for this farm some days ago. Of course we rebooted all XenApp servers, but not the Webinterface servers because they don’t use a special license server. A reboot of the Citrix Webinterface server solved this issue.

XenDesktop & XenApp FMA (7.x) HealthCheck – Oops!… I Did It Again

Some months ago I’ve created the Citrix PVS Health-Check Script which is a based on the idea and some parts of code from the Health-Check Script for XenApp 6.x of Jason Poyer (http://deptive.co.nz/) .
Because now XenApp 7 with the Release 7.6 is finally in a state where considering an upgrade of the 6.x farms make sense, I belief that the demand for a XenApp 7.x Health Check Script grows.

So I did it again and took the “HealthCheck framework” to build a new version which combines the Power of the Citrix PS-Snappins for XenDesktop/XenApp and  the HTML-Output-Script of the existing HealthCheck Scripts.

The result is a new HealthCheck Script which is usable for XenApp and XenDesktop 7.x and what makes me also happy, with only a few line of more code the Script is downwardly compatible for XenDesktop 5.6 environments.

  XenDesktop XenApp Health Check HTML Output

This is just the first version and I’m sure that more check’s need to be added. Feedback and “Feature requests” are welcome … And to be honest I have not yet a big environment to test my Script, so please be insightfully if you find some bugs and report them to me.

In the first part of the Script you are able to configure some parameters. You can decide if you only want to see the “bad” Desktops on which something it’s going wrong or if you want see everything. In huge XenDesktop environments you want definitely only see the bad machines …  ( $ShowOnlyErrorVDI = 1 ) Also you can decide if you want only report XenApp or only XenDesktop or both. The Desktops and XenApps are in two different Tables. It’s also possible to exclude Collections ($ExcludeCatalogs) from the Check, so virtual Desktops which are for testing purposes are not checked.

xd76-configure-shadow

 

If you have a feature request or a bug report please post it direct on GitHub.

Update 12.05.2016 (Version 0.95):
– Check CPU, Memory and C: of Controllers
– XenApp: Add values: CPU & Memory and Disk Usage
– XenApp: Option to toggle on/off to show Connected Users
– XenApp: DesktopFree set to N/A because not relevant
If you need a Health Check Script for XenApp Version which are older than XenApp 7.x see http://deptive.co.nz/xenapp-farm-health-check-v2 where it’s an excellent work and the inspiration for my HealthCheck-Scripts!

The code is on GitHub:

https://github.com/sacha81/XA-and-XD-HealthCheck/

For Bug Reports or feature Request please use GitHub, of course you can also contribute on this code!

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.

Create published applications XenApp to each server

For testing purposes it can be helpful to have a published application without loadbalancing to every single server of your Citrix Farm. The creation of such application can be easily done with the following example script which create a CMD on every farm server:


Add-PSSnapin Citrix.XenApp.Commands -ErrorAction SilentlyContinue
$serverlist = Get-XAServer #Get all server from farm

#Mainprogram
# loop through all servers

 foreach($srv in $serverlist)
 {

 echo $srv.ServerName

 New-XAApplication -BrowserName "cmd on $srv" -ApplicationType "ServerInstalled" -DisplayName "cmd on $srv" -FolderPath "Applications/Published Apps/z_admin" -ClientFolder "Administration\cmd" -Enabled $true -CommandLineExecutable "C:\Windows\system32\cmd.exe" -WorkingDirectory "C:\Windows\system32" -AnonymousConnectionsAllowed $false -AddToClientStartMenu $false -InstanceLimit "-1" #-WindowType "1024×768" -ColorDepth "Colors256"

 Add-XAApplicationAccount -BrowserName "cmd on $srv" -Accounts "domain\USER1"
 Add-XAApplicationAccount -BrowserName "cmd on $srv" -Accounts "domain\USER2"
 Add-XAApplicationServer -BrowserName "cmd on $srv" -ServerNames $srv.ServerName

 }


By the way … if you made a mistake you can remove the created applications by the same foreach-loop with Remove-XAApplication:

 

$serverlist = Get-XAServer #Get all server from farm
#Mainprogram
# loop through all servers
foreach($srv in $serverlist)
 {
 
 echo $srv.ServerName
 Remove-XAApplication -BrowserName "cmd on $srv"
}

 

All scripts are provided AS IS without warranty of any kind.