Removing faulty Horizon desktops using PowerCLI

So last week there where a couple of posts on vmtn about people wanting to automatically removing or refreshing faulty Horizon desktops. With faulty I mean desktops in Agent Unreachable or in error state or whatever status are available. Since this was something i had been investigating anyway I decided to make a script for it that had separate menu’s for the status the desktop needs to be and to pick the desktop to be deleted. The latter part can be rebuild to do all those desktops at once  in case something breaks pretty badly during a recompose of the pool.

The largest part of the script is for creating the menu’s. Since the amount of returned desktops is variable and names differ it’s not possible to use a static menu. Instead I have used a menu structure created by Roman Gelman and that can be found inside this script on github. The part that gets things done i have listed below. The $spec array doesn’t need to be created but it is required in the API call to remove the desktop, Powershell assumes everything true by default when it’s empty but it just has to be called otherwise you will get a big fat red error. To remove multiple desktops at once machine_deletemachines needs to be used with an array filled with desktop id’s and $spec.

$spec = New-Object VMware.Hv.machinedeletespec
$spec.deleteFromDisk=$TRUE

[email protected]()
$desktops=get-hvmachine -state $targetstate
[email protected]()
foreach ($desktop in $desktops){
    $selectdesktop+= New-Object PSObject -Property @{"Name" = $desktop.base.name
    "ID" = $desktop.id;
    }
}

$selectdesktop=write-menu -menu ($desktops.base.name) -header "Select the desktop you want to remove"
$removedesktop=$desktops | where {$_.base.name -eq $selectdesktop}


try {
	$services1.machine.machine_delete($removedesktop.id, $spec)
	#$services1.machine.machine_reset($removedesktop.id, $spec)	
	write-host "$selectdesktop will be marked for deletion" -ForegroundColor Green
}
catch {
	write-host "Error deleting $selectdesktop" -ForegroundColor Red
}

As always the complete script can be found at Github where it will also be updated. This is how it looks in the end:

Update

After the comment below I decided to create the script to delete all desktops in a certain state. It’s a variation of the script above, just a bit shorter. Again it can be found on Github. Please be aware that due to a limitation in get-hvmachine both these scripts will only handle 1000 desktops at a time. It is safe to just repeat the script to do the rest.

https://github.com/Magneet/Various_Scripts/blob/master/remove_faulty_VDI_desktop.ps1

https://github.com/Magneet/Various_Scripts/blob/master/remove_multiple_faulty_VDI_desktops.ps1

Creating local ESXi user in a locked down situation and add it to exception list

So my customer asked for a solution to add local users on ESXi hosts that are in lockdown mode. A side quest was to add these to the lockdown exception list. The use case for this is app volumes, they want to be able to keep using them in case the vCenter server goes down. The trick to this that you need to talk to two different viserver entities. The vCenter server and the local ESXi host since you can add those users via vCenter.

Offcourse PowerCLI to the rescue! I decided to do everything in a try catch construction for some error handling and to give some visual output. These cab be stripped if you want but i like some feedback.

Some of the outtakes:

(get-vmhost $vmhost | get-view).ExitLockdownMode()
(get-vmhost $vmhost | get-view).EnterLockdownMode()

These two disable and enable the current lockdown mode, this is necessary before being able to create the local user.

Try {
$account = Get-VMHostAccount -server $vmhost.name -Id $accountName -ErrorAction Stop |
Set-VMHostAccount -server $vmhost.name -Password ([Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($accountPswd))) -Description $accountDescription 
}
Catch   {
$account = New-VMHostAccount -server $vmhost.name -Id $accountName -Password ([Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($accountPswd))) -Description $accountDescription -UserAccount -GrantShellAccess 
}

Some encryption stuff in here but that’s because I dislike having password visible as plain tekst. This first test if the account exists and then sets the password and description. If the user doesn’t exist it will create the user for you.

 $rootFolder = Get-Folder -server $vmhost.name -Name ha-folder-root
 New-VIPermission -server $vmhost.name -Entity $rootFolder -Principal $account -Role admin

This gives the newly created or edited user the admin role. If you want to use a custom role this could be added to the script, we decided to go for the admin role since app volumes needs an awful lot of rights anyway. In that case i would recommend to use a variable for role name and create it per host using new-VIrole

$HostAccessManager = Get-View -Server $vCenter $vmhost.ExtensionData.ConfigManager.HostAccessManager
$HostAccessManager.UpdateLockdownExceptions($accountName)

This simply adds the user to the lockdown exception list.

So now the complete script:

#-------------------------------------------------
# Create local ESXi user with admin rights and lockdown exception while the host is in lockdown mode
# 
# Requires PowerCLI 6.5 or higher (module based not snappin)
# Based on scripts by Luc Dekens and others
#
# Version 1.0
# 09-10-2017
# Created by: Wouter Kursten
#
#-------------------------------------------------
#
# Load the required VMware modules (for PowerShell only)

Write-Host "Loading VMware PowerCLI Modules" -ForegroundColor Green
try	{
    get-module -listavailable vm* | import-module -erroraction stop
}
catch	{
    write-host "No Powercli found" -ForegroundColor Red
}

#Ask for connection information

$vcenter=Read-Host "Enter vCenter server name"
$Target = Read-Host "Which hosts? (i.e. server*)"
$rootpassword = Read-Host "Enter root Password" -AsSecureString
$accountName = $userPassword = Read-Host "Enter New Username"
$accountDescription = $userPassword = Read-Host "Enter New User description"
$accountPswd = Read-Host "Enter New User Password" -AsSecureString
$rootuser="root"

# Connect to vCenter
$connectedvCenter = $global:DefaultVIServer

if($connectedvCenter.name -ne $vcenter){
	Connect-VIServer $vCenter -wa 0 | Out-Null
	Write-Host "Connected"
	Write-Host " "
}

# Get the host inventory from vCenter
$vmhosts = Get-VMHost $Target | Sort Name

foreach($vmhost in $vmhosts){
    try {
        (get-vmhost $vmhost | get-view).ExitLockdownMode()
        write-host "Lockdown disabled for $vmhost" -foregroundcolor green
    }
    catch   {
        write-host "can't disable lockdown for $vmhost maybe it's already disabled" -foregroundcolor Red
    }

    connect-viserver -server $vmhost -user $rootuser -password ([Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($rootpassword))) -wa 0 -notdefault | Out-Null

    Try {
        $account = Get-VMHostAccount -server $vmhost.name -Id $accountName -ErrorAction Stop |
        Set-VMHostAccount -server $vmhost.name -Password ([Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($accountPswd))) -Description $accountDescription 
    }
    Catch   {
        $account = New-VMHostAccount -server $vmhost.name -Id $accountName -Password ([Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($accountPswd))) -Description $accountDescription -UserAccount -GrantShellAccess 
    }
    
    $rootFolder = Get-Folder -server $vmhost.name -Name ha-folder-root
    New-VIPermission -server $vmhost.name -Entity $rootFolder -Principal $account -Role admin

    #Adding the new user to the Lockdown Exceptions list
    $HostAccessManager = Get-View -Server $vCenter $vmhost.ExtensionData.ConfigManager.HostAccessManager
    $HostAccessManager.UpdateLockdownExceptions($accountName)
     
      
    Disconnect-VIServer $vmhost.name -Confirm:$false  
    try {	
        (get-vmhost $vmhost | get-view).EnterLockdownMode()
        write-host "Lockdown enabled for $vmhost" -foregroundcolor green
    }
    catch   {
        write-host "can't disable lockdown for $vmhost maybe it's already Enabled?" -foregroundcolor Red}
    }
}

    Disconnect-VIServer -Confirm:$false

Future versions of this script will not be edited on here so always check the latest version on github.

 

Horizon view vCheck : Pool Overview plugin

So one of the things still missing in the Horizon View vCheck was a plugin that simply gives an overview of all Pools with their status. In short what I am talking about is a translation from this view:

Although this sounds easy there where a lot of challenges for this one. First of all there are three separate pool types: Automated,Manual and RDS and all of them have subtypes like VIEW_COMPOSER,VIRTUAL_CENTER,FULL_CLONES,INSTANT_CLONE_ENGINE,UNMANAGED or RDS and not all of these subtypes are available for all pool types. This gives a lot of options that need to be separated for the pool types. Also the VIRTUAL_CENTER subtype is used for both manually added desktops that reside on a vSphere environment and for an automatic pool creating full clones. The FULL_CLONES subtype I haven’t been able to create in my lab yet.

Further outputs like true, false or any of the subtypes above weren’t clear enough for me to use as output. For this I learned a new trick in my book called switch.

switch ($source)
		{
			VIRTUAL_CENTER {$sourceoutput="vCenter Managed Desktop"}
			FULL_CLONES {$sourceoutput="Full Clones"}
			VIEW_COMPOSER {$sourceoutput="Linked Clones"}
			INSTANT_CLONE_ENGINE {$sourceoutput="Instant Clones"}
			UNMANAGED {$sourceoutput="Non-vCenter Desktops"}
			RDS {$sourceoutput="RDS Desktops"}
			{$_ -eq "VIRTUAL_CENTER" -AND $pool.type -eq "Automated"} {$sourceoutput="Full Clones"}
			{$_ -eq "VIRTUAL_CENTER" -AND $pool.type -eq "MANUAL"} {$sourceoutput="Manually Added vCenter Managed Desktops"}
			default {$sourceoutput="No Source data available"}
		}

Some documentation for the switch command can be found here but what it in short does is match the variable u use as input and sets or gives some output based on that. Also it can do a comparison as in above example so I was able to distinguish between Full Clones and Manually Added vCenter Managed Desktops. One thing to be aware of is that it will go trough the complete list. At first I had the two lines with the comparison in it at the top but that got overwritten since below it VIRTUAL_CENTER was recognized and the $sourceoutput would be based on that.

The Automated and Manual pools use a very similar set of code, the biggest difference is that one gets the data from the AutomatedDesktopData propertywhile the other gets it from the manualdesktopdata property.

	if ($pool.type -eq "Automated"){
		$Automaticassignment=$pool.AutomatedDesktopData.UserAssignment.AutomaticAssignment
		switch ($Automaticassignment)
		{
			$TRUE {$Automaticassignmentoutput="Automatic"}
			$FALSE {$Automaticassignmentoutput="Manual"}
			default {$Automaticassignmentoutput="No Assignment Status Available"}
		}
		$Pooloverview+=New-Object PSObject -Property @{"Name" = $pool.base.name;
			"Displayname" = $pool.base.DisplayName;
			"Description" = $pool.base.Description;
			"Status" = $poolstatusoutput;
			"Provisioning" = $ProvisioningStatusoutput;
			"Type" = $pool.type;
			"Source" = $sourceoutput;
			"User_Assignment" = $pool.AutomatedDesktopData.UserAssignment.userassignment;
			"Assignment_Type" = $Automaticassignmentoutput;
			}
		}
	elseif ($pool.type -eq "MANUAL"){
		$Automaticassignment= $pool.manualdesktopdata.UserAssignment.AutomaticAssignment
		switch ($Automaticassignment)
		{
			$TRUE {$Automaticassignmentoutput="Automatic"}
			$FALSE {$Automaticassignmentoutput="Manual"}
			default {$Automaticassignmentoutput="No Assignment Status Available"}
		}
		$Pooloverview+=New-Object PSObject -Property @{"Name" = $pool.base.name;
		"Displayname" = $pool.base.DisplayName;
		"Description" = $pool.base.Description;
		"Status" = $poolstatusoutput;
		"Provisioning" = $ProvisioningStatusoutput;
		"Type" = $pool.type;
		"Source" = $sourceoutput;
		"User_Assignment" = $pool.manualdesktopdata.UserAssignment.UserAssignment;
		"Assignment_Type" = $Automaticassignmentoutput;
			}
		}

The RDS block gives a totally different view though. The information had to be pulled from the farms that are the backend for the desktops.

	elseif ($pool.type -eq "RDS"){
		$source=($services1.farm.farm_get($pool.rdsdesktopdata.farm)).source
		$ProvisioningStatus=($services1.farm.farm_get($pool.rdsdesktopdata.farm)).automatedfarmdata.VirtualCenterProvisioningSettings.enableprovisioning
		switch ($source)
		{
			VIEW_COMPOSER {$sourceoutput="Linked Clones RDS Hosts"}
			INSTANT_CLONE_ENGINE {$sourceoutput="Instant Clones RDS Hosts"}
			default {$sourceoutput="Manually Added RDS Hosts"}
		}

		switch ($ProvisioningStatus)
		{
			$True {$ProvisioningStatusoutput="Enabled"}
			$False {$ProvisioningStatusoutput="Disabled"}
			default {$ProvisioningStatusoutput="N/A"}
		}

		$Pooloverview+=New-Object PSObject -Property @{"Name" = $pool.base.name;
		"Displayname" = $pool.base.DisplayName;
		"Description" = $pool.base.Description;
		"Status" = $poolstatusoutput;
		"Provisioning" = $ProvisioningStatusoutput;
		"Type" = ($services1.farm.farm_get($pool.rdsdesktopdata.farm)).type;
		"Source" = $sourceoutput;
		"User_Assignment" = "N/A";
		"Assignment_Type" = "N/A";
			}
		}

And when done I ended up with the following script. As usual it might get some improvements or I need to squash some bug so better check the latest version on Github.

# Start of Settings
# End of Settings

[email protected]()
foreach ($pool in $pools){
	$poolstatus=$pool.DesktopSettings.Enabled 
	$ProvisioningStatus=$pool.AutomatedDesktopData.VirtualCenterProvisioningSettings.enableprovisioning
	$source=$pool.source
	switch ($poolstatus)
		{
			$True {$poolstatusoutput="Enabled"}
			$False {$poolstatusoutput="Disabled"}
			default {$poolstatusoutput="No Pool Status available"}
		}

	switch ($ProvisioningStatus)
		{
			$True {$ProvisioningStatusoutput="Enabled"}
			$False {$ProvisioningStatusoutput="Disabled"}
			default {$ProvisioningStatusoutput="No Pool Provisioning Status available"}
		}

	switch ($source)
		{
			VIRTUAL_CENTER {$sourceoutput="vCenter Managed Desktop"}
			FULL_CLONES {$sourceoutput="Full Clones"}
			VIEW_COMPOSER {$sourceoutput="Linked Clones"}
			INSTANT_CLONE_ENGINE {$sourceoutput="Instant Clones"}
			UNMANAGED {$sourceoutput="Non-vCenter Desktops"}
			RDS {$sourceoutput="RDS Desktops"}
			{$_ -eq "VIRTUAL_CENTER" -AND $pool.type -eq "Automated"} {$sourceoutput="Full Clones"}
			{$_ -eq "VIRTUAL_CENTER" -AND $pool.type -eq "MANUAL"} {$sourceoutput="Manually Added vCenter Managed Desktops"}
			default {$sourceoutput="No Source data available"}
		}

	if ($pool.type -eq "Automated"){
		$Automaticassignment=$pool.AutomatedDesktopData.UserAssignment.AutomaticAssignment
		switch ($Automaticassignment)
		{
			$TRUE {$Automaticassignmentoutput="Automatic"}
			$FALSE {$Automaticassignmentoutput="Manual"}
			default {$Automaticassignmentoutput="No Assignment Status Available"}
		}
		$Pooloverview+=New-Object PSObject -Property @{"Name" = $pool.base.name;
			"Displayname" = $pool.base.DisplayName;
			"Description" = $pool.base.Description;
			"Status" = $poolstatusoutput;
			"Provisioning" = $ProvisioningStatusoutput;
			"Type" = $pool.type;
			"Source" = $sourceoutput;
			"User_Assignment" = $pool.AutomatedDesktopData.UserAssignment.userassignment;
			"Assignment_Type" = $Automaticassignmentoutput;
			}
		}

	elseif ($pool.type -eq "MANUAL"){
		$Automaticassignment= $pool.manualdesktopdata.UserAssignment.AutomaticAssignment
		switch ($Automaticassignment)
		{
			$TRUE {$Automaticassignmentoutput="Automatic"}
			$FALSE {$Automaticassignmentoutput="Manual"}
			default {$Automaticassignmentoutput="No Assignment Status Available"}
		}
		$Pooloverview+=New-Object PSObject -Property @{"Name" = $pool.base.name;
		"Displayname" = $pool.base.DisplayName;
		"Description" = $pool.base.Description;
		"Status" = $poolstatusoutput;
		"Provisioning" = $ProvisioningStatusoutput;
		"Type" = $pool.type;
		"Source" = $sourceoutput;
		"User_Assignment" = $pool.manualdesktopdata.UserAssignment.UserAssignment;
		"Assignment_Type" = $Automaticassignmentoutput;
			}
		}	

	elseif ($pool.type -eq "RDS"){
		$source=($services1.farm.farm_get($pool.rdsdesktopdata.farm)).source
		$ProvisioningStatus=($services1.farm.farm_get($pool.rdsdesktopdata.farm)).automatedfarmdata.VirtualCenterProvisioningSettings.enableprovisioning
		switch ($source)
		{
			VIEW_COMPOSER {$sourceoutput="Linked Clones RDS Hosts"}
			INSTANT_CLONE_ENGINE {$sourceoutput="Instant Clones RDS Hosts"}
			default {$sourceoutput="Manually Added RDS Hosts"}
		}

		switch ($ProvisioningStatus)
		{
			$True {$ProvisioningStatusoutput="Enabled"}
			$False {$ProvisioningStatusoutput="Disabled"}
			default {$ProvisioningStatusoutput="N/A"}
		}

		$Pooloverview+=New-Object PSObject -Property @{"Name" = $pool.base.name;
		"Displayname" = $pool.base.DisplayName;
		"Description" = $pool.base.Description;
		"Status" = $poolstatusoutput;
		"Provisioning" = $ProvisioningStatusoutput;
		"Type" = ($services1.farm.farm_get($pool.rdsdesktopdata.farm)).type;
		"Source" = $sourceoutput;
		"User_Assignment" = "N/A";
		"Assignment_Type" = "N/A";
			}
		}
}

$Pooloverview | select Name,Displayname,Description,Status,Provisioning,Type,Source,User_Assignment,Assignment_Type
$Title = "Overview of all Pools"
$Header = "Overview of all Pools"
$Comments = "Gives an overview of the general status of all pools"
$Display = "Table"
$Author = "Wouter Kursten"
$PluginVersion = 0.1
$PluginCategory = "View"

And a screenshot of the result:

VMworld EU 2017 Day 3

Day three, the last one, was a short day for me this year. I had an appointment with the VMware Design studio at 8am but the gates didn’t open until 8 as well and I had to drop my suitcase first so I rescheduled it to 9.15 while talking to the guy that I would have the session with! After this I decided to go to the vmtn area to finish up my powerpoint for the vBrownbag I would be doing by noon. I kept changing and changing stuf and somehow managed to remove the one slide you need when generating output: the output itself. Also during the presentation I never got into a good flow so I wasn’t happy with the end result. After this there where some rumors about horrible queues at the airport so I scrambled to get there but in the end the line to drop off my suitcase took longer then security.

VMworld EU Hackaton join me to have some View vCheck fun!

So for a long time I have been looking forward to the VMworld EU Hackaton. The announcement has now been made and it will take place on Monday evening the 11th from 6pm to officially 10pm but my guess is that it might run a little longer 😉 It will be held at the Valkiria Hub Space and you can join as a member of mine or another team or as spectator, just schedule the right session in the schedule builder.

My Team

So I have signed up to lead a team to improve and expand the Horizon View vCheck I have been building. Since this probably also wil involve the Horizon View Community module I wouldn’t be too surprised if improvements will be made to that as well.

Who am I looking for?

For my team i don’t care how much experience you have with PowerCLI or Horizon view but it might be handy if you at least have some idea when we talk about it. Just bring your laptop and if we end up having learned something by the end of the night it has been a success for me!

Do I need something installed on my laptop?

Yes, make sure PowerCLI (at least 6.5) has been installed and your favorite script editor. I will prepare the connection brokers, composers and other vm’s on my laptop.

Anything else?

Just have fun!

Pulling Horizon View events with PowerCLI in a nicer way

Update: There is a new way to pull the event information without having to enter the sql password please see this post about it.

 

So in one of the first posts I did about Powercli 6.5 was a bit about pulling events from the Horizon View events database. This off course was not the crude way to pull the events. Another was was using the event dumper fling but that one is a bit to slow for my taste. I decided to create a script that leveraged and had the speed of PowerCli but actually gave some options and that dumped the results in a .csv file since reading them from the command line will be too much at some point.

What I ended up with is a 71 line script where 18 are informational, 8 blanks, 1 that pulls the information and the rest for the menus and error handling. I decided on not giving any real errors when things don’t work but just red text. A lot is done with try <> catch and a if elseif else.

There is no need to load any modules but the scripts assumes at least PowerCLI 6.5 with the vmware.hv.helper module loaded.

I have created a github repo for these smaller scripts over here.

#-------------------------------------------------
# Get the Horizon view event for the last x time and export to a csv
#
# Requires PowerCLI 6.5 or higher
# Requires vmware.hv.helper module
# Module can be found at https://github.com/vmware/PowerCLI-Example-Scripts
#
# Version 1.0
# 16-06-2017
# Created by: Wouter Kursten
#
#-------------------------------------------------

# Load the required VMware modules (for PowerShell only)

Write-Host "Loading VMware PowerCLI Modules" -ForegroundColor Green
try	{
	get-module -listavailable vm* | import-module -erroraction stop
	}
catch	{
	write-host "No Powercli 6.5 or higher found" -ForegroundColor Red
		}
$version=get-powercliversion -WarningAction silentlyContinue
if ($version.build -lt 4624819)	{
	write-host "Horizon View api's require Powercli 6.5 or higher to function, please upgrade PowerCLI" -ForegroundColor Red
	exit
	}
elseif (get-module vmware.hv.helper  ) {
	write-host "VMware.hv.helper found"
	}
else {
	write-host "Please download and install the VMware.hv.helper module from https://github.com/vmware/PowerCLI-Example-Scripts" -ForegroundColor Red
	exit
	}

#Ask for connection information

$hvservername=Read-host "Welke Connection broker?"
$domain=read-host "Please enter your active directory domain?"
$username=Read-host "Please enter your useraccount"
$password=Read-host -assecurestring "Please enter your password"

#Connect to View Connection broker
write-host "Connecting to the connection broker" -ForegroundColor Green
try {
$hvserver=connect-hvserver $hvservername -domain $domain -username $username -password $password -WarningAction silentlyContinue -erroraction stop
}
catch {
	Write-host "Can't connect to the Connection server please check the credentials." -ForegroundColor Red
	exit
	}

#connect to the Event Database

$dbpassword=Read-host -assecurestring "Please enter the password of the account configured in Horizon View to access the event database."
write-host "Connecting to the database" -ForegroundColor Green
try {
	$eventdb=connect-hvevent -dbpassword $dbpassword -erroraction stop
	}
catch {
	Write-host "Can't connect to the Database server please check the password." -ForegroundColor Red
	exit
	}

#Retreive information

write-host "Please provide the following information use % as wildcard." -ForegroundColor Green
$searchuser=Read-Host "Please enter the accountname you need information on?"
$module=Read-Host "What module do you want the logs for? (Agent,Broker,Client,Tunnel,Framework,Client)"
$sevfilter=Read-Host "What is the severity level you need information on?(Audit_fail, Audit_Success,Info,Warning,Error)"
$message=Read-host "Looking for any specific text in the message?"
$maxage=Read-Host "How far do you want to look back in event history? (Day,week,month,all)"
$filelocation=Read-host "Please provide filename and location for the exported csv file"

#Export to file

$lastevent=get-hvevent -hvdbserver $eventdb -timeperiod $maxage -SeverityFilter $sevfilter -userfilter $searchuser -modulefilter $module -messagefilter $message

try {
	$lastevent.events  | export-csv $filelocation -erroraction stop
	}
catch{
	write-host "Unable to create the file, please check name and location" -ForegroundColor Red
	exit
	}
Write-host "Events have been successfully exported." -ForegroundColor Green

And how does this look in the end??

Building a Horizon View vCheck (part 3)

So it’s time for part 3 already of building checks for Horizon View. I got some remarks after last post and thus I need to say that the checks have been created for View 7 because some commands might not work against a 6.* installation. three new plugins this time or actually two since one was already in the original uploads on github but I didn’t mention it on here yet.

11 Linked Clone Desktop Pool Information.ps166

Just like the full clone pool information but tailored for linked pools.

# Start of Settings
# End of Settings

[email protected]()
foreach ($pool in $pools){
$poolname=$pool.base.name
if ($pool.type -like "*automated*" -AND $pool.source -like "*VIEW_COMPOSER*"){
$desktops=get-hvmachinesummary -pool $poolname
$automatedpoolstatus+=New-Object PSObject -Property @{"Name" = $Poolname;
								"Pool_Image" = $pool.automateddesktopdata.VirtualCenternamesdata.parentvmpath;
								"Pool_Snapshot" = $pool.automateddesktopdata.VirtualCenternamesdata.snapshotpath;
								"Desktop_Count" = ($desktops).count;
								"Available" = ($desktops | where {$_.base.basicstate -eq "AVAILABLE"}).count;
								"Connected" = ($desktops | where {$_.base.basicstate -eq "CONNECTED"}).count;
								"Disconnected" = ($desktops | where {$_.base.basicstate -eq "DISCONNECTED"}).count;
								"Maintenance" = ($desktops | where {$_.base.basicstate -eq "MAINTENANCE"}).count;
								"Provisioning" = ($desktops | where {$_.base.basicstate -eq "PROVISIONING"}).count;
								"Customizing" = ($desktops | where {$_.base.basicstate -eq "CUSTOMIZING"}).count;
								"Already_Used" = ($desktops | where {$_.base.basicstate -eq "ALREADY_USED"}).count;
								"Agent_Unreachable" = ($desktops | where {$_.base.basicstate -eq "AGENT_UNREACHABLE"}).count;
								"Error" = ($desktops | where {$_.base.basicstate -eq "ERROR"}).count;
								"Deleting" = ($desktops | where {$_.base.basicstate -eq "DELETING"}).count;
								"Provisioning_Error" = ($desktops | where {$_.base.basicstate -eq "PROVISIONING_ERROR"}).count;
}
}
}
$automatedpoolstatus | select Name,Pool_Image,Pool_Snapshot,Desktop_Count,Available,Connected,Disconnected,Maintenance,Provisioning,Customizing,Already_Used,Agent_Unreachable,Error,Deleting,Provisioning_Error
$Title = "Linked Clone Desktop Pool Status"
$Header = "Linked Clone Desktop Pool Status"
$Comments = "These are the pools that have floating linked clones. Not all but the most common status's are counted."
$Display = "Table"
$Author = "Wouter Kursten"
$PluginVersion = 0.1
$PluginCategory = "View"

13 Dedicated Full Clones Assignment.ps1

This plugin is targeted at the dedicated full clones (and I just realize that one can also have dedicated linked clones so will need to build one for that as well). It gives information about which desktop is assigned to which account but also with information about the host it is running on if this information is available (not in my case).

# Start of Settings
# End of Settings

[email protected]()
foreach ($pool in $pools){
$poolname=$pool.base.name
if ($pool.type -like "*automated*" -AND $pool.source -like "*VIRTUAL_CENTER*"){
	$desktops=get-hvmachinesummary -pool $poolname
	foreach ($desktop in $desktops){
	if ($desktop.namesdata.username){
		$username=$desktop.namesdata.username
		}
	else{
		$username="Unassigned"
		}
$fulldesktopassignment+=New-Object PSObject -Property @{"Pool_Name" = $Poolname;
								"Desktop_Name" = $desktop.base.name;
								"Desktop_State" = $desktop.base.basicstate;
								"Desktop_Assigned_to" = $username;
								"Desktop_OperatingSystem" = $desktop.base.Operatingsystem;
								"Agent_version" = $desktop.base.agentversion;
								"Host" = $desktop.managedmachinesdata.hostname;
								"Datastore" = $desktop.ManagedMachineNamesData.datastorepaths | out-string;
}
}
}
}
$fulldesktopassignment | select Pool_Name,Desktop_Name,Desktop_State,Desktop_Assigned_to,Desktop_OperatingSystem,Agent_version,Host,Datastore
$Title = "Dedicated Desktop Pool Assignment"
$Header = "Dedicated Desktop Pool Assignment"
$Comments = "These are the dedicated desktops with their current user assignment"
$Display = "Table"
$Author = "Wouter Kursten"
$PluginVersion = 0.1
$PluginCategory = "View"

04 License Status.ps1

This plugin gives licensing information including expiration date and what techniques are allowed under this license.

# Start of Settings
# End of Settings

[email protected]()

$license=($services1).license.license_get()
$licensestatus+=New-Object PSObject -Property @{"Licensed" = $license.Licensed;
								"LicenseKey" = $license.LicenseKey;
								"ExpirationTime" = $license.ExpirationTime;
								"ViewComposerEnabled" = $license.ViewComposerEnabled;
								"DesktopLaunchingEnabled" = $license.DesktopLaunchingEnabled;
								"ApplicationLaunchingEnabled" = $license.ApplicationLaunchingEnabled;
								"InstantCloneEnabled" = $license.InstantCloneEnabled;
								"UsageModel" = $license.UsageModel;
}								

$licensestatus | select Licensed,LicenseKey,ExpirationTime,ViewComposerEnabled,DesktopLaunchingEnabled,ApplicationLaunchingEnabled,InstantCloneEnabled,UsageModel

$Title = "License Status"
$Header = "License Status"
$Comments = "This is the license status information"
$Display = "Table"
$Author = "Wouter Kursten"
$PluginVersion = 0.1
$PluginCategory = "View"

Building a Horizon View vCheck (part 2)

So last time I created some simple scripts for the Horizon View vCheck. This time I wanted to add some information about the Composer, Connection, security servers and the event database. So all in all I added four scripts that might seem to do the same but since the api’s treat the types of servers differently I decided to make separate scripts as well.

Please pull the scripts from Github since by the time you read this post things might have changed.

05 Connection Servers Status.ps1

This plugin pulls some information about the connection servers, if the status is ok and gives a warning if the Certificate will expire within 30 days. This period is something I need to change in the future to a setting so it will be customizable.

# Start of Settings
# End of Settings

$date=get-date
$datemaxexp=(get-date).adddays(30)
[email protected]()
$conservers=$services1.connectionserverhealth.connectionserverhealth_list()
foreach ($conserver in $conservers) {
if ($conserver.CertificateHealth.ExpirationTime -lt $date){
$expiring="Already Expired"
}
elseif ($conserver.CertificateHealth.ExpirationTime -lt $datemaxexp){
$expiring="Expiring in 30 days"
}
else {
$expiring="False"
}

$conserverstatus+=New-Object PSObject -Property @{"Name" = $conserver.name;
								"Status" = $conserver.Status;
								"Version" = $conserver.Version;
								"Build" = $conserver.Build
								"Certificate_Status" = $conserver.CertificateHealth.Valid;
								"Certificate_Expiration_Time" = $conserver.CertificateHealth.ExpirationTime;
								"Certificate_Expiring" = $expiring;
								"Certificate_Invalidation_Reason" = $conserver.CertificateHealth.InValidReason;
								
}
}
$conserverstatus | select name,Status,Version,Build,Certificate_Status,Certificate_Expiring,Certificate_Expiration_Time,Certificate_Invalidation_Reason 

$Title = "Connection Servers Status"
$Header = "Connection Servers Status"
$Comments = "These are the used Connection Servers"
$Display = "Table"
$Author = "Wouter Kursten"
$PluginVersion = 0.1
$PluginCategory = "View"

06 Security Servers Status.ps1

Almost the same as the connection servers but this time it checks for the security servers. I don’t run the appliance yet so will need to check on those somewhere as well.

# Start of Settings
# End of Settings

$date=get-date
$datemaxexp=(get-date).adddays(30)
[email protected]()
$secservers=$services1.securityserverhealth.securityserverhealth_list()
foreach ($secserver in $secservers) {
if ($secserver.CertificateHealth.ExpirationTime -lt $date){
$expiring="Already Expired"
}
elseif ($secserver.CertificateHealth.ExpirationTime -lt $datemaxexp){
$expiring="Expiring in 30 days"
}
else {
$expiring="False"
}

$secserverstatus+=New-Object PSObject -Property @{"Name" = $secserver.name;
								"Status" = $secserver.Status;
								"Version" = $secserver.Version;
								"Build" = $secserver.Build
								"Certificate_Status" = $secserver.CertificateHealth.Valid;
								"Certificate_Expiration_Time" = $secserver.CertificateHealth.ExpirationTime;
								"Certificate_Expiring" = $expiring;
								"Certificate_Invalidation_Reason" = $secserver.CertificateHealth.InValidReason;
								
}
}
$secserverstatus | select name,Status,Version,Build,Certificate_Status,Certificate_Expiring,Certificate_Expiration_Time,Certificate_Invalidation_Reason 

$Title = "Security Servers Status"
$Header = "Security Servers Status"
$Comments = "These are the used Security Servers"
$Display = "Table"
$Author = "Wouter Kursten"
$PluginVersion = 0.1
$PluginCategory = "View"

07 Composer Servers Status.ps1

Strangely enough the composer server doesn’t have a status in the api’s. I would have expected at least something for it being available or not. Because of this I only added information and the vCenter server it is connecting to. Another weird thing (might be me offcourse) is that the vcentername actually gives a plural output even though composer and vCenter have a 1on1 relation,

# Start of Settings
# End of Settings

[email protected]()
$comservers=$services1.viewcomposerhealth.viewcomposerhealth_list()
foreach ($comserver in $comservers) {
$vcenters=$comserver.data.virtualcenters

foreach ($vcenter in $vcenters){
if ($vcenternames){
$vcenternames+=","
$vcenternames+=($services1.virtualcenterhealth.virtualcenterhealth_get($vcenter)).data.name
}
else{
$vcenternames+=($services1.virtualcenterhealth.virtualcenterhealth_get($vcenter)).data.name
}
}
$comserverstatus+=New-Object PSObject -Property @{"Name" = $comserver.ServerName;
								"Version" = $comserver.Data.Version;
								"Build" = $comserver.Data.Build;
								"vCenter_Server"= $vcenternames
								
}
}
$comserverstatus | select name,Version,Build,vcenter_server

$Title = "Composer Servers Status"
$Header = "Composer Servers Status"
$Comments = "These are the used Composer Servers"
$Display = "Table"
$Author = "Wouter Kursten"
$PluginVersion = 0.1
$PluginCategory = "View"

08 Event Database Status.ps1

For the event database I decided to give as much information as possible from the api’s. Maybe in the future it could pull some information from the sql server itself but I think that should be covered by a sql check.

# Start of Settings
# End of Settings


[email protected]()
$eventdb=$services1.EventDatabaseHealth.EventDatabaseHealth_get()
if ($eventdb.configured -eq $True){
$eventdbstatus+=New-Object PSObject -Property @{"Servername" = $eventdb.data.Servername;
								"Port" = $eventdb.data.Port;
								"Status" = $eventdb.data.State;
								"Username" = $eventdb.data.Username;
								"DatabaseName" = $eventdb.data.DatabaseName
								"TablePrefix" = $eventdb.data.TablePrefix;
								"State" = $eventdb.data.State;
								"Error" = $eventdb.data.Error;
}
}
$eventdbstatus | select Servername,Port,Status,Username,DatabaseName,TablePrefix,State,Error 

$Title = "Event Database Status"
$Header = "Event Database Status"
$Comments = "These are the settings for the Event Database"
$Display = "Table"
$Author = "Wouter Kursten"
$PluginVersion = 0.1
$PluginCategory = "View"

The result

Building a Horizon View vCheck with those nice api’s (part 1 of ??)

Intro

Ever since I saw Alan Renouf’s vCheck script first in action years ago it has been one of the tools I have been promoting to use for daily checks. The fact that you can disable and enable plugins makes it a flexible adjustable solution that helped me preventing companies having big problems or proving that I have been warning them about things for weeks or months. Also I have whipped many colleague or customer around the ears with questions why they didn’t remove those snapshot they created 3 days before

Getting started

Fast forward until a couple of months ago when I saw those release note’s for PowerCli 6.5 with more options to talk to the Horizon View api’s. This immediately gave me the idea to build a set of vCheck scripts for Horizon View. One of the first things to do was finding out how the vCheck framework actually works. This turned out to be a matter of outputting the info you would like in the output as if it is on the command line. Also adding a section that contains a description helps in building the output:

$Title = "Composer Servers Status"
$Header = "Composer Servers Status"
$Comments = "These are the used Composer Servers"
$Display = "Table"
$Author = "Wouter Kursten"
$PluginVersion = 0.1
$PluginCategory = "View"

The 2nd thing to do is deciding on what checks needed to be build. After checking on the vExpert slack and with some co-workers and friends I came up with a shortlist:

  • Dashboard error status (Sean Massey)
  • Desktops with error (non-standard) status (Myself,Sean Massey)
  • Compare the Snapshots that have been set to the ones actually used on desktops to see if recompose might not have run (Brian Suhr, myself)
  • relation between Composer and vCenter (Kevin Leclaire)
  • last use time for dedicated desktops (Kees Baggeman)
  • Event Database status
  • Connection,composer,security server status
  • Information and status about the various desktop pool types
  • RDS farm status

Getting things done

Before actually building any checks a connecton has to be made this is done in the Connection plugin:

$Title = "Connection settings for View"
$Author = "Wouter Kursten"
$PluginVersion = 0.1
$Header = "Connection Settings"
$Comments = "Connection Plugin for connecting to View"
$Display = "None"
$PluginCategory = "View"

# Start of Settings
# Please Specify the address of one of the connection servers or the address for the general View environment
$Server = "Servername"
# Maximum number of samples to gather for events
$MaxSampleVIEvent = 100000
# Please give the user account to connect to Connection Server
$hvcsUser= "username"															
# Please give the domain for the user to connect to Connection Server
$hvcsDomain = "domain"														

# End of Settings



# Credential file for the user to connect to the Connection Server
$hvcsPassword = get-content .\hvcs_Credentials.txt | convertto-securestring		
# Credential file for the User configured n View to connect to the Database
$hvedbpassword=get-content .\hvedb_Credentials.txt | convertto-securestring   	

# Loading 
Import-Module VMware.VimAutomation.HorizonView
Import-Module VMware.VimAutomation.Core

# --- Connect to Horizon Connection Server API Service ---
$hvServer1 = Connect-HVServer -Server $server -User $hvcsUser -Password $hvcsPassword -Domain $hvcsDomain

# --- Get Services for interacting with the View API Service ---
$Services1= $hvServer1.ExtensionData

# --- Connect to the view events database ---
#$eventdb=connect-hvevent -dbpassword $hvedbpassword

# --- Get Desktop pools
$pools=(get-hvpool)

As you might notice the vmware.hv.helper plugin is required to do this.

The first real check I decided to build was to see if the desktops are actually build on the same snapshot as configured on pool level. With this you are able to see if a recompose ran into trouble. Let me highlight some of the code:

if ($pool.type -like "*automated*" -AND $pool.source -like "*VIEW_COMPOSER*"){

There are a couple of pooltypes and one of them is automated, since we’re looking for linked clones we also need to make sure the pool source is VIEW_COMPOSER if this says VIRTUAL_CENTER you’re looking at full clones.

$wrongsnaps=$poolmachines | where {$_.managedmachinedata.viewcomposerdata.baseimagesnapshotpath -notlike  $pool.automateddesktopdata.VirtualCenternamesdata.snapshotpath -OR $_.managedmachinedata.viewcomposerdata.baseimagesnapshotpath -notlike $pool.automateddesktopdata.VirtualCenternamesdata.snapshotpath}

I could have shortened this one by defining a couple of variables but this gives an impression of how deep you might have to go to get the required data. WHat I do is check if the snapshot has the same name AND if the selected source VM has the same name if either of the two is different the vm wil be entered on the output.

$wrongsnapdesktops+= New-Object PSObject -Property @{"VM Name" = $wrongsnap.base.name;
								"VM Snapshot" = $wrongsnap.managedmachinedata.viewcomposerdata.baseimagesnapshotpath;
								"VM GI" = $wrongsnap.managedmachinedata.viewcomposerdata.baseimagepath;
								"Pool Snapshot" = $pool.automateddesktopdata.VirtualCenternamesdata.snapshotpath;
								"Pool GI" = $pool.automateddesktopdata.VirtualCenternamesdata.parentvmpath;

Last of the real code is about displaying the actual info for the desktop.

This all results in the following plugin, be aware that this might be a bit slow to run since it needs go go trough all desktops. For my customer it takes about 3 minutes on 1350 desktops.

# Start of Settings
# End of Settings


[email protected]()
foreach ($pool in $pools){
$poolname=$pool.base.name
if ($pool.type -like "*automated*" -AND $pool.source -like "*VIEW_COMPOSER*"){

$poolmachines=get-hvmachine ($pool.base.name)
$wrongsnaps=$poolmachines | where {$_.managedmachinedata.viewcomposerdata.baseimagesnapshotpath -notlike  $pool.automateddesktopdata.VirtualCenternamesdata.snapshotpath -OR $_.managedmachinedata.viewcomposerdata.baseimagesnapshotpath -notlike $pool.automateddesktopdata.VirtualCenternamesdata.snapshotpath}
foreach ($wrongsnap in $wrongsnaps){
$wrongsnapdesktops+= New-Object PSObject -Property @{"VM Name" = $wrongsnap.base.name;
								"VM Snapshot" = $wrongsnap.managedmachinedata.viewcomposerdata.baseimagesnapshotpath;
								"VM GI" = $wrongsnap.managedmachinedata.viewcomposerdata.baseimagepath;
								"Pool Snapshot" = $pool.automateddesktopdata.VirtualCenternamesdata.snapshotpath;
								"Pool GI" = $pool.automateddesktopdata.VirtualCenternamesdata.parentvmpath;
}
}
}
}
$wrongsnapdesktops

$Title = "VDI Desktops based on wrong snapshot"
$Header = "VDI Desktops based on wrong snapshot"
$Comments = "These desktops have not been recomposed with the correct Golden Image Snapshot"
$Display = "Table"
$Author = "Wouter Kursten"
$PluginVersion = 0.1
$PluginCategory = "View"

And this is how it looks:

Another script I already made is a simple one to get the status of all full clone pools. Not really fancy but it gets information about what template is used as the base and several counts for the various status of desktops:

# Start of Settings
# End of Settings

[email protected]()
foreach ($pool in $pools){
$poolname=$pool.base.name
if ($pool.type -like "*automated*" -AND $pool.source -like "*VIRTUAL_CENTER*"){
$desktops=get-hvmachinesummary -pool $poolname
$fullpoolstatus+=New-Object PSObject -Property @{"Name" = $Poolname;
								"Template" = $pool.AutomatedDesktopData.VirtualCenterNamesData.TemplatePath;
								"Desktop_Count" = ($desktops).count;
								"Desktops_Unassigned" = ($desktops | where {$_.base.User -eq $null}).count;
								"Available" = ($desktops | where {$_.base.basicstate -eq "AVAILABLE"}).count;
								"Connected" = ($desktops | where {$_.base.basicstate -eq "CONNECTED"}).count;
								"Disconnected" = ($desktops | where {$_.base.basicstate -eq "DISCONNECTED"}).count;
								"Maintenance" = ($desktops | where {$_.base.basicstate -eq "MAINTENANCE"}).count;
								"Provisioning" = ($desktops | where {$_.base.basicstate -eq "PROVISIONING"}).count;
								"Customizing" = ($desktops | where {$_.base.basicstate -eq "CUSTOMIZING"}).count;
								"Already_Used" = ($desktops | where {$_.base.basicstate -eq "ALREADY_USED"}).count;
								"Agent_Unreachable" = ($desktops | where {$_.base.basicstate -eq "AGENT_UNREACHABLE"}).count;
								"Error" = ($desktops | where {$_.base.basicstate -eq "ERROR"}).count;
								"Deleting" = ($desktops | where {$_.base.basicstate -eq "DELETING"}).count;
								"Provisioning_Error" = ($desktops | where {$_.base.basicstate -eq "PROVISIONING_ERROR"}).count;
}
}
}
$fullpoolstatus | select Name,Template,Desktop_Count,Desktops_Unassigned,Available,Connected,Disconnected,Maintenance,Provisioning,Customizing,Already_Used,Agent_Unreachable,Error,Deleting,Provisioning_Error
$Title = "Full Clone Desktop Pool Status"
$Header = "Full Clone Desktop Pool Status"
$Comments = "These are all pools with full clones and their most common counters"
$Display = "Table"
$Author = "Wouter Kursten"
$PluginVersion = 0.1
$PluginCategory = "View"

and again this is how it can look:

Github

After Alan Renouf saw me posting screenshots on Twitter he offered to setup a github project for this. Last week this was done and I have already done my first few commits. Hopefully more people will jump on the bandwagon so we can make this check as awesome as the original is.

Using PowerCLI to get Horizon view status & events

Update: There is a new way to pull the event information without having to enter the sql password please see this post about it.

So two weeks ago I had a nice little post about talking to Horizon View using PowerCLI. I also promised to be digging a bit more into PowerCLI by grabbing the script posted on the VMware blog and editing it a little to my taste. It’s a very useful script they have on there but still I prefer to know what might have caused the issues. I decided I needed to know who the last user was that used the desktop and the last entry into the eventlog and the time of that log. So actually most code used talks to the eventlog database, something already available pre PowerCLI 6.5 but what I hardly ever used.

The basics for connecting I won’t post but we do need an extra connection and that is to the event database:

$eventdb=connect-hvevent -dbpassword $hvedbpassword

As with the Horizon View connection it’s best to put this into a variable so it can be used later on. The $hvedbpassword should be the password for the user that View uses to connect to the database server in plain text! Earlier in the script I read the password from hashed contents in a text file.The request has been dropped to be able to pass encrypted credentials and/or creta a credentialstore for this.

Next up is grabbing the events for a certain Desktop

$lastevent=get-hvevent -hvdbserver $eventdb -timeperiod 'day' -messagefilter $problemvm.base.name

This could use some rework since I would prefer the time period to be a variable based on the current date but if the event is older then a day it will be hard to find anything on it anyway.

if ($lastevent.events){
$lasteventtime=$lastevent.events | select -expandproperty eventtime -first 1
$lasteventmessage=$lastevent.events | select -expandproperty message -first 1 
$lasteventusername=$lastevent.events | select -expandproperty Username -first 1 
}

This grabs the latest event, the time it happened and the user it happened to. This can be anything including a logoff. It might be able to help you why a lot of desktops are ending up in a rotten state.

The rest of the script is basic building of arrays, filling them, mailing it etc etc. So still not a lot of complicated code that some people build but it’s a bit of the basics in talking to the View Api and the event database.

This is the output you will get (this is from an html file and heavily edited to anonimize it)

The complete script, please do use and abuse it to your own taste as I have done with the original:

#########################################################################################
#																						#
# Get List of Desktops that are not available or connected		 						#
# This is based on the script posted here:												#
# https://blogs.vmware.com/euc/2017/01/vmware-horizon-7-powercli-6-5.html				#
# Required:																				#
# Powercli 6.5 Release 1																#
# The VMware.Hv.Helper Module from https://github.com/vmware/PowerCLI-Example-Scripts	#
#																						#
#########################################################################################

#region variables
#########################################################################################
#								Variables												#
#	Password files need to be filled firs using:										#
#	Read-Host -AsSecureString | ConvertFrom-SecureString | Out-File 'filename.txt'		#
#	Enter password and press enter														#
#	vCenter things have been marked out but I left them in here 						#
#	because they might be usefull for when someone else uses this script				#
#########################################################################################
$cs = "connectionbroker"														# Horizon Connection Server
$hvcsUser= "Service_Account"													# User account to connect to Connection Server
$hvcsPassword = get-content .\hvcs_Credentials.txt | convertto-securestring		# Password for user to connect to Connection Server
$csDomain = "domain"															# Domain for user to connect to Connection Server
$hvedbpassword=get-content .\hvedb_Credentials.txt | convertto-securestring   	# password to access event database
$mailto="[email protected]"														# Address to send the status mail to
$mailfrom="[email protected]"											# Address to send the mail from
$mailsubject="Overview bad VDI desktops"										# Mail subject
$smtpserver="mailserver.domain.com"												# Mail server			
#$vcuser="vcuser"																# User account to access the vCenter server
#$vcpassword=get-content .\vCenter_Credentials.txt | convertto-securestring		# password to access the vCenter server
#vc = "Enter vCenter name"														# vCenter Server



$baseStates = @('PROVISIONING_ERROR',
                'ERROR',
                'MAINTENANCE',
                'DISCONNECTED',
                'AGENT_UNREACHABLE',
                'AGENT_ERR_STARTUP_IN_PROGRESS',
                'AGENT_ERR_DISABLED',
                'AGENT_ERR_INVALID_IP',
                'AGENT_ERR_NEED_REBOOT',
                'AGENT_ERR_PROTOCOL_FAILURE',
                'AGENT_ERR_DOMAIN_FAILURE',
                'AGENT_CONFIG_ERROR',
                'UNKNOWN')
				

#endregion variables

#region initialize
###################################################################
#                    Initialize                                  #
###################################################################
# --- Import the PowerCLI Modules required ---
Import-Module VMware.VimAutomation.HorizonView
Import-Module VMware.VimAutomation.Core

# --- Connect to Horizon Connection Server API Service ---
$hvServer1 = Connect-HVServer -Server $cs -User $hvcsUser -Password $hvcsPassword -Domain $csDomain

# --- Get Services for interacting with the View API Service ---
$Services1= $hvServer1.ExtensionData

# --- Connect to the vCenter Server ---
#Connect-VIServer -Server $vc -User $vcUser -Password $vcPassword

# --- Connect to the view events database ---
$eventdb=connect-hvevent -dbpassword $hvedbpassword

#endregion initialize

#region html
###################################################################
#                    HTML                                         #
###################################################################

$style = "<style>BODY{font-family: Arial; font-size: 10pt;}"
$style = $style + "TABLE{border: 1px solid black; border-collapse: collapse;}"
$style = $style + "TH{border: 1px solid black; background: #dddddd; padding: 5px; }"
$style = $style + "TD{border: 1px solid black; padding: 5px; }"
$style = $style + "</style>"

#endregion

#region main
###################################################################
#                    Main                                        #
###################################################################
[email protected]()
#Write-Output ""
if ($Services1) 
	{
     foreach ($baseState in $baseStates) 
		{
           # --- Get a list of VMs in this state ---
           $ProblemVMs = Get-HVMachineSummary -State $baseState

           foreach ($ProblemVM in $ProblemVMs) 
		   {
		   			$lastevent=get-hvevent -hvdbserver $eventdb -timeperiod 'day' -messagefilter $problemvm.base.name
			
				if ($lastevent.events){
					$lasteventtime=$lastevent.events | select -expandproperty eventtime -first 1
					$lasteventmessage=$lastevent.events | select -expandproperty message -first 1 
					$lasteventusername=$lastevent.events | select -expandproperty Username -first 1 
					}
				else{
				$lasteventtime="Last event is longer then 1 day ago"
				$lasteventmessage="Not Available"
				}
			$lastmaintenancedate=(Get-HVMachine -machinename $problemvm.base.name)
		   	$item = New-Object PSObject
			$item | Add-Member -type NoteProperty -Name Name -Value $problemvm.base.name 
			$item | Add-Member -type NoteProperty -Name State -Value $problemvm.base.basicstate 
			$item | Add-Member -type NoteProperty -Name Pool -Value $problemvm.namesdata.desktopname
			$item | Add-Member -type NoteProperty -Name Last_event_time -Value $lasteventtime
			$item | Add-Member -type NoteProperty -Name Last_event_user -Value $lasteventusername
			$item | Add-Member -type NoteProperty -Name Last_event_message -Value $lasteventmessage
			$Problemarray+= $item
           }
		}
	
		if ($problemarray)	
			{
			$mailbody=$Problemarray | sort state,name | convertto-html -head $style -property  name,state,Pool,Last_event_time,Last_event_user,Last_event_message | out-string
			send-mailmessage -smtpserver $smtpserver -to $mailto -from $mailfrom -subject $mailsubject -body $mailbody -bodyashtml 
			}
		else
			{
			send-mailmessage -smtpserver $smtpserver -to $mailto -from $mailfrom -subject $mailsubject -body "No problems found in the Horizon View Environment" 
			}

     Write-Output "Disconnect from Connection Server."
     Disconnect-HVServer -Server $cs -confirm:$false
		} 

else 
	{
     Write-Output "Failed to login in to Connection Server."
     
     }
# --- Disconnect from the vCenter Server ---
#Write-Output "Disconnect from vCenter Server."
#Disconnect-VIServer -Server $vc
#endregion main