Finding and Remediating Horizon View Desktops with the Wrong Snapshot/Image

I want you to imagine this scenario for a moment.  You schedule an overnight recompose operation for some of your linked-clone desktop pools.  You go to bed knowing that your desktops will have that shiny new image in the morning.  But when you wake up, you have a warning from your monitoring system or the vCheck script that shows the datastore that your replica volumes run on are running out of space.  When you log in to check, you have a couple of extra replicas that shouldn’t be there.  There is no easy way to say which pools, or desktops, are the issue because the replica names are GUIDs that do not relate to the pools.

I’ve been in this spot many times.  Sometimes, a recompose fails with a recoverable error.  Other times a few desktops didn’t recompose because they were refreshed sometime between when I scheduled the recompose and when it actually kicked off. And on some very rare occassions, there was a desktop that clung to its image like a security blanket, and it wouldn’t update until it was deleted.

When this happens, there is, currently, only one way to find out which pools or desktops are the problem.  In order to properly diagnose and correct this, an administrator would need to log into View Administrator, open each pool individually, go into Inventory and select the View Composer details.  While it is a powerful management interface, View Administrator is not exactly the switftest tool for an administrator to use, and it could take a while for this information to load if you have a large number of pools and/or desktops per pool.
There had to be a better, and automated, way to handle this.  And there is.

View PowerCLI

The preferred method for automating Horizon View deployments is the View PowerCLI cmdlets.  These cmdlets are included on the View Connection Servers.  The two cmdlets that look like they would be useful for resolving this issue are Get-Pool and Get-DesktopVM.

However, this won’t quite work.  The output from the Get-DesktopVM cmdlet does not provide any information about the image or snapshot that the linked clones are using.  Even if we could find the desktops using View PowerCLI, there does not seem to be any way to remotely delete a linked-clone desktop using the View PowerCLI cmdlets, so this is not the solutions.  Deleting the desktop VMs in vCenter is not a good idea, and bad things happen when you remove the linked-clone desktop directly from vCenter.

Horizon View and LDAP

There is another way, though.  Before that option can be explored, we have to understand how Horizon View stores it’s configuration data.  The main database for Horizon View is an LDAP directory.  This directory contains all of the information about the Horizon View environment such as pools, desktop VMs, Active Directory users, and even ThinApp applications that are deployed through View Administrator.

Since this is an LDAP directory, we can directly query for the information that we need, and we’re not limited to PowerShell.  Any scripting or programming language that can talk to an LDAP directory can be used.  I use PowerShell with the Quest Active Directory Cmdlets because that is the language that I’m comfortable with.

If you’re not familar with LDAP, there are some terms that might not make sense.  Attribute is one term that I will be using.  An attribute is like a file, and this is where a data value is stored.  A group of attributes is called an object-class.  Object-classes are like folders, and there can be multiple levels of object-classes in the directory schema, or layout.
There are two object-classes that we are going to be concerned with.  Those two object-classes are:

  • pae-ServerPool –  This object-class defines the desktop pools.
  • pae-Server –  This object-class defines the desktops.

There is a memberDN attribute for each desktop pool.  This attribute contains the list of distinguished names for the desktops in that pool, and it will assist our search for desktops.
The Desktop Pool and Desktop VM records also contain a field that has the vCenter snapshot ID.  This field is the one that will be used to compare the desktop VM to the standard that has been applied to the pool.  This field is called pae-SVIVmSnapshotMOID in both the ServerPool (desktop pool) and Server(desktop VM) object-classes.
In order to find the desktops that aren’t using the correct image/snapshot, we need to compare the values in these two fields.  If they don’t match, they will be added to an object called DesktopExceptions that will be used for later processing.

Once we’ve identified all the desktops that need to be remediated, we need to find a method for removing the desktop so View will recreate it.  There are no native commands to do this, but there is a method provided by Andre Leibovici on his blog myvirtualcloud.net.  This method allows us to set the desktop state remotely through the LDAP datastore.  For our desktops that are using the wrong snapshots, the state will need to be set to “Deleting.”  Once we do this, the desktops will be deleted and recreated automatically.

These steps are wrapped up in the script below.  Since this talks directly to the LDAP directory for View and avoids using the View PowerCLI cmdlets, this can be run from any server with PowerShell and the Quest AD Cmdlets.

This code is also available on my GitHub site.

       

            <#
.SYNOPSIS
   Get-DesktopExceptions is a script that will locate VMware View Linked Clones that are not using the correct snapshot/image.  The script also contains an option to remediate any non-compliant desktops by deleting them and letting View recreate them.
.DESCRIPTION
   Get-DesktopExceptions will look in the View LDAP datastore to find the snapshot IDs used by the desktops and the pool. It compares these values to find any desktops that do not match the pool.  If the -Remediate switch is selected, the script will then remove them.  In order to run this script, the Quest Active Directory Cmdlets will need to be installed.
.PARAMETER ConnectionServer
   The View Connection server that you want to run this script against.
.PARAMETER Remediate
   Delete desktops that do not have the correct snapshots
.PARAMETER EmailRcpt
   Person or group who should receive the email report
.PARAMETER SMTPServer
   Email server
.EXAMPLE
   Get-DesktopExceptions -ConnectionServer connection.domain.com -Remediate -EmailRcpt user@domain.com -SMTPServer smtp.domain.com
#>
param($ConnectionServer,[switch]$Remediate,$EmailRcpt,$SMTPServer)

Function Send-Email
{
 Param([string]$SMTPBody,[string]$SMTPSubject = "View Snapshot Compliance Report",[string]$SMTPTo,$SMTPServer)
Send-MailMessage -To $SMTPTo -Body $SMTPBody -Subject $SMTPSubject -SmtpServer $SMTPServer -From "Notifications_noreply@gbdioc.org" -BodyAsHtml
}

Function Get-Pools
{
param($ConnectionServer)

$PoolList = @()

$arrIncludedProperties = "cn,name,pae-DisplayName,pae-MemberDN,pae-SVIVmParentVM,pae-SVIVmSnapshot,pae-SVIVmSnapshotMOID".Split(",")
$pools = Get-QADObject -Service $ConnectionServer -DontUseDefaultIncludedProperties -IncludedProperties $arrIncludedProperties -LdapFilter "(objectClass=pae-ServerPool)" -SizeLimit 0 | Sort-Object "pae-DisplayName" | Select-Object Name, "pae-DisplayName", "pae-SVIVmParentVM" , "pae-SVIVmSnapshot", "pae-SVIVmSnapshotMOID", "pae-MemberDN"

ForEach($pool in $pools)
{
$obj = New-Object PSObject -Property @{
           "cn" = $pool.cn
           "name" = $pool.name
           "DisplayName" = $pool."pae-DisplayName"
           "MemberDN" = $pool."pae-MemberDN"
           "SVIVmParentVM" = $pool."pae-SVIVmParentVM"
           "SVIVmSnapshot" = $pool."pae-SVIVmSnapshot"
           "SVIVmSnapshotMOID" = $pool."pae-SVIVmSnapshotMOID"
    }
$PoolList += $obj
}
Return $PoolList
}

Function Get-Desktop
{
param($MemberDN, $ConnectionServer)

$arrIncludedProperties = "cn,name,pae-DisplayName,pae-MemberDN,pae-SVIVmParentVM,pae-SVIVmSnapshot,pae-SVIVmSnapshotMOID".Split(",")
$Desktop = Get-QADObject -Service $ConnectionServer -DontUseDefaultIncludedProperties -IncludedProperties $arrIncludedProperties -LdapFilter "(&(objectClass=pae-Server)(distinguishedName=$MemberDN))" -SizeLimit 0 | Sort-Object "pae-DisplayName" | Select-Object Name, "pae-DisplayName", "pae-SVIVmParentVM" , "pae-SVIVmSnapshot", "pae-SVIVmSnapshotMOID"

Return $Desktop
}

$DesktopExceptions = @()
$pools = Get-Pools -ConnectionServer $ConnectionServer

ForEach($pool in $pools)
{
$MemberDNs = $pool.memberdn
 ForEach($MemberDN in $MemberDNs)
 {
 $Desktop = Get-Desktop -MemberDN $MemberDN -ConnectionServer $ConnectionServer

 If($Desktop."pae-SVIVmSnapshotMOID" -ne $pool.SVIVmSnapshotMOID)
  {
  $obj = New-Object PSObject -Property @{
           "PoolName" = $pool.DisplayName
     "DisplayName" = $Desktop."pae-DisplayName"
     "PoolSnapshot" = $pool.SVIVmSnapshot
     "PoolSVIVmSnapshotMOID" = $pool.SVIVmSnapshotMOID
           "DesktopSVIVmSnapshot" = $Desktop."pae-SVIVmSnapshot"
           "DesktopSVIVmSnapshotMOID" = $Desktop."pae-SVIVmSnapshotMOID"
     "DesktopDN" = $MemberDN
    }
$DesktopExceptions += $obj
  }
 }

}

If($DesktopExceptions -eq $null)
 {
 $SMTPBody = "All desktops are currently using the correct snapshots."
 }
Else
 {
 $SMTPBody = $DesktopExceptions | Select-Object DisplayName,PoolName,PoolSnapshot,DesktopSVIVmSnapshot | ConvertTo-HTML
 }

Send-Email -SMTPBody $SMTPBody -SMTPTo $EmailRcpt

If($Remediate -eq $true)
{
 ForEach($Exception in $DesktopExceptions)
 {
  Set-QADObject -Identity $Exception.DesktopDN -Service $ConnectionServer -IncludedProperties "pae-vmstate" -ObjectAttributes @{"pae-vmstate"="DELETING"}
 }

}

       

One thought on “Finding and Remediating Horizon View Desktops with the Wrong Snapshot/Image

  1. Pingback: Where I Go Spelunking into the Horizon View LDAP Database–Part 1 | Sean's IT Blog

Comments are closed.