by: Helge, published: Oct 16, 2013, in

Shutting Down Unused Persistent XenDesktop VMs

When you use XenDesktop the only way it makes sense you may find that Citrix has not really put much effort into making that a smooth experience.

Persistent is a Second-Grade Citizen

XenDesktop is really designed to be used with pooled desktops – machines that are reset to a pristine state when the user logs off. Of course, stateless desktops are much better (and, importantly, cheaper) served from XenApp. This has been the topic of many a debate which I will not repeat here. But I will state that if you give a so-called knowledge worker a personal desktop, you better make sure that desktop is persistent.

Reality is merely an illusion, albeit a very persistent one. – Albert Einstein

Automatic Shutdown?

One of the many things that should be automatic but are not is power management. To be more exact: shutdown of unused private desktops (in XenDesktop 5.6). Although that capability is built into XenDesktop, it is de facto broken. Why? It only turns off machines that have been idle for a certain amount of time after a user logged off. That is all well, but what if you turn on all machines for patching and virus scanning regularly? In that case, XenDesktop power management remains inactive and machines never get shut down – turned on once, running forever.

Unlimited Disk, Limited RAM

If you have disk deduplication in place – which is practically a necessity with persistent desktops – you can create a nearly unlimited number of machines. Many more than you can run concurrently because you would run out of RAM. Why you would do that? Think test environment, where you probably only have a server or two, but everybody and his sister want a VM for the once a quarter application test they need to perform. In order for that scenario to work well, VMs that have not been used for some time need to be powered down or you will quickly run into the situation that RAM is exhausted and users complain because their VMs cannot be powered on when they try to connect.

So, what do we do when a product does not work the way we expect it to? We script our way around it!


Here is my simple script ShutdownUnusedVMs.ps1 which shuts down VMs that meet certain criteria:

  • Last connection at least 8 hours ago
  • No user currently connected
  • VMware Tools installed (otherwise the machine could only be turned off)

Configure the script to run as a scheduled task on a regular basis and the number of concurrently running machines should stay within reasonable limits. Just make sure to run it from a user account that has the appropriate permissions in XenDesktop and vSphere.

# ShutdownUnusedVMs by Helge Klein

# Variables that must be adjusted prior to use
$vCenter = "Name of your vCenter server"
$DDC = "Name of your XenDesktop DDC"

# Add the required snapins
Add-PSSnapin vmware.vimautomation.core
Add-PSSnapin citrix.*

# Connect to vCenter
Connect-VIServer $vCenter

# Define how long ago the last connection must have been for the VM to be considered for shutdown
$earliestTime = (Get-Date).AddHours(-8)

# Get the XenDesktop machine objects whose last connection time is long enough in the past and which are not in use
$xdMachinesToShutdown = Get-BrokerDesktop -AdminAddress $DDC | where {$_.LastConnectionTime -lt $earliestTime -and $_.PowerState -eq "on" -and $_.SummaryState -ne "InUse" -and $_.SummaryState -ne "Disconnected"}

# Log
$xdMachinesToShutdown | select HostedMachineName, LastConnectionTime, AssociatedUserFullNames | ft -AutoSize -Wrap | Out-File -Force .\xdMachinesToShutdown.txt

# Get the VMs to shutdown
$vmsToProcess = new-object system.collections.arraylist
foreach ($xdMachine in $xdMachinesToShutdown) {if ((Get-VM $xdMachine.HostedMachineName | get-view).summary.guest.toolsRunningStatus -eq "guestToolsRunning") {$vmsToProcess.add((Get-VM $xdMachine.HostedMachineName))}}

# Log
$vmsToProcess | select Name | ft -AutoSize -Wrap | Out-File  -Force .\vmsToShutdown.txt

# Shutdown
foreach ($vm in $vmsToProcess) {Get-VM $vm | Shutdown-VMGuest -Confirm:$false}
Previous Article How to Process Terabytes - per Day (or: my account of Splunk .conf 2013)
Next Article Workaround: "554 rejected due to spam content" sending e-mail