XenDesktop 7 – First Thoughts

Citrix hosted an amazing event last week, and outlined a distinct roadmap of their 2013 strategy. They placed a strong emphasis on mobility with some updates to their Zenprise acquisition (XenMobile, aka Worx), and announced the first implementation of Project Avalon in the form of ‘XenDesktop 7’. Since I’ve spent a lot of time with XenDesktop (both IMA and Storm based) and XenApp, I thought I’d share my general impression of XenDesktop 7 as it relates to achieving the goals set forth by Avalon.

First off, the unification of XenDesktop and XenApp was a necessary evil based on Citrix’s decision to combine the management and provisioning of  ‘desktops’ & ‘servers’ (SBC and VDI) within the same console. Through what Citrix is calling the ‘FlexCast Management Architecture’ (Storm+RDS), they are replacing ‘IMA’, which was used for all versions of XenApp, as well as XenDesktop versions prior to Rhone (Barossa, Sonoma, Rioja, Bordeaux, Medoc, etc.).

This change is a great move in terms of farm design, scalability, and stability. In my opinion, the Storm framework is easier to install, troubleshoot, and support than IMA (written in .NET, readable database, excellent SDK, better logging, etc), and should be familiar to anyone who has worked with XenDesktop 5.x. The site is just as dependent on availability of the central database as in XD5 (no local host cache), which means no zones, data collectors, or any other sort of ‘master’ server (the database is the master). All of the same ICA/HDX functionality is still there (plus any new additions), as is the policy engine and brokering functionality.

I’m not too fond of the licensing model which provides published Windows client OS in the least expensive edition, whereas Windows server OS requires a more expensive license. I suppose that’s representative of Citrix choosing to call Excalibur XenDesktop instead of XenApp, though I never really thought of this distinction since I assumed it was called XenDesktop because they used the Storm site architecture (now called FMA). I’m also concerned about feature parity with XenApp, and am sure there will be more than a few features that either don’t live up to XenApp, or just aren’t there yet.

At the end of the day I’m excited about XenDesktop 7, as it provides an easier product to sell. There’s no more worrying about whether or not you need to publish apps from Windows client or server OS (besides the licensing), and all of the management and provisioning (except for Provisioning Services :)) is done in a central console. The new Director looks fantastic, and the refreshed Studio is much more responsive and elegant than that of XenDesktop 5. Also, my SiteDiag tool (Site Checker v2.0) was designed to run on the Excalibur tech preview, and I’ll be sure to get it working for XenDesktop 7 once its released.

I get the feeling that the rest of the Citrix community is generally as excited about XenDesktop 7 as I am, but I guess we’ll see how it plays out once we start implementing it!

PVS Write Cache Monitor

I was recently working on a PVS deployment where we wanted to monitor and alert on target devices that were exceeding 70% write cache utilization. Since there wasn’t a way to do this in the PVS console, I dug into the PVS PoSH SDK to find a way to do this programatically.

After some research I came up with the following script that will check all target devices in a PVS farm, and send an email alert listing any machines that are using 70%+ of their write cache (adjustable via the $threshold variable):

$pass = cat .\securestring.txt | ConvertTo-SecureString
$mycred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList "domain\admin",$pass
$message = @()
$threshold = 70

function get-value{
param([string]$strText="",[string]$strDelimiter="")
return $strText.SubString($strText.IndexOf($strDelimiter)+2)
}
function get-name{
param([string]$strText="",[string]$strDelimiter="")
return $strText.SubString(0,$strText.IndexOf($strDelimiter))
}
Add-PSSnapin McliPS* -ErrorAction SilentlyContinue
$all = @()
$obj = New-Object System.Collections.ArrayList
$lines = Mcli-Get DeviceInfo -f ServerName,ServerIpConnection,DeviceName,SiteName,CollectionName,Active,Status,diskLocatorName
for($i=0;$i -lt $lines.length;$i++){
	if(($lines[$i].length -gt 0) -and ($lines[$i].contains(":")) -and -not ($lines[$i] -match "Executing: Get "))
	{
		$name = get-name -strText $lines[$i] -strDelimiter ":"
		$value = get-value -strText $lines[$i] -strDelimiter ":"
		if ($name -eq "status" -and $value.Length -le 0)
		{
			$value = "0"
		}
		if ($value.Contains(",") -and $name -eq "status")
		{
		$obj | Add-Member -membertype noteproperty -name $name -Value $value.Split(",")[1]
		}else{
		$obj | Add-Member -membertype noteproperty -name $name -Value $value
		}
		}
		if($lines[$i].contains("#") -or (($i+1) -eq $lines.length)){
		$all += $obj
		$obj = New-Object psObject
		}
	}

foreach ($item in $all)
{
	if ([int] $item.status -gt $threshold)
	{
		$message += $item
	}
}
$message = $message | Where-Object -FilterScript { ([int] $_.status -gt 0) -and ([int]$_.status -le 100) } | Sort-Object {[int] $_.status} -descending | Format-Table @{Expression={$_.deviceName};Label="Device"}, @{Expression={$_.status};Label="RAM Cache Used (%)"}
function sendMail{

     Write-Host "Sending Email"

     #SMTP server name
     $smtpServer = "mail.domain.com"

     #Creating a Mail object
     $msg = new-object Net.Mail.MailMessage

     #Creating SMTP server object
     $smtp = new-object Net.Mail.SmtpClient($smtpServer)
	 $smtp.Credentials = $mycred

     #Email structure 
     $msg.From = "[email protected]"
     $msg.ReplyTo = "[email protected]"
     $msg.To.Add("[email protected]")
     $msg.subject = "PVS Write Cache $($threshold)%+ Utilization: " + $summary
	 $msg.body = Out-String -InputObject $message
	 $msg.priority = "High"

     #Sending email 
     $smtp.Send($msg)

}
if ($message.Count -gt 0)
{
	sendMail
}

Adding RAM to a PVS ‘Streamed’ XenDesktop catalog in vSphere

I was working on a PVS deployment recently and needed to quickly add some RAM to ~200 PVS streamed VMs. To automate this task, I put together the following PoSH script that combines PowerCLI and the XenDesktop PoSH SDK to add RAM to all machines in a particular desktop group:

###################################################################
#
# Change-VM_Memory_CPU_Count.ps1
#
# -MemoryMB the amount of Memory you want 
#  to add or remove from the VM in MB
# -MemoryOption Add/Remove
# -CPUCount the amount of vCPU's you want 
#  to add or remove from the VM
# -CPUOption Add/Remove
# -DesktopGroup the XenDesktop Desktop Group to run against
# -AdminAddress host name of the XenDesktop DDC to run against
#
# Example:
# .\Change-VM_Memory_CPU_Count.ps1 -vCenter vmvcatl05 -MemoryMB 1024 -MemoryOption Add -DesktopGroup 'All User Windows 7' -AdminAddress CTXXDATL01
#

#
####################################################################

param(
    [parameter(Mandatory = $true)]
    [string[]]$vCenter,
    [int]$MemoryMB,
    [string]$MemoryOption,
    [int]$CPUCount,
    [string]$CPUOption,
	[string]$DesktopGroup,
	[string]$AdminAddress
)    

function PowerOff-VM{
    param([string] $vm)

    Shutdown-VMGuest -VM (Get-VM $vm) -Confirm:$false | Out-Null
    Write-Host "Shutdown $vm"
    do {
        $status = (get-VM $vm).PowerState
    }until($status -eq "PoweredOff")
    return "OK"
}

function PowerOn-VM{
    param( [string] $vm)

    if($vm -eq ""){    Write-Host "Please enter a valild VM name"}

    if((Get-VM $vm).powerstate -eq "PoweredOn"){
        Write-Host "$vm is already powered on"}

    else{
        Start-VM -VM (Get-VM $vm) -Confirm:$false | Out-Null
        Write-Host "Starting $vm"
        do {
            $status = (Get-vm $vm | Get-View).Guest.ToolsRunningStatus
        }until($status -eq "guestToolsRunning")
        return "OK"
    }
}

function Change-VMMemory{
    param([string]$vmName, [int]$MemoryMB, [string]$Option)
    if($vmName -eq ""){
        Write-Host "Please enter a VM Name"
        return
    }
    if($MemoryMB -eq ""){
        Write-Host "Please enter an amount of Memory in MB"
        return
    }
    if($Option -eq ""){
        Write-Host "Please enter an option to add or remove memory"
        return
    }	
    $vm = Get-VM $vmName    
    $CurMemoryMB = ($vm).MemoryMB

    if($vm.Powerstate -eq "PoweredOn"){
        Write-Host "The VM must be Powered Off to continue"
        return
    }

    if($Option -eq "Add"){
        $NewMemoryMB = $CurMemoryMB + $MemoryMB
    }
    elseif($Option -eq "Remove"){
        if($MemoryMB -ge $CurMemoryMB){
            Write-Host "The amount of memory entered is greater or equal than 
            the current amount of memory allocated to this VM"
            return
        }
        $NewMemoryMB = $CurMemoryMB - $MemoryMB
    }

    $vm | Set-VM -MemoryMB $NewMemoryMB -Confirm:$false
    Write-Host "The new configured amount of memory is"(Get-VM $VM).MemoryMB
}

function Change-VMCPUCount{
    param([string]$vmName, [int]$NumCPU, [string]$Option)
    if($vmName -eq ""){
        Write-Host "Please enter a VM Name"
        return
    }
    if($NumCPU -eq ""){
        Write-Host "Please enter the number of vCPU's you want to add"
        return
    }
    if($Option -eq ""){
        Write-Host "Please enter an option to add or remove vCPU"
        return
    }

    $vm = Get-VM $vmName    
    $CurCPUCount = ($vm).NumCPU

    if($vm.Powerstate -eq "PoweredOn"){
        Write-Host "The VM must be Powered Off to continue"
        return
    }

    if($Option -eq "Add"){
        $NewvCPUCount = $CurCPUCount + $NumCPU
    }
    elseif($Option -eq "Remove"){
        if($NumCPU -ge $CurCPUCount){
            Write-Host "The number of vCPU's entered is higher or equal 
            than the current number of vCPU's allocated to this VM"
            return
        }
        $NewvCPUCount = $CurCPUCount - $NumCPU
    }

    $vm | Set-VM -NumCPU $NewvCPUCount -Confirm:$false
    Write-Host "The new configured number of vCPU's is"(Get-VM $VM).NumCPU
}

#######################################################################################
# Main script
#######################################################################################

$VIServer = Connect-VIServer $vCenter
If ($VIServer.IsConnected -ne $true){
    Write-Host "error connecting to $vCenter" -ForegroundColor Red
    exit
}

if($MemoryMB -or $CPUCount -ne "0"){
    foreach ($vm in get-brokerdesktop -DesktopGroupName $DesktopGroup -AdminAddress $AdminAddress -PowerState Off)
	{	
		$vmwvm = Get-VM -Name $vm.HostedMachineName
		if ($vmwvm.MemoryMB -lt 4000)
		{
        	if($MemoryMB -ne "0"){
	            if($MemoryOption -eq " ") {Write-Host "Please enter an option to add or remove memory"}
	            else
				{					
	                Change-VMMemory $vm.HostedMachineName $MemoryMB $MemoryOption					
	        	}    
			}
        }

        if($CPUCount -ne "0"){
            if($CPUOption -eq " ") {Write-Host "Please enter an option to add or remove cpu"}
            else{
                Change-VMCPUCount $vmName $CPUCount $CPUOption
            }
        }

    }
}

Disconnect-VIServer -Confirm:$false