Added Checks to the vCheck for Horizon View

Starting this year I decided to really restart working on the vCheck for Horizon. I had several requests for RDS checks & Active directory plus I really wanted to get rid of everything related to the vmware.hv.helper module to make using it a little easier.

Just like the pools I pull the farms in the connection plugin so these can be used from other plugins.

# --- Get Desktop pools
$poolqueryservice=new-object vmware.hv.queryserviceservice
$pooldefn = New-Object VMware.Hv.QueryDefinition
$pooldefn.queryentitytype='DesktopSummaryView'
$poolqueryResults = $poolqueryService.QueryService_Create($Services1, $pooldefn)
$pools = foreach ($poolresult in $poolqueryResults.results){$services1.desktop.desktop_get($poolresult.id)}

# --- Get RDS Farms

$Farmqueryservice=new-object vmware.hv.queryserviceservice
$Farmdefn = New-Object VMware.Hv.QueryDefinition
$Farmdefn.queryentitytype='FarmSummaryView'
$FarmqueryResults = $FarmqueryService.QueryService_Create($Services1, $Farmdefn)
$farms = foreach ($farmresult in $farmqueryResults.results){$services1.farm.farm_get($farmresult.id)}

# ---- Remove queries
$services1.QueryService.QueryService_DeleteAll()

The deleteall() for the queries needs to be added to clean things up, otherwise you will run out of queries pdq. The RDS plugins I created are visible down below.

Also an AD check was added

The vCenter api call was split into three checks for vCenter itself, ESXi and datastores.

Besides these I have also added a saml check (tested by Aresh Sarkari, thank you!) and truesso checks (don’t have it in my lab so can’t test).

If you want an example of the vCheck that can be found HERE.

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

$Pooloverview=@()
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!

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

$automatedpoolstatus=@()
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

$fulldesktopassignment=@()
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

$licensestatus=@()

$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)
$conserverstatus=@()
$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)
$secserverstatus=@()
$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

$comserverstatus=@()
$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


$eventdbstatus=@()
$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


$wrongsnapdesktops=@()
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

$fullpoolstatus=@()
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="user@domain.com"														# Address to send the status mail to
$mailfrom="connectionbroker@domain.com"											# 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                                        #
###################################################################
$Problemarray=@()
#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

 

 

Login Monitor Script & Check MK

Last night Paul Grevink posted a nice post about the basic setup for Check MK and i am really looking forward to the rest of the series. At my current customer we are also using Check MK so i decided to use the script I made for the VMware Login monitor fling to give output usable for Check MK. At first I was messing with the plugin folder in the check mk folder on the windows server hosting the txt files but a colleague pointed me at the local folder. The big difference is that with the local folder Check MK directly uses the output and the plugin monitor it needs another python file on the check mk server to use the data.

The script:

# This script was created by Wouter Kursten 
# contact: wouter.kursten@detron.nl or w.kursten@gmail.com or https://retouw.eu or @Magneet_NL on twitter
#
# Feel free to grab/copy/alter the script no need to mention me
# But if you create a better / more complete version please send me a mail so I can use that script also
#
# This script is meant to use with the VMware Logon Monitor FLing
# https://labs.vmware.com/flings/vmware-logon-monitor
# This awesome tools actually shows how long it takes to login to your systems
#
# And yes the info block is longer then the script itself
#
# There are only 5 variables you can set
#
# $filefolder for where the Logon Monitor Output files are stored
# $filefilter for when you want to filter what files are being read
# $fileage for how far back in time you want to go
# $warning for the warning value above wich Check MK will give a Warning.
# $critical Gues what, this is the value above wich Check MK will give a critical report.

#Region Variables
$filefolder= "d:\logonmonitor\"
$filefilter="*.txt"
$fileage="5"
$warning="20"
$critical="30"
#endregion

#region Run 

$filelocation="$filefolder"+"$filefilter"
$filelist=get-childitem "$filelocation" | where-object {$_.LastWriteTime -gt (get-date).addminutes(-$fileage)}
$count=($filelist).count
$timing=@()

foreach ($file in $filelist)
	{
	$duration=(get-content $file | select-string -pattern "LogSummary] Logon Time:" | %{$_ -split " "})[6]
	$timing += $duration
	}

$avg= $timing | measure-object -average  
$average = [System.Math]::Round($Avg.average,2)

if ($average -le "$warning")
	{
	write-output "0 VMware_Horizon_View_Logon_Time LogonTime=$average|Logons=$count Logon time : $average sec for $count logons in the last $fileage minutes."
	}
	elseif ($average -gt "$warning" -and $average -lt "$critical")
	{
	write-output "1 VMware_Horizon_View_Logon_Time LogonTime=$average|Logons=$count Logon time : $average sec for $count logons in the last $fileage minutes."
	}
	elseif ($average -ge "$critical")
	{
	write-output "2 VMware_Horizon_View_Logon_Time LogonTime=$average|Logons=$count Logon time : $average sec for $count logons in the last $fileage minutes."
	}
#endregion

As you can see I am not only using the average logontime as before, I also count the amount of logons in the time where we measure this time. Offcourse you can create lots more data to use in Check MK this way

The output I create:

"2 VMware_Horizon_View_Logon_Time LogonTime=$average|Logons=$count Logon time : $average sec for $count logons in the last $fileage minutes."
  • The first digit is the status, 0 for ok 1 for warning and 2 for critical.
  • After that the service name that shows in Check MK
  • The come the 2 numbers we created with their own description separated by | This is used by Check MK to create a diagram
  • then separated by a space (and after this you can use spaces) the text that wil show in Check MK.

The result:

2016-08-11 21_12_01-Beheerders Desktop

The diagram:

2016-08-11 21_14_01-Beheerders Desktop

Back to basics: Daily checks

Something I still hear a lot that system engineers take their vSphere environment for granted and hardly check anything on a daily basis. I always point them at Alan Renouf‘s brilliant health check script while there are other ways to get your daily dose of health this one still rocks for me. You can remove unwanted plugins or make different selections of plugins for daily, weekly or monthly checks. Now and then I still hear people that had issues because of snapshots and there is no need for that anymore and hasn’t been for years! This script has saved me lots of times already + it helped me get management support for limiting other people’s access to the environment because they had no idea what they where doing.

Example of the output you can get:

2016-07-03 20_13_59-192.168.0.11 vCheck