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

Match.Geek canceled :-(

Due to low registrations for Match.Geek sessions on Thursday my Match.Geek Sessions are canceled. It’s a pity, because I know there was some registrations and I was happy to volunteering for that sessions. Because that’s my first Synergy as a CTP I just planned Match.Geek for Thursday what means there is no possibility to shift a meeting with me to previous day. After the first shock, I think it’s not so bad, it gives me more free space for other things, and it looks like I can now attend a working meeting of the Top-Secret project on which I’m working since some months with Citrix and will be announced on Synergy …

If you were registered or you want to have a chat with me about Citrix Provisioning Services, XenApp, XenDesktop or about real world experience of a Windows 10 migration, send me a direct message over twitter or leave a comment here, so we can maybe meet independent.

If you like to know more about CUGC visit one of the CUCG Synergy activities or if you are especially interested to be a CUGC leader, visit the Local Leader Panel


Match.Geek with Sacha Thomet

As part of the CTP program I am invited to participate in a lot of Citrix Synergy activities. For me this will be the first year as a CTP at Synergy so I don’t exactly know what I have to expect. I just know that I will have a pretty busy agenda for those days …

One thing where I volunteer is the Match.Geek, a session where Geeks are leading a chat about 30 minutes with some Synergy attendees. If you are on Synergy you can book your slot with me here:[]=Match.Geek


If you are on Citrix Synergy and don’t want attend the Match.Geek, contact me on-site via twitter – if I have time I would be pleased to have a drink with one or the other of my blog readers.

Looking back to Synergy 2015 and preparing for Citrix Synergy 2016 – my first as a CTP …

This blog post is originally written for

Even though I’ve worked over 10 years with Citrix technologies, last year was my first at Citrix Synergy. This event was, for me, very impressive and also a kickstart to do more in the Citrix community, though, I have had my personal blog for 3 years and I try to share some technical knowledge frequently.

Some weeks before Synergy 2015, I stumbled over the application form for the GeekOvation award and filled it out. In a quirk of fate, I was nominated as a GeekOvation Top 3 finalist and at the end, I won this contest. I’m still amazed by this, especially because my two competitors, Shane O’Neil and David Ott, had excellent solutions for technical real-world problems.

I realized how cool the Citrix community really is and what can be possible. This fact motivated me to do more for the community and I decided to help the CUGC as a local leader for Switzerland. In the meantime, I’m also a Citrix CTP, and I will travel again this year to Citrix Synergy (though I’m sure that I will be even busier than last year).

As a CTP, my journey already starts earlier than last year, because there are CTP meetings on Sunday and Monday. I have heard these are two very intensive days … but I’m looking forward to them and happy to have this opportunity.

Here are some pointers about my personal Citrix Synergy preparation:

As an attendee from overseas, reduce jet lag:

Originally, before I knew that I would be traveling as a CTP, I planned to arrive on Saturday to Las Vegas. Because I’m from Switzerland and in the GMT+1 time zone, I have a 9-hour time difference to Vegas. I need to consider this because I want to avoid, or reduce, my jet lag during the conference. It would be a pity if I overslept most of the sessions. So I plan to take some spare free days before the conference.

My other recipe against jetlag sounds easy but it isn’t: Sleep at night, stay awake in the day. When I arrive in Las Vegas at 8:30pm, in Switzerland and in my brain it’s 5am. I will try to stay awake until 11pm. On the next day I will get up at 8am (in Switzerland it’s 5pm) and not earlier (that would be the hard part). During the day I will try to soak up the sun as much as possible to re-adjust my biological clock.

Planning the conference activities:

Like probably every attendee, I make use of the Session Catalog on the website to choose my favorite sessions and then download and import it to my calendar with the *.ics file. What obviously will be a “must-attend” session for me is GeekSpeak Tonight with the GeekOvation award, as I need to pass my crown to the new winner! Honestly, I learned last year that I overcrowded my agenda, but I will do it again and perhaps I may decide to skip a session or two.

On the contrary, if I have spare time, I will attend the Self-paced Learning Labs. I did this last year and it was great. You can work through the instructions at your own speed and have access to very good labs to test this and that. Honestly, I assume this year will not allow much spare time for me. Another good alternative to learn a lot of new things in a short time would be the Instructor-led Learning Labs on Sunday and Monday.

Even though I will be busy, I want try attending a Citrix certification exam, as I need to renew my 1Y0-401. If you want to attend to an exam, you need to register early, otherwise all slots are gone.

Citrix Synergy is a chance to meet old friends from the community and become acquainted with new people. For me, it will be also a chance to have first time real-life conversations with people I interact with often, but just virtually in Twitter or the forums.

And to reach the complete conference-overdose… of course, I will attend at the E2EVC Las Vegas which is on Friday and Saturday. The E2EVC stands for Expert to Expert Virtualization Conference, a series of worldwide non-commercial virtualization community Events organized by Citrix CTP Alex Cooper. At E2EVC you can meet a lot of Geeks and “virtualization Rockstars.”
If you need more detailed hints how to prepare for Synergy, have a look on Ultimate Synergy Survival Guide from CTP Neil Spellings blog:

victim of a good reputation – Low free pooled XenDesktops

The Citrix Director is not so bad, and with Version 7.7 it is still better because now it’s possible to send email alerts. But one problem I still have … our provided pooled XenDesktop which are used for remote access with private computers are victim of a good reputation … this means a good word-of-mouth advertising in our company more and more people are tempted to use it … and we need to know when the number of free pooled desktop is low. And no I won’t go the read the numbers every day in director or Citrix Studio.

So I created a small script which send me an email as soon a defined threshold of free desktops is reached:



Feel free to download my script here or

# Created on: 03.2016 Version: 1.0
# Created by: Sacha Thomet
# File name: Citrix-XD-Alert-low-free-desktops.ps1
# Description: Check for Free Desktops in DeliveryGroups
# Prerequisite: This script need to run on a Desktop Controller
# 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
# V1.0 Minor corrections
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 should be changed according your environment

$DeliveryGroups = @("Win7","Win10")
$minDesktops = 10
$EnvironmentName="Production XD 7.8"

# E-mail report details
$emailFrom = ""
$emailTo = ""#,""
$smtpServer = ""

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

$mailbody = $mailbody + ""
$mailbody = $mailbody + ""

$mailbody = $mailbody + "" $mailbody = $mailbody + "

$mailbody = $mailbody + "


$mailbody = $mailbody + ""
$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!


$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 += "

Launch Citrix Studio or browse to <a href="$directoraddress">Citrix Director</a> see more information about the current Desktop usage

$mailbody = $mailbody + ""
$mailbody = $mailbody + ""

# 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 }

Avoid blanks and dots in StoreFront 3.5 farm names!

Last day’s I upgraded my existing Storefront 3.01 to StoreFront 3.5 and in some farms I had a very bad issue after the upgrade, it seemed that all is ok but users are no more able to start any application or desktop.

On the StoreFront server I saw an Warning Event 28 from Citrix Store Service “ Failed to launch the resource “Farm Name.ApplicationName” as it was not found.


The issue was that a blank and a dot (.) was in my Farmname, seems this is a bug in SF3.5 that you can configure that but It won’t work!



after I changed the display name to something like xa65farm without blank and point all works well.

An awesome thing happened … I’m now a CTP

2 day’s ago I received a very pleasant email:

“Hello Sacha,
Congratulations! You have been selected to receive the Citrix Technology Professional (CTP) award! We’re thrilled to welcome you to this elite family group of technology experts!”

I’ve done in the last months and years some stuff for the Citrix community with this blog, with the participation on Twitter, Citrix Forums, and other Social media channels and I’m leading together with Stefan Beckmann the Swiss Citrix User Group Community. Also I participated on the development of the 1Y1-401 exam and I won the GeekOvation award 2015. Seem’s my passion for Citrix and contribution on the community get honored.

What is the CTP award?

This program is the highest recognition what a Citrix Expert can get and definitely a milestone in my career.

ctpMore information about the CTP Prgramm you can get here:

More about the CTP 2016 class here: 


I’m very thankful to be chosen as a CTP and I’m sure that people surrounding me are jointly responsible for my success!

I want to say THANK YOU!

  • To my Family
  • To my coworkers and my employer Die Mobiliar (a swiss insurance company)
  • To Citrix, especially Perrine Crampton
  • To the CTPs I met last year at the Citrix Synergy on the GeekSpeak-Tonight with the GeekOvation Award, especially Aaron Parker who supported me on my Presentation, but also Carl Webster who supported me later with PoSh stuff and of course Jo Harder, GeekOvation MC (Mistress of Ceremonies), who crowned me 🙂
  • To my current and former coworkers who supported me always to keep my Citrix heart beating
  • To all other which supported me in my doing and I missed here  (Sorry!)

And I won’t forget to congratulate to the other new 2016 CTPs



What next? 

Now I’m really feel obligated to give even more back to the community, of course I will continue to contribute on forums, write more posts on this blog. But I’m pretty sure I will have the chance to hold one or two technical presentations … Good chance to improve my english language … :-S

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:



# Created on: 12/2015 Version: 1.2
# Created by: Sacha Thomet, /
# Filename: Citrix-PVS77-Farm-Health-toHTML.ps1
# Description: This script checks some Citrix Provisioning Server, Farm, vDisk & Target device parameters.
# This Script is working on PVS 7.7 and higher, for older PVS Version (7.6 and below) use the Script here:
# Prerequisite: Script must run on a PVS server, where PVS snap-in is registered with this command:
# 1. set-alias installutil C:\Windows\Microsoft.NET\Framework64\v4.0.30319\installutil.exe
# 2. installutil “C:\Program Files\Citrix\Provisioning Services Console\Citrix.PVS.SnapIn.dll”
# 3. Add-PSSnapin Citrix*
# Call by : Scheduled Task, e.g. once a day
# Change Log: 
# V0.9: Creation of the Script based on the Script used for PVS 7.6 and below.
# - Change all the PoSh commands from the mcli-get to PVS-Get...
# - Creation of functions for each test sequence
# - Add Service-Checks for PVS-Servers 
# V1.0: Update that the Script works with PVS 7.7 final version. (Name of the Snapin is now Citrix.PVS.SnapIn)
# V1.1: Performance-Update, Timeout for get-content personality.ini, Target-Device table at latest in report
# V1.2: - Bug fixes (vDisk state, if one vDisk version is proper synced background is green)
# - Free Disk Space on PVS Servers (Thanks to Jay )
if ((Get-PSSnapin "Citrix.PVS.SnapIn" -EA silentlycontinue) -eq $null) {
try { Add-PSSnapin Citrix.PVS.SnapIn -ErrorAction Stop }
catch { write-error "Error loading Citrix.PVS.SnapIn PowerShell snapin"; Return }
# Change the below variables to suit your environment
# Information about the site you want to check: --------------------------------------------
$siteName="site" # site name on which the according Store is.
# 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","XA7")
# Example2: $Collections = @("every")
$Collections = @("every")
# Information about your Email infrastructure: --------------------------------------------
# E-mail report details
$emailFrom = ""
$emailTo = ""#,""
$smtpServer = ""
$emailSubjectStart = "PVS Farm Report"
$mailprio = "High"
# Check's &amp;amp; Jobs you want to perform
$PerformPVSvDiskCheck = "yes"
$PerformPVSTargetCheck = "yes"
$PerformSendMail = "yes"
#Don't change below here if you don't know what you are doing ... 
$currentDir = Split-Path $MyInvocation.MyCommand.Path
$logfile = Join-Path $currentDir ("PVSHealthCheck.log")
$resultsHTM = Join-Path $currentDir ("PVSFarmReport.htm")
$errorsHTM = Join-Path $currentDir ("PVSHealthCheckErrors.htm") 

if ($PerformPVSTargetCheck -eq "yes") {
#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

if ($PerformPVSvDiskCheck -eq "yes") {
#Header for Table 2 "vDisk Checks"
$vDiksFirstheaderName = "vDiskName"
$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","CFreespace","DFreespace","AvgCPU","MemUsg"
$PVSheaderWidths = "8", "4", "4","4","4","4","4","4","4","4"
$PVStablewidth = 800
#Header for Table 4 "Farm"
$PVSFirstFarmheaderName = "Farm"
$PVSFarmHeaderNames = "DBServerName", "DatabaseName", "OfflineDB", "LicenseServer"
$PVSFarmWidths = "4", "4", "4", "4"
$PVSFarmTablewidth = 400

#log function
function LogMe() {
[parameter(Mandatory = $true, ValueFromPipeline = $true)] $logEntry,
 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
# The function will check the processor counter and check for the CPU usage. Takes an average CPU usage for 5 seconds. It check the current CPU usage for 5 secs.
Function CheckCpuUsage() 
 param ($hostname)
 Try { $CpuUsage=(get-counter -ComputerName $hostname -Counter "\Processor(_Total)\% Processor Time" -SampleInterval 1 -MaxSamples 5 -ErrorAction Stop | select -ExpandProperty countersamples | select -ExpandProperty cookedvalue | Measure-Object -Average).average
 $CpuUsage = "{0:N1}" -f $CpuUsage; return $CpuUsage
 } Catch { "Error returned while checking the CPU usage. Perfmon Counters may be fault" | LogMe -error; return 101 } 
# The function check the memory usage and report the usage value in percentage
Function CheckMemoryUsage() 
 param ($hostname)
 { $SystemInfo = (Get-WmiObject -computername $hostname -Class Win32_OperatingSystem -ErrorAction Stop | Select-Object TotalVisibleMemorySize, FreePhysicalMemory)
 $TotalRAM = $SystemInfo.TotalVisibleMemorySize/1MB 
 $FreeRAM = $SystemInfo.FreePhysicalMemory/1MB 
 $UsedRAM = $TotalRAM - $FreeRAM 
 $RAMPercentUsed = ($UsedRAM / $TotalRAM) * 100 
 $RAMPercentUsed = "{0:N2}" -f $RAMPercentUsed
 return $RAMPercentUsed
 } Catch { "Error returned while checking the Memory usage. Perfmon Counters may be fault" | LogMe -error; return 101 } 
Function writeHtmlHeader
param($title, $fileName)
$date = ( Get-Date -format R)
$head = @"
<meta http-equiv='Content-Type' content='text/html; charset=iso-8859-1'>
<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 {
border: thin solid #000000;
<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>
$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>"
$tableHeader += "</tr>"
$tableHeader | Out-File $fileName -append
# ==============================================================================================
Function writeTableFooter
"</table><br/>"| 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
<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><br>
<tr bgcolor='#CCCCCC'>
<tr bgcolor='#CCCCCC'>
"@ | Out-File $FileName -append
 function Farmcheck() {
# ======= PVS Farm Check ====================================================================
"Read some PVS Farm Parameters" | LogMe -display -progress
" " | LogMe -display -progress
$global:PVSFarmResults = @{}
$PVSfarm = Get-PVSFarm

$global:farmname_short = $PVSfarm.FarmName 
$PVSFarmtests = @{}

$DBServer = $PVSFarm | %{ $_.DatabaseServerName }
$PVSFarmtests.DBServerName = "NEUTRAL", $DBServer

$dbname = $PVSFarm | %{ $_.DatabaseName }
$PVSFarmtests.databaseName = "NEUTRAL", $dbname

$OfflineDB = $PVSFarm | %{ $_.OfflineDatabaseSupportEnabled }
$PVSFarmtests.OfflineDB = "NEUTRAL", $OfflineDB

$LicenseServer = $PVSFarm | %{ $_.LicenseServer }
$PVSFarmtests.LicenseServer = "NEUTRAL", $LicenseServer

$global:PVSFarmResults.$global:farmname_short = $PVSFarmtests

function PVSServerCheck() {
# ======= PVS Server Check ==================================================================
"Check PVS Servers" | LogMe -display -progress
" " | LogMe -display -progress
$global:PVSResults = @{}
$allPVSServer = Get-PvsServer

foreach($PVServerName in $allPVSServer){
$PVStests = @{}
$PVServerName_short = $PVServerName | %{ $_.ServerName }
"PVS-Server: $PVServerName_short" | LogMe -display -progress

# Ping server 
$result = Ping $PVServerName_short 100
"Ping: $result" | LogMe -display -progress
if ($result -ne "SUCCESS") { $PVStests.Ping = "ERROR", $result }
else { $PVStests.Ping = "SUCCESS", $result 

# 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 the AvgCPU value for 5 seconds
 $AvgCPUval = CheckCpuUsage ($PVServerName_short)
 #$VDtests.LoadBalancingAlgorithm = "SUCCESS", "LB is set to BEST EFFORT"} 
 if( [int] $AvgCPUval -lt 75) { "CPU usage is normal [ $AvgCPUval % ]" | LogMe -display; $PVStests.AvgCPU = "SUCCESS", "$AvgCPUval %" }
 elseif([int] $AvgCPUval -lt 85) { "CPU usage is medium [ $AvgCPUval % ]" | LogMe -warning; $PVStests.AvgCPU = "WARNING", "$AvgCPUval %" } 
 elseif([int] $AvgCPUval -lt 95) { "CPU usage is high [ $AvgCPUval % ]" | LogMe -error; $PVStests.AvgCPU = "ERROR", "$AvgCPUval %" }
 elseif([int] $AvgCPUval -eq 101) { "CPU usage test failed" | LogMe -error; $PVStests.AvgCPU = "ERROR", "Err" }
 else { "CPU usage is Critical [ $AvgCPUval % ]" | LogMe -error; $PVStests.AvgCPU = "ERROR", "$AvgCPUval %" } 
 $AvgCPUval = 0

 # Check the Physical Memory usage 
 $UsedMemory = CheckMemoryUsage ($PVServerName_short)
 if( [int] $UsedMemory -lt 75) { "Memory usage is normal [ $UsedMemory % ]" | LogMe -display; $PVStests.MemUsg = "SUCCESS", "$UsedMemory %" }
 elseif([int] $UsedMemory -lt 85) { "Memory usage is medium [ $UsedMemory % ]" | LogMe -warning; $PVStests.MemUsg = "WARNING", "$UsedMemory %" } 
 elseif([int] $UsedMemory -lt 95) { "Memory usage is high [ $UsedMemory % ]" | LogMe -error; $PVStests.MemUsg = "ERROR", "$UsedMemory %" }
 elseif([int] $UsedMemory -eq 101) { "Memory usage test failed" | LogMe -error; $PVStests.MemUsg = "ERROR", "Err" }
 else { "Memory usage is Critical [ $UsedMemory % ]" | LogMe -error; $PVStests.MemUsg = "ERROR", "$UsedMemory %" } 
 $UsedMemory = 0 

 # Check C Disk Usage 
 $HardDisk = Get-WmiObject Win32_LogicalDisk -ComputerName $PVServerName_short -Filter "DeviceID='C:'" | Select-Object Size,FreeSpace 
 $DiskTotalSize = $HardDisk.Size 
 $DiskFreeSpace = $HardDisk.FreeSpace 

 $PercentageDS = (($DiskFreeSpace / $DiskTotalSize ) * 100); $PercentageDS = "{0:N2}" -f $PercentageDS 

 If ( [int] $PercentageDS -gt 15) { "Disk Free is normal [ $PercentageDS % ]" | LogMe -display; $PVStests.CFreespace = "SUCCESS", "$frSpace GB" } 
 ElseIf ([int] $PercentageDS -lt 15) { "Disk Free is Low [ $PercentageDS % ]" | LogMe -warning; $PVStests.CFreespace = "WARNING", "$frSpace GB" } 
 ElseIf ([int] $PercentageDS -lt 5) { "Disk Free is Critical [ $PercentageDS % ]" | LogMe -error; $PVStests.CFreespace = "ERROR", "$frSpace GB" } 
 ElseIf ([int] $PercentageDS -eq 0) { "Disk Free test failed" | LogMe -error; $PVStests.CFreespace = "ERROR", "Err" } 
 Else { "Disk Free is Critical [ $PercentageDS % ]" | LogMe -error; $PVStests.CFreespace = "ERROR", "$frSpace GB" } 
 $PercentageDS = 0 

 # Check D Disk Usage 
 $DHardDisk = Get-WmiObject Win32_LogicalDisk -ComputerName $PVServerName_short -Filter "DeviceID='D:'" | Select-Object Size,FreeSpace 
 $DDiskTotalSize = $DHardDisk.Size 
 $DDiskFreeSpace = $DHardDisk.FreeSpace 
 $PercentageDS = (($DDiskFreeSpace / $DDiskTotalSize ) * 100); $PercentageDS = "{0:N2}" -f $PercentageDS 

 If ( [int] $PercentageDS -gt 15) { "Disk Free is normal [ $PercentageDS % ]" | LogMe -display; $PVStests.DFreespace = "SUCCESS", "$DfrSpace GB" } 
 ElseIf ([int] $PercentageDS -lt 15) { "Disk Free is Low [ $PercentageDS % ]" | LogMe -warning; $PVStests.DFreespace = "WARNING", "$DfrSpace GB" } 
 ElseIf ([int] $PercentageDS -lt 5) { "Disk Free is Critical [ $PercentageDS % ]" | LogMe -error; $PVStests.DFreespace = "ERROR", "$DfrSpace GB" } 
 ElseIf ([int] $PercentageDS -eq 0) { "Disk Free test failed" | LogMe -error; $PVStests.DFreespace = "ERROR", "Err" } 
 Else { "Disk Free is Critical [ $PercentageDS % ]" | LogMe -error; $PVStests.DFreespace = "ERROR", "$DfrSpace GB" } 

 $PercentageDS = 0 

#Check PVS Activity Status (over PVS Framework)
$serverstatus = Get-PvsServerStatus -ServerName $PVServerName_short
$actviestatus = $serverstatus.Status
if ($actviestatus -eq 1) { $PVStests.Active = "SUCCESS", "active" }
else { $PVStests.Active = "Error","inactive" }
"PVS-Active-Status: $actviestatus" | LogMe -display -progress

#Check PVS deviceCount
$numberofdevices = $serverstatus.DeviceCount
if ($numberofdevices -gt 1) { $PVStests.deviceCount = "SUCCESS", " $numberofdevices active" }
else { $PVStests.deviceCount = "WARNING","No devices on this server" }
"Number of devices: $numberofdevices" | LogMe -display -progress

$global:PVSResults.$PVServerName_short = $PVStests

function PVSvDiskCheck() {
 # ======= PVS vDisk Check #==================================================================
 "Check PVS vDisks" | LogMe -display -progress
 " " | LogMe -display -progress
 $AllvDisks = Get-PvsDiskInfo
 $global:vdiskResults = @{}
 foreach($vDisk in $AllvDisks )
 $VDtests = @{}
 $vDiskName = $vDisk | %{ $_.Name }
 "Name of vDisk: $vDiskName" | LogMe -display -progress
 $vDiskStore = $vDisk | %{ $_.StoreName }
 "vDiskDtore: $vDiskStore" | LogMe -display -progress
 $VDtests.Store = "NEUTRAL", $vDiskStore
 #Get details of each version of the vDisk: 
 $vDiskVersions = Get-PvsDiskVersion -Name $vDiskName -SiteName $SiteName -StoreName $vDiskStore
 $vDiskVersionTable = @{}
 foreach($diskVersion in $vDiskVersions){
 $diskversionfilename = $diskVersion | %{ $_.DiskFileName }
 "Filename of Version: $diskversionfilename" | LogMe -display -progress
 $vDiskVersionTable.diskversionfilename += $diskversionfilename +="<br>"
 $diskversionDeviceCount = $diskVersion | %{ $_.DeviceCount }
 $StringDiskversionDeviceCount = $diskversionDeviceCount | Out-String
 "Version: $StringDiskversionDeviceCount" | LogMe -display -progress
 $vDiskVersionTable.StringDiskversionDeviceCount += $StringDiskversionDeviceCount +="<br>"
 $diskversionCreateDate = $diskVersion | %{ $_.CreateDate }
 "Filename of Version: $diskversionCreateDate" | LogMe -display -progress
 $vDiskVersionTable.diskversionCreateDate += $diskversionCreateDate +="<br>"
 #VdiskVersion ReplState (GoodInventoryStatus)
 $diskversionGoodInventoryStatus = $diskVersion | %{ $_.GoodInventoryStatus }
 $StringDiskversionGoodInventoryStatus = $diskversionGoodInventoryStatus | Out-String
 "Filename of Version: $StringDiskversionGoodInventoryStatus" | LogMe -display -progress
 #Check if correct replicated, count Replication Errors
 Write-Host "Schreibe hier: " $DiskversionGoodInventoryStatus
 $ReplErrorCount = 0
 if($DiskversionGoodInventoryStatus -like "True" ){
 $ReplErrorCount += 0
 } else {
 $ReplErrorCount += 1}
 $vDiskVersionTable.StringDiskversionGoodInventoryStatus += $StringDiskversionGoodInventoryStatus +="<br>"
 #Check if correct replicated THE LAST DISK
 if($ReplErrorCount -eq 0 ){
 "$diskversionfilename correct replicated" | LogMe
 $ReplStateStatus = "SUCCESS"
 } else {
 "$diskversionfilename not correct replicated $ReplErrorCount errors" | LogMe -display -error
 $ReplStateStatus = "ERROR"}
 $VDtests.vDiskFileName = "Neutral", $vDiskVersionTable.diskversionfilename
 $VDtests.DeviceCount = "Neutral", $vDiskVersionTable.StringDiskversionDeviceCount
 $VDtests.CreateDate = "Neutral", $vDiskVersionTable.diskversionCreateDate
 $VDtests.ReplState = "$ReplStateStatus", $vDiskVersionTable.StringDiskversionGoodInventoryStatus
 #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 9=RamOfToHD 0=PrivateMode 4=DeviceHD 8=DeviceHDPersistent 3=DeviceRAM 1=PVSServer 7=ServerPersistent 
 $vDiskWriteCacheType = $vDisk | %{ $_.WriteCacheType }
 if($vDiskWriteCacheType -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($vDiskWriteCacheType -eq 0 ){
 "WC is not set because vDisk is in PrivateMode (R/W)" | LogMe
 $VDtests.WriteCacheType = "Error", "vDisk is in PrivateMode (R/W) "}
 elseif($vDiskWriteCacheType -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($vDiskWriteCacheType -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($vDiskWriteCacheType -eq 3 ){
 "WC is set to Cache to Device Ram" | LogMe
 $VDtests.WriteCacheType = "WARNING", "WC is set to Cache to Device Ram"}
 elseif($vDiskWriteCacheType -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($vDiskWriteCacheType -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"}
 #Vdisk SubnetAffinity or fixed ServerName
 $vDiskfixServerName = $vDisk | %{ $_.ServerName }
 "vDisk is fix assigned to Server: $vDiskfixServerName" | LogMe -display -progress
 if($vDiskfixServerName -eq "" )
 $vDiskSubnetAffinity = $vDisk | %{ $_.SubnetAffinity }
 "vDiskDtore: $vDiskSubnetAffinity" | LogMe -display -progress
 #SubnetAffinity: 1=Best Effort, 2= fixed, 0=none
 if($vDiskSubnetAffinity -eq 1 ){
 "LB-Algorythm is set to BestEffort" | LogMe
 $VDtests.LoadBalancingAlgorithm = "SUCCESS", "LB is set to BEST EFFORT"} 
 elseif($vDiskSubnetAffinity -eq 2 ){
 "LB-Algorythm is set to fixed" | LogMe
 $VDtests.LoadBalancingAlgorithm = "WARNING", "LB is set to FIXED"}
 elseif($vDiskSubnetAffinity -eq 0 ){
 "LB-Algorythm is set to none" | LogMe
 $VDtests.LoadBalancingAlgorithm = "SUCCESS", "LB is set to NONE, least busy server is used"}
 $VDtests.LoadBalancingAlgorithm = "ERROR", "No LoadBalancing! Server is fix assigned to $vDiskfixServerName"}

 $global:vdiskResults.$vDiskName = $VDtests


function PVSTargetCheck() {
# ======= PVS Target Device Check ========
"Check PVS Target Devices" | LogMe -display -progress
" " | LogMe -display -progress

$global:allResults = @{}
$pvsdevices = Get-PvsDevice

foreach($target in $pvsdevices) {
$tests = @{} 
 # Check to see if the server is in an excluded folder path
 $CollectionName = $target | %{ $_.CollectionName }
 #Only Check Servers in defined Collections: 
 if ($Collections -contains $CollectionName -Or $Collections -contains "every") { 
 $targetName = $target | %{ $_.Name }
 #Name of CollectionName
 $CollectionName = $target | %{ $_.CollectionName }
 "Collection: $CollectionName" | LogMe -display -progress
 $tests.CollectionName = "NEUTRAL", "$CollectionName"
 $targetNamePvsDeviceInfo = Get-PvsDeviceInfo -Name $targetName
 $DeviceUsedDiskVersion = $targetNamePvsDeviceInfo.DiskVersion
 "Used DiskVersion: $DeviceUsedDiskVersion" | LogMe -display -progress
 $tests.vDisk_Version = "NEUTRAL", "$DeviceUsedDiskVersion"
 $DeviceUsedServerName= $targetNamePvsDeviceInfo.ServerName
 "Used Server: $DeviceUsedServerName" | LogMe -display -progress
 $tests.PVSServer = "NEUTRAL", "$DeviceUsedServerName"
 $targetNamePvsDeviceStatus = Get-PvsDeviceStatus -Name $targetName
 $RetryStatus = $targetNamePvsDeviceStatus.Status
 "Retry: $RetryStatus" | LogMe -display -progress
 $tests.Retry = "NEUTRAL", "$RetryStatus"
 # Ping target 
 $result = Ping $targetName 100
 if ($result -ne "SUCCESS") { $tests.Ping = "ERROR", $result }
 else { $tests.Ping = "SUCCESS", $result 

 $DiskFileNameStatus = $targetNamePvsDeviceStatus.DiskFileName
 "Retry: $DiskFileNameStatus" | LogMe -display -progress
 $tests.vDisk_PVS = "NEUTRAL", "$DiskFileNameStatus"

 ################ PVS WriteCache SECTION ############### 
 $short_diskLocatorID = $targetNamePvsDeviceInfo.DiskLocatorId
 $diskinfo = Get-PvsDiskInfo -DiskLocatorId $short_diskLocatorID
 $short_DeviceWriteCacheType = $diskinfo.WriteCacheType
 #if (test-path \\$targetName\c$\Personality.ini)
 if ($short_DeviceWriteCacheType = "4")

 $wconhd = ""
 $job = Start-Job {
 $wconhd = Get-Content \\$targetName\c$\Personality.ini | Where-Object {$_.Contains("WriteCacheType=4") }
 $res = Wait-Job $job -timeout 3
 if(-not $res) {Write-Host "Timeout"}

 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 "\\$targetName" $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
 "WriteCache file size is high" | LogMe -display -error
 $tests.WriteCache = "ERORR", $CacheDiskGB
 $Cachedisk = 0
 $VDISKImage = get-content \\$targetName\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
 {Write-Host Cache on Ram
 #Get-RamCache from each target, code from Matthew Nics
 $RAMCache = [math]::truncate((Get-WmiObject Win32_PerfFormattedData_PerfOS_Memory -ComputerName $targetName).PoolNonPagedBytes /1MB)
 $tests.WriteCache = "Neutral", "$RamCache MB on Ram"
 {Write-Host WriteCache not readable
 $tests.WriteCache = "Neutral", "Cache not readable" 
 ############## END PVS WriteCache SECTION #############

 $global:allResults.$targetName = $tests

#HTML function
function WriteHTML() {
# ======= Write all results to an html file =================================================
Write-Host ("Saving results to html report: " + $resultsHTM)
writeHtmlHeader "PVS Farm Report $global:farmname_short" $resultsHTM

if ($PerformPVSvDiskCheck -eq "yes") {
writeTableHeader $resultsHTM $vDiksFirstheaderName $vDiskheaderNames $vDiskheaderWidths $vDisktablewidth
$global:vdiskResults | sort-object -property ReplState | % { writeData $vdiskResults $resultsHTM $vDiskheaderNames }
writeTableFooter $resultsHTM

writeTableHeader $resultsHTM $PVSFirstheaderName $PVSheaderNames $PVSheaderWidths $PVStablewidth
$global:PVSResults | sort-object -property PVServerName_short | % { writeData $PVSResults $resultsHTM $PVSheaderNames}
writeTableFooter $resultsHTM
writeTableHeader $resultsHTM $PVSFirstFarmheaderName $PVSFarmHeaderNames $PVSFarmWidths $PVSFarmTablewidth
$global:PVSFarmResults | % { writeData $PVSFarmResults $resultsHTM $PVSFarmHeaderNames}
writeTableFooter $resultsHTM

if ($PerformPVSTargetCheck -eq "yes") {
writeTableHeader $resultsHTM $TargetFirstheaderName $TargetheaderNames $TargetheaderWidths $TargetTablewidth
$allResults | sort-object -property collectionName | % { writeData $allResults $resultsHTM $TargetheaderNames}
writeTableFooter $resultsHTM

writeHtmlFooter $resultsHTM
#send email
$emailSubject = ("$emailSubjectStart - $global:farmname_short - " + (Get-Date -format R))
$global:mailMessageParameters = @{
From = $emailFrom
To = $emailTo
Subject = $emailSubject
SmtpServer = $smtpServer
Body = (gc $resultsHTM) | Out-String
Attachment = $resultsHTM
#Mail function
# Send mail 
function SendMail() {
Send-MailMessage @global:mailMessageParameters -BodyAsHtml -Priority $mailprio

$scriptstart = Get-Date
rm $logfile -force -EA SilentlyContinue
"Begin with Citrix Provisioning Services HealthCheck" | LogMe -display -progress
" " | LogMe -display -progress
if ($PerformPVSTargetCheck -eq "yes") {
"Initiate PVS Target check" | LogMe
} else {
" PVS Target check skipped" | LogMe

if ($PerformPVSvDiskCheck -eq "yes") {
"Initiate PVS Target check" | LogMe
} else {
" PVSvDiskCheck check skipped" | LogMe


if ($PerformSendMail -eq "yes") {
"Initiate send of Email " | LogMe
} else {
"send of Email skipped" | LogMe

$scriptend = Get-Date
$scriptruntime = $scriptend - $scriptstart | select TotalSeconds
$scriptruntimeInSeconds = $scriptruntime.TotalSeconds
#Write-Host $scriptruntime.TotalSeconds
"Script was running for $scriptruntimeInSeconds " | LogMe -display -progress

or Download the file here.

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.

Planned updates:  CIFS vDisk store check

Known Bugs: –

Control MyStrom smart plug by a trigger (ifttt or octoblu)

mystromSome weeks ago I bought my first smart plug from MyStrom. If you are living  in Switzerland you have only a limited choice of smart plugs because of the non EU standard plug (T13).

MyStrom is pretty cool because it mets almost all my requirement, there is a possibility to measure to power consumption within two rates (day and night), it’s controllable with the mobile phone app and there is a nice web interface.

But one thing I miss, I cannot react to triggers and power of the smart plug on a defined event. Like already in an earlier post mentioned there is a REST API which allows to control the plug from the internal network. (See Another LaMetric IoT script – power control)

Last day’s I just found the API documentation which allows to control the MyStrom out of the cloud: 

So I have now all to connect this Thing to the Internet and react on a trigger.

Possibility 1: The everything imaginable way,
MyStrom and Octoblu

mystromlogo      +      ocoblu

If you are using Octoblu beta, which is currently available for everyone for free you can create very intelligent workflows to control your smart plug. You can react to twitter and power of or on depending what is twittered, you can power off your heater as soon a temperature is reached, you can stop charge as soon your device have a defined state of charge.

The Things you connect use with Octoblu are nearly infinite.


Octoblu is for advanced use, and to be honest I’m also on the learning phase! I do not describe here more details for this case. There are a lot of sample blueprints on .  Octoblu was acquired by Citrix in 2015.


Possibility 2: The easy way,
MyStrom and IFTTT

 mystromlogo      +      iftttlogo

This way is suitable for “Home use” and for people who only want to create a simple workflow where a trigger initiate for example a Power On or Power Of of the smart plug.

All what you need is a Control-Script on a Webserver with PHP on the Internet, and a IFTTT account.

How it works: The script is very simple, it just handle the  authentication process on the API, read the AuthToken out of the JSON answer and Push out the payload with the AuthToken. IFTTT just take the trigger and call the script with the username, passwort and the payload which you can build from the API

Example: I want to control my smart plug by twitter, if I twitter #lightsoff

Step 1:

Take this script and put it on your webserver:


V0.1 by Sacha Thomet,, 12-2015

This script can be used to control the MyStrom power outlet with IFTTT.
In IFTTT you need to take the MAKER component to make a web request.

The script must be called with 3 parameters:
- Your MyStrom username (email)
- Your MyStrom password
- the decided payload 
 e.g. ../device/switch?on=true%id=64002D012305 to turn on your device with Mac 64002D012305
 Documentation of all API calls:
 Attention! You need to replace in your payload all "&" with a "%" !!

//create variables from parameter
$email = ($_GET["email"]);
$password = ($_GET["password"]);
$payload = ($_GET["payload"]);

//myStrom host with the parameters to gather the AuthToken
$host = "$email&password=$password"; 
$filestring = file_get_contents($host); 

//format the AuthToken that only the token remain from the JSON Answer
$startpos = 0; 
while($pos = strpos($filestring, "authToken", $startpos)) 
 $string = substr($filestring, $pos, strpos($filestring, "name", $pos + 1) - $pos); 

 //echo $string. " "; 
 $newstring = str_replace("authToken","","$string");
 $newstring2 = str_replace('"',"",$newstring);
 $newstring3 = str_replace(":","","$newstring2");
 $authCode = str_replace(",","","$newstring3");

 echo " AuthCode is $authCode <br>" ;

 $startpos = $pos + 1; 

// Because & cannot submitted in a $_GET we subsitute it with a %
$correctpayload = str_replace("%","&","$payload");

echo "Full payload is: $correctpayload&authToken=$authCode";

$fullpayload = "$correctpayload&authToken=$authCode";
$payloadString = file_get_contents($fullpayload); 


Step 2:

Create a recipe on IFTTT, use Twitter as Trigger and Maker as Action. In maker you create your request:


e.g. the payload to turn of the device with Mac 64002D012325 is:

The result should be that:


You create 2 of those recipies, one to Power On and one to Power Off

Now you can twitter you defined hashtags to control your smart plug.

Another LaMetric IoT script – power control

Because I have currently holiday here is another post which is not related to virtualization …

I combined my new “gadgets” the LaMetric time and my new wifi Smart-Plug from MyStrom. The goal was to report the current power consumption of power plug and the possibility to control the plug with the smart ticker.

MyStrom has a very straight forward API (, and the possibility to create apps for LaMetric is pretty easy what I’ve already mentioned in an past blog post.

All what is needed to implement this solution is:

  • a web server which can parse PHP
  • two small scripts
  • and a private app for LaMetric


This is the script to see the current power consumption:

    "frames": [
            "index": 0,
            "text": " <?php 
			//$host = "";
			//Call with parameter, e.g.:
			$host = "http://" . $_GET["host"] . "/report"; 

			$filestring = file_get_contents($host); 
			$startpos = 0; 

			while($pos = strpos($filestring, "\"power\"", $startpos)) 
    		$string = substr($filestring, $pos, strpos($filestring, ",", $pos + 1) - $pos); 

		    //echo $string. " "; 
    		$newstring = str_replace("\"power\":	","","$string");
			echo round($newstring, 2); 
    		$startpos = $pos + 1; 

			?> Watt",
            "icon": "i1692"

I just read the content from the API, which is already in JSON format, but I reformat it that it is in that way which I need to bring it to the LaMetric.
This is the script to control the power outlet:

$host = "http://" . $_GET["host"] . "/report"; 
$filestring = file_get_contents($host); 
$startpos = 0; 

	while($pos = strpos($filestring, "relay", $startpos)) 
    $string = substr($filestring, $pos, strpos($filestring, "}", $pos + 1) - $pos); 
	$newstring = str_replace("relay\":	","","$string");		
	//echo $newstring;
	$startpos = $pos + 1; 

$newstring = str_replace(chr(13), "", $newstring);

if ($newstring == "false
") {
 echo "Ist ausgeschaltet, wird nun eingeschaltet";
 $URL = "http://" . $_GET["host"] . "/relay?state=1";
 $data = file_get_contents($URL);

else {
 echo "Ist eingeschaltet, wird nun ausgeschaltet";
  $URL = "http://" . $_GET["host"] . "/relay?state=0";
  $data = file_get_contents($URL);


And the LaMetric private app:

This simple poll app call the script on the webserver ( with the parameter of the MyStrom plug (

On button action the controller script is launched with change the status (off to on or visa verse)


( is the ip of my Power outlet. is the address of my web server, this can be a raspberry pi or a Synology.)

Monitor you Profile directories

Since I had worked as a Citrix administrator the size of the profile directories was always a contemporary issue. It’s a fact that with new technologies like Citrix User Profile Manager the possibilities are grown and the life is easier with exclusion lists and profile streaming features. But it’s still a topic to consider if you are a Citrix admin or system engineer. I worked in former company with tools like TreeSize ( or WinDir Stat ( But in some company they wont to spend money for tools which is only usable for one administrator or they don’t trust to OpenSource software (TreeSize).

I have the approach to solve every possible problem with on-board tools. So I created now a small script what give me the possibility to check my Profile directory with one recursive level so that I get each Size of a Profile within his subdirectories.:


# Created on: 10.2015 Version: 0.3
# Created by: Sacha Thomet
# Filename: GetSubdirSizes.ps1
# Description: This script checks some Citrix Provisioning Server, Farm, vDisk & Target device parameters.
# Prerequisite: None
# Call by : Manual 
# Change Log: 
# V0.1 first version
# V0.2 only 1 recursive subfolder
# V0.3 corrections of calculation

$path = "\\localhost\C$\temp\profiles\"
$top = "4000"
$outputpath = "C:\temp\SubdirSize-Report.txt"

Function Get-FolderSize
 $Sizes = 0
 ForEach ($Item in (Get-ChildItem $Path)) 
 If ($Item.PSIsContainer) {$Sizes = (Get-ChildItem $path"$Item" -recurse | Measure-Object -property length -sum).sum}
 Else {Write-Host "No Subfolder"}
 $SizeFormated = "{0:N2}" -f ($Sizes/1MB)
 New-Object PSObject -Property @{'Folder'=$Item;'Size'=$SizeFormated } 
$outfile = Get-FolderSize $path | sort -property Size –Descending |select -first $top 
$outfile | out-file $outputpath
echo $outfile