├── CONTRIBUTING.md ├── Content └── VMWare-Horizon-Icon.png ├── EndScript.ps1 ├── GlobalVariables.ps1 ├── Plugins ├── 00 Initialize │ └── 00 Connection Plugin for View.ps1 ├── 10 General │ ├── 04 License Status.ps1 │ ├── 05 Connection Servers Status.ps1 │ ├── 06 Security Servers Status.ps1_disabled │ ├── 07 Composer Servers Status.ps1_disabled │ └── 08 Event Database Status.ps1 ├── 20 Desktop │ ├── 01 Pool Overview.ps1 │ ├── 10 Automated Pool Desktop Wrong Snapshot.ps1 │ ├── 11 Linked Clone Desktop Pool Information.ps1 │ ├── 12 Full Clone Desktop Pool Status.ps1 │ ├── 13 Dedicated Full Clones Assignment.ps1 │ ├── 14 Manual Desktop Pool Information.ps1 │ └── 15 Instant Clone Desktop Pool Information.ps1 ├── 30 Pods │ └── 00 Pod Health.ps1 ├── 40 RDS │ ├── 01 RDS Farm information.ps1 │ ├── 10 Automated Farm Wrong Snapshot.ps1 │ └── 21 RDS Farm health.ps1 └── 90 Various │ ├── 01 AD Domain Health Status.ps1 │ ├── 01 SAML Health Status.ps1 │ ├── 02 vCenter Health Status.ps1 │ ├── 03 ESXi Host Health Status.ps1 │ ├── 04 Datastore Health Status.ps1 │ ├── 05 Gateway Health Status.ps1 │ ├── 25 Certificate SSO Connector Health Status.ps1 │ ├── 26 Certificate SSO Domain Health Status.ps1 │ ├── 27 Certificate SSO Template Health Status.ps1 │ └── 28 Certificate SSO Certificate Server Health Status.ps1 ├── README.md ├── Select-Plugins.ps1 ├── Styles ├── Clarity │ ├── Header-vmware.png │ ├── Header.jpg │ └── Style.ps1 ├── CleanGreen │ ├── Header.jpg │ └── Style.ps1 └── VMware │ ├── Header-vmware.png │ ├── Header.jpg │ └── Style.ps1 ├── plugins.xml ├── vCheck.ps1 └── vCheckUtils.ps1 /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribute to the vCheck-vSphere repository 2 | 3 | Hi! We can't thank you enough for wanting to contribute; the community is what keeps the wheels moving on this awesome project. 4 | All we ask is that you follow some simple guidelines. The roots of these guidelines stem from the developer community and the actual document has been borrowed from [Microsoft's DscResources](https://github.com/PowerShell/DscResources) repository; they did an excellent job putting these guidelines together; why reinvent the wheel? 5 | 6 | ## Using GitHub, Git, and this repository 7 | 8 | We are working on more detailed instructions that outline the basics. 9 | 10 | ## Contributing to the existing vCheck-vSphere repository 11 | 12 | ### Forks and Pull Requests 13 | 14 | GitHub fosters collaboration through the notion of [pull requests](https://help.github.com/articles/using-pull-requests/). 15 | On GitHub, anyone can [fork](https://help.github.com/articles/fork-a-repo/) an existing repository into their own branch where they can make private changes to the original repository. 16 | To contribute these changes back into the original repository, a user simply creates a pull request in order to "request" that the changes be taken "upstream". 17 | 18 | #### Lifecycle of a pull reqeust 19 | 20 | * **Always create pull requests to the `dev` branch of a repository**. 21 | For more information, learn about the [branch structure](#branch-structure) that we are using. 22 | 23 | ![PR-Dev.png](https://github.com/vScripter/vCheck-vSphere/blob/dev/Images/PR-Dev.PNG) 24 | 25 | * When you create a pull request, fill out the description with a summary of what's included in your changes. 26 | If the changes are related to an existing GitHub issue, please reference the issue in your description. 27 | * Once the PR is submitted, we will review your code 28 | * Once the code review is done, and all merge conflicts are resolved, a maintainer will merge your changes. 29 | 30 | ### Contributing to documentation 31 | One of the easiest ways to contribute to a PowerShell project is by helping to write and edit documentation. 32 | All of our documentation hosted on GitHub is written using [GitHub Flavored Markdown](https://help.github.com/articles/github-flavored-markdown/) 33 | 34 | *We are at lest working on shifting things over to GFM, for 'core' documentation. Some things may still live on the GitHub wiki, but using GFM allows the documentation to exist in the repo, so you always have a local copy to reference ;)* 35 | 36 | To [edit an existing file](https://help.github.com/articles/editing-files-in-another-user-s-repository/), simply navigate to it and click the "Edit" button. 37 | GitHub will automatically create your own fork of our repository where you can make your changes. 38 | Once you're finished, save your edits and submit a pull request to get your changes merged upstream. 39 | 40 | If you want to contribute new documentation, first check for [issues tagged as "Documentation"](https://github.com/alanrenouf/vCheck-vSphere/labels/documentation) to make sure you're not duplicating efforts. 41 | If no one seems to be working on what you have planned: 42 | * Open a new issue tagged as "Documentation" to tell others what you're working on 43 | * Create a fork of our repository and start adding new Markdown-based documentation to it 44 | * When you're ready to contribute your documentation, submit a pull request to the *dev* branch 45 | 46 | 47 | #### GitHub Flavored Markdown (GFM) 48 | 49 | All of the articles in this repository use [GitHub Flavored Markdown (GFM)](https://help.github.com/articles/github-flavored-markdown/). 50 | 51 | If you are looking for a good editor, try [Markdown Pad](http://markdownpad.com/) or 52 | GitHub also provides a web interface for Markdown editing with syntax highlighting and the ability to preview changes. 53 | 54 | Some of the more basic GFM syntax includes: 55 | 56 | * **Line breaks vs. paragraphs:** In Markdown there is no HTML `
` or `

` element. 57 | Instead, a new paragraph is designated by an empty line between two blocks of text. 58 | (Note: Please add a single newline after each sentence to simplify the command-line output of diffs and history.) 59 | It will simplify diffs and history. 60 | * **Italics:** The HTML `some text` is written as `*some text*` 61 | * **Bold:** The HTML `some text` element is written as `**some text**` 62 | * **Headings:** HTML headings are designated using `#` characters at the start of the line. 63 | The number of `#` characters corresponds to the hierarchical level of the heading (for example, `#` = `

` and `###` = ```

```). 64 | * **Numbered lists:** To make a numbered (ordered) list start the line with `1. `. 65 | If you want multiple elements within a single list element, format your list as follows: 66 | ``` 67 | 1. For the first element (like this one), insert a tab stop after the 1. 68 | 69 | To include a second element (like this one), insert a line break after the first and align indentations. 70 | ``` 71 | to get this output: 72 | 73 | 1. For the first element (like this one), insert a tab stop after the 1. 74 | 75 | To include a second element (like this one), insert a line break after the first and align indentations. 76 | 77 | * **Bulleted lists:** Bulleted (unordered) lists are almost identical to ordered lists except that the `1. ` is replaced with either `* `, `- `, or `+ `. 78 | Multiple element lists work the same way as with ordered lists. 79 | * **Links:** The syntax for a hyperlink is `[visible link text](link url)`. 80 | Links can also have references, which will be discussed in the "Link and Image References" section below. 81 | 82 | ## Editing an existing plugin 83 | 84 | We are in the process of adding/consolidating more detailed documentation around this. 85 | 86 | ## Creating a new plugin 87 | 88 | We are in the process of adding/consolidating more detailed documentation around this. In the meantime, you can review the documentation that we do have inside the [README.md](README.md) file for the repo. 89 | 90 | ## Gitter & Waffle 91 | 92 | We are using [![Join the chat at https://gitter.im/alanrenouf/vCheck-vSphere](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/alanrenouf/vCheck-vSphere?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) for general discussion around the vCheck utility. This is a good place for general questions or minor issues that someone might be able to answer, on the spot. If something cannot be resolved in the chat, it may warrant the submission of an Issue. 93 | 94 | We are also using Waffle.io to help track and resolve issues. You can visit the the dashboard [![Stories in Ready](http://badge.waffle.io/alanrenouf/vCheck-vSphere.png)](http://waffle.io/alanrenouf/vCheck-vSphere) to view or submit issues (you can also still use the GitHub web interface to submit/view issues). 95 | 96 | 97 | ## Style guidelines 98 | 99 | When contributing to this repository, please follow the following guidelines: 100 | 101 | * For all indentation, use 4 spaces instead of tab stops 102 | * Make sure all files are encoding using UTF-8. 103 | * When writing Markdown, if a paragraph includes more than one setence, end each sentence with a newline. 104 | GitHub will still render the sentences as a single paragraph, but the readability of `git diff` will be greatly improved. 105 | 106 | 107 | ## Branch structure 108 | 109 | We are using a [git flow](http://nvie.com/posts/a-successful-git-branching-model/) model for development. 110 | We recommend that you create local working branches that target a specific scope of change. 111 | Each branch should be limited to a single feature/bugfix both to streamline workflows and reduce the possibility of merge conflicts. 112 | ![git flow picture](http://nvie.com/img/git-model@2x.png) 113 | -------------------------------------------------------------------------------- /Content/VMWare-Horizon-Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vCheckReport/vCheck-HorizonView/e57705263fc0e2c66542eb55a8d497b762a00296/Content/VMWare-Horizon-Icon.png -------------------------------------------------------------------------------- /EndScript.ps1: -------------------------------------------------------------------------------- 1 | # Everything in this script will run at the end of vCheck 2 | -------------------------------------------------------------------------------- /GlobalVariables.ps1: -------------------------------------------------------------------------------- 1 | # You can change the following defaults by altering the below settings: 2 | # 3 | 4 | 5 | # Set the following to true to enable the setup wizard for first time run 6 | $SetupWizard = $False 7 | 8 | 9 | # Start of Settings 10 | # Report header 11 | $reportHeader = "vCheck" 12 | # Would you like the report displayed in the local browser once completed ? 13 | $DisplaytoScreen = $true 14 | # Display the report even if it is empty? 15 | $DisplayReportEvenIfEmpty = $true 16 | # Use the following item to define if an email report should be sent once completed 17 | $SendEmail = $false 18 | # Please Specify the SMTP server address (and optional port) [servername(:port)] 19 | $SMTPSRV = "mysmtpserver.mydomain.local" 20 | # Would you like to use SSL to send email? 21 | $EmailSSL = $false 22 | # Please specify the email address who will send the vCheck report 23 | $EmailFrom = "me@mydomain.local" 24 | # Please specify the email address(es) who will receive the vCheck report (separate multiple addresses with comma) 25 | $EmailTo = "me@mydomain.local" 26 | # Please specify the email address(es) who will be CCd to receive the vCheck report (separate multiple addresses with comma) 27 | $EmailCc = "" 28 | # Please specify an email subject 29 | $EmailSubject = "$Server vCheck Report" 30 | # Send the report by e-mail even if it is empty? 31 | $EmailReportEvenIfEmpty = $true 32 | # If you would prefer the HTML file as an attachment then enable the following: 33 | $SendAttachment = $false 34 | # Set the style template to use. 35 | $Style = "Clarity" 36 | # Do you want to include plugin details in the report? 37 | $reportOnPlugins = $true 38 | # List Enabled plugins first in Plugin Report? 39 | $ListEnabledPluginsFirst = $true 40 | # Set the following setting to $true to see how long each Plugin takes to run as part of the report 41 | $TimeToRun = $true 42 | # Report on plugins that take longer than the following amount of seconds 43 | $PluginSeconds = 30 44 | # End of Settings 45 | 46 | # End of Global Variables 47 | -------------------------------------------------------------------------------- /Plugins/00 Initialize/00 Connection Plugin for View.ps1: -------------------------------------------------------------------------------- 1 | $Title = "Connection settings for Horizon" 2 | $Author = "Wouter Kursten" 3 | $PluginVersion = 0.3 4 | $Header = "Connection Settings" 5 | $Comments = "Connection Plugin for connecting to Horizon" 6 | $Display = "None" 7 | $PluginCategory = "View" 8 | 9 | # Start of Settings 10 | # Please Specify the address of one of the connection servers or the address for the general Horizon environment 11 | $Server = "Servername" 12 | # Maximum number of samples to gather for events 13 | $MaxSampleVIEvent = 100000 14 | 15 | # End of Settings 16 | 17 | # The credsfile contains both username and password create one using the following: 18 | # $creds = get-credential 19 | # $creds | export-clixml c:\scripts\credentials.xml 20 | 21 | $credsfile = .\hvcs_credentials.xml 22 | 23 | # Credential file for the user to connect to the Connection Server 24 | # $hvcsPassword = get-content .\hvcs_Credentials.txt | convertto-securestring 25 | $creds = import-clixml $credsfile 26 | 27 | # Credential file for the User configured n Horizon to connect to the Database 28 | # only to be used pre-horizon 7.3 29 | # $hvedbpassword=get-content .\hvedb_Credentials.txt | convertto-securestring 30 | 31 | # Loading 32 | Import-Module VMware.VimAutomation.HorizonView 33 | Import-Module VMware.VimAutomation.Core 34 | 35 | # --- Connect to Horizon Connection Server API Service --- 36 | $hvServer1 = Connect-HVServer -Server $server -credential $creds 37 | 38 | # --- Get Services for interacting with the Horizon API Service --- 39 | $Services1= $hvServer1.ExtensionData 40 | 41 | # --- Get Desktop pools 42 | $poolqueryservice=new-object vmware.hv.queryserviceservice 43 | $pooldefn = New-Object VMware.Hv.QueryDefinition 44 | $pooldefn.queryentitytype='DesktopSummaryView' 45 | $poolqueryResults = $poolqueryService.QueryService_Create($Services1, $pooldefn) 46 | $pools = foreach ($poolresult in $poolqueryResults.results){$services1.desktop.desktop_get($poolresult.id)} 47 | 48 | # --- Get RDS Farms 49 | 50 | $Farmqueryservice=new-object vmware.hv.queryserviceservice 51 | $Farmdefn = New-Object VMware.Hv.QueryDefinition 52 | $Farmdefn.queryentitytype='FarmSummaryView' 53 | $FarmqueryResults = $FarmqueryService.QueryService_Create($Services1, $Farmdefn) 54 | $farms = foreach ($farmresult in $farmqueryResults.results){$services1.farm.farm_get($farmresult.id)} -------------------------------------------------------------------------------- /Plugins/10 General/04 License Status.ps1: -------------------------------------------------------------------------------- 1 | # Start of Settings 2 | # End of Settings 3 | 4 | $licensestatus=@() 5 | 6 | $license=($services1).license.license_get() 7 | $licensestatus+=New-Object PSObject -Property @{"Licensed" = $license.Licensed; 8 | "LicenseKey" = $license.LicenseKey; 9 | "ExpirationTime" = $license.ExpirationTime; 10 | "ViewComposerEnabled" = $license.ViewComposerEnabled; 11 | "DesktopLaunchingEnabled" = $license.DesktopLaunchingEnabled; 12 | "ApplicationLaunchingEnabled" = $license.ApplicationLaunchingEnabled; 13 | "InstantCloneEnabled" = $license.InstantCloneEnabled; 14 | "UsageModel" = $license.UsageModel; 15 | } 16 | 17 | $licensestatus | select Licensed,LicenseKey,ExpirationTime,ViewComposerEnabled,DesktopLaunchingEnabled,ApplicationLaunchingEnabled,InstantCloneEnabled,UsageModel 18 | 19 | $Title = "License Status" 20 | $Header = "License Status" 21 | $Comments = "This is the license status information" 22 | $Display = "Table" 23 | $Author = "Wouter Kursten" 24 | $PluginVersion = 0.1 25 | $PluginCategory = "View" -------------------------------------------------------------------------------- /Plugins/10 General/05 Connection Servers Status.ps1: -------------------------------------------------------------------------------- 1 | # Start of Settings 2 | # End of Settings 3 | 4 | $date=get-date 5 | $datemaxexp=(get-date).adddays(30) 6 | $conserverstatus=@() 7 | $conservers=$services1.connectionserverhealth.connectionserverhealth_list() 8 | foreach ($conserver in $conservers) { 9 | if ($conserver.CertificateHealth.ExpirationTime -lt $date){ 10 | $expiring="Already Expired" 11 | } 12 | elseif ($conserver.CertificateHealth.ExpirationTime -lt $datemaxexp){ 13 | $expiring="Expiring in 30 days" 14 | } 15 | else { 16 | $expiring="False" 17 | } 18 | 19 | $conserverstatus+=New-Object PSObject -Property @{"Name" = $conserver.name; 20 | "Status" = $conserver.Status; 21 | "Version" = $conserver.Version; 22 | "Build" = $conserver.Build 23 | "Certificate_Status" = $conserver.CertificateHealth.Valid; 24 | "Certificate_Expiration_Time" = $conserver.CertificateHealth.ExpirationTime; 25 | "Certificate_Expiring" = $expiring; 26 | "Certificate_Invalidation_Reason" = $conserver.CertificateHealth.InValidReason; 27 | 28 | } 29 | } 30 | $conserverstatus | select name,Status,Version,Build,Certificate_Status,Certificate_Expiring,Certificate_Expiration_Time,Certificate_Invalidation_Reason 31 | 32 | $Title = "Connection Servers Status" 33 | $Header = "Connection Servers Status" 34 | $Comments = "These are the used Connection Servers" 35 | $Display = "Table" 36 | $Author = "Wouter Kursten" 37 | $PluginVersion = 0.1 38 | $PluginCategory = "View" -------------------------------------------------------------------------------- /Plugins/10 General/06 Security Servers Status.ps1_disabled: -------------------------------------------------------------------------------- 1 | # Start of Settings 2 | # End of Settings 3 | 4 | $date=get-date 5 | $datemaxexp=(get-date).adddays(30) 6 | $secserverstatus=@() 7 | $secservers=$services1.securityserverhealth.securityserverhealth_list() 8 | foreach ($secserver in $secservers) { 9 | if ($secserver.CertificateHealth.ExpirationTime -lt $date){ 10 | $expiring="Already Expired" 11 | } 12 | elseif ($secserver.CertificateHealth.ExpirationTime -lt $datemaxexp){ 13 | $expiring="Expiring in 30 days" 14 | } 15 | else { 16 | $expiring="False" 17 | } 18 | 19 | $secserverstatus+=New-Object PSObject -Property @{"Name" = $secserver.name; 20 | "Status" = $secserver.Status; 21 | "Version" = $secserver.Version; 22 | "Build" = $secserver.Build 23 | "Certificate_Status" = $secserver.CertificateHealth.Valid; 24 | "Certificate_Expiration_Time" = $secserver.CertificateHealth.ExpirationTime; 25 | "Certificate_Expiring" = $expiring; 26 | "Certificate_Invalidation_Reason" = $secserver.CertificateHealth.InValidReason; 27 | 28 | } 29 | } 30 | $secserverstatus | select name,Status,Version,Build,Certificate_Status,Certificate_Expiring,Certificate_Expiration_Time,Certificate_Invalidation_Reason 31 | 32 | $Title = "Security Servers Status" 33 | $Header = "Security Servers Status" 34 | $Comments = "These are the used Security Servers" 35 | $Display = "Table" 36 | $Author = "Wouter Kursten" 37 | $PluginVersion = 0.1 38 | $PluginCategory = "View" -------------------------------------------------------------------------------- /Plugins/10 General/07 Composer Servers Status.ps1_disabled: -------------------------------------------------------------------------------- 1 | # Start of Settings 2 | # End of Settings 3 | 4 | $comserverstatus=@() 5 | $comservers=$services1.viewcomposerhealth.viewcomposerhealth_list() 6 | foreach ($comserver in $comservers) { 7 | $vcenters=$comserver.data.virtualcenters 8 | 9 | foreach ($vcenter in $vcenters){ 10 | if ($vcenternames){ 11 | $vcenternames+="," 12 | $vcenternames+=($services1.virtualcenterhealth.virtualcenterhealth_get($vcenter)).data.name 13 | } 14 | else{ 15 | $vcenternames+=($services1.virtualcenterhealth.virtualcenterhealth_get($vcenter)).data.name 16 | } 17 | } 18 | $comserverstatus+=New-Object PSObject -Property @{"Name" = $comserver.ServerName; 19 | "Version" = $comserver.Data.Version; 20 | "Build" = $comserver.Data.Build; 21 | "vCenter_Server"= $vcenternames 22 | 23 | } 24 | $vcenternames=$null 25 | } 26 | $comserverstatus | select name,Version,Build,vcenter_server 27 | 28 | $Title = "Composer Servers Status" 29 | $Header = "Composer Servers Status" 30 | $Comments = "These are the used Composer Servers" 31 | $Display = "Table" 32 | $Author = "Wouter Kursten" 33 | $PluginVersion = 0.1 34 | $PluginCategory = "View" 35 | -------------------------------------------------------------------------------- /Plugins/10 General/08 Event Database Status.ps1: -------------------------------------------------------------------------------- 1 | # Start of Settings 2 | # End of Settings 3 | 4 | 5 | $eventdbstatus=@() 6 | $eventdb=$services1.EventDatabaseHealth.EventDatabaseHealth_get() 7 | if ($eventdb.configured -eq $True){ 8 | $eventdbstatus+=New-Object PSObject -Property @{"Servername" = $eventdb.data.Servername; 9 | "Port" = $eventdb.data.Port; 10 | "Status" = $eventdb.data.State; 11 | "Username" = $eventdb.data.Username; 12 | "DatabaseName" = $eventdb.data.DatabaseName 13 | "TablePrefix" = $eventdb.data.TablePrefix; 14 | "State" = $eventdb.data.State; 15 | "Error" = $eventdb.data.Error; 16 | } 17 | } 18 | $eventdbstatus | select Servername,Port,Status,Username,DatabaseName,TablePrefix,State,Error 19 | 20 | $Title = "Event Database Status" 21 | $Header = "Event Database Status" 22 | $Comments = "These are the settings for the Event Database" 23 | $Display = "Table" 24 | $Author = "Wouter Kursten" 25 | $PluginVersion = 0.1 26 | $PluginCategory = "View" -------------------------------------------------------------------------------- /Plugins/20 Desktop/01 Pool Overview.ps1: -------------------------------------------------------------------------------- 1 | # Start of Settings 2 | # End of Settings 3 | 4 | $Pooloverview=@() 5 | foreach ($pool in $pools){ 6 | $poolstatus=$pool.DesktopSettings.Enabled 7 | $ProvisioningStatus=$pool.AutomatedDesktopData.VirtualCenterProvisioningSettings.enableprovisioning 8 | $source=$pool.source 9 | switch ($poolstatus) 10 | { 11 | $True {$poolstatusoutput="Enabled"} 12 | $False {$poolstatusoutput="Disabled"} 13 | default {$poolstatusoutput="No Pool Status available"} 14 | } 15 | 16 | switch ($ProvisioningStatus) 17 | { 18 | $True {$ProvisioningStatusoutput="Enabled"} 19 | $False {$ProvisioningStatusoutput="Disabled"} 20 | default {$ProvisioningStatusoutput="No Pool Provisioning Status available"} 21 | } 22 | 23 | switch ($source) 24 | { 25 | VIRTUAL_CENTER {$sourceoutput="vCenter Managed Desktop"} 26 | FULL_CLONES {$sourceoutput="Full Clones"} 27 | VIEW_COMPOSER {$sourceoutput="Linked Clones"} 28 | INSTANT_CLONE_ENGINE {$sourceoutput="Instant Clones"} 29 | UNMANAGED {$sourceoutput="Non-vCenter"} 30 | RDS {$sourceoutput="RDS Desktops"} 31 | {$_ -eq "VIRTUAL_CENTER" -AND $pool.type -eq "Automated"} {$sourceoutput="Full Clones"} 32 | {$_ -eq "VIRTUAL_CENTER" -AND $pool.type -eq "MANUAL"} {$sourceoutput="Manually Added vCenter Managed Desktops"} 33 | default {$sourceoutput="No Source data available"} 34 | } 35 | 36 | if ($pool.type -eq "Automated"){ 37 | $Automaticassignment=$pool.AutomatedDesktopData.UserAssignment.AutomaticAssignment 38 | switch ($Automaticassignment) 39 | { 40 | $TRUE {$Automaticassignmentoutput="Automatic"} 41 | $FALSE {$Automaticassignmentoutput="Manual"} 42 | default {$Automaticassignmentoutput="No Assignment Status Available"} 43 | } 44 | $Pooloverview+=New-Object PSObject -Property @{"Name" = $pool.base.name; 45 | "Displayname" = $pool.base.DisplayName; 46 | "Description" = $pool.base.Description; 47 | "Status" = $poolstatusoutput; 48 | "Provisioning" = $ProvisioningStatusoutput; 49 | "Type" = $pool.type; 50 | "Source" = $sourceoutput; 51 | "User_Assignment" = $pool.AutomatedDesktopData.UserAssignment.userassignment; 52 | "Assignment_Type" = $Automaticassignmentoutput; 53 | } 54 | } 55 | 56 | elseif ($pool.type -eq "MANUAL"){ 57 | $Automaticassignment= $pool.manualdesktopdata.UserAssignment.AutomaticAssignment 58 | switch ($Automaticassignment) 59 | { 60 | $TRUE {$Automaticassignmentoutput="Automatic"} 61 | $FALSE {$Automaticassignmentoutput="Manual"} 62 | default {$Automaticassignmentoutput="No Assignment Status Available"} 63 | } 64 | $Pooloverview+=New-Object PSObject -Property @{"Name" = $pool.base.name; 65 | "Displayname" = $pool.base.DisplayName; 66 | "Description" = $pool.base.Description; 67 | "Status" = $poolstatusoutput; 68 | "Provisioning" = $ProvisioningStatusoutput; 69 | "Type" = $pool.type; 70 | "Source" = $sourceoutput; 71 | "User_Assignment" = $pool.manualdesktopdata.UserAssignment.UserAssignment; 72 | "Assignment_Type" = $Automaticassignmentoutput; 73 | } 74 | } 75 | 76 | elseif ($pool.type -eq "RDS"){ 77 | $source=($services1.farm.farm_get($pool.rdsdesktopdata.farm)).source 78 | $ProvisioningStatus=($services1.farm.farm_get($pool.rdsdesktopdata.farm)).automatedfarmdata.VirtualCenterProvisioningSettings.enableprovisioning 79 | switch ($source) 80 | { 81 | VIEW_COMPOSER {$sourceoutput="Linked Clones RDS Hosts"} 82 | INSTANT_CLONE_ENGINE {$sourceoutput="Instant Clones RDS Hosts"} 83 | default {$sourceoutput="Manually Added RDS Hosts"} 84 | } 85 | 86 | switch ($ProvisioningStatus) 87 | { 88 | $True {$ProvisioningStatusoutput="Enabled"} 89 | $False {$ProvisioningStatusoutput="Disabled"} 90 | default {$ProvisioningStatusoutput="N/A"} 91 | } 92 | 93 | $Pooloverview+=New-Object PSObject -Property @{"Name" = $pool.base.name; 94 | "Displayname" = $pool.base.DisplayName; 95 | "Description" = $pool.base.Description; 96 | "Status" = $poolstatusoutput; 97 | "Provisioning" = $ProvisioningStatusoutput; 98 | "Type" = ($services1.farm.farm_get($pool.rdsdesktopdata.farm)).type; 99 | "Source" = $sourceoutput; 100 | "User_Assignment" = "N/A"; 101 | "Assignment_Type" = "N/A"; 102 | } 103 | } 104 | } 105 | 106 | $Pooloverview | select Name,Displayname,Description,Status,Provisioning,Type,Source,User_Assignment,Assignment_Type 107 | $Title = "Overview of all Pools" 108 | $Header = "Overview of all Pools" 109 | $Comments = "Gives an overview of the general status of all pools" 110 | $Display = "Table" 111 | $Author = "Wouter Kursten" 112 | $PluginVersion = 0.1 113 | $PluginCategory = "View" -------------------------------------------------------------------------------- /Plugins/20 Desktop/10 Automated Pool Desktop Wrong Snapshot.ps1: -------------------------------------------------------------------------------- 1 | # Start of Settings 2 | # End of Settings 3 | 4 | 5 | $wrongsnapdesktops=@() 6 | foreach ($pool in $pools){ 7 | $poolname=$pool.base.name 8 | 9 | if ($pool.type -like "*automated*"){ 10 | $PoolID=($pool).id 11 | $queryservice=new-object vmware.hv.queryserviceservice 12 | $defn = New-Object VMware.Hv.QueryDefinition 13 | $defn.queryentitytype='MachineSummaryView' 14 | 15 | $defn.filter = New-Object VMware.Hv.QueryFilterEquals -Property @{ 'memberName' = 'base.desktop'; 'value' = $pool.id } 16 | 17 | $queryResults = $queryService.QueryService_Create($Services1, $defn) 18 | 19 | if ($queryResults.results.count -ge 1){ 20 | $poolmachines=$services1.machine.machine_getinfos($queryResults.results.id) 21 | $wrongsnaps=$poolmachines | where {$_.managedmachinedata.viewcomposerdata.baseimagesnapshotpath -notlike $pool.automateddesktopdata.VirtualCenternamesdata.snapshotpath -OR $_.managedmachinedata.viewcomposerdata.baseimagepath -notlike $pool.automateddesktopdata.VirtualCenternamesdata.parentvmpath} 22 | if ($wrongsnaps){ 23 | foreach ($wrongsnap in $wrongsnaps){ 24 | $wrongsnapdesktops+= New-Object PSObject -Property @{ 25 | "VM Name" = $wrongsnap.base.name; 26 | "VM Snapshot" = $wrongsnap.managedmachinedata.viewcomposerdata.baseimagesnapshotpath; 27 | "VM GI" = $wrongsnap.managedmachinedata.viewcomposerdata.baseimagepath; 28 | "Pool Snapshot" = $pool.automateddesktopdata.VirtualCenternamesdata.snapshotpath; 29 | "Pool GI" = $pool.automateddesktopdata.VirtualCenternamesdata.parentvmpath; 30 | } 31 | } 32 | 33 | } 34 | } 35 | $services1.QueryService.QueryService_DeleteAll() 36 | } 37 | } 38 | $wrongsnapdesktops 39 | 40 | $Title = "VDI Desktops based on wrong snapshot" 41 | $Header = "VDI Desktops based on wrong snapshot" 42 | $Comments = "These desktops have not been recomposed with the correct Golden Image Snapshot" 43 | $Display = "Table" 44 | $Author = "Wouter Kursten" 45 | $PluginVersion = 0.4 46 | $PluginCategory = "View" 47 | -------------------------------------------------------------------------------- /Plugins/20 Desktop/11 Linked Clone Desktop Pool Information.ps1: -------------------------------------------------------------------------------- 1 | # Start of Settings 2 | # End of Settings 3 | 4 | $automatedpoolstatus=@() 5 | foreach ($pool in $pools){ 6 | $poolname=$pool.base.name 7 | if ($pool.type -like "*automated*" -AND $pool.source -like "*VIEW_COMPOSER*"){ 8 | $queryservice=new-object vmware.hv.queryserviceservice 9 | $defn = New-Object VMware.Hv.QueryDefinition 10 | $defn.queryentitytype='MachineSummaryView' 11 | $defn.filter=New-Object VMware.Hv.QueryFilterEquals -property @{'memberName'='base.desktop'; 'value'=$pool.id} 12 | $queryResults = $queryService.QueryService_Create($Services1, $defn) 13 | $desktops=$queryResults.results 14 | $automatedpoolstatus+=New-Object PSObject -Property @{ 15 | "Name" = $Poolname; 16 | "Pool_Image" = $pool.automateddesktopdata.VirtualCenternamesdata.parentvmpath; 17 | "Pool_Snapshot" = $pool.automateddesktopdata.VirtualCenternamesdata.snapshotpath; 18 | "Desktop_Count" = ($desktops).count; 19 | "Available" = ($desktops | where {$_.base.basicstate -eq "AVAILABLE"}).count; 20 | "Connected" = ($desktops | where {$_.base.basicstate -eq "CONNECTED"}).count; 21 | "Disconnected" = ($desktops | where {$_.base.basicstate -eq "DISCONNECTED"}).count; 22 | "Maintenance" = ($desktops | where {$_.base.basicstate -eq "MAINTENANCE"}).count; 23 | "Provisioning" = ($desktops | where {$_.base.basicstate -eq "PROVISIONING"}).count; 24 | "Customizing" = ($desktops | where {$_.base.basicstate -eq "CUSTOMIZING"}).count; 25 | "Already_Used" = ($desktops | where {$_.base.basicstate -eq "ALREADY_USED"}).count; 26 | "Agent_Unreachable" = ($desktops | where {$_.base.basicstate -eq "AGENT_UNREACHABLE"}).count; 27 | "Error" = ($desktops | where {$_.base.basicstate -eq "ERROR"}).count; 28 | "Deleting" = ($desktops | where {$_.base.basicstate -eq "DELETING"}).count; 29 | "Provisioning_Error" = ($desktops | where {$_.base.basicstate -eq "PROVISIONING_ERROR"}).count; 30 | } 31 | $services1.QueryService.QueryService_DeleteAll() 32 | } 33 | } 34 | 35 | $automatedpoolstatus | select Name,Pool_Image,Pool_Snapshot,Desktop_Count,Available,Connected,Disconnected,Maintenance,Provisioning,Customizing,Already_Used,Agent_Unreachable,Error,Deleting,Provisioning_Error 36 | $Title = "Linked Clone Desktop Pool Status" 37 | $Header = "Linked Clone Desktop Pool Status" 38 | $Comments = "These are the pools that have floating linked clones. Not all but the most common status's are counted." 39 | $Display = "Table" 40 | $Author = "Hackathon Team 1" 41 | $PluginVersion = 0.3 42 | $PluginCategory = "View" 43 | -------------------------------------------------------------------------------- /Plugins/20 Desktop/12 Full Clone Desktop Pool Status.ps1: -------------------------------------------------------------------------------- 1 | # Start of Settings 2 | # End of Settings 3 | 4 | $fullpoolstatus=@() 5 | foreach ($pool in $pools){ 6 | $poolname=$pool.base.name 7 | if ($pool.type -like "*automated*" -AND $pool.source -like "*VIRTUAL_CENTER*"){ 8 | $queryservice=new-object vmware.hv.queryserviceservice 9 | $defn = New-Object VMware.Hv.QueryDefinition 10 | $defn.queryentitytype='MachineSummaryView' 11 | $defn.filter=New-Object VMware.Hv.QueryFilterEquals -property @{'memberName'='base.desktop'; 'value'=$pool.id} 12 | $queryResults = $queryService.QueryService_Create($Services1, $defn) 13 | $desktops=$queryResults.results 14 | $fullpoolstatus+=New-Object PSObject -Property @{ 15 | "Name" = $Poolname; 16 | "Template" = $pool.AutomatedDesktopData.VirtualCenterNamesData.TemplatePath; 17 | "Desktop_Count" = ($desktops).count; 18 | "Desktops_Unassigned" = ($desktops | where {$_.base.User -eq $null}).count; 19 | "Available" = ($desktops | where {$_.base.basicstate -eq "AVAILABLE"}).count; 20 | "Connected" = ($desktops | where {$_.base.basicstate -eq "CONNECTED"}).count; 21 | "Disconnected" = ($desktops | where {$_.base.basicstate -eq "DISCONNECTED"}).count; 22 | "Maintenance" = ($desktops | where {$_.base.basicstate -eq "MAINTENANCE"}).count; 23 | "Provisioning" = ($desktops | where {$_.base.basicstate -eq "PROVISIONING"}).count; 24 | "Customizing" = ($desktops | where {$_.base.basicstate -eq "CUSTOMIZING"}).count; 25 | "Already_Used" = ($desktops | where {$_.base.basicstate -eq "ALREADY_USED"}).count; 26 | "Agent_Unreachable" = ($desktops | where {$_.base.basicstate -eq "AGENT_UNREACHABLE"}).count; 27 | "Error" = ($desktops | where {$_.base.basicstate -eq "ERROR"}).count; 28 | "Deleting" = ($desktops | where {$_.base.basicstate -eq "DELETING"}).count; 29 | "Provisioning_Error" = ($desktops | where {$_.base.basicstate -eq "PROVISIONING_ERROR"}).count; 30 | } 31 | $services1.QueryService.QueryService_DeleteAll() 32 | } 33 | } 34 | 35 | $fullpoolstatus | select Name,Template,Desktop_Count,Desktops_Unassigned,Available,Connected,Disconnected,Maintenance,Provisioning,Customizing,Already_Used,Agent_Unreachable,Error,Deleting,Provisioning_Error 36 | $Title = "Full Clone Desktop Pool Status" 37 | $Header = "Full Clone Desktop Pool Status" 38 | $Comments = "These are all pools with full clones and their most common counters" 39 | $Display = "Table" 40 | $Author = "Wouter Kursten" 41 | $PluginVersion = 0.2 42 | $PluginCategory = "View" 43 | -------------------------------------------------------------------------------- /Plugins/20 Desktop/13 Dedicated Full Clones Assignment.ps1: -------------------------------------------------------------------------------- 1 | # Start of Settings 2 | # End of Settings 3 | 4 | $fulldesktopassignment=@() 5 | foreach ($pool in $pools){ 6 | $poolname=$pool.base.name 7 | if ($pool.type -like "*automated*" -AND $pool.source -like "*VIRTUAL_CENTER*"){ 8 | $queryservice=new-object vmware.hv.queryserviceservice 9 | $defn = New-Object VMware.Hv.QueryDefinition 10 | $defn.queryentitytype='MachineSummaryView' 11 | $defn.filter=New-Object VMware.Hv.QueryFilterEquals -property @{'memberName'='base.desktop'; 'value'=$pool.id} 12 | $queryResults = $queryService.QueryService_Create($Services1, $defn) 13 | $desktops=$queryResults.results 14 | foreach ($desktop in $desktops){ 15 | if ($desktop.namesdata.username){ 16 | $username=$desktop.namesdata.username 17 | } 18 | else{ 19 | $username="Unassigned" 20 | } 21 | $fulldesktopassignment+=New-Object PSObject -Property @{ 22 | "Pool_Name" = $Poolname; 23 | "Desktop_Name" = $desktop.base.name; 24 | "Desktop_State" = $desktop.base.basicstate; 25 | "Desktop_Assigned_to" = $username; 26 | "Desktop_OperatingSystem" = $desktop.base.Operatingsystem; 27 | "Agent_version" = $desktop.base.agentversion; 28 | "Host" = $desktop.managedmachinesdata.hostname; 29 | "Datastore" = $desktop.ManagedMachineNamesData.datastorepaths | out-string; 30 | } 31 | $services1.QueryService.QueryService_DeleteAll() 32 | } 33 | } 34 | } 35 | 36 | $fulldesktopassignment | select Pool_Name,Desktop_Name,Desktop_State,Desktop_Assigned_to,Desktop_OperatingSystem,Agent_version,Host,Datastore 37 | $Title = "Dedicated Desktop Pool Assignment" 38 | $Header = "Dedicated Desktop Pool Assignment" 39 | $Comments = "These are the dedicated desktops with their current user assignment" 40 | $Display = "Table" 41 | $Author = "Wouter Kursten" 42 | $PluginVersion = 0.2 43 | $PluginCategory = "View" 44 | -------------------------------------------------------------------------------- /Plugins/20 Desktop/14 Manual Desktop Pool Information.ps1: -------------------------------------------------------------------------------- 1 | # Start of Settings 2 | # End of Settings 3 | 4 | $manualpoolstatus=@() 5 | foreach ($pool in $pools){ 6 | $poolname=$pool.base.name 7 | if ($pool.Type -like "Manual"){ 8 | $queryservice=new-object vmware.hv.queryserviceservice 9 | $defn = New-Object VMware.Hv.QueryDefinition 10 | $defn.queryentitytype='MachineSummaryView' 11 | $defn.filter=New-Object VMware.Hv.QueryFilterEquals -property @{'memberName'='base.desktop'; 'value'=$pool.id} 12 | $queryResults = $queryService.QueryService_Create($Services1, $defn) 13 | $desktops=$queryResults.results 14 | $manualpoolstatus+=New-Object PSObject -Property @{ 15 | "Name" = $Poolname; 16 | "Available" = ($desktops | where {$_.base.basicstate -eq "AVAILABLE"}).count; 17 | "Connected" = ($desktops | where {$_.base.basicstate -eq "CONNECTED"}).count; 18 | "Disconnected" = ($desktops | where {$_.base.basicstate -eq "DISCONNECTED"}).count; 19 | "Already_Used" = ($desktops | where {$_.base.basicstate -eq "ALREADY_USED"}).count; 20 | "Agent_Unreachable" = ($desktops | where {$_.base.basicstate -eq "AGENT_UNREACHABLE"}).count; 21 | "Error" = ($desktops | where {$_.base.basicstate -eq "ERROR"}).count; 22 | } 23 | $services1.QueryService.QueryService_DeleteAll() 24 | } 25 | } 26 | 27 | $manualpoolstatus | select Name,Available,Connected,Disconnected,Already_Used,Agent_Unreachable,Error 28 | 29 | $Title = "Manual Desktop Pool Status" 30 | $Header = "Manual Desktop Pool Status" 31 | $Comments = "These are the pools that have manual Desktops. Not all but the most common status's are counted." 32 | $Display = "Table" 33 | $Author = "Hackathon Team 1" 34 | $PluginVersion = 0.2 35 | $PluginCategory = "View" 36 | -------------------------------------------------------------------------------- /Plugins/20 Desktop/15 Instant Clone Desktop Pool Information.ps1: -------------------------------------------------------------------------------- 1 | # Start of Settings 2 | # End of Settings 3 | 4 | $automatedpoolstatus=@() 5 | foreach ($pool in $pools){ 6 | $poolname=$pool.base.name 7 | if ($pool.type -like "*automated*" -AND $pool.source -like "*INSTANT_CLONE_ENGINE*"){ 8 | $queryservice=new-object vmware.hv.queryserviceservice 9 | $defn = New-Object VMware.Hv.QueryDefinition 10 | $defn.queryentitytype='MachineSummaryView' 11 | $defn.filter=New-Object VMware.Hv.QueryFilterEquals -property @{'memberName'='base.desktop'; 'value'=$pool.id} 12 | $queryResults = $queryService.QueryService_Create($Services1, $defn) 13 | $desktops=$queryResults.results 14 | $automatedpoolstatus+=New-Object PSObject -Property @{ 15 | "Name" = $Poolname; 16 | "Pool_Image" = $pool.automateddesktopdata.VirtualCenternamesdata.parentvmpath; 17 | "Pool_Snapshot" = $pool.automateddesktopdata.VirtualCenternamesdata.snapshotpath; 18 | "Desktop_Count" = ($desktops).count; 19 | "Available" = ($desktops | where {$_.base.basicstate -eq "AVAILABLE"}).count; 20 | "Connected" = ($desktops | where {$_.base.basicstate -eq "CONNECTED"}).count; 21 | "Disconnected" = ($desktops | where {$_.base.basicstate -eq "DISCONNECTED"}).count; 22 | "Maintenance" = ($desktops | where {$_.base.basicstate -eq "MAINTENANCE"}).count; 23 | "Provisioning" = ($desktops | where {$_.base.basicstate -eq "PROVISIONING"}).count; 24 | "Customizing" = ($desktops | where {$_.base.basicstate -eq "CUSTOMIZING"}).count; 25 | "Already_Used" = ($desktops | where {$_.base.basicstate -eq "ALREADY_USED"}).count; 26 | "Agent_Unreachable" = ($desktops | where {$_.base.basicstate -eq "AGENT_UNREACHABLE"}).count; 27 | "Error" = ($desktops | where {$_.base.basicstate -eq "ERROR"}).count; 28 | "Deleting" = ($desktops | where {$_.base.basicstate -eq "DELETING"}).count; 29 | "Provisioning_Error" = ($desktops | where {$_.base.basicstate -eq "PROVISIONING_ERROR"}).count; 30 | } 31 | $services1.QueryService.QueryService_DeleteAll() 32 | } 33 | } 34 | 35 | $automatedpoolstatus | select Name,Pool_Image,Pool_Snapshot,Desktop_Count,Available,Connected,Disconnected,Maintenance,Provisioning,Customizing,Already_Used,Agent_Unreachable,Error,Deleting,Provisioning_Error 36 | $Title = "Instant Clone Desktop Pool Status" 37 | $Header = "Instant Clone Desktop Pool Status" 38 | $Comments = "These are the pools that have Instant clones. Not all but the most common status's are counted." 39 | $Display = "Table" 40 | $Author = "Wouter Kursten" 41 | $PluginVersion = 0.1 42 | $PluginCategory = "View" 43 | -------------------------------------------------------------------------------- /Plugins/30 Pods/00 Pod Health.ps1: -------------------------------------------------------------------------------- 1 | # Start of Settings 2 | # End of Settings 3 | $pods=$services1.pod.pod_list() | where {$_.localpod -eq $false} 4 | $podhealth=@() 5 | foreach ($pod in $pods) { 6 | $endpoints=$services1.podhealth.podhealth_get($pod.id).data.endpointhealth 7 | foreach ($endpoint in $endpoints){ 8 | $podhealth+= New-Object PSObject -Property @{ 9 | "Remote_POD_Name" = $services1.pod.pod_get($pod.id) | select -expandproperty DisplayName 10 | "Endpoint_Name" = $endpoint.EndpointInfo | select -expandproperty Name; 11 | "Endpoint_Address" = $endpoint.EndpointInfo | select -expandproperty ServerAddress; 12 | "Status" = $endpoint.EndpointInfo | select -expandproperty Enabled; 13 | "State" = $endpoint | select -expandproperty State; 14 | "Roundtrip_Time" = $endpoint | select -expandproperty RoundTripTime; 15 | } 16 | } 17 | } 18 | $podhealth | select Remote_POD_Name,Endpoint_Name,Endpoint_Address,Status,State,Roundtrip_Time 19 | 20 | $Title = "VDI Remote Pod Health" 21 | $Header = "VDI Remote Pod Health" 22 | $Comments = "This is the check to see if all Pod Communications are ok" 23 | $Display = "Table" 24 | $Author = "Wouter Kursten" 25 | $PluginVersion = 0.1.2 26 | $PluginCategory = "View" 27 | -------------------------------------------------------------------------------- /Plugins/40 RDS/01 RDS Farm information.ps1: -------------------------------------------------------------------------------- 1 | # Start of Settings 2 | # End of Settings 3 | 4 | 5 | $Farmoverview=@() 6 | 7 | foreach ($farm in $farms){ 8 | $farmstatus=$farm.data.Enabled 9 | 10 | $ProvisioningStatus=$farm.AutomatedFarmData.VirtualCenterProvisioningSettings.enableprovisioning 11 | $source=$farm.source 12 | $type=$farm.type 13 | switch ($farmstatus){ 14 | $True {$farmstatusoutput="Enabled"} 15 | $False {$farmstatusoutput="Disabled"} 16 | default {$farmstatusoutput="No farm Status available"} 17 | } 18 | 19 | if ($type -eq "AUTOMATED"){ 20 | switch ($ProvisioningStatus){ 21 | $True {$ProvisioningStatusoutput="Enabled"} 22 | $False {$ProvisioningStatusoutput="Disabled"} 23 | default {$ProvisioningStatusoutput="No farm Provisioning Status available"} 24 | } 25 | 26 | switch ($source){ 27 | VIEW_COMPOSER {$sourceoutput="Linked Clones"} 28 | INSTANT_CLONE_ENGINE {$sourceoutput="Instant Clones"} 29 | default {$sourceoutput="No Source data available"} 30 | } 31 | 32 | $farmoverview+=New-Object PSObject -Property @{ 33 | "Name" = $farm.data.name; 34 | "Displayname" = $farm.data.DisplayName; 35 | "Description" = $farm.data.Description; 36 | "Status" = $farmstatusoutput; 37 | "Provisioning" = $ProvisioningStatusoutput; 38 | "Type" = $farm.type; 39 | "Source" = $sourceoutput; 40 | "Max_Host_Sessions"=$farm.AutomatedFarmData.RdsServerMaxSessionsData.maxsessions; 41 | "Max_Host_Session_Type"=$farm.AutomatedFarmData.RdsServerMaxSessionsData.maxsessionstype; 42 | } 43 | } 44 | 45 | elseif ($farm.type -eq "MANUAL"){ 46 | $farmoverview+=New-Object PSObject -Property @{ 47 | "Name" = $farm.base.name; 48 | "Displayname" = $farm.base.DisplayName; 49 | "Description" = $farm.base.Description; 50 | "Status" = $farmstatusoutput; 51 | "Provisioning" = $ProvisioningStatusoutput; 52 | "Type" = $farm.type; 53 | "Max_Host_Sessions"=$farm.AutomatedFarmData.RdsServerMaxSessionsData.maxsessions; 54 | "Max_Host_Session_Type"=$farm.AutomatedFarmData.RdsServerMaxSessionsData.maxsessionstype; 55 | } 56 | } 57 | } 58 | $farmoverview | select-object Name,Displayname,Description,Status,Provisioning,Type,Source,Max_Host_Session_Type,Max_Host_Sessions 59 | 60 | $Title = "RDS Farm Overview" 61 | $Header = "RDS Farm Overview" 62 | $Comments = "These shows the configuration for the RDS farms" 63 | $Display = "Table" 64 | $Author = "Wouter Kursten" 65 | $PluginVersion = 0.1 66 | $PluginCategory = "View" 67 | -------------------------------------------------------------------------------- /Plugins/40 RDS/10 Automated Farm Wrong Snapshot.ps1: -------------------------------------------------------------------------------- 1 | # Start of Settings 2 | # End of Settings 3 | 4 | 5 | $wrongsnapdesktops=@() 6 | foreach ($farm in $farms){ 7 | $farmname=$farm.data.name 8 | 9 | if ($farm.type -like "*automated*"){ 10 | $farmID=($farm).id 11 | $queryservice=new-object vmware.hv.queryserviceservice 12 | $defn = New-Object VMware.Hv.QueryDefinition 13 | $defn.queryentitytype='RDSServerInfo' 14 | 15 | $defn.filter = New-Object VMware.Hv.QueryFilterEquals -Property @{ 'memberName' = 'base.farm'; 'value' = $farm.ID } 16 | 17 | $queryResults = $queryService.QueryService_Create($Services1, $defn) 18 | #$farmmachines=$services1.machine.machine_getinfos($queryResults.results.id) 19 | $farmmachines=$queryResults.Results 20 | if ($farmmachines.count -ge 1){ 21 | $wrongsnaps=$farmmachines | where {$_.rdsservermaintenancedata.baseimagesnapshotpath -notlike $farm.automatedfarmdata.VirtualCenternamesdata.snapshotpath -OR $_.rdsservermaintenancedata.baseimagepath -notlike $farm.automatedfarmdata.VirtualCenternamesdata.parentvmpath} 22 | if ($wrongsnaps){ 23 | foreach ($wrongsnap in $wrongsnaps){ 24 | $wrongsnapdesktops+= New-Object PSObject -Property @{ 25 | "RDS Name" = $wrongsnap.base.name; 26 | "VM Snapshot" = $wrongsnap.rdsservermaintenancedata.baseimagesnapshotpath; 27 | "VM GI" = $wrongsnap.rdsservermaintenancedata.baseimagepath; 28 | "Farm Snapshot" = $farm.automatedfarmdata.VirtualCenternamesdata.snapshotpath; 29 | "Farm GI" = $farm.automatedfarmdata.VirtualCenternamesdata.parentvmpath; 30 | } 31 | } 32 | 33 | } 34 | $services1.QueryService.QueryService_DeleteAll() 35 | } 36 | } 37 | } 38 | $wrongsnapdesktops 39 | 40 | $Title = "RDS Farms based on wrong snapshot" 41 | $Header = "RDS Farms based on wrong snapshot" 42 | $Comments = "These farms have not been rebuild with the correct Golden Image Snapshot" 43 | $Display = "Table" 44 | $Author = "Wouter Kursten" 45 | $PluginVersion = 0.1 46 | $PluginCategory = "View" 47 | -------------------------------------------------------------------------------- /Plugins/40 RDS/21 RDS Farm health.ps1: -------------------------------------------------------------------------------- 1 | # Start of Settings 2 | # End of Settings 3 | 4 | 5 | $farmhealth=@() 6 | 7 | foreach ($farm in $farms){ 8 | $health=$services1.Farmhealth.farmhealth_get($farm.id) 9 | $farmhealthstatus=$health.health 10 | $farmname=$farm.data.name 11 | foreach ($rdsserver in $health.RdsServerHealth){ 12 | $missingapps=$null 13 | if ($rdsserver.missingapplications){ 14 | $missingapps = [system.String]::Join(",", $rdsserver.missingapplications.name) 15 | } 16 | $farmhealth+=New-Object PSObject -Property @{ 17 | "FarmName" = $farmname; 18 | "FarmHealth" = $farmhealthstatus; 19 | "RDS_Hostname" = $rdsserver.name; 20 | "RDS_Status" = $rdsserver.status; 21 | "RDS_health" = $rdsserver.health; 22 | "RDS_Available" = $rdsserver.available; 23 | "RDS_Missing_Apps" = $missingapps; 24 | "RDS_LoadPreference" = $rdsserver.LoadPreference; 25 | } 26 | } 27 | } 28 | 29 | 30 | $farmhealth | select-object FarmName,Farmhealth,RDS_Hostname,RDS_Status,RDS_health,RDS_Available,RDS_Missing_Apps,RDS_LoadPreference 31 | 32 | $Title = "RDS Farm Health" 33 | $Header = "RDS Farm Health" 34 | $Comments = "This is an overview of the health of the RDS farms" 35 | $Display = "Table" 36 | $Author = "Wouter Kursten" 37 | $PluginVersion = 0.3 38 | $PluginCategory = "View" -------------------------------------------------------------------------------- /Plugins/90 Various/01 AD Domain Health Status.ps1: -------------------------------------------------------------------------------- 1 | # Start of Settings 2 | # End of Settings 3 | 4 | $ADDomainHealthoverview=@() 5 | $ADDomainHealthlist=$services1.ADDomainHealth.ADDomainHealth_list() 6 | foreach ($ADDomainHealth in $ADDomainHealthlist){ 7 | $netBiosName=$ADDomainHealth.netBiosName 8 | $dnsName=$ADDomainHealth.dnsName 9 | $nt4Domain=$ADDomainHealth.nt4Domain 10 | foreach ($Connectionserver in $ADDomainHealth.connectionServerState){ 11 | $ADDomainHealthoverview+=New-Object PSObject -Property @{ 12 | "netBiosName" = $netBiosName; 13 | "dnsName"=$dnsName; 14 | "nt4Domain"=$nt4Domain; 15 | "Connectionserver" = $Connectionserver.connectionServerName; 16 | "Status" = $Connectionserver.Status; 17 | "trustRelationship" = $Connectionserver.trustRelationship; 18 | "contactable" = $Connectionserver.contactable; 19 | } 20 | } 21 | } 22 | 23 | $ADDomainHealthoverview | select-object netBiosName,dnsName,nt4Domain,Connectionserver,Status,trustRelationship,contactable 24 | 25 | $Title = "AD Domain Health Overview" 26 | $Header = "AD Domain Health Overview" 27 | $Comments = "This shows the health for every AD Domain from every connectionserver." 28 | $Display = "Table" 29 | $Author = "Wouter Kursten" 30 | $PluginVersion = 0.1 31 | $PluginCategory = "View" 32 | -------------------------------------------------------------------------------- /Plugins/90 Various/01 SAML Health Status.ps1: -------------------------------------------------------------------------------- 1 | # Start of Settings 2 | # End of Settings 3 | 4 | $SAMLHealthoverview=@() 5 | $SAMLAuthenticatorhealthlist=$services1.SAMLAuthenticatorHealth.SAMLAuthenticatorHealth_list() 6 | foreach ($SAMLAuthenticatorhealth in $SAMLAuthenticatorhealthlist){ 7 | $label=$SAMLAuthenticatorhealth.data.label 8 | $metadataurl=$SAMLAuthenticatorhealth.data.metadataURL 9 | foreach ($Connectionserver in $SAMLAuthenticatorhealth.connectionServerData){ 10 | $SAMLHealthoverview+=New-Object PSObject -Property @{ 11 | "Label" = $label; 12 | "metadataurl"=$metadataurl; 13 | "Connectionserver" = $Connectionserver.name; 14 | "Status" = $Connectionserver.Status; 15 | "Errormessage" = $Connectionserver.Error; 16 | "thumbprintAccepted" = $Connectionserver.thumbprintAccepted; 17 | "Certificate_Valid" = $Connectionserver.certificateHealth.valid; 18 | "Certificate_startTime" = $Connectionserver.certificateHealth.startTime; 19 | "Certificate_invalidReason" = $Connectionserver.certificateHealth.invalidReason; 20 | } 21 | } 22 | } 23 | 24 | $SAMLHealthoverview | select-object Label,metadataurl,Connectionserver,Status,Errormessage,thumbprintAccepted,Certificate_Valid,Certificate_startTime,Certificate_invalidReason 25 | 26 | $Title = "SAML Connection Health Overview" 27 | $Header = "SAML Connection Health Overview" 28 | $Comments = "This shows the health for every saml connection on every connectionserver." 29 | $Display = "Table" 30 | $Author = "Wouter Kursten" 31 | $PluginVersion = 0.3 32 | $PluginCategory = "View" 33 | -------------------------------------------------------------------------------- /Plugins/90 Various/02 vCenter Health Status.ps1: -------------------------------------------------------------------------------- 1 | # Start of Settings 2 | # End of Settings 3 | 4 | $VirtualCenterHealthoverview=@() 5 | $VirtualCenterHealthlist=$services1.VirtualCenterHealth.VirtualCenterHealth_list() 6 | foreach ($VirtualCenterHealth in $VirtualCenterHealthlist){ 7 | $Name=$VirtualCenterHealth.data.name 8 | $version=$VirtualCenterHealth.data.version 9 | $build=$VirtualCenterHealth.data.build 10 | $apiVersion=$VirtualCenterHealth.data.apiVersion 11 | foreach ($Connectionserver in $VirtualCenterHealth.connectionServerData){ 12 | $VirtualCenterHealthoverview+=New-Object PSObject -Property @{ 13 | "name" = $Name; 14 | "version"=$version; 15 | "build"=$build; 16 | "apiVersion"=$apiVersion; 17 | "Connectionserver" = $Connectionserver.name; 18 | "Status" = $Connectionserver.Status; 19 | "thumbprintAccepted" = $Connectionserver.thumbprintAccepted; 20 | "certificateHealth" = $Connectionserver.certificateHealth; 21 | } 22 | } 23 | } 24 | 25 | $VirtualCenterHealthoverview | select-object name,version,build,apiVersion,Connectionserver,Status,thumbprintAccepted,certificateHealth 26 | 27 | $Title = "vCenter Health Overview" 28 | $Header = "vCenter Health Overview" 29 | $Comments = "This shows the health for every vCenter from every connectionserver." 30 | $Display = "Table" 31 | $Author = "Wouter Kursten" 32 | $PluginVersion = 0.1 33 | $PluginCategory = "View" 34 | -------------------------------------------------------------------------------- /Plugins/90 Various/03 ESXi Host Health Status.ps1: -------------------------------------------------------------------------------- 1 | # Start of Settings 2 | # End of Settings 3 | 4 | $ESXiHealthoverview=@() 5 | $VirtualCenterHealthlist=$services1.VirtualCenterHealth.VirtualCenterHealth_list() 6 | foreach ($VirtualCenterHealth in $VirtualCenterHealthlist){ 7 | $Name=$VirtualCenterHealth.data.name 8 | foreach ($ESXiHost in $VirtualCenterHealth.hostData){ 9 | if ($esxihost.vGPUTypes){ 10 | $vGPUTypes= [system.String]::Join(",", $ESXiHost.vGPUTypes) 11 | } 12 | else{ 13 | $vGPUTypes="n/a" 14 | } 15 | $ESXiHealthoverview+=New-Object PSObject -Property @{ 16 | "Name" = $Name; 17 | "ESXi_Host" = $ESXiHost.name; 18 | "Version" = $ESXiHost.version; 19 | "apiVersion" = $ESXiHost.apiVersion; 20 | "status" = $ESXiHost.status; 21 | "clusterName" = $ESXiHost.clusterName; 22 | "vGPUTypes" = $vGPUTypes; 23 | } 24 | } 25 | } 26 | 27 | $ESXiHealthoverview | select-object Name,ESXi_Host,Version,apiVersion,Status,clusterName,vGPUTypes | sort-object name,Clustername,ESXi_Host 28 | 29 | $Title = "ESXi Host Health Overview" 30 | $Header = "ESXi Host Health Overview" 31 | $Comments = "This shows the health for every ESXi host." 32 | $Display = "Table" 33 | $Author = "Wouter Kursten" 34 | $PluginVersion = 0.1 35 | $PluginCategory = "View" 36 | -------------------------------------------------------------------------------- /Plugins/90 Various/04 Datastore Health Status.ps1: -------------------------------------------------------------------------------- 1 | # Start of Settings 2 | # End of Settings 3 | 4 | $DatastoreHealthoverview=@() 5 | $VirtualCenterHealthlist=$services1.VirtualCenterHealth.VirtualCenterHealth_list() 6 | foreach ($VirtualCenterHealth in $VirtualCenterHealthlist){ 7 | $Name=$VirtualCenterHealth.data.name 8 | foreach ($Datastore in $VirtualCenterHealth.datastoreData){ 9 | $DatastoreHealthoverview+=New-Object PSObject -Property @{ 10 | "Name" = $Name; 11 | "Datastore" = $Datastore.name; 12 | "accessible" = $Datastore.accessible; 13 | "path" = $Datastore.path; 14 | "datastoreType" = $Datastore.datastoreType; 15 | "capacityMB" = $Datastore.capacityMB; 16 | "freeSpaceMB" = $Datastore.freeSpaceMB; 17 | } 18 | } 19 | } 20 | 21 | $DatastoreHealthoverview | select-object Name,Datastore,accessible,path,datastoreType,capacityMB,freeSpaceMB | sort-object name 22 | 23 | $Title = "Datastore Health Overview" 24 | $Header = "Datastore Health Overview" 25 | $Comments = "This shows the health for every datastore." 26 | $Display = "Table" 27 | $Author = "Wouter Kursten" 28 | $PluginVersion = 0.1 29 | $PluginCategory = "View" 30 | -------------------------------------------------------------------------------- /Plugins/90 Various/05 Gateway Health Status.ps1: -------------------------------------------------------------------------------- 1 | # Start of Settings 2 | # End of Settings 3 | 4 | function Get-HVUAGGatewayZone { 5 | param ( 6 | [parameter(Mandatory = $true, 7 | HelpMessage = "Boolean for the GatewayZoneInternal property of the GatewayHealth.")] 8 | [bool]$GatewayZoneInternal 9 | ) 10 | try { 11 | if ($GatewayZoneInternal -eq $False) { 12 | $GatewayZoneType="External" 13 | } 14 | elseif ($GatewayZoneInternal -eq $True) { 15 | $GatewayZoneType="Internal" 16 | } 17 | # Return the results 18 | return $GatewayZoneType 19 | } 20 | catch { 21 | write-error -Message 'There was a problem determining the gateway zone type.' -Exception $_ 22 | } 23 | } 24 | 25 | function Get-HVGatewayType { 26 | param ( 27 | [parameter(Mandatory = $true, 28 | HelpMessage = "Boolean for the GatewayZoneInternal property of the GatewayHealth.")] 29 | [string]$HVGatewayType 30 | ) 31 | try { 32 | if ($HVGatewayType -eq "AP") { 33 | $GatewayType="UAG" 34 | } 35 | elseif ($HVGatewayType -eq "F5") { 36 | $GatewayType="F5 Load Balancer" 37 | } 38 | elseif ($HVGatewayType -eq "SG") { 39 | $GatewayType="Security Server" 40 | } 41 | elseif ($HVGatewayType -eq "SG-cohosted") { 42 | $GatewayType="Cohosted CS" 43 | } 44 | elseif ($HVGatewayType -eq "Unknown") { 45 | $GatewayType="Unknownr" 46 | } 47 | # Return the results 48 | return $GatewayType 49 | } 50 | catch { 51 | write-error -Message 'There was a problem determining the gateway type.' -Exception $_ 52 | } 53 | } 54 | 55 | $uaghealthstatuslist=@() 56 | [array]$uaglist=$services1.Gateway.Gateway_List() 57 | foreach ($uag in $uaglist){ 58 | [VMware.Hv.GatewayHealthInfo]$uaghealth=$services1.GatewayHealth.GatewayHealth_Get($uag.id) 59 | $lastcontact = (([System.DateTimeOffset]::FromUnixTimeMilliSeconds(($uaghealth.LastUpdatedTimestamp)).DateTime).ToString("s")) 60 | $uaghealthstatuslist+=New-Object PSObject -Property @{ 61 | "Podname" = $podname; 62 | "Gateway_Name" = $uaghealth.name; 63 | "Gateway_Address" = $uaghealth.name; 64 | "Gateway_GatewayZone" = (Get-HVUAGGatewayZone -GatewayZoneInternal ($uaghealth.GatewayZoneInternal)); 65 | "Gateway_Version" = $uaghealth.Version; 66 | "Gateway_Type" = (Get-HVGatewayType -HVGatewayType ($uaghealth.type)); 67 | "Gateway_Active" = $uaghealth.GatewayStatusActive; 68 | "Gateway_Stale" = $uaghealth.GatewayStatusStale; 69 | "Gateway_Contacted" = $uaghealth.GatewayContacted; 70 | "Gateway_Active_Connections" = $uaghealth.ConnectionData.NumActiveConnections; 71 | "Gateway_Blast_Connections" = $uaghealth.ConnectionData.NumBlastConnections; 72 | "Gateway_PCOIP_Connections" = $uaghealth.ConnectionData.NumPcoipConnections; 73 | "Gateway_Last_Contact" = $lastcontact; 74 | } 75 | } 76 | 77 | 78 | $uaghealthstatuslist | select-object Podname,Gateway_Name,Gateway_Address,Gateway_GatewayZone,Gateway_Version,Gateway_Type,Gateway_Active,Gateway_Stale,Gateway_Contacted,Gateway_Active_Connections,Gateway_Blast_Connections,Gateway_PCOIP_Connections,Gateway_Last_Contact | sort-object Gateway_Name 79 | 80 | $Title = "Gateway Health Overview" 81 | $Header = "Gateway Health Overview" 82 | $Comments = "This shows the health for all conected Gateways." 83 | $Display = "Table" 84 | $Author = "Wouter Kursten" 85 | $PluginVersion = 0.1 86 | $PluginCategory = "View" -------------------------------------------------------------------------------- /Plugins/90 Various/25 Certificate SSO Connector Health Status.ps1: -------------------------------------------------------------------------------- 1 | # Start of Settings 2 | # End of Settings 3 | 4 | $CertificateSSOConnectorHealthoverview=@() 5 | $CertificateSSOConnectorHealthlist=$services1.CertificateSSOConnectorHealth.CertificateSSOConnectorHealth_list() 6 | foreach ($CertificateSSOConnectorHealth in $CertificateSSOConnectorHealthlist){ 7 | $Primary_enrollmentServer_stateReasons= [system.String]::Join(",", $CertificateSSOConnectorHealth.data.primaryEnrollmentServerHealth.stateReasons) 8 | $Secondary_enrollmentServer_stateReasons= [system.String]::Join(",", $CertificateSSOConnectorHealth.data.secondaryEnrollmentServerHealth.stateReasons) 9 | $CertificateSSOConnectorHealthoverview+=New-Object PSObject -Property @{ 10 | "Displayname" = $CertificateSSOConnectorHealth.displayName; 11 | "enabled" = $CertificateSSOConnectorHealth.enabled; 12 | "overallState"= $CertificateSSOConnectorHealth.data.overallState; 13 | "Primary_enrollmentServer" = $CertificateSSOConnectorHealth.data.primaryEnrollmentServerHealth.dnsName; 14 | "Primary_enrollmentServer_State" = $CertificateSSOConnectorHealth.data.primaryEnrollmentServerHealth.state; 15 | "Primary_enrollmentServer_stateReasons" = $Primary_enrollmentServer_stateReasons; 16 | "Secondary_enrollmentServer" = $CertificateSSOConnectorHealth.data.primaryEnrollmentServerHealth.dnsName; 17 | "Secondary_enrollmentServer_State" = $CertificateSSOConnectorHealth.data.primaryEnrollmentServerHealth.state; 18 | "Secondary_enrollmentServer_stateReasons" = $Secondary_enrollmentServer_stateReasons; 19 | } 20 | } 21 | 22 | $CertificateSSOConnectorHealthoverview | select-object Displayname,enabled,overallState,Primary_enrollmentServer,Primary_enrollmentServer_State,Primary_enrollmentServer_stateReasons,Secondary_enrollmentServer,Secondary_enrollmentServer_State,Secondary_enrollmentServer_stateReasons 23 | 24 | $Title = "True SSO Connector Health Overview" 25 | $Header = "True SSO Connector Health Overview" 26 | $Comments = "This shows the health for every True SSO Connector." 27 | $Display = "Table" 28 | $Author = "Wouter Kursten" 29 | $PluginVersion = 0.1 30 | $PluginCategory = "View" -------------------------------------------------------------------------------- /Plugins/90 Various/26 Certificate SSO Domain Health Status.ps1: -------------------------------------------------------------------------------- 1 | # Start of Settings 2 | # End of Settings 3 | 4 | $CertificateSSODomainHealthoverview=@() 5 | $CertificateSSODomainHealthlist=$services1.CertificateSSOConnectorHealth.CertificateSSOConnectorHealth_list() 6 | foreach ($CertificateSSODomainHealth in $CertificateSSODomainHealthlist){ 7 | $Primary_enrollmentServer_stateReasons= [system.String]::Join(",", $CertificateSSODomainHealth.data.domainHealth.primaryEnrollmentServerStateReasons) 8 | $Secondary_enrollmentServer_stateReasons= [system.String]::Join(",", $CertificateSSODomainHealth.data.domainHealth.secondaryEnrollmentServerStateReasons) 9 | $CertificateSSODomainHealthoverview+=New-Object PSObject -Property @{ 10 | "ConnectorName" = $CertificateSSODomainHealth.displayName; 11 | "domain" = $CertificateSSODomainHealth.data.domainHealth.domain; 12 | "dnsName" = $CertificateSSODomainHealth.data.domainHealth.dnsName; 13 | "state" = $CertificateSSODomainHealth.data.domainHealth.state; 14 | "Primary_enrollmentServer_stateReasons" = $Primary_enrollmentServer_stateReasons; 15 | "Secondary_enrollmentServer_stateReasons" = $Secondary_enrollmentServer_stateReasons; 16 | } 17 | } 18 | 19 | $CertificateSSODomainHealthoverview | select-object ConnectorName,domain,dnsName,state,Primary_enrollmentServer_stateReasons,Secondary_enrollmentServer_stateReasons 20 | 21 | $Title = "True SSO Connector Domain Health Overview" 22 | $Header = "True SSO Connector Domain Health Overview" 23 | $Comments = "This shows the health for every True SSO Connector Domain." 24 | $Display = "Table" 25 | $Author = "Wouter Kursten" 26 | $PluginVersion = 0.1 27 | $PluginCategory = "View" -------------------------------------------------------------------------------- /Plugins/90 Various/27 Certificate SSO Template Health Status.ps1: -------------------------------------------------------------------------------- 1 | # Start of Settings 2 | # End of Settings 3 | 4 | $CertificateSSOTemplateHealthoverview=@() 5 | $CertificateSSOTemplateHealthlist=$services1.CertificateSSOConnectorHealth.CertificateSSOConnectorHealth_list() 6 | foreach ($CertificateSSOTemplateHealth in $CertificateSSOTemplateHealthlist){ 7 | $Primary_enrollmentServer_stateReasons= [system.String]::Join(",", $CertificateSSOTemplateHealth.data.TemplateHealth.primaryEnrollmentServerStateReasons) 8 | $Secondary_enrollmentServer_stateReasons= [system.String]::Join(",", $CertificateSSOTemplateHealth.data.TemplateHealth.secondaryEnrollmentServerStateReasons) 9 | $CertificateSSOTemplateHealthoverview+=New-Object PSObject -Property @{ 10 | "ConnectorName" = $CertificateSSOTemplateHealth.displayName; 11 | "Name" = $CertificateSSOTemplateHealth.data.TemplateHealth.name; 12 | "state" = $CertificateSSOTemplateHealth.data.TemplateHealth.state; 13 | "Primary_enrollmentServer_stateReasons" = $Primary_enrollmentServer_stateReasons; 14 | "Secondary_enrollmentServer_stateReasons" = $Secondary_enrollmentServer_stateReasons; 15 | } 16 | } 17 | 18 | $CertificateSSOTemplateHealthoverview | select-object ConnectorName,Name,State,Primary_enrollmentServer_stateReasons,Secondary_enrollmentServer_stateReasons 19 | 20 | $Title = "True SSO Connector Template Health Overview" 21 | $Header = "True SSO Connector Template Health Overview" 22 | $Comments = "This shows the health for every True SSO Connector Template." 23 | $Display = "Table" 24 | $Author = "Wouter Kursten" 25 | $PluginVersion = 0.1 26 | $PluginCategory = "View" -------------------------------------------------------------------------------- /Plugins/90 Various/28 Certificate SSO Certificate Server Health Status.ps1: -------------------------------------------------------------------------------- 1 | # Start of Settings 2 | # End of Settings 3 | 4 | $CertificateSSOcertificateServerHealthoverview=@() 5 | $CertificateSSOconnectorHealthlist=$services1.CertificateSSOConnectorHealth.CertificateSSOConnectorHealth_list() 6 | foreach ($CertificateSSOconnectorHealth in $CertificateSSOconnectorHealthlist){ 7 | $connectorname=$CertificateSSOcertificateServerHealth.displayName 8 | foreach ($certificateserverhealth in $CertificateSSOconnectorHealth.data.certificateServerHealths){ 9 | $Primary_enrollmentServer_stateReasons= [system.String]::Join(",", $certificateserverhealth.primaryEnrollmentServerStateReasons) 10 | $Secondary_enrollmentServer_stateReasons= [system.String]::Join(",", $certificateserverhealth.secondaryEnrollmentServerStateReasons) 11 | $CertificateSSOcertificateServerHealthoverview+=New-Object PSObject -Property @{ 12 | "ConnectorName" = connectorname; 13 | "Name" = $certificateserverhealth.name; 14 | "state" = $certificateserverhealth.state; 15 | "Primary_enrollmentServer_stateReasons" = $Primary_enrollmentServer_stateReasons; 16 | "Secondary_enrollmentServer_stateReasons" = $Secondary_enrollmentServer_stateReasons; 17 | } 18 | } 19 | } 20 | 21 | $CertificateSSOcertificateServerHealthoverview | select-object ConnectorName,Name,State,Primary_enrollmentServer_stateReasons,Secondary_enrollmentServer_stateReasons 22 | 23 | $Title = "True SSO Connector certificate Server Health Overview" 24 | $Header = "True SSO Connector certificate Server Health Overview" 25 | $Comments = "This shows the health for every True SSO Connector certificate Server." 26 | $Display = "Table" 27 | $Author = "Wouter Kursten" 28 | $PluginVersion = 0.1 29 | $PluginCategory = "View" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | 6 | 7 | # vCheck Daily Report for HorizonView 8 | 9 | 10 | 11 | [Join the VMware Code and #vCheck channel on slack and ask questions here!](https://code.vmware.com/slack/) 12 | 13 | |Navigation| 14 | |-----------------| 15 | |[What is vCheck-HorizonView](#About)| 16 | |[About](#About)| 17 | |[Features](#Features)| 18 | |[Installing](#Installing)| 19 | |[Enhancements](#Enhancements)| 20 | |[Release Notes](#ReleaseNotes)| 21 | |[Contributing](#Contributing)| 22 | |[Plugins](#Plugins)| 23 | |[Styles](#Styles)| 24 | |[Jobs & Settings](#JobsSettings)| 25 | |[More Info](#More)| 26 | 27 | 28 | 29 | 30 | # What is vCheck-View? # 31 | vCheck-HorizonView is an View-focussed reporting tool that can give you a periodic (i.e. Daily) look into the health of your platform. 32 | * This tools is based on the awesome [vCheck-vSphere](https://github.com/alanrenouf/vCheck-vSphere) project 33 | * Initially developed against SQL 2014, vSphere 6.0, PowerCLI 6.5 and View 7.0.2 34 | * This report REQUIRES [PowerCLI](https://www.vmware.com/support/developer/PowerCLI/) and [vmware.hv.helper](https://github.com/vmware/PowerCLI-Example-Scripts) installation 35 | * This plugin also REQUIRES a connection to the SQL server for the event database 36 | * If you have multiple Horizon View environments , create multiple copies of the vCheck-HorizonView folder 37 | 38 | 39 | 40 | # About vCheck 41 | [*Back to top*](#Title) 42 | 43 | vCheck is a PowerShell HTML framework script, the script is designed to run as a scheduled task before you get into the office to present you with key information via an email directly to your inbox in a nice easily readable format. 44 | 45 | This script picks on the key known issues and potential issues scripted as plugins for various technologies written as Powershell scripts and reports it all in one place so all you do in the morning is check your email. 46 | 47 | One of they key things about this report is if there is no issue in a particular place you will not receive that section in the email, for example if there are no datastores with less than 5% free space (configurable) then the disk space section in the virtual infrastructure version of this script, it will not show in the email, this ensures that you have only the information you need in front of you when you get into the office. 48 | 49 | This script is not to be confused with an Audit script, although the reporting framework can also be used for auditing scripts too. I don't want to remind you that you have 5 hosts and what there names are and how many CPUs they have each and every day as you don't want to read that kind of information unless you need it, this script will only tell you about problem areas with your infrastructure. 50 | 51 | 52 | 53 | 54 | # What is checked for in the Horizon ? 55 | [*Back to top*](#Title) 56 | 57 | The following items are included as part of the vCheck NSX download, they are included as vCheck Plugins and can be removed or altered very easily by editing the specific plugin file which contains the data. vCheck Plugins are found under the Plugins folder. 58 | 59 | For each check that's written, here's a brief description of what it does. 60 | 61 | *** 62 | ## Connection Plugin for Horizon ## 63 | ### Function ### 64 | * Uses the hvcs_credentials.txt file to connect to the configured Connection Broker and polls all pools. 65 | 66 | 67 | 68 | 69 | 70 | 71 | # Installing 72 | [*Back to top*](#Title) 73 | 74 | Download this project and copy the vCheck folder to the desired location, note the content structure, in particular the Plugins folder. 75 | 76 | Before you can use vCheck-HorizonView, you need to create an HorizonView password txt file. 77 | 78 | Use the following to create a password txt file 79 | 80 | ``` 81 | Read-Host -AsSecureString | ConvertFrom-SecureString | Out-File 'hvcs_Credentials.txt' 82 | 83 | ``` 84 | 85 | This file will need to be created where the script runs. 86 | 87 | Also in the connection plugin the Connection server, username and domainname need to be set. 88 | The username used for the eventdatabase is the one actually configured in your Horizon View environment. 89 | 90 | Once the files are in the correct location and the credentials XML file has been created, you need to start the installer. 91 | Open a PowerCLI window **AS AN ADMINISTRATOR** then run... 92 | ` vCheck.ps1 -config` 93 | ...which will start the process of configuring your install. 94 | 95 | 96 | 97 | # Enhancements 98 | [*Back to top*](#Title) 99 | 100 | 101 | In the meantime, don't hesitate to pop over to the [#vCheck channel on slack](https://code.vmware.com/slack/) and join in on active conversations about anything you see- or don't see- here! 102 | 103 | # Credentials 104 | [*Back to top*](#Title) 105 | The credentials file can be created using the following: 106 | * $creds = get-credential 107 | * $creds | export-clixml c:\path\to\credsfile.xml 108 | 109 | # Settings 110 | [*Back to top*](#Title) 111 | 112 | edit the 00 Initialize\00 Connection Plugin for View.ps1 file with the following 113 | * $server = connectionserver.fqdn.com 114 | * $credfile = c:\path\to\credsfile.xml 115 | 116 | 117 | 118 | # Changelog 119 | [*Back to top*](#Title) 120 | 121 | * 06-05-2021 - replaced the password only credentials file with a credetials file holding both username and password 122 | 123 | 124 | 125 | 126 | # Contributing 127 | [*Back to top*](#Title) 128 | 129 | See out [Contributions](CONTRIBUTING.md) guidelines 130 | 131 | 132 | 133 | # Plugins 134 | [*Back to top*](#Title) 135 | 136 | ## Suggested Plugins for Development ## 137 | * Dashboard error status 138 | * Desktops with error (non-standard) status 139 | * Compare the Snapshots that have been set to the ones actually used on desktops to see if recompose might not have run 140 | * relation between Composer and vCenter 141 | * last use time for dedicated desktops 142 | * Event Database status 143 | * Connection,composer,security server status 144 | * Information and status about the various desktop pool types 145 | * RDS farm status 146 | 147 | Some of the above suggestions might have been taken from a [VMTN post](https://communities.vmware.com/message/2673396) 148 | 149 | ## Plugin Structure 150 | This section describes the basic structure of a vCheck plugin so that you can write your own plugins for either private use, or to contribute to the vCheck project. 151 | 152 | ### Settings 153 | Your plugin must contain a section for settings. This may be blank, or may contain one or more variables that must be defined for your plugin to determine how it operates. 154 | 155 | **Examples** 156 | 157 | No Settings 158 | ``` 159 | # Start of Settings 160 | # End of Settings 161 | ``` 162 | 163 | Settings to define two variables 164 | ``` 165 | # Start of Settings 166 | # Comment - presented as part of the setup wizard 167 | $variable = "value" 168 | # Second variable 169 | $variable2 = "value2" 170 | ... 171 | # End of Settings 172 | ``` 173 | 174 | ### Required variables 175 | Each plugin **must** define the following variables: 176 | $Title - The display name of the plugin 177 | $Header - the header of the plugin in the report 178 | $Display - The format of the plugin (See Content section) 179 | $Author - The author's name 180 | $PluginVersion - Version of the plugin 181 | $PluginCategory - The Category of the plugin 182 | 183 | ### Content 184 | #### Report output 185 | Anything that is written to stdout is included in the report. This should be either an object or hashtable in order to generate the report information. 186 | 187 | #### $Display variable 188 | - List 189 | - Table 190 | - Chart - Not currently merged to master 191 | 192 | #### Plugin Template 193 | ``` 194 | # Start of Settings 195 | # End of Settings 196 | 197 | # generate your report content here. Simple placeholder hashtable for the sake of example 198 | @{"Plugin"="Awesome"} 199 | 200 | $Title = "Plugin Template" 201 | $Header = "Plugin Template" 202 | $Comments = "Comment about this awesome plugin" 203 | $Display = "List" 204 | $Author = "Plugin Author" 205 | $PluginVersion = 1.0 206 | $PluginCategory = "View" 207 | ``` 208 | ## Table Formatting 209 | Since v6.16, vCheck has supported Table formatting rules in plugins. This allows you to define a set of rules for data, in order to provide more richly formatted HTML reports. 210 | 211 | ### Using Formatting Rules 212 | 213 | To use formatting rules, a `$TableFormat` variable must be defined in the module. 214 | 215 | The `$TableFormat` variable is a Hastable, with the key being the "column" of the table that the rule should apply to. 216 | 217 | The Value of the Hashtable is an array of rule. Each rule is a hashtable, with the Key being the expression to evaluate, and the value containing the formatting options. 218 | 219 | ### Formatting options 220 | 221 | The Formatting options are made up of two comma-separated values: 222 | 1. The scope of the formatting rule - "Row" to apply to the entire row, or "Cell" to only apply to that particular cell. 223 | 2. A pipe-separated HTML attribute, and value. E.g. class|green to apply the "green" class to the HTML element specified in number 1. 224 | 225 | ### Examples 226 | 227 | #### Example 1 228 | 229 | Assume you have a CSS class named "green", which you want to apply to any compliant objects. Similarly, you have a "red" class that you wish to use to highlight non-compliant objects. We would define the formatting rules as follows: 230 | 231 | `$TableFormat = @{"Compliant" = @(@{ "-eq $true" = "Cell,class|green"; }, @{ "-eq$false" = "Cell,class|red" })}` 232 | 233 | Here we can see two rules; the first checks if the value in the Compliant column is equal to $true, in which case it applies the "green" class to the table cell (i.e. 234 | element). The second rule applies when the compliant column is equal to $false, and applied the "red" class. 235 | 236 | #### Example 2 237 | 238 | Suppose you now want to run a report on Datastores. You wish to highlight datastores with less than 25% free space as "warning", those with free space less than 15% as "critical". To make them stand out more, you want to highlight the entire row on the report. You also wish to highlight datastores with a capacity less than 500GB as silver. 239 | 240 | To achieve this, you could use the following: 241 | ``` 242 | $TableFormat = @{"PercentFree" = @(@{ "-le 25" = "Row,class|warning"; }, @{ "-le 15" = "Row,class|critical" }); "CapacityGB" = @(@{ "-lt 500" = "Cell,style|background-color: silver"})} 243 | ``` 244 | Here we see the rules that apply to two different columns, with rules applied to the values in a fashion similar to Example 1. 245 | 246 | 247 | 248 | # Styles 249 | [*Back to top*](#Title) 250 | 251 | Each style *must* implement a function named Get-ReportHTML, as this is what is called by vCheck to generate the HTML report. 252 | 253 | An array of plugin results is passed to Get-ReportHTML, which contains the following properties: 254 | * Title 255 | * Author 256 | * Version 257 | * Details 258 | * Display 259 | * TableFormat 260 | * Header 261 | * Comments 262 | * TimeToRun 263 | 264 | Additionally, if the style is to define colours to be used by charts, the following variables need to be defined: 265 | * `[string[]] $ChartColours` - Array containing HTML colours without the hash e.g. $ChartColours = @("377C2B", "0A77BA", "1D6325", "89CBE1") 266 | * `[string] $ChartBackground` - HTML colour without the hash. e.g. "FFFFFF" 267 | * `[string] $ChartSize` - YYYxZZZ formatted string - where YYY is horizontal size, and ZZZ is height. E.g. "200x200" 268 | 269 | To include image resources, you may call Add-ReportResource, specifying CID and data. As these are not referenced by table formatting rules, this will need to be called with the `-Used $true` parameter. 270 | 271 | 272 | 273 | # Jobs & Settings 274 | [*Back to top*](#Title) 275 | 276 | ## Job XML Specifications 277 | 278 | In order to use the `-Job` parameter, an XML configuration file is used. 279 | 280 | The root element is ``, under this there are two elements: 281 | * `` element specifies the path to the file containing the vCheck settings (by default globalVariables.ps1) 282 | * `` element has a semi-colon separated attribute name path, which contains the path(s) to search for plugins contained in child `` elements. 283 | 284 | Each `` element contains the plugin name. 285 | 286 | ### Config Example 287 | ``` 288 | 289 | GlobalVariables.ps1 290 | 291 | 00 Connection Plugin for vCenter.ps1 292 | 03 Datastore Information.ps1 293 | 11 VMs with over CPU Count LOL WRONG PATH.ps1 294 | 99 VeryLastPlugin Used to Disconnect.ps1 295 | 296 | 297 | ``` 298 | ## Export/Import Settings 299 | This section describes how to import and export your vCheck settings between builds. 300 | 301 | These functions were added to vCheckUtils.ps1 in June '14 (first release build TBD) 302 | 303 | You can copy a newer version of vCheckUtils.ps1 to your existing build in order to use the new functions. 304 | 305 | To utilize the new functions, simply dot source the vCheckUtils.ps1 file in a PowerShell console: 306 | ``` 307 | PS E:\scripts\vCheck-HorizonView> . .\vCheckUtils.ps1 308 | ``` 309 | This should load and list the functions available to you. 310 | We will be focusing on Export-vCheckSettings and Import-vCheckSettings. If you do not see these listed, you will need a newer version of vCheckUtils.ps1. 311 | 312 | ### Example 313 | Lets assume we have an existing build located at 314 | `E:\Scripts\vCheck-HorizonView` 315 | 316 | First lets rename the folder 317 | `E:\Scripts\vCheck-HorizonView-old` 318 | 319 | Now we can download the latest build, unblock the zip file and unpack to `E:\Scripts` leaving us with two builds in our Scripts directory - `vCheck-HorizonView-old` and `vCheck-HorizonView` 320 | 321 | Next we'll export the settings from the old build - using PowerShell navigate to `E:\Scripts\vCheck-HorizonView-old` and dot source `vCheckUtils.ps1` 322 | 323 | ### Export Settings 324 | Running `Export-vCheckSettings` will by default create a CSV file named `vCheckSettings.csv` in the current directory. 325 | You can also specify a settings file 326 | ``` 327 | PS E:\scripts\vCheck-HorizonView-old> Export-vCheckSettings -outfile E:\MyvCheckSettings.csv 328 | ``` 329 | 330 | That's all there is to exporting your vCheck settings. Note that the settings file will be overwritten if you were to run the function again. 331 | 332 | ### Import Settings 333 | To import your vCheck settings, in PowerShell navigate to the new build at `E:\Scripts\vCheck-HorizonView` and dot source `vCheckUtils.ps1` once again. 334 | 335 | Here we have two options - if we run `Import-vCheckSettings` with no parameters it will expect the `vCheckSettings.csv` file to be in the same directory. If not found it will prompt for the full path to the settings CSV file. 336 | The second option is to specify the path to the settings CSV file when running Import-vCheckSettings 337 | ``` 338 | PS E:\scripts\vCheck-HorizonView> Import-vCheckSettings -csvfile E:\MyvCheckSettings.csv 339 | ``` 340 | If new settings or plugins have been added to the new build you will be asked to answer the questions, similar to running the initial config. During the import, the initial config is disabled, so once the import is complete you are ready to run your new build. 341 | 342 | 343 | 344 | # More Info 345 | [*Back to top*](#Title) 346 | 347 | For more information please read here: http://www.virtu-al.net/vcheck-pluginsheaders/vcheck/ 348 | 349 | For an example HorizonView output (doesnt contain all info) click here http://virtu-al.net/Downloads/vCheck/vCheck.htm 350 | 351 | -------------------------------------------------------------------------------- /Select-Plugins.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .NOTES 3 | Select-Plugins.ps1 4 | 5 | selectively enable / disable vCheck Plugins 6 | 7 | presents a list of plugins whose names match *.ps1 or *.ps1.disabled 8 | 9 | disabled plugins will be renamed as appropriate to .ps1.disabled 10 | enabled plugins will be renamed as appropriate to .ps1 11 | 12 | To use, run from the vCheck directory or, if you wish to be perverse, copy to the plugins 13 | directory and rename to "ZZ Select Plugins for Next Run.ps1" and run vCheck as normal. 14 | 15 | Great for testing plugins. When done, untick it... 16 | 17 | If run as a plugin, it will affect the next vCheck run, not the current one, 18 | as vCheck has already collected its list of plugins when it is invoked 19 | so make it the very last plugin executed to avoid counter-intuitive behaviour 20 | 21 | based on code from Select-GraphicalFilteredObject.ps1 in 22 | "Windows Powershell Cookbook" by Lee Holmes. 23 | Copyright 2007 Lee Holmes. 24 | Published by O'Reilly ISBN 978-0-596-528492 25 | and used under the 'free use' provisions specified on Preface page xxv 26 | 27 | Changelog 28 | ============================ 29 | 2.2 - Kevin Kirkpatrick 30 | [X] Add cmdletbinding/param 31 | [X] Move comments into proper comment based help block so it can be reviewed via standard 32 | PS help system (EX: C:\PS>.\Get-Help Select-Plugins.ps1 -ShowWindow) 33 | [X] Remove Write-Host; converted to Write-Warning 34 | 35 | 2.1 - Phil Randal 36 | [X] Added Select All/Deselect All buttons - Changed sort to numeric 37 | #> 38 | 39 | [cmdletbinding()] 40 | param() 41 | 42 | $Title = "Plugin Selection Plugin" 43 | $Author = "Phil Randal" 44 | $PluginVersion = 2.1 45 | $Header = "Plugin Selection" 46 | $Comments = "Plugin Selection" 47 | $Display = "None" 48 | 49 | # Start of Settings 50 | # End of Settings 51 | 52 | $PluginPath = (Split-Path ((Get-Variable MyInvocation).Value).MyCommand.Path) 53 | If ($PluginPath -notmatch 'plugins$') { 54 | $PluginPath += "\Plugins" 55 | } 56 | $plugins = Get-ChildItem -Path $PluginPath -Include *.ps1, *.ps1.disabled -Recurse | 57 | Sort {[int]($_.Name -replace '\D')} | 58 | Select FullName, Name, 59 | @{Label="Plugin";expression={$_.Name -replace '(.*)\.ps1(?:\.disabled|)$', '$1'}}, 60 | @{Label="Enabled";expression={$_.Name -notmatch '.*\.disabled$'}} 61 | 62 | $selectallButton_OnClick = { 63 | for($i = 0; $i -lt $listbox.Items.Count; $i++) { 64 | $listbox.SetItemChecked($i,$true) 65 | } 66 | } 67 | 68 | $deselectallButton_OnClick = { 69 | for($i = 0; $i -lt $listbox.Items.Count; $i++) { 70 | $listbox.SetItemChecked($i,$false) 71 | } 72 | } 73 | 74 | ## Load the Windows Forms assembly 75 | [void] [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") 76 | 77 | ## Create the main form 78 | $form = New-Object Windows.Forms.Form 79 | $form.Size = New-Object Drawing.Size @(600,600) 80 | 81 | ## Create the listbox to hold the items from the pipeline 82 | $listbox = New-Object Windows.Forms.CheckedListBox 83 | $listbox.CheckOnClick = $true 84 | $listbox.Dock = "Fill" 85 | $form.Text = "Select the plugins you wish to enable" 86 | # create list box items from plugin list, tick as enabled where appropriate 87 | ForEach ($plugin in $Plugins) { 88 | $i=$listBox.Items.Add($plugin.Plugin) 89 | $listbox.SetItemChecked($i, $Plugin.Enabled) 90 | } 91 | 92 | ## Create the button panel to hold the OK and Cancel buttons 93 | $buttonPanel = New-Object Windows.Forms.Panel 94 | $buttonPanel.Size = New-Object Drawing.Size @(600,30) 95 | $buttonPanel.Dock = "Bottom" 96 | 97 | ## Create the Cancel button, which will anchor to the bottom right 98 | $cancelButton = New-Object Windows.Forms.Button 99 | $cancelButton.Text = "Cancel" 100 | $cancelButton.DialogResult = "Cancel" 101 | $cancelButton.Top = $buttonPanel.Height - $cancelButton.Height - 5 102 | $cancelButton.Left = $buttonPanel.Width - $cancelButton.Width - 10 103 | $cancelButton.Anchor = "Right" 104 | 105 | ## Create the OK button, which will anchor to the left of Cancel 106 | $okButton = New-Object Windows.Forms.Button 107 | $okButton.Text = "Ok" 108 | $okButton.DialogResult = "Ok" 109 | $okButton.Top = $cancelButton.Top 110 | $okButton.Left = $cancelButton.Left - $okButton.Width - 5 111 | $okButton.Anchor = "Right" 112 | 113 | ## Create the Select All button, which will anchor to the bottom left 114 | $selectallButton = New-Object Windows.Forms.Button 115 | $selectallButton.Text = "Select All" 116 | $selectallButton.Top = $cancelButton.Top 117 | $selectallButton.Left = 10 118 | $selectallButton.Anchor = "Left" 119 | $selectallButton.add_Click($selectallButton_OnClick) 120 | 121 | ## Create the Deselect All button, which will anchor to the right of Select All 122 | $deselectallButton = New-Object Windows.Forms.Button 123 | $deselectallButton.Text = "Deselect All" 124 | $deselectallButton.Top = $cancelButton.Top 125 | $deselectallButton.Left = $selectallButton.Width + 15 126 | $deselectallButton.Anchor = "Left" 127 | $deselectallButton.add_Click($deselectallButton_OnClick) 128 | 129 | ## Add the buttons to the button panel 130 | $buttonPanel.Controls.Add($okButton) 131 | $buttonPanel.Controls.Add($cancelButton) 132 | $buttonPanel.Controls.Add($selectallButton) 133 | $buttonPanel.Controls.Add($deselectallButton) 134 | 135 | ## Add the button panel and list box to the form, and also set 136 | ## the actions for the buttons 137 | $form.Controls.Add($listBox) 138 | $form.Controls.Add($buttonPanel) 139 | $form.AcceptButton = $okButton 140 | $form.CancelButton = $cancelButton 141 | $form.Add_Shown( { $form.Activate() } ) 142 | 143 | ## Show the form, and wait for the response 144 | $result = $form.ShowDialog() 145 | 146 | ## If they pressed OK (or Enter,) 147 | ## enumerate list of plugins and rename those whose status has changed 148 | if($result -eq "OK") { 149 | $i = 0 150 | ForEach ($plugin in $plugins) { 151 | $oldname = $plugin.Name 152 | $newname = $plugin.Plugin + $(If ($listbox.GetItemChecked($i)) {'.ps1'} else {'.ps1.disabled'}) 153 | If ($newname -ne $oldname) { 154 | If (Test-Path (($plugin.FullName | Split-Path) + "\" + $newname)) { 155 | Write-Warning "Attempting to rename ""$oldname"" to ""$newname"", which already exists - please delete or rename the superfluous file and try again" 156 | } Else { 157 | Rename-Item (($plugin.FullName | Split-Path) + "\" + $oldname) $newname 158 | } 159 | } 160 | $i++ 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /Styles/Clarity/Header-vmware.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vCheckReport/vCheck-HorizonView/e57705263fc0e2c66542eb55a8d497b762a00296/Styles/Clarity/Header-vmware.png -------------------------------------------------------------------------------- /Styles/Clarity/Header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vCheckReport/vCheck-HorizonView/e57705263fc0e2c66542eb55a8d497b762a00296/Styles/Clarity/Header.jpg -------------------------------------------------------------------------------- /Styles/CleanGreen/Header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vCheckReport/vCheck-HorizonView/e57705263fc0e2c66542eb55a8d497b762a00296/Styles/CleanGreen/Header.jpg -------------------------------------------------------------------------------- /Styles/CleanGreen/Style.ps1: -------------------------------------------------------------------------------- 1 | $StyleVersion = 1.1 2 | 3 | # Define Chart Colours 4 | $ChartColours = @("377C2B", "0A77BA", "1D6325", "89CBE1") 5 | $ChartBackground = "FFFFFF" 6 | 7 | # Set Chart dimensions (WidthxHeight) 8 | $ChartSize = "200x200" 9 | 10 | # Header Images 11 | Add-ReportResource "Header-vCheck" ($StylePath + "\Header.jpg") -Used $true 12 | 13 | # Hash table of key/value replacements 14 | $StyleReplace = @{"_HEADER_" = ("'$reportHeader'"); 15 | "_CONTENT_" = "Get-ReportContentHTML"; 16 | "_TOC_" = "Get-ReportTOC"} 17 | 18 | #region Function Defniitions 19 | <# 20 | Get-ReportHTML - *REQUIRED* 21 | Returns the HTML for the report 22 | #> 23 | function Get-ReportHTML { 24 | foreach ($replaceKey in $StyleReplace.Keys.GetEnumerator()) { 25 | $ReportHTML = $ReportHTML -replace $replaceKey, (Invoke-Expression $StyleReplace[$replaceKey]) 26 | } 27 | 28 | return $reportHTML 29 | } 30 | 31 | <# 32 | Get-ReportContentHTML 33 | Called to replace the content section of the HTML template 34 | #> 35 | function Get-ReportContentHTML { 36 | $ContentHTML = "" 37 | 38 | foreach ($pr in $PluginResult) { 39 | if ($pr.Details) { 40 | $ContentHTML += Get-PluginHTML $pr 41 | } 42 | } 43 | return $ContentHTML 44 | } 45 | <# 46 | Get-PluginHTML 47 | Called to populate the plugin content in the report 48 | #> 49 | function Get-PluginHTML { 50 | param ($PluginResult) 51 | 52 | $FinalHTML = $PluginHTML -replace "_TITLE_", $PluginResult.Title 53 | $FinalHTML = $FinalHTML -replace "_COMMENTS_", $PluginResult.Comments 54 | $FinalHTML = $FinalHTML -replace "_PLUGINCONTENT_", $PluginResult.Details 55 | $FinalHTML = $FinalHTML -replace "_PLUGINID_", $PluginResult.PluginID 56 | 57 | return $FinalHTML 58 | } 59 | 60 | <# 61 | Get-ReportTOC 62 | Generate table of contents 63 | #> 64 | function Get-ReportTOC { 65 | $TOCHTML = "" 70 | 71 | return $TOCHTML 72 | } 73 | #endregion 74 | 75 | # Report HTML structure 76 | $ReportHTML = @" 77 | 78 | 79 | 80 | _HEADER_ 81 | 82 | 112 | 113 | 114 | 115 | 116 | 117 | 120 | 121 |
118 | vCheck 119 |
122 |
 
123 |
_HEADER_
124 |
_TOC_
125 | _CONTENT_ 126 | 127 |
 
128 |
vCheck v$($vCheckVersion) by Alan Renouf generated on $($ENV:Computername) on $($Date.ToLongDateString()) at $($Date.ToLongTimeString())
129 | 130 | 131 | "@ 132 | 133 | # Structure of each Plugin 134 | $PluginHTML = @" 135 | 136 |
 
137 |
138 | 139 | 140 | 141 |
_TITLE_
_COMMENTS_
_PLUGINCONTENT_
Back To Top 142 |
143 | 144 | "@ -------------------------------------------------------------------------------- /Styles/VMware/Header-vmware.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vCheckReport/vCheck-HorizonView/e57705263fc0e2c66542eb55a8d497b762a00296/Styles/VMware/Header-vmware.png -------------------------------------------------------------------------------- /Styles/VMware/Header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vCheckReport/vCheck-HorizonView/e57705263fc0e2c66542eb55a8d497b762a00296/Styles/VMware/Header.jpg -------------------------------------------------------------------------------- /Styles/VMware/Style.ps1: -------------------------------------------------------------------------------- 1 | # Start of Settings 2 | # Show table of centents in report? 3 | $ShowTOC = $true 4 | # End of Settings 5 | 6 | $StyleVersion = 1.3 7 | 8 | # Define Chart Colours 9 | $ChartColours = @("377C2B", "0A77BA", "1D6325", "89CBE1") 10 | $ChartBackground = "FFFFFF" 11 | 12 | # Set Chart dimensions (WidthxHeight) 13 | $ChartSize = "200x200" 14 | 15 | # Header Images 16 | Add-ReportResource "Header-vCheck" ($StylePath + "\Header.jpg") -Used $true 17 | Add-ReportResource "Header-VMware" ($StylePath + "\Header-vmware.png") -Used $true 18 | 19 | # Hash table of key/value replacements 20 | $StyleReplace = @{"_HEADER_" = ("'$reportHeader'"); 21 | "_CONTENT_" = "Get-ReportContentHTML"; 22 | "_TOC_" = "Get-ReportTOC"} 23 | 24 | #region Function Defniitions 25 | <# 26 | Get-ReportHTML - *REQUIRED* 27 | Returns the HTML for the report 28 | #> 29 | function Get-ReportHTML { 30 | foreach ($replaceKey in $StyleReplace.Keys.GetEnumerator()) { 31 | $ReportHTML = $ReportHTML -replace $replaceKey, (Invoke-Expression $StyleReplace[$replaceKey]) 32 | } 33 | 34 | return $reportHTML 35 | } 36 | 37 | <# 38 | Get-ReportContentHTML 39 | Called to replace the content section of the HTML template 40 | #> 41 | function Get-ReportContentHTML { 42 | $ContentHTML = "" 43 | 44 | foreach ($pr in $PluginResult) { 45 | if ($pr.Details) { 46 | $ContentHTML += Get-PluginHTML $pr 47 | } 48 | } 49 | return $ContentHTML 50 | } 51 | <# 52 | Get-PluginHTML 53 | Called to populate the plugin content in the report 54 | #> 55 | function Get-PluginHTML { 56 | param ($PluginResult) 57 | 58 | $FinalHTML = $PluginHTML -replace "_TITLE_", $PluginResult.Header 59 | $FinalHTML = $FinalHTML -replace "_COMMENTS_", $PluginResult.Comments 60 | $FinalHTML = $FinalHTML -replace "_PLUGINCONTENT_", $PluginResult.Details 61 | $FinalHTML = $FinalHTML -replace "_PLUGINID_", $PluginResult.PluginID 62 | 63 | return $FinalHTML 64 | } 65 | 66 | <# 67 | Get-ReportTOC 68 | Generate table of contents 69 | #> 70 | function Get-ReportTOC { 71 | if ($ShowTOC) { 72 | $TOCHTML = "
" 79 | 80 | return $TOCHTML 81 | } 82 | } 83 | #endregion 84 | 85 | # Report HTML structure 86 | $ReportHTML = @" 87 | 88 | 89 | 90 | _HEADER_ 91 | 92 | 118 | 119 | 120 | 121 | 122 | 123 | 126 | 129 | 130 |
124 | vCheck 125 | 127 | VMware 128 |
131 |
 
132 |
_HEADER_
133 |
_TOC_
134 | _CONTENT_ 135 | 136 |
 
137 |
vCheck v$($vCheckVersion) by Alan Renouf generated on $($ENV:Computername) on $($Date.ToLongDateString()) at $($Date.ToLongTimeString())
138 | 139 | 140 | "@ 141 | 142 | # Structure of each Plugin 143 | $PluginHTML = @" 144 | 145 |
 
146 |
147 | 148 | 149 | 150 |
_TITLE_
_COMMENTS_
_PLUGINCONTENT_
Back To Top 151 |
152 | 153 | "@ 154 | -------------------------------------------------------------------------------- /plugins.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Connection settings for vCenter 4 | 5 | 6 | 7 | 8 | 1.5 9 | vSphere 10 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 11 | 12 | 13 | General Information 14 | General details on the infrastructure 15 | Alan Renouf, Frederic Martin 16 | 1.2 17 | vSphere 18 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/00 Initialize/01 General Information.ps1 19 | 20 | 21 | Checking VI Events 22 | The following errors were logged in the vCenter Events tab, you may wish to investigate these 23 | Alan Renouf 24 | 1.1 25 | vSphere 26 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/10 vCenter/23 VI Events.ps1 27 | 28 | 29 | VC Services 30 | The following vCenter Services are not in the required state 31 | Alan Renouf 32 | 1.1 33 | vSphere 34 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/10 vCenter/41 vCenter Services.ps1 35 | 36 | 37 | Windows vCenter Error Event Logs 38 | The following errors were found in the vCenter Event Logs, you may wish to check these further 39 | Alan Renouf 40 | 1.2 41 | vSphere 42 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/10 vCenter/42 Windows vCenter Error Event Logs.ps1 43 | 44 | 45 | Windows vCenter Warning Event Logs 46 | The following warnings were found in the vCenter Event Logs, you may wish to check these further 47 | Alan Renouf 48 | 1.2 49 | vSphere 50 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/10 vCenter/43 Windows vCenter Warning Event Logs.ps1 51 | 52 | 53 | vCenter Sessions Age 54 | The following displays vCenter sessions that exceed the maximum session age ($MaxvCenterSessionAge Hour(s)). 55 | Rudolf Kleijwegt 56 | 1.1 57 | vSphere 58 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/10 vCenter/47 vCenter Session Age.ps1 59 | 60 | 61 | vCenter License Report 62 | The following displays licenses registered with this server and usage. 63 | Justin Mercier 64 | 1.0 65 | vSphere 66 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/10 vCenter/82 License Report.ps1 67 | 68 | 69 | HA configuration issues 70 | The following clusters have HA configuration issues. This will impact your disaster recovery. 71 | John Sneddon 72 | 1.0 73 | vSphere 74 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/20 Cluster/10 HA Configuration Issues.ps1 75 | 76 | 77 | Clusters Without Host Profile attached 78 | The following clusters do not have a host profile attached 79 | John Sneddon 80 | 1.0 81 | vSphere 82 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/20 Cluster/104 Clusters with no Host Profile.ps1 83 | 84 | 85 | HA VMs restarted 86 | The following VMs have been restarted by HA in the last $HAVMresetold days 87 | Alan Renouf 88 | 1.1 89 | vSphere 90 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/20 Cluster/15 HA VMs restarted.ps1 91 | 92 | 93 | DRS & SDRS Migrations 94 | Multiple DRS Migrations may be an indication of overloaded hosts, check resource levels of the cluster 95 | Alan Renouf, Jonathan Medd 96 | 1.3 97 | vSphere 98 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/20 Cluster/17 DRS Migrations.ps1 99 | 100 | 101 | Cluster Slot Sizes 102 | Slot sizes in the below cluster are less than is specified, this may cause issues with creating new VMs, for more information click here: <a href='http://www.yellow-bricks.com/vmware-high-availability-deepdiv/' target='_blank'>Yellow-Bricks HA Deep Dive</a> 103 | Alan Renouf 104 | 1.1 105 | vSphere 106 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/20 Cluster/18 Cluster Slot Sizes.ps1 107 | 108 | 109 | Cluster Configuration Issues 110 | The following alarms have been registered against clusters in vCenter 111 | Alan Renouf 112 | 1.1 113 | vSphere 114 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/20 Cluster/38 Cluster Configuration Issues.ps1 115 | 116 | 117 | Datastore Consistency 118 | Virtual Machines residing on these datastores will not be able to run on all hosts in the cluster 119 | Robert Sexstone 120 | 1.5 121 | vSphere 122 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/20 Cluster/52 Datastore Consistency.ps1 123 | 124 | 125 | Clusters with DRS disabled 126 | The following clusters have DRS disabled. This may impact the performance of your cluster. 127 | Robert van den Nieuwendijk 128 | 1.2 129 | vSphere 130 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/20 Cluster/55 Clusters with DRS Disabled.ps1 131 | 132 | 133 | Cluster Node version 134 | Display per cluster nodes version if unique or mismatch 135 | Raphael Schitz, Frederic Martin 136 | 1.1 137 | vSphere 138 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/20 Cluster/70 Cluster Node Version.ps1 139 | 140 | 141 | QuickStats Capacity Planning 142 | The following gives brief capacity information for each cluster based on QuickStats CPU/Mem usage and counting for HA failover requirements 143 | Raphael Schitz, Frederic Martin 144 | 1.4 145 | vSphere 146 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/20 Cluster/71 Capacity Planning.ps1 147 | 148 | 149 | s/vMotion Information 150 | s/vMotions and how long they took to migrate between hosts and datastores 151 | Alan Renouf 152 | 1.1 153 | vSphere 154 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/20 Cluster/72 s-vMotion Information.ps1 155 | 156 | 157 | More RAM than free space on Datastore 158 | The following VMs can't vMotion because they have more RAM than free space on datastore 159 | Olivier TABUT, Bob Cote 160 | 1.1 161 | vSphere 162 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/20 Cluster/73 VM - More RAM than free space on Datastore.ps1 163 | 164 | 165 | DRS Rules 166 | Contains all DRS rules defined in this vCenter - {0} 167 | John Sneddon 168 | 1.0 169 | vSphere 170 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/20 Cluster/75 DRS Rules.ps1 171 | 172 | 173 | Hosts Overcommit state 174 | Overcommitted hosts may cause issues with performance if memory is not issued when needed, this may cause ballooning and swapping 175 | Alan Renouf 176 | 1.3 177 | vSphere 178 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/30 Host/07 Hosts Overcommit State.ps1 179 | 180 | 181 | Hosts Dead LUN Path 182 | Dead LUN Paths may cause issues with storage performance or be an indication of loss of redundancy 183 | Alan Renouf, Frederic Martin 184 | 1.1 185 | vSphere 186 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/30 Host/08 Hosts Dead LUN Path.ps1 187 | 188 | 189 | ESXi Inode Exhaustion 190 | The following hosts have an excessive amount of Inodes in use on the local ESXi filesystem. This can cause hosts to disconnect from vCenter and becoming completely unmanageable even locally, requiring a hard reboot. See <a href=` 191 | Matthias Koehler 192 | 1.1 193 | vSphere 194 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/30 Host/101 ESXi Inode Usage.ps1 195 | 196 | 197 | Host Profile Compliance 198 | Failures 199 | John Sneddon 200 | 1.2 201 | vSphere 202 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/30 Host/105 Host Profile Compliance.ps1 203 | 204 | 205 | Hosts with Upcoming Certificate Expiration 206 | The following hosts have certificates that will expire soon and will need to be replaced. 207 | 208 | 209 | 1.1 210 | vSphere 211 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/30 Host/110 Host Certificate Expiration Check.ps1 212 | 213 | 214 | Host Swapfile datastores 215 | The following hosts are in a cluster which is set to store the swap file in the datastore specified by the host but no location has been set on the host 216 | Alan Renouf 217 | 1.2 218 | vSphere 219 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/30 Host/16 Host Swapfile datastores.ps1 220 | 221 | 222 | ESXi with Technical Support mode or ESXi Shell enabled 223 | The following ESXi Hosts have Technical support mode or ESXi Shell enabled, this may not be the best security option, see here for more information: <a href='http://www.yellow-bricks.com/2010/03/01/disable-tech-support-on-esxi/' target='_blank'>Yellow-Bricks Disable Tech Support on ESXi</a>. 224 | Alan Renouf 225 | 1.3 226 | vSphere 227 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/30 Host/20 ESXi with Technical Support mode enabled.ps1 228 | 229 | 230 | ESXi hosts which do not have Lockdown mode enabled 231 | The following ESXi Hosts do not have lockdown enabled, think about using lockdown as an extra security feature. 232 | Alan Renouf 233 | 1.1 234 | vSphere 235 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/30 Host/21 ESXi hosts which do not have Lockdown mode enabled.ps1 236 | 237 | 238 | NTP Name and Service 239 | The following hosts do not have the correct NTP settings and may cause issues if the time becomes far apart from the vCenter/Domain or other hosts 240 | Alan Renouf 241 | 1.1 242 | vSphere 243 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/30 Host/31 NTP Name and Service.ps1 244 | 245 | 246 | Host Configuration Issues 247 | The following configuration issues have been registered against Hosts in vCenter 248 | Alan Renouf 249 | 1.1 250 | vSphere 251 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/30 Host/35 Host Configuration Issues.ps1 252 | 253 | 254 | Host Alarms 255 | The following alarms have been registered against hosts in vCenter 256 | Alan Renouf, John Sneddon 257 | 1.2 258 | vSphere 259 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/30 Host/36 Host Alarms.ps1 260 | 261 | 262 | VMKernel Warnings 263 | The following VMKernel issues were found, it is suggested all unknown issues are explored on the VMware Knowledge Base. Use the below links to automatically search for the string 264 | Alan Renouf, Frederic Martin 265 | 1.3 266 | vSphere 267 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/30 Host/44 VMKernel Warnings.ps1 268 | 269 | 270 | Missing ESX(i) updates and patches 271 | KB 272 | Luc Dekens 273 | 1.1 274 | vSphere 275 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/30 Host/46 Missing ESX patches.ps1 276 | 277 | 278 | Syslog Name 279 | The following hosts do not have the correct Syslog settings which may cause issues if ESXi hosts experience issues and logs need to be investigated 280 | Jonathan Medd 281 | 1.1 282 | vSphere 283 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/30 Host/51 Syslog Name.ps1 284 | 285 | 286 | Hardware status warnings/errors 287 | Details can be found in the Hardware Status tab 288 | Raphael Schitz 289 | 1.1 290 | vSphere 291 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/30 Host/53 Hardware status warnings-errors.ps1 292 | 293 | 294 | Hosts with reboot required 295 | The following hosts require a reboot. 296 | Robert van den Nieuwendijk 297 | 1.1 298 | vSphere 299 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/30 Host/57 Hosts with reboot required.ps1 300 | 301 | 302 | Host Build versions in use 303 | The following host builds are in use in this vCenter 304 | Frederic Martin 305 | 1.1 306 | vSphere 307 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/30 Host/67 Host OS Pivot Table.ps1 308 | 309 | 310 | Disk Max Total Latency 311 | Check VM per LUN dispatch and esxtop for very high values over $diskmaxtotallatency 312 | Raphael Schitz, Frederic Martin 313 | 1.1 314 | vSphere 315 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/30 Host/68 Disk Max Total Latency.ps1 316 | 317 | 318 | Network redundancy lost 319 | The following Hosts have lost network redundancy 320 | Olivier TABUT 321 | 1.1 322 | vSphere 323 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/30 Host/74 Host - Network Redundancy lost.ps1 324 | 325 | 326 | Hosts with different hostname 327 | The following hosts have a different hostname than their name in the vCenter Server. This might give troubles with HA. 328 | Robert van den Nieuwendijk 329 | 1.0 330 | vSphere 331 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/30 Host/75 Hosts with Different Hostnames.ps1 332 | 333 | 334 | Lost Access to Volume 335 | The following hosts have lost access to a volume. This may indicate a problem with your storage solution. 336 | Robert van den Nieuwendijk, Jonathan Medd 337 | 1.1 338 | vSphere 339 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/30 Host/77 Lost Access to Volume.ps1 340 | 341 | 342 | Check LUNS have the recommended number of paths 343 | Not enough storage paths can effect storage availability in a FC SAN environment 344 | Craig Smith 345 | 1.0 346 | vSphere 347 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/30 Host/81 LUN Paths Check.ps1 348 | 349 | 350 | Hosts not Connected or Alarms Disabled 351 | Shows hosts not in service and those with alarms disabled. 352 | Chris Monahan 353 | 1.0 354 | vSphere 355 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/30 Host/83 Hosts not Connected or Alarms Disabled.ps1 356 | 357 | 358 | Datastore Information 359 | Datastores which run out of space will cause impact on the virtual machines held on these datastores 360 | Alan Renouf, Jonathan Medd 361 | 1.3 362 | vSphere 363 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/40 Datastore/03 Datastore Information.ps1 364 | 365 | 366 | Datastore Clusters with sDRS Disabled 367 | The following Datastore Clusters either do not have sDRS enabled or it is set to manual 368 | Shawn Masterson 369 | 1.0 370 | vSphere 371 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/40 Datastore/115 Datastore Clusters with sDRS Disabled.ps1 372 | 373 | 374 | sDRS VM Behavior not Default 375 | The following VMs are overriding the Datastore Cluster sDRS automation level 376 | Shawn Masterson 377 | 1.0 378 | vSphere 379 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/40 Datastore/116 sDRS VM Behavior not Default.ps1 380 | 381 | 382 | Number of VMs per Datastore 383 | The Maximum number of VMs per datastore is 256, the following VMs are above the defined $NumVMsPerDatastore and may cause performance issues 384 | Alan Renouf, Frederic Martin 385 | 1.2 386 | vSphere 387 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/40 Datastore/34 Number of VMs per Datastore.ps1 388 | 389 | 390 | Datastore OverAllocation 391 | The following datastores may be overcommitted, it is strongly suggested you check these 392 | Alan Renouf 393 | 1.3 394 | vSphere 395 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/40 Datastore/39 Datastore OverAllocation.ps1 396 | 397 | 398 | Datastores with Storage IO Control Disabled 399 | Datastores with Storage I/O Control Disabled can impact the performance of your virtual machines. 400 | Robert van den Nieuwendijk 401 | 1.2 402 | vSphere 403 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/40 Datastore/59 Datastores with Storage IO Control Disabled.ps1 404 | 405 | 406 | Datastores in Maintenance Mode 407 | Datastore held in Maintenance mode will not be hosting any virtual machine, check the below Datastore are in an expected state 408 | Frederic Martin 409 | 1.1 410 | vSphere 411 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/40 Datastore/69 Datastores in Maintenance mode.ps1 412 | 413 | 414 | VSAN Datastore Capacity 415 | VSAN Datastore Capacity Report - Modified version from Alan Renouf & Jonathan Medd's Datastore Report 416 | William Lam 417 | 1.0 418 | vSphere 419 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/40 Datastore/990 VSAN Capacity Report.ps1 420 | 421 | 422 | VSAN Configuration Maximum Disk Group Per Host Report 423 | VSAN hosts approaching 424 | William Lam 425 | 1.0 426 | vSphere 427 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/40 Datastore/991 VSAN Configuration Maximum Disk Group Per Host Report.ps1 428 | 429 | 430 | VSAN Configuration Maximum Magnetic Disks Per Disk Group Report 431 | VSAN hosts approaching 432 | William Lam 433 | 1.0 434 | vSphere 435 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/40 Datastore/992 VSAN Configuration Maximum Magnetic Disks Per Disk Group Report.ps1 436 | 437 | 438 | VSAN Configuration Maximum Total Magnetic Disks In All Disk Groups Per Host Report 439 | VSAN hosts approaching 440 | William Lam 441 | 1.0 442 | vSphere 443 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/40 Datastore/993 VSAN Configuration Maximum Total Magnetic Disks In All Disk Groups Per Host Report.ps1 444 | 445 | 446 | VSAN Configuration Maximum Components Per Host Report 447 | VSAN hosts approaching 448 | William Lam 449 | 1.0 450 | vSphere 451 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/40 Datastore/994 VSAN Configuration Maximum Component Per Host Report.ps1 452 | 453 | 454 | VSAN Configuration Maximum Hosts Per VSAN Cluster Report 455 | VSAN hosts approaching 456 | William Lam 457 | 1.0 458 | vSphere 459 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/40 Datastore/995 VSAN Configuration Maximum Hosts Per Cluster Report.ps1 460 | 461 | 462 | VSAN Configuration Maximum VMs Per Host Report 463 | VSAN hosts approaching 464 | William Lam 465 | 1.0 466 | vSphere 467 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/40 Datastore/996 VSAN Configuration Maximum VMs Per Host Report.ps1 468 | 469 | 470 | VSAN Configuration Maximum VMs Per VSAN Cluster Report 471 | VSAN hosts approaching 472 | William Lam 473 | 1.0 474 | vSphere 475 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/40 Datastore/997 VSAN Configuration Maximum VMs Per Cluster Report.ps1 476 | 477 | 478 | Checking Standard vSwitch Ports Free 479 | The following standard vSwitches have less than $vSwitchLeft left 480 | Alan Renouf 481 | 1.1 482 | vSphere 483 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/50 Network/24 vSwitch with less than x Ports Free.ps1 484 | 485 | 486 | Checking Distributed vSwitch Port Groups for Ports Free 487 | The following Distributed vSwitch Port Groups have less than $vSwitchLeft left 488 | Kyle Ruddy 489 | 1.2 490 | vSphere 491 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/50 Network/80 DvPG with less than x Ports Free.ps1 492 | 493 | 494 | vSwitch Security 495 | All security options for standard vSwitches should be set to REJECT. Distributed vSwitches may require <em>ForgedTrasmits</em> in the default portgroup but should be disabled in other VM Network portgroups unless expressly required. 496 | Justin Mercier, Sam McGeown, John Sneddon 497 | 1.2 498 | vSphere 499 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/50 Network/98 vSwitch Security.ps1 500 | 501 | 502 | Snapshot Information 503 | 504 | 505 | Alan Renouf, Raphael Schitz 506 | 1.4 507 | vSphere 508 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/02 Snapshot Information.ps1 509 | 510 | 511 | Map disk region event 512 | These may occur due to VCB issues, check <a href='http://kb.vmware.com/kb/1007331' target='_blank'>this article</a> for more details 513 | Alan Renouf 514 | 1.1 515 | vSphere 516 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/04 Map disk region event.ps1 517 | 518 | 519 | Created or cloned VMs 520 | The following VMs have been created over the last {0} Days 521 | Alan Renouf 522 | 1.2 523 | vSphere 524 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/09 Created or cloned VMs.ps1 525 | 526 | 527 | Removed VMs 528 | The following VMs have been removed/deleted over the last $($VMsNewRemovedAge) days 529 | Alan Renouf 530 | 1.2 531 | vSphere 532 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/10 Removed VMs.ps1 533 | 534 | 535 | VMs with CPU or Memory Reservations Configured 536 | The following VMs have a CPU or Memory Reservation configured which may impact the performance of the VM. Note: -1 indicates no reservation 537 | Dan Jellesma 538 | 1.0 539 | vSphere 540 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/100 VMs with CPU or Memory Reservations.ps1 541 | 542 | 543 | VM Logging 544 | The following virtual machines are not configured to rotate logs at $RotateSize bytes and/or to store $KeepOld logs. 545 | Bob Cote 546 | 1.0 547 | vSphere 548 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/102 VM Logging.ps1 549 | 550 | 551 | CPU/Mem HotPlug 552 | VMs needs to be shutdown to modify CPU/Mem HotPlug in case this settings is disabled. 553 | Eric Lannier 554 | 1.0 555 | vSphere 556 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/103 VMs CPU MEM Add Hot Disabled.ps1 557 | 558 | 559 | Find Phantom Snapshots 560 | The following VM's have Phantom Snapshots 561 | Mads Fog Albrechtslund 562 | 1.1 563 | vSphere 564 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/106 Find Phantom Snapshots.ps1 565 | 566 | 567 | VMs with over $vCPU vCPUs 568 | The following VMs have over $vCPU CPU(s) and may impact performance due to CPU scheduling 569 | Alan Renouf 570 | 1.1 571 | vSphere 572 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/11 VMs with over CPU Count.ps1 573 | 574 | 575 | VM Tools Not Up to Date 576 | The following VMs are running an older version of Tools than is available on its Host (Max Shown: $VMTMaxReturn Exceptions: $VMTDoNotInclude) 577 | Alan Renouf, Shawn Masterson 578 | 1.0 579 | vSphere 580 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/114 VM Tools Not Up to Date.ps1 581 | 582 | 583 | VMs Ballooning or Swapping 584 | Ballooning and swapping may indicate a lack of memory or a limit on a VM, this may be an indication of not enough memory in a host or a limit held on a VM, <a href='http://www.virtualinsanity.com/index.php/2010/02/19/performance-troubleshooting-vmware-vsphere-memory/' target='_blank'>further information is available here</a>. 585 | Alan Renouf, Frederic Martin 586 | 1.1 587 | vSphere 588 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/12 VMs Swapping or Ballooning.ps1 589 | 590 | 591 | Multi-writer 592 | The following VMs have multi-writer parameter. A problem will occur in case of svMotion without reconfiguration of the applications which are using these virtual disks and also change of the VM configuration concerned. More information <a href='http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1034165'>here</a>. 593 | Petar Enchev, Luc Dekens 594 | 1.0 595 | vSphere 596 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/120 Multi-writer.ps1 597 | 598 | 599 | BusSharingMode - Physical and Virtual 600 | The following VMs have physical and/or virtual bus sharing. A problem will occur in case of svMotion without reconfiguration of the applications which are using these virtual disks and also change of the VM configuration concerned. 601 | Petar Enchev, Luc Dekens 602 | 1.0 603 | vSphere 604 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/121 BusSharingMode - Physical and Virtual.ps1 605 | 606 | 607 | Invalid or inaccessible VM 608 | The following VMs are marked as inaccessible or invalid 609 | Alan Renouf 610 | 1.1 611 | vSphere 612 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/13 Invalid or inaccessible VMs.ps1 613 | 614 | 615 | VMs restarted due to Guest OS Error 616 | The following VMs have been restarted by HA in the last $HAVMresetold days 617 | Alan Renouf 618 | 1.1 619 | vSphere 620 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/14 VMs restarted due to Guest OS Error.ps1 621 | 622 | 623 | Guests with less than $MBFree MB 624 | The following guests have less than $MBFree MB Free, if a guest disk fills up it may cause issues with the guest Operating System 625 | Alan Renouf 626 | 1.1 627 | vSphere 628 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/19 Guests with less than X MB free.ps1 629 | 630 | 631 | Checking VM Hardware Version 632 | The following VMs are not at the latest hardware version, you may gain performance enhancements if you convert them to the latest version 633 | Alan Renouf 634 | 1.1 635 | vSphere 636 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/22 Checking VM Hardware Version.ps1 637 | 638 | 639 | VMs in inconsistent folders 640 | The following VMs are not stored in folders consistent to their names, this may cause issues when trying to locate them from the datastore manually 641 | Alan Renouf 642 | 1.2 643 | vSphere 644 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/25 VMs in inconsistent folders.ps1 645 | 646 | 647 | No VM Tools 648 | The following VMs have No VMTools installed, for optimal configuration and performance these should be installed 649 | Alan Renouf 650 | 1.1 651 | vSphere 652 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/26 No VM Tools.ps1 653 | 654 | 655 | VM Tools Issues 656 | The following VMs have issues with VMTools, these should be checked and reinstalled if necessary 657 | Alan Renouf 658 | 1.1 659 | vSphere 660 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/27 VM Tools Issues.ps1 661 | 662 | 663 | Removable Media Connected 664 | The following VMs have removable media connected (i.e. CD/Floppy), this may cause issues if this machine needs to be migrated to a different host 665 | John Sneddon 666 | 1.0 667 | vSphere 668 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/28 Removable Media Connected.ps1 669 | 670 | 671 | Single Storage VMs 672 | The following VMs are located on storage which is only accessible by 1 host, these will not be compatible with vMotion and may be disconnected in the event of host failure 673 | Alan Renouf, Frederic Martin 674 | 1.3 675 | vSphere 676 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/30 Single Storage VMs.ps1 677 | 678 | 679 | VM CPU %RDY 680 | The following VMs have high CPU RDY times, this can cause performance issues for more information please read <a href='http://communities.vmware.com/docs/DOC-7390' target='_blank'>This article</a> 681 | Alan Renouf 682 | 1.1 683 | vSphere 684 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/32 VM CPU Percent RDY.ps1 685 | 686 | 687 | VM CPU Usage 688 | The following VMs have high CPU usage and may have rogue guest processes or not enough CPU resource assigned 689 | Alan Renouf, Sam McGeown 690 | 1.3 691 | vSphere 692 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/33 VM CPU Usage.ps1 693 | 694 | 695 | VM Alarms 696 | The following alarms have been registered against VMs in vCenter 697 | Alan Renouf 698 | 1.1 699 | vSphere 700 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/37 VM Alarms.ps1 701 | 702 | 703 | VCB/Veeam/NetBackup Garbage 704 | The following snapshots have been left over from using VCB/Veeam or Netbackup, you may wish to investigate if these are still needed 705 | Alan Renouf, Frederic Martin 706 | 1.2 707 | vSphere 708 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/40 VCB Garbage.ps1 709 | 710 | 711 | VMs needing snapshot consolidation 712 | The following VMs have snapshots that failed to consolidate. See <a href='http://blogs.vmware.com/vsphere/2011/08/consolidate-snapshots.html' target='_blank'>this article</a> for more details 713 | Luc Dekens, Frederic Martin 714 | 1.2 715 | vSphere 716 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/45 VMs needing snapshot consolidation.ps1 717 | 718 | 719 | Find VMs with thick or thin provisioned vmdk 720 | The following VMs have have $diskformat provisioned vmdk(s) 721 | David Chung 722 | 1.2 723 | vSphere 724 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/48 Find VM Disk Format.ps1 725 | 726 | 727 | VMs with CPU or Memory Limits Configured 728 | The following VMs have a CPU or memory limit configured which may impact the performance of the VM. Note: -1 indicates no limit 729 | Jonathan Medd 730 | 1.1 731 | vSphere 732 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/50 VMs with CPU or Memory Limits Configured.ps1 733 | 734 | 735 | Virtual machines with incorrect OS configuration 736 | The following virtual machines have an installed OS that is different from the configured OS. This can impact the performance of the virtual machine. 737 | Robert van den Nieuwendijk 738 | 1.1 739 | vSphere 740 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/54 Virtual Machines with incorrect OS Configuration.ps1 741 | 742 | 743 | Virtual machines with less hard disks than partitions 744 | Virtual machines with less hard disks than partitions. Probably they have more than one partition on a hard disk. 745 | Robert van den Nieuwendijk 746 | 1.2 747 | vSphere 748 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/58 Virtual machines with less hard disks than partitions.ps1 749 | 750 | 751 | Powered Off VMs 752 | May want to consider deleting VMs that have been powered off for more than 30 days 753 | Adam Schwartzberg 754 | 1.2 755 | vSphere 756 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/60 Powered Off VMs.ps1 757 | 758 | 759 | VMs by Operating System 760 | The following Operating Systems are in use in this vCenter 761 | Raymond 762 | 1.2 763 | vSphere 764 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/61 Guest OS Pivot table.ps1 765 | 766 | 767 | Unwanted virtual hardware found 768 | Certain kinds of hardware are unwanted on virtual machines as they may cause unnecessary vMotion constraints. 769 | Frederic Martin 770 | 1.1 771 | vSphere 772 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/62 Unwanted Virtual Hardware.ps1 773 | 774 | 775 | VM - Display all VMs with CBT not enabled 776 | List all VMs with CBT status disabled. It's not a good option for backup! 777 | Cyril Epiney 778 | 1.0 779 | vSphere 780 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/620 CBT Status.ps1 781 | 782 | 783 | Snapshots Oversize 784 | VMware snapshots which are kept for a long period of time may cause issues, filling up datastores and also may impact performance of the virtual machine. 785 | Raphael Schitz, Shawn Masterson 786 | 1.3 787 | vSphere 788 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/64 Snapshots Oversize.ps1 789 | 790 | 791 | Mis-named virtual machines 792 | The following guest names do not match the name inside of the guest. 793 | Frederic Martin 794 | 1.2 795 | vSphere 796 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/66 Misnamed VM.ps1 797 | 798 | 799 | VM - is my network connected? 800 | Check if all network cards are connected 801 | Cyril Epiney 802 | 1.2 803 | vSphere 804 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/76 VM Network State.ps1 805 | 806 | 807 | Reset VMs 808 | The following VMs have been reset over the last $($VMsResetAge) days 809 | James Scholefield 810 | 1.0 811 | vSphere 812 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/78 Reset VMs.ps1 813 | 814 | 815 | VMs in uncontrolled snapshot mode 816 | The following VMs are in snapshot mode, but vCenter isn't aware of it. See http://kb.vmware.com/kb/1002310 817 | Rick Glover, Matthias Koehler 818 | 1.3 819 | vSphere 820 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/79 Find VMs in Uncontrolled Snapshot Mode.ps1 821 | 822 | 823 | Snapshot activity 824 | TaskEvent 825 | Chris Monahan, but is a minor mod of two plugins by Raphael Schitz and Frederic Martin 826 | 1.0 827 | vSphere 828 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/60 VM/85 Snapshot Activity.ps1 829 | 830 | 831 | Site Recovery Manager - RPO Violation Report 832 | This is a customizable report of RPO violations found in the vCenter event log. 833 | Joel Gibson, based on work by Alan Renouf 834 | 0.5 835 | vSphere 836 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/70 Misc/108 SRM RPO Violations.ps1 837 | 838 | 839 | Report on Plugins 840 | 841 | 842 | Phil Randal 843 | 1.1 844 | vCheck 845 | https://raw.github.com/alanrenouf/vCheck-vCheck/master/Plugins/80 Finish/119 Report on Plugins.ps1 846 | 847 | 848 | Disconnecting from vCenter 849 | Disconnect plugin 850 | Alan Renouf 851 | 1.1 852 | vSphere 853 | https://raw.github.com/alanrenouf/vCheck-vSphere/master/Plugins/80 Finish/999 VeryLastPlugin Used to Disconnect.ps1 854 | 855 | -------------------------------------------------------------------------------- /vCheck.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | vCheck is a PowerShell HTML framework script, designed to run as a scheduled 4 | task before you get into the office to present you with key information via 5 | an email directly to your inbox in a nice easily readable format. 6 | .DESCRIPTION 7 | vCheck Daily Report for vSphere 8 | 9 | vCheck is a PowerShell HTML framework script, the script is designed to run 10 | as a scheduled task before you get into the office to present you with key 11 | information via an email directly to your inbox in a nice easily readable format. 12 | 13 | This script picks on the key known issues and potential issues scripted as 14 | plugins for various technologies written as powershell scripts and reports 15 | it all in one place so all you do in the morning is check your email. 16 | 17 | One of they key things about this report is if there is no issue in a particular 18 | place you will not receive that section in the email, for example if there are 19 | no datastores with less than 5% free space (configurable) then the disk space 20 | section in the virtual infrastructure version of this script, it will not show 21 | in the email, this ensures that you have only the information you need in front 22 | of you when you get into the office. 23 | 24 | This script is not to be confused with an Audit script, although the reporting 25 | framework can also be used for auditing scripts too. I dont want to remind you 26 | that you have 5 hosts and what there names are and how many CPUs they have each 27 | and every day as you dont want to read that kind of information unless you need 28 | it, this script will only tell you about problem areas with your infrastructure. 29 | 30 | .NOTES 31 | File Name : vCheck.ps1 32 | Author : Alan Renouf - @alanrenouf 33 | Version : 6.23 34 | 35 | Thanks to all who have commented on my blog to help improve this project 36 | all beta testers and previous contributors to this script. 37 | 38 | .LINK 39 | http://www.virtu-al.net/vcheck-pluginsheaders/vcheck 40 | .LINK 41 | https://github.com/alanrenouf/vCheck-vSphere/ 42 | 43 | .INPUTS 44 | No inputs required 45 | .OUTPUTS 46 | HTML formatted email, Email with attachment, HTML File 47 | 48 | .PARAMETER config 49 | If this switch is set, run the setup wizard 50 | 51 | .PARAMETER Outputpath 52 | This parameter specifies the output location for files. 53 | 54 | .PARAMETER job 55 | This parameter lets you specify an xml config file for this invokation 56 | #> 57 | [CmdletBinding()] 58 | param ( 59 | [Switch]$config, 60 | 61 | [ValidateScript({ Test-Path $_ -PathType 'Container' })] 62 | [string]$Outputpath=$Env:TEMP, 63 | 64 | [ValidateScript({ Test-Path $_ -PathType 'Leaf' })] 65 | [string]$job 66 | ) 67 | 68 | $vCheckVersion = "6.23" 69 | $Date = Get-Date 70 | 71 | #region Internationalization 72 | ################################################################################ 73 | # Internationalization # 74 | ################################################################################ 75 | $lang = DATA { 76 | ConvertFrom-StringData @' 77 | setupMsg01 = 78 | setupMsg02 = Welcome to vCheck by Virtu-Al http://virtu-al.net 79 | setupMsg03 = ================================================= 80 | setupMsg04 = This is the first time you have run this script or you have re-enabled the setup wizard. 81 | setupMsg05 = 82 | setupMsg06 = To re-run this wizard in the future please use vCheck.ps1 -Config 83 | setupMsg07 = To get usage information, please use Get-Help vCheck.ps1 84 | setupMsg08 = 85 | setupMsg09 = Please complete the following questions or hit Enter to accept the current setting 86 | setupMsg10 = After completing this wizard the vCheck report will be displayed on the screen. 87 | setupMsg11 = 88 | resFileWarn = Image File not found for {0}! 89 | pluginInvalid = Plugin does not exist: {0} 90 | pluginpathInvalid = Plugin path "{0}" is invalid, defaulting to {1} 91 | gvInvalid = Global Variables path invalid in job specification, defaulting to {0} 92 | varUndefined = Variable `${0} is not defined in GlobalVariables.ps1 93 | pluginActivity = Evaluating plugins 94 | pluginStatus = [{0} of {1}] {2} 95 | Complete = Complete 96 | pluginBegin = \nBegin Plugin Processing 97 | pluginStart = ..start calculating {0} by {1} v{2} [{3} of {4}] 98 | pluginEnd = ..finished calculating {0} by {1} v{2} [{3} of {4}] 99 | repTime = This report took {0} minutes to run all checks, completing on {1} at {2} 100 | repPRTitle = Plugin Report 101 | repTTRTitle = Time to Run 102 | slowPlugins = The following plugins took longer than {0} seconds to run, there may be a way to optimize these or remove them if not needed 103 | emailSend = ..Sending Email 104 | emailAtch = vCheck attached to this email 105 | HTMLdisp = ..Displaying HTML results 106 | '@ 107 | } 108 | 109 | Import-LocalizedData -BaseDirectory ($ScriptPath + "\lang") -BindingVariable lang -ErrorAction SilentlyContinue 110 | 111 | #endregion Internationalization 112 | 113 | #region functions 114 | ################################################################################ 115 | # Functions # 116 | ################################################################################ 117 | <# Write timestamped output to screen #> 118 | function Write-CustomOut ($Details) { 119 | $LogDate = Get-Date -Format "HH:mm:ss" 120 | Write-OutPut "[$($LogDate)] $Details" 121 | } 122 | 123 | <# Search $file_content for name/value pair with ID_Name and return value #> 124 | Function Get-ID-String ($file_content, $ID_name) { 125 | if ($file_content | Select-String -Pattern "\$+$ID_name\s*=") { 126 | $value = (($file_content | Select-String -pattern "\$+${ID_name}\s*=").toString().split("=")[1]).Trim(' "') 127 | return ($value) 128 | } 129 | } 130 | 131 | <# Get basic information abount a plugin #> 132 | Function Get-PluginID ($Filename) { 133 | # Get the identifying information for a plugin script 134 | $file = Get-Content $Filename 135 | $Title = Get-ID-String $file "Title" 136 | if (!$Title) { $Title = $Filename } 137 | $PluginVersion = Get-ID-String $file "PluginVersion" 138 | $Author = Get-ID-String $file "Author" 139 | $Ver = "{0:N1}" -f $PluginVersion 140 | 141 | return @{ "Title" = $Title; "Version" = $Ver; "Author" = $Author } 142 | } 143 | 144 | 145 | Function Invoke-Settings { 146 | 147 | <# 148 | .DESCRIPTION 149 | Run through settings for specified file, expects question on one line, and variable/value on following line 150 | .NOTES 151 | Updated: 20150428 152 | Updated By: Kevin Kirkpatrick (@vScripter - Twitter/GitHub) 153 | Update Notes: 154 | - Remove Write-Host in favor of Write-Warning; this was based on setting the color of Write-Host to 'warning' colors 155 | - converted function to advanced function 156 | - moved parameters out of function declaration and into the param declaration 157 | - moved all code into the PROCESS block 158 | - improved code spacing for improved readability 159 | - added comment based help section for notes/comments 160 | #> 161 | 162 | [CmdletBinding(PositionalBinding = $true)] 163 | param ( 164 | [parameter(Position = 0)] 165 | $Filename, 166 | [parameter(Position = 1)] 167 | $GB 168 | ) 169 | 170 | PROCESS { 171 | 172 | $file = Get-Content $filename 173 | $OriginalLine = ($file | Select-String -Pattern "# Start of Settings").LineNumber 174 | $EndLine = ($file | Select-String -Pattern "# End of Settings").LineNumber 175 | 176 | if (!(($OriginalLine + 1) -eq $EndLine)) { 177 | 178 | $Array = @() 179 | $Line = $OriginalLine 180 | $PluginName = (Get-PluginID $Filename).Title 181 | 182 | If ($PluginName.EndsWith(".ps1", 1)) { 183 | 184 | $PluginName = ($PluginName.split("\")[-1]).split(".")[0] 185 | 186 | } # end if 187 | 188 | Write-Warning -Message "`n$PluginName" 189 | 190 | do { 191 | 192 | $Question = $file[$Line] 193 | $Line++ 194 | $Split = ($file[$Line]).Split("=") 195 | $Var = $Split[0] 196 | $CurSet = $Split[1].Trim() 197 | 198 | # Check if the current setting is in speech marks 199 | $String = $false 200 | if ($CurSet -match '"') { 201 | $String = $true 202 | $CurSet = $CurSet.Replace('"', '').Trim() 203 | } # end if 204 | 205 | $NewSet = Read-Host "$Question [$CurSet]" 206 | 207 | If (-not $NewSet) { 208 | $NewSet = $CurSet 209 | } # end if 210 | 211 | If ($String) { 212 | $Array += $Question 213 | $Array += "$Var= `"$NewSet`"" 214 | } Else { 215 | $Array += $Question 216 | $Array += "$Var= $NewSet" 217 | } # end if/else 218 | 219 | $Line++ 220 | 221 | } Until ($Line -ge ($EndLine - 1)) 222 | 223 | $Array += "# End of Settings" 224 | 225 | $out = @() 226 | $out = $File[0..($OriginalLine - 1)] 227 | $out += $array 228 | $out += $File[$Endline..($file.count - 1)] 229 | 230 | if ($GB) { 231 | $out[$SetupLine] = '$SetupWizard = $False' 232 | } # end if 233 | 234 | $out | Out-File $Filename 235 | 236 | } # end if 237 | 238 | } # end PROCESS block 239 | 240 | } # end Function Invoke-Settings 241 | 242 | <# Replace HTML Entities in string. Used to stop
tags from being mangled in tables #> 243 | function Format-HTMLEntities { 244 | param ([string]$content) 245 | 246 | $replace = @{ 247 | "<" = "<"; 248 | ">" = ">"; 249 | } 250 | 251 | foreach ($r in $replace.Keys.GetEnumerator()) { 252 | $content = $content -replace $r, $replace[$r] 253 | } 254 | return $content 255 | } 256 | 257 | <# Takes an array of content, and optional formatRules and generated HTML table #> 258 | Function Get-HTMLTable { 259 | param ($Content, $FormatRules) 260 | 261 | # Use an XML object for ease of use 262 | $XMLTable = [xml]($content | ConvertTo-Html -Fragment) 263 | $XMLTable.table.SetAttribute("width", "100%") 264 | 265 | # If only one column, fix up the table header 266 | if (($content | Get-Member -MemberType Properties).count -eq 1) 267 | { 268 | $XMLTable.table.tr[0].th = (($content | Get-Member -MemberType Properties) | Select -ExpandProperty Name -First 1).ToString() 269 | } 270 | 271 | # If format rules are specified 272 | if ($FormatRules) { 273 | # Check each cell to see if there are any format rules 274 | for ($RowN = 1; $RowN -lt $XMLTable.table.tr.count; $RowN++) { 275 | for ($ColN = 0; $ColN -lt $XMLTable.table.tr[$RowN].td.count; $ColN++) { 276 | if ($FormatRules.keys -contains $XMLTable.table.tr[0].th[$ColN]) { 277 | # Current cell has a rule, test to see if they are valid 278 | foreach ($rule in $FormatRules[$XMLTable.table.tr[0].th[$ColN]]) { 279 | if ($XMLTable.table.tr[$RowN].td[$ColN]."#text") 280 | { 281 | $value = $XMLTable.table.tr[$RowN].td[$ColN]."#text" 282 | } 283 | else 284 | { 285 | $value = $XMLTable.table.tr[$RowN].td[$ColN] 286 | } 287 | if ($value -notmatch "^[0-9.]+$") { 288 | $value = """$value""" 289 | } 290 | if (Invoke-Expression ("{0} {1}" -f $value, [string]$rule.Keys)) { 291 | # Find what to 292 | $RuleScope = ([string]$rule.Values).split(",")[0] 293 | $RuleActions = ([string]$rule.Values).split(",")[1].split("|") 294 | 295 | switch ($RuleScope) { 296 | "Row" { 297 | for ($TRColN = 0; $TRColN -lt $XMLTable.table.tr[$RowN].td.count; $TRColN++) { 298 | $XMLTable.table.tr[$RowN].selectSingleNode("td[$($TRColN + 1)]").SetAttribute($RuleActions[0], $RuleActions[1]) 299 | } 300 | } 301 | "Cell" { 302 | if ($RuleActions[0] -eq "cid") { 303 | # Do Image - create new XML node for img and clear #text 304 | $XMLTable.table.tr[$RowN].selectSingleNode("td[$($ColN + 1)]")."#text" = "" 305 | $elem = $XMLTable.CreateElement("img") 306 | $elem.SetAttribute("src", ("cid:{0}" -f $RuleActions[1])) 307 | # Add img size if specified 308 | if ($RuleActions[2] -match "(\d+)x(\d+)") { 309 | $elem.SetAttribute("width", $Matches[1]) 310 | $elem.SetAttribute("height", $Matches[2]) 311 | } 312 | 313 | $XMLTable.table.tr[$RowN].selectSingleNode("td[$($ColN + 1)]").AppendChild($elem) | Out-Null 314 | # Increment usage counter (so we don't have .bin attachments) 315 | Set-ReportResource $RuleActions[1] 316 | } else { 317 | $XMLTable.table.tr[$RowN].selectSingleNode("td[$($ColN + 1)]").SetAttribute($RuleActions[0], $RuleActions[1]) 318 | } 319 | } 320 | } 321 | } 322 | } 323 | } 324 | } 325 | } 326 | } 327 | return (Format-HTMLEntities ([string]($XMLTable.OuterXml))) 328 | } 329 | 330 | <# Takes an array of content, and returns HTML table with header column #> 331 | Function Get-HTMLList { 332 | param ([array]$content) 333 | 334 | if ($content.count -gt 0) { 335 | # Create XML doc from HTML. Remove colgroup and header row 336 | if ($content.count -gt 1) { 337 | [xml]$XMLTable = $content | ConvertTo-HTML -Fragment 338 | $XMLTable.table.RemoveChild($XMLTable.table.colgroup) | out-null 339 | $XMLTable.table.RemoveChild($XMLTable.table.tr[0]) | out-null 340 | $XMLTable.table.SetAttribute("width", "100%") 341 | } else { 342 | [xml]$XMLTable = $content | ConvertTo-HTML -Fragment -As List 343 | } 344 | 345 | # Replace the first column td with th 346 | for ($i = 0; $i -lt $XMLTable.table.tr.count; $i++) { 347 | $node = $XMLTable.table.tr[$i].SelectSingleNode("/table/tr[$($i + 1)]/td[1]") 348 | $elem = $XMLTable.CreateElement("th") 349 | $elem.InnerText = $node."#text" 350 | $trNode = $XMLTable.SelectSingleNode("/table/tr[$($i + 1)]") 351 | $trNode.ReplaceChild($elem, $node) | Out-Null 352 | } 353 | 354 | # If only one column, fix up the table header 355 | if (($content | Get-Member -MemberType Properties).count -eq 1) 356 | { 357 | $XMLTable.table.tr[0].th = (($content | Get-Member -MemberType Properties) | Select -ExpandProperty Name -First 1).ToString() 358 | } 359 | 360 | return (Format-HTMLEntities ([string]($XMLTable.OuterXml))) 361 | } 362 | } 363 | 364 | <# Returns HTML fragment for chart. Calls Get-ChartResource to generate chart image #> 365 | function Get-HTMLChart { 366 | param ( 367 | [string]$cidbase, 368 | [Object[]]$ChartObjs 369 | ) 370 | $html = "" 371 | $i = 0 372 | foreach ($ChartObj in $ChartObjs) { 373 | $i++ 374 | $base64 = Get-ChartResource $ChartObj 375 | $cid = $cidbase + "-" + $i 376 | Add-ReportResource -cid $cid -ResourceData $Base64 -Type "Base64" -Used $true 377 | $html += "" 378 | } 379 | return $html 380 | } 381 | 382 | <# Create a new Chert object, this will get fed back down the output stream as part 383 | of plugin processing. This allows us to keep the same interface for plugins content #> 384 | function New-Chart { 385 | param ( 386 | [int]$height, 387 | [int]$width, 388 | [Parameter(Mandatory = $true)] 389 | [Hashtable[]]$data, 390 | [string]$title, 391 | [string]$titleX, 392 | [string]$titleY, 393 | [ValidateSet("Area", "Bar", "BoxPlot", "Bubble", "Candlestick", "Column", "Doughnut", "ErrorBar", "FastLine", 394 | "FastPoint", "Funnel", "Kagi", "Line", "Pie", "Point", "PointAndFigure", "Polar", "Pyramid", 395 | "Radar", "Range", "RangeBar", "RangeColumn", "Renko", "Spline", "SplineArea", "SplineRange", 396 | "StackedArea", "StackedArea100", "StackedBar", "StackedBar100", "StackedColumn", 397 | "StackedColumn100", "StepLine", "Stock", "ThreeLineBreak")] 398 | $ChartType = "bar" 399 | ) 400 | 401 | # If chartsize is specified in style, use it unless explicitly set 402 | if ($ChartSize -and (-not $height -and -not $width)) { 403 | if ($ChartSize -match "(\d+)x(\d+)") { 404 | $height = $Matches[1] 405 | $width = $Matches[2] 406 | } 407 | } 408 | # if size not set in style or function call, default to 400x400 (maybe make this a globalVariable?) 409 | if (-not $ChartSize -and (-not $height -and -not $width)) { 410 | $height = 400 411 | $width = 400 412 | } 413 | 414 | return New-Object PSObject -Property @{ 415 | "height" = $height; 416 | "width" = $width; 417 | "data" = $data; 418 | "title" = $title; 419 | "titleX" = $titleX; 420 | "titleY" = $titleY; 421 | "ChartType" = $ChartType 422 | } 423 | } 424 | 425 | <# Creates a chart Image #> 426 | function Get-ChartResource { 427 | param ( 428 | $ChartDef 429 | ) 430 | [void][Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms.DataVisualization") 431 | 432 | # Create a new chart object 433 | $Chart = New-object System.Windows.Forms.DataVisualization.Charting.Chart 434 | $Chart.Width = $ChartDef.width 435 | $Chart.Height = $ChartDef.height 436 | $Chart.AntiAliasing = "All" 437 | 438 | # Create a chartarea to draw on and add to chart 439 | $ChartArea = New-Object System.Windows.Forms.DataVisualization.Charting.ChartArea 440 | $Chart.ChartAreas.Add($ChartArea) 441 | 442 | # Set title and axis labels 443 | if ($ChartDef.title) { 444 | $titleRef = $Chart.Titles.Add($ChartDef.title) 445 | } 446 | if ($ChartDef.titleX) { 447 | $ChartArea.AxisX.Title = $ChartDef.titleX 448 | } 449 | if ($ChartDef.titleY) { 450 | $ChartArea.AxisY.Title = $ChartDef.titleY 451 | } 452 | 453 | # change chart colours 454 | if ($ChartBackground) { 455 | $Chart.BackColor = Get-ChartColours $ChartBackground 456 | $ChartArea.BackColor = Get-ChartColours $ChartBackground 457 | } else { 458 | $Chart.BackColor = [System.Drawing.Color]::Transparent 459 | $ChartArea.BackColor = [System.Drawing.Color]::Transparent 460 | } 461 | # If we have style 462 | if ($ChartColours) { 463 | $Chart.PaletteCustomColors = Get-ChartColours $ChartColours 464 | $Chart.Palette = [System.Windows.Forms.DataVisualization.Charting.ChartColorPalette]::None 465 | } 466 | 467 | if ($ChartFontColour) { 468 | $Chart.ForeColor = Get-ChartColours $ChartFontColour 469 | } 470 | 471 | # Add data to chart and set chart type 472 | for ($i = 0; $i -lt $ChartDef.data.count; $i++) { 473 | [void]$Chart.Series.Add("Data$i") 474 | $Chart.Series["Data$i"].Points.DataBindXY($ChartDef.data[$i].Keys, $ChartDef.data[$i].Values) 475 | $Chart.Series["Data$i"].ChartType = [System.Windows.Forms.DataVisualization.Charting.SeriesChartType]::($ChartDef.ChartType) 476 | } 477 | 478 | # Do some funky work to increase the DPI so charts look nice. Default 96 DPI looks terrible :( 479 | [void][System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") 480 | 481 | $bmp = New-Object System.Drawing.Bitmap(($ChartDef.width), ($ChartDef.height)) 482 | $bmp.SetResolution(384, 384); 483 | if ($ChartArea.BackColor -eq [System.Drawing.Color]::Transparent) { 484 | $bmp.MakeTransparent() 485 | } 486 | $chart.DrawToBitmap($bmp, (new-object System.Drawing.Rectangle(0, 0, $ChartDef.width, $ChartDef.height))) 487 | $ms = new-Object IO.MemoryStream 488 | $bmp.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png); 489 | $ms.Seek(0, [System.IO.SeekOrigin]::Begin) | Out-Null 490 | $byte = New-Object byte[] $ms.Length 491 | $ms.read($byte, 0, $ms.length) | Out-Null 492 | 493 | return ("png|{0}" -f [System.Convert]::ToBase64String($byte)) 494 | } 495 | 496 | <# Takes Array of HTML colour codes and returns Color object #> 497 | function Get-ChartColours { 498 | param ( 499 | [string[]]$ChartColours 500 | ) 501 | 502 | foreach ($colour in $ChartColours) { 503 | [System.Drawing.Color]::FromArgb([Convert]::ToInt32($colour.Substring(0, 2), 16), 504 | [Convert]::ToInt32($colour.Substring(2, 2), 16), 505 | [Convert]::ToInt32($colour.Substring(4, 2), 16)); 506 | } 507 | } 508 | 509 | <# Adds a resource to the resource array, to be included in report. 510 | At the moment, only "File" types are supported- this will be expanded to include 511 | SystemIcons and raw byte data (so images can be packaged completely in styles if desired 512 | #> 513 | function Add-ReportResource { 514 | param ( 515 | $cid, 516 | $ResourceData, 517 | [ValidateSet("File", "SystemIcons", "Base64")] 518 | $Type = "File", 519 | $Used = $false 520 | ) 521 | 522 | # If cid does not exist, add it 523 | if ($global:ReportResources.Keys -notcontains $cid) { 524 | $global:ReportResources.Add($cid, @{ 525 | "Data" = ("{0}|{1}" -f $Type, $ResourceData); 526 | "Uses" = 0 527 | }) 528 | } 529 | 530 | # Update uses count if $Used set (Should normally be incremented with Set-ReportResource) 531 | # Useful for things like headers where they are always required. 532 | if ($Used) { 533 | ($global:ReportResources[$cid].Uses)++ 534 | } 535 | } 536 | 537 | Function Set-ReportResource { 538 | param ( 539 | $cid 540 | ) 541 | 542 | # Increment use 543 | ($global:ReportResources[$cid].Uses)++ 544 | } 545 | 546 | <# Gets a resource in the specified ReturnType (eventually support both a 547 | base64 encoded string, and Linked Resource for email #> 548 | function Get-ReportResource { 549 | param ( 550 | $cid, 551 | [ValidateSet("embed", "linkedresource")] 552 | $ReturnType = "embed" 553 | ) 554 | 555 | $data = $global:ReportResources[$cid].Data.Split("|") 556 | 557 | # Process each resource type differently 558 | switch ($data[0]) { 559 | "File" { 560 | # Check the path exists 561 | if (Test-Path $data[1] -ErrorAction SilentlyContinue) { 562 | if ($ReturnType -eq "embed") { 563 | # return a MIME/Base64 combo for embedding in HTML 564 | $imgData = Get-Content ($data[1]) -Encoding Byte 565 | $type = $data[1].substring($data[1].LastIndexOf(".") + 1) 566 | return ("data:image/{0};base64,{1}" -f $type, [System.Convert]::ToBase64String($imgData)) 567 | } 568 | if ($ReturnType -eq "linkedresource") { 569 | # return a linked resource to be added to mail message 570 | $lr = New-Object system.net.mail.LinkedResource($data[1]) 571 | $lr.ContentId = $cid 572 | return $lr; 573 | } 574 | } else { 575 | Write-Warning ($lang.resFileWarn -f $cid) 576 | } 577 | } 578 | "SystemIcons" { 579 | # Take the SystemIcon Name - see http://msdn.microsoft.com/en-us/library/system.drawing.systemicons(v=vs.110).aspx 580 | # Load the image into a MemoryStream in PNG format (to preserve transparency) 581 | [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") 582 | $bmp = ([System.Drawing.SystemIcons]::($data[1])).toBitmap() 583 | $bmp.MakeTransparent() 584 | $ms = new-Object IO.MemoryStream 585 | $bmp.Save($ms, [System.Drawing.Imaging.ImageFormat]::PNG) 586 | $ms.Seek(0, [System.IO.SeekOrigin]::Begin) | Out-Null 587 | 588 | if ($ReturnType -eq "embed") { 589 | # return a MIME/Base64 combo for embedding in HTML 590 | $byte = New-Object byte[] $ms.Length 591 | $ms.read($byte, 0, $ms.length) | Out-Null 592 | return ("data:image/png;base64," + [System.Convert]::ToBase64String($byte)) 593 | } 594 | if ($ReturnType -eq "linkedresource") { 595 | # return a linked resource to be added to mail message 596 | $lr = New-Object system.net.mail.LinkedResource($ms) 597 | $lr.ContentId = $cid 598 | return $lr; 599 | } 600 | } 601 | "Base64" { 602 | if ($ReturnType -eq "embed") { 603 | return ("data:image/{0};base64,{1}" -f $data[1], $data[2]) 604 | } 605 | if ($ReturnType -eq "linkedresource") { 606 | $w = [system.convert]::FromBase64String($data[2]) 607 | $ms = new-Object IO.MemoryStream 608 | $ms.Write($w, 0, $w.Length); 609 | $ms.Seek(0, [System.IO.SeekOrigin]::Begin) | Out-Null 610 | $lr = New-Object system.net.mail.LinkedResource($ms) 611 | $lr.ContentId = $cid 612 | return $lr; 613 | } 614 | } 615 | } 616 | } 617 | 618 | #endregion functions 619 | 620 | #region initialization 621 | ################################################################################ 622 | # Initialization # 623 | ################################################################################ 624 | # Setup all paths required for script to run 625 | $ScriptPath = (Split-Path ((Get-Variable MyInvocation).Value).MyCommand.Path) 626 | $PluginsFolder = $ScriptPath + "\Plugins\" 627 | 628 | # if we have the job parameter set, get the paths from the config file. 629 | if ($job) { 630 | [xml]$jobConfig = Get-Content $job 631 | 632 | # Use GlobalVariables path if it is valid, otherwise use default 633 | if (Test-Path $jobConfig.vCheck.globalVariables) { 634 | $GlobalVariables = (Get-Item $jobConfig.vCheck.globalVariables).FullName 635 | } else { 636 | $GlobalVariables = $ScriptPath + "\GlobalVariables.ps1" 637 | Write-Warning ($lang.gvInvalid -f $GlobalVariables) 638 | } 639 | 640 | # Get Plugin paths 641 | $PluginPaths = @() 642 | if ($jobConfig.vCheck.plugins.path) { 643 | foreach ($PluginPath in ($jobConfig.vCheck.plugins.path -split ";")) { 644 | if (Test-Path $PluginPath) { 645 | $PluginPaths += (Get-Item $PluginPath).Fullname 646 | $PluginPaths += Get-Childitem $PluginPath -Recurse | ?{ $_.PSIsContainer } | Select -ExpandProperty FullName 647 | } else { 648 | $PluginPaths += $ScriptPath + "\Plugins" 649 | Write-Warning ($lang.pluginpathInvalid -f $PluginPath, ($ScriptPath + "\Plugins")) 650 | } 651 | } 652 | $PluginPaths = $PluginPaths | Sort-Object -unique 653 | 654 | # Get all plugins and test they are correct 655 | $vCheckPlugins = @() 656 | foreach ($plugin in $jobConfig.vCheck.plugins.plugin) { 657 | $testedPaths = 0 658 | foreach ($PluginPath in $PluginPaths) { 659 | $testedPaths++ 660 | if (Test-Path ("{0}\{1}" -f $PluginPath, $plugin)) { 661 | $vCheckPlugins += Get-Item ("{0}\{1}" -f $PluginPath, $plugin) 662 | break; 663 | } 664 | # Plugin not found in any search path 665 | elseif ($testedPaths -eq $PluginPaths.Count) { 666 | Write-Warning ($lang.pluginInvalid -f $plugin) 667 | } 668 | } 669 | } 670 | } 671 | # if no valid plugins specified, fall back to default 672 | if (!$vCheckPlugins) { 673 | $vCheckPlugins = Get-ChildItem -Path $PluginsFolder -filter "*.ps1" -Recurse | Sort FullName 674 | } 675 | } else { 676 | $ToNatural = { [regex]::Replace($_, '\d+', { $args[0].Value.PadLeft(20) }) } 677 | $vCheckPlugins = @(Get-ChildItem -Path $PluginsFolder -filter "*.ps1" -Recurse | where { $_.Directory -match "initialize" } | Sort $ToNatural) 678 | $PluginsSubFolder = Get-ChildItem -Path $PluginsFolder | where { ($_.PSIsContainer) -and ($_.Name -notmatch "initialize") -and ($_.Name -notmatch "finish") } 679 | $vCheckPlugins += $PluginsSubFolder | % { Get-ChildItem -Path $_.FullName -filter "*.ps1" | Sort $ToNatural } 680 | $vCheckPlugins += Get-ChildItem -Path $PluginsFolder -filter "*.ps1" -Recurse | where { $_.Directory -match "finish" } | Sort $ToNatural 681 | $GlobalVariables = $ScriptPath + "\GlobalVariables.ps1" 682 | } 683 | 684 | ## Determine if the setup wizard needs to run 685 | $file = Get-Content $GlobalVariables 686 | $Setup = ($file | Select-String -Pattern '# Set the following to true to enable the setup wizard for first time run').LineNumber 687 | $SetupLine = $Setup++ 688 | $SetupSetting = Invoke-Expression (($file[$SetupLine]).Split("="))[1] 689 | 690 | if ($SetupSetting -or $config) { 691 | 692 | Clear-Host 693 | 694 | ($lang.GetEnumerator() | Where-Object { $_.Name -match "setupMsg[0-9]*" } | Sort-Object Name) | ForEach-Object { 695 | Write-Warning -Message "$($_.value)" 696 | } 697 | 698 | Invoke-Settings -Filename $GlobalVariables -GB $true 699 | Foreach ($plugin in $vCheckPlugins) { 700 | Invoke-Settings -Filename $plugin.Fullname 701 | } 702 | } 703 | 704 | ## Include GlobalVariables and validate settings (at the moment just check they exist) 705 | . $GlobalVariables 706 | 707 | $vcvars = @("SetupWizard", "reportHeader", "SMTPSRV", "EmailFrom", "EmailTo", "EmailSubject", "DisplaytoScreen", "SendEmail", "SendAttachment", "TimeToRun", "PluginSeconds", "Style", "Date") 708 | foreach ($vcvar in $vcvars) { 709 | if (!($(Get-Variable -Name "$vcvar" -Erroraction 'SilentlyContinue'))) { 710 | Write-Error ($lang.varUndefined -f $vcvar) 711 | } 712 | } 713 | 714 | # Create empty array of resources (i.e. Images) 715 | $global:ReportResources = @{ } 716 | 717 | ## Set the StylePath and include it 718 | $StylePath = $ScriptPath + "\Styles\" + $Style 719 | if (!(Test-Path ($StylePath))) { 720 | # The path is not valid 721 | # Use the default style 722 | Write-Warning "Style path ($($StylePath)) is not valid" 723 | $StylePath = $ScriptPath + "\Styles\VMware" 724 | Write-Warning "Using $($StylePath)" 725 | } 726 | 727 | # Import the Style 728 | . ("$($StylePath)\Style.ps1") 729 | 730 | #endregion initialization 731 | 732 | #region scriptlogic 733 | ################################################################################ 734 | # Script logic # 735 | ################################################################################ 736 | # Start generating the report 737 | $PluginResult = @() 738 | 739 | Write-Warning -Message $lang.pluginBegin 740 | 741 | # Loop over all enabled plugins 742 | $p = 0 743 | $vCheckPlugins | Foreach { 744 | $TableFormat = $null 745 | $PluginInfo = Get-PluginID $_.Fullname 746 | $p++ 747 | Write-CustomOut ($lang.pluginStart -f $PluginInfo["Title"], $PluginInfo["Author"], $PluginInfo["Version"], $p, $vCheckPlugins.count) 748 | $pluginStatus = ($lang.pluginStatus -f $p, $vCheckPlugins.count, $_.Name) 749 | Write-Progress -ID 1 -Activity $lang.pluginActivity -Status $pluginStatus -PercentComplete (100 * $p/($vCheckPlugins.count)) 750 | $TTR = [math]::round((Measure-Command { $Details = @(. $_.FullName)}).TotalSeconds, 2) 751 | 752 | Write-CustomOut ($lang.pluginEnd -f $PluginInfo["Title"], $PluginInfo["Author"], $PluginInfo["Version"], $p, $vCheckPlugins.count) 753 | # Do a replacement for [count] for number of items returned in $header 754 | $Header = $Header -replace "\[count\]", $Details.count 755 | 756 | $PluginResult += New-Object PSObject -Property @{ 757 | "Title" = $Title; 758 | "Author" = $PluginInfo["Author"]; 759 | "Version" = $PluginInfo["Version"]; 760 | "Details" = $Details; 761 | "Display" = $Display; 762 | "TableFormat" = $TableFormat; 763 | "Header" = $Header; 764 | "Comments" = $Comments; 765 | "TimeToRun" = $TTR; 766 | } 767 | } 768 | Write-Progress -ID 1 -Activity $lang.pluginActivity -Status $lang.Complete -Completed 769 | 770 | # Add report on plugins 771 | if ($reportOnPlugins) { 772 | $Comments = "Plugins in numerical order" 773 | $Plugins = @() 774 | foreach ($Plugin in (Get-ChildItem $PluginsFolder -Include *.ps1, *.ps1.disabled -Recurse)) { 775 | $Plugins += New-Object PSObject -Property @{ 776 | "Name" = (Get-PluginID $Plugin.FullName).Title; 777 | "Enabled" = (($vCheckPlugins | Select -ExpandProperty FullName) -Contains $plugin.FullName) 778 | } 779 | } 780 | 781 | if ($ListEnabledPluginsFirst) { 782 | $Plugins = $Plugins | Sort -property @{ Expression = "Enabled"; Descending = $true } 783 | $Comments = "Plugins in numerical order, enabled plugins listed first" 784 | } 785 | 786 | $PluginResult += New-Object PSObject -Property @{ 787 | "Title" = $lang.repPRTitle; 788 | "Author" = "vCheck"; 789 | "Version" = $vCheckVersion; 790 | "Details" = $Plugins; 791 | "Display" = "Table"; 792 | "TableFormat" = $null; 793 | "Header" = $lang.repPRTitle; 794 | "Comments" = $Comments; 795 | "TimeToRun" = 0; 796 | } 797 | } 798 | 799 | # Add Time to Run detail for plugins - if specified in GlobalVariables.ps1 800 | if ($TimeToRun) { 801 | $Finished = Get-Date 802 | $PluginResult += New-Object PSObject -Property @{ 803 | "Title" = $lang.repTTRTitle; 804 | "Author" = "vCheck"; 805 | "Version" = $vCheckVersion; 806 | "Details" = ($PluginResult | Where { $_.TimeToRun -gt $PluginSeconds } | Select Title, TimeToRun | Sort-Object TimeToRun -Descending); 807 | "Display" = "List"; 808 | "TableFormat" = $null; 809 | "Header" = ($lang.repTime -f [math]::round(($Finished - $Date).TotalMinutes, 2), ($Finished.ToLongDateString()), ($Finished.ToLongTimeString())); 810 | "Comments" = ($lang.slowPlugins -f $PluginSeconds); 811 | "TimeToRun" = 0; 812 | } 813 | } 814 | 815 | #endregion scriptlogic 816 | 817 | #region output 818 | ################################################################################ 819 | # Output # 820 | ################################################################################ 821 | # Loop over plugin results and generate HTML from style 822 | $emptyReport = $true 823 | $p = 1 824 | Foreach ($pr in $PluginResult) { 825 | If ($pr.Details) { 826 | $emptyReport = $false 827 | switch ($pr.Display) { 828 | "List" { $pr.Details = Get-HTMLList $pr.Details } 829 | "Table" { $pr.Details = Get-HTMLTable $pr.Details $pr.TableFormat } 830 | "Chart" { $pr.Details = Get-HTMLChart "plugin$($p)" $pr.Details } 831 | default { $pr.Details = $null } 832 | } 833 | $pr | Add-Member -Type NoteProperty -Name pluginID -Value "plugin-$p" 834 | $p++ 835 | } 836 | } 837 | 838 | # Run Style replacement 839 | $MyReport = Get-ReportHTML 840 | 841 | # Set the output filename 842 | if (-not (Test-Path -PathType Container $Outputpath)) { New-Item $Outputpath -type directory | Out-Null } 843 | $Filename = ("{0}\{1}_vCheck_{2}.htm" -f $Outputpath, $Server, (Get-Date -Format "yyyyMMdd_HHmm")) 844 | 845 | # Always generate the report with embedded images 846 | $embedReport = $MyReport 847 | # Loop over all CIDs and replace them 848 | Foreach ($cid in $global:ReportResources.Keys) { 849 | $embedReport = $embedReport -replace ("cid:{0}" -f $cid), (Get-ReportResource $cid -ReturnType "embed") 850 | } 851 | $embedReport | Out-File -encoding ASCII -filepath $Filename 852 | 853 | # Display to screen 854 | if ($DisplayToScreen -and (!($emptyReport -and !$DisplayReportEvenIfEmpty))) { 855 | Write-CustomOut $lang.HTMLdisp 856 | Invoke-Item $Filename 857 | } 858 | 859 | # Generate email 860 | if ($SendEmail -and (!($emptyReport -and !$EmailReportEvenIfEmpty))) { 861 | Write-CustomOut $lang.emailSend 862 | $msg = New-Object System.Net.Mail.MailMessage ($EmailFrom, $EmailTo) 863 | # If CC address specified, add 864 | If ($EmailCc -ne "") { 865 | $msg.CC.Add($EmailCc) 866 | } 867 | $msg.subject = $EmailSubject 868 | 869 | # if send attachment, just send plaintext email with HTML report attached 870 | If ($SendAttachment) { 871 | $msg.Body = $lang.emailAtch 872 | $attachment = new-object System.Net.Mail.Attachment $Filename 873 | $msg.Attachments.Add($attachment) 874 | } 875 | # Otherwise send the HTML email 876 | else { 877 | $msg.IsBodyHtml = $true; 878 | $html = [System.Net.Mail.AlternateView]::CreateAlternateViewFromString($MyReport, $null, 'text/html') 879 | $msg.AlternateViews.Add($html) 880 | 881 | # Loop over all CIDs and replace them 882 | Foreach ($cid in $global:ReportResources.Keys) { 883 | if ($global:ReportResources[$cid].Uses -gt 0) { 884 | $lr = (Get-ReportResource $cid -ReturnType "linkedresource") 885 | $html.LinkedResources.Add($lr); 886 | } 887 | } 888 | } 889 | # Send the email 890 | $smtpClient = New-Object System.Net.Mail.SmtpClient 891 | 892 | # Find the VI Server and port from the global settings file 893 | $smtpClient.Host = ($SMTPSRV -Split ":")[0] 894 | if (($SMTPSRV -split ":")[1]) { 895 | $smtpClient.Port = ($SMTPSRV -split ":")[1] 896 | } 897 | 898 | if ($EmailSSL -eq $true) { 899 | $smtpClient.EnableSsl = $true 900 | } 901 | $smtpClient.UseDefaultCredentials = $true; 902 | $smtpClient.Send($msg) 903 | If ($SendAttachment) { $attachment.Dispose() } 904 | $msg.Dispose() 905 | } 906 | 907 | # Run EndScript once everything else is complete 908 | if (Test-Path ($ScriptPath + "\EndScript.ps1")) { 909 | . ($ScriptPath + "\EndScript.ps1") 910 | } 911 | 912 | #endregion output 913 | -------------------------------------------------------------------------------- /vCheckUtils.ps1: -------------------------------------------------------------------------------- 1 | $global:vCheckPath = $MyInvocation.MyCommand.Definition | Split-Path 2 | $global:pluginXMLURL = "https://raw.github.com/alanrenouf/vCheck-vSphere/master/plugins.xml" 3 | $global:pluginURL = "https://raw.github.com/alanrenouf/vCheck-{0}/master/Plugins/{1}/{2}" 4 | 5 | <# 6 | .SYNOPSIS 7 | Retrieves installed vCheck plugins and available plugins from the Virtu-Al.net repository. 8 | 9 | .DESCRIPTION 10 | Get-vCheckPlugin parses your vCheck plugins folder, as well as searches the online plugin respository in Virtu-Al.net. 11 | After finding the plugin you are looking for, you can download and install it with Add-vCheckPlugin. Get-vCheckPlugins 12 | also supports finding a plugin by name. Future version will support categories (e.g. Datastore, Security, vCloud) 13 | 14 | .PARAMETER name 15 | Name of the plugin. 16 | 17 | .PARAMETER proxy 18 | URL for proxy usage. 19 | 20 | .PARAMETER proxy_user 21 | username for proxy auth. 22 | 23 | .PARAMETER proxy_password 24 | password for proxy auth. 25 | 26 | .PARAMETER proxy_domain 27 | domain for proxy auth. 28 | 29 | .EXAMPLE 30 | Get list of all vCheck Plugins 31 | Get-vCheckPlugin 32 | 33 | .EXAMPLE 34 | Get plugin by name 35 | Get-vCheckPlugin PluginName 36 | 37 | .EXAMPLE 38 | Get plugin by name using proxy 39 | Get-vCheckPlugin PluginName -proxy "http://127.0.0.1:3128" 40 | 41 | .EXAMPLE 42 | Get plugin by name using proxy with auth (domain optional depending on your proxy auth) 43 | Get-vCheckPlugin PluginName -proxy "http://127.0.0.1:3128" -proxy_user "username" -proxy_pass "password -proxy_domain "domain" 44 | 45 | .EXAMPLE 46 | Get plugin information 47 | Get-vCheckPlugins PluginName 48 | #> 49 | function Get-vCheckPlugin 50 | { 51 | [CmdletBinding()] 52 | Param 53 | ( 54 | [Parameter(mandatory=$false)] [String]$name, 55 | [Parameter(mandatory=$false)] [String]$proxy, 56 | [Parameter(mandatory=$false)] [String]$proxy_user, 57 | [Parameter(mandatory=$false)] [String]$proxy_pass, 58 | [Parameter(mandatory=$false)] [String]$proxy_domain, 59 | [Parameter(mandatory=$false)] [Switch]$installed, 60 | [Parameter(mandatory=$false)] [Switch]$notinstalled, 61 | [Parameter(mandatory=$false)] [Switch]$pendingupdate, 62 | [Parameter(mandatory=$false)] [String]$category 63 | ) 64 | Process 65 | { 66 | $pluginObjectList = @() 67 | 68 | foreach ($localPluginFile in (Get-ChildItem -Path $vCheckPath\Plugins\* -Include *.ps1, *.ps1.disabled -Recurse)) 69 | { 70 | $localPluginContent = Get-Content $localPluginFile 71 | 72 | if ($localPluginContent | Select-String -SimpleMatch "title") 73 | { 74 | $localPluginName = ($localPluginContent | Select-String -SimpleMatch "Title").toString().split("`"")[1] 75 | } 76 | if($localPluginContent | Select-String -SimpleMatch "description") 77 | { 78 | $localPluginDesc = ($localPluginContent | Select-String -SimpleMatch "description").toString().split("`"")[1] 79 | } 80 | elseif ($localPluginContent | Select-String -SimpleMatch "comments") 81 | { 82 | $localPluginDesc = ($localPluginContent | Select-String -SimpleMatch "comments").toString().split("`"")[1] 83 | } 84 | if ($localPluginContent | Select-String -SimpleMatch "author") 85 | { 86 | $localPluginAuthor = ($localPluginContent | Select-String -SimpleMatch "author").toString().split("`"")[1] 87 | } 88 | if ($localPluginContent | Select-String -SimpleMatch "PluginVersion") 89 | { 90 | $localPluginVersion = @($localPluginContent | Select-String -SimpleMatch "PluginVersion")[0].toString().split(" ")[-1] 91 | } 92 | if ($localPluginContent | Select-String -SimpleMatch "PluginCategory") 93 | { 94 | $localPluginCategory = @($localPluginContent | Select-String -SimpleMatch "PluginCategory")[0].toString().split("`"")[1] 95 | } 96 | 97 | $pluginObject = New-Object PSObject 98 | $pluginObject | Add-Member -MemberType NoteProperty -Name name -value $localPluginName 99 | $pluginObject | Add-Member -MemberType NoteProperty -Name description -value $localPluginDesc 100 | $pluginObject | Add-Member -MemberType NoteProperty -Name author -value $localPluginAuthor 101 | $pluginObject | Add-Member -MemberType NoteProperty -Name version -value $localPluginVersion 102 | $pluginObject | Add-Member -MemberType NoteProperty -Name category -Value $localPluginCategory 103 | $pluginObject | Add-Member -MemberType NoteProperty -Name status -value "Installed" 104 | $pluginObject | Add-Member -MemberType NoteProperty -Name location -Value $LocalpluginFile.FullName 105 | $pluginObjectList += $pluginObject 106 | } 107 | 108 | if (!$installed) 109 | { 110 | try 111 | { 112 | $webClient = new-object system.net.webclient 113 | if ($proxy) 114 | { 115 | $proxyURL = new-object System.Net.WebProxy $proxy 116 | if (($proxy_user) -and ($proxy_pass)) 117 | { 118 | $proxyURL.UseDefaultCredentials = $false 119 | $proxyURL.Credentials = New-Object Net.NetworkCredential("$proxy_user","$proxy_pass") 120 | } 121 | elseif (($proxy_user) -and ($proxy_pass) -and ($proxy_domain)) 122 | { 123 | $proxyURL.UseDefaultCredentials = $false 124 | $proxyURL.Credentials = New-Object Net.NetworkCredential("$proxy_user","$proxy_pass","$proxy_domain") 125 | } 126 | else 127 | { 128 | $proxyURL.UseDefaultCredentials = $true 129 | } 130 | $webclient.proxy = $proxyURL 131 | } 132 | $response = $webClient.openread($pluginXMLURL) 133 | $streamReader = new-object system.io.streamreader $response 134 | [xml]$plugins = $streamReader.ReadToEnd() 135 | 136 | foreach ($plugin in $plugins.pluginlist.plugin) 137 | { 138 | $pluginObjectList | where {$_.name -eq $plugin.name -and [double]$_.version -lt [double]$plugin.version}| 139 | foreach{ 140 | $_.status = "New Version Available - " + $plugin.version 141 | } 142 | if (!($pluginObjectList | where {$_.name -eq $plugin.name})) 143 | { 144 | $pluginObject = New-Object PSObject 145 | $pluginObject | Add-Member -MemberType NoteProperty -Name name -value $plugin.name 146 | $pluginObject | Add-Member -MemberType NoteProperty -Name description -value $plugin.description 147 | $pluginObject | Add-Member -MemberType NoteProperty -Name author -value $plugin.author 148 | $pluginObject | Add-Member -MemberType NoteProperty -Name version -value $plugin.version 149 | $pluginObject | Add-Member -MemberType NoteProperty -Name category -Value $plugin.category 150 | $pluginObject | Add-Member -MemberType NoteProperty -Name status -value "Not Installed" 151 | $pluginObject | Add-Member -MemberType NoteProperty -name location -value $plugin.href 152 | $pluginObjectList += $pluginObject 153 | } 154 | } 155 | } 156 | catch [System.Net.WebException] 157 | { 158 | write-error $_.Exception.ToString() 159 | return 160 | } 161 | 162 | } 163 | 164 | if ($name){ 165 | $pluginObjectList | where {$_.name -eq $name} 166 | } Else { 167 | if ($category){ 168 | $pluginObjectList | Where {$_.Category -eq $category} 169 | } Else { 170 | if($notinstalled){ 171 | $pluginObjectList | where {$_.status -eq "Not Installed"} 172 | } elseif($pendingupdate) { 173 | $pluginObjectList | where {$_.status -like "New Version Available*"} 174 | } 175 | Else { 176 | $pluginObjectList 177 | } 178 | } 179 | } 180 | } 181 | 182 | } 183 | 184 | <# 185 | .SYNOPSIS 186 | Installs a vCheck plugin from the Virtu-Al.net repository. 187 | 188 | .DESCRIPTION 189 | Add-vCheckPlugin downloads and installs a vCheck Plugin (currently by name) from the Virtu-Al.net repository. 190 | 191 | The downloaded file is saved in your vCheck plugins folder, which automatically adds it to your vCheck report. vCheck plugins may require 192 | configuration prior to use, so be sure to open the ps1 file of the plugin prior to running your next report. 193 | 194 | .PARAMETER name 195 | Name of the plugin. 196 | 197 | .EXAMPLE 198 | Install via pipeline from Get-vCheckPlugins 199 | Get-vCheckPlugin "Plugin name" | Add-vCheckPlugin 200 | 201 | .EXAMPLE 202 | Install Plugin by name 203 | Add-vCheckPlugin "Plugin name" 204 | #> 205 | function Add-vCheckPlugin 206 | { 207 | [CmdletBinding(DefaultParametersetName="name")] 208 | Param 209 | ( 210 | [Parameter(parameterSetName="name",Position=0,mandatory=$true)] [String]$name, 211 | [Parameter(parameterSetName="object",Position=0,mandatory=$true,ValueFromPipeline=$true)] [PSObject]$pluginobject 212 | ) 213 | Process 214 | { 215 | if($name) 216 | { 217 | Get-vCheckPlugin $name | Add-vCheckPlugin 218 | } 219 | elseif ($pluginObject) 220 | { 221 | Add-Type -AssemblyName System.Web 222 | $filename = $pluginObject.location.split("/")[-2,-1] -join "/" 223 | $filename = [System.Web.HttpUtility]::UrlDecode($filename) 224 | try 225 | { 226 | Write-Host "Downloading File..." 227 | $webClient = new-object system.net.webclient 228 | $webClient.DownloadFile($pluginObject.location,"$vCheckPath\Plugins\$filename") 229 | Write-Host -ForegroundColor green "The plugin `"$($pluginObject.name)`" has been installed to $vCheckPath\Plugins\$filename" 230 | Write-Host -ForegroundColor green "Be sure to check the plugin for additional configuration options." 231 | 232 | } 233 | catch [System.Net.WebException] 234 | { 235 | write-error $_.Exception.ToString() 236 | return 237 | } 238 | } 239 | } 240 | } 241 | 242 | <# 243 | .SYNOPSIS 244 | Removes a vCheck plugin. 245 | 246 | .DESCRIPTION 247 | Remove-vCheckPlugin Uninstalls a vCheck Plugin. 248 | 249 | Basically, just looks for the plugin name and deletes the file. Sure, you could just delete the ps1 file from the plugins folder, but what fun is that? 250 | 251 | .PARAMETER name 252 | Name of the plugin. 253 | 254 | .EXAMPLE 255 | Remove via pipeline 256 | Get-vCheckPlugin "Plugin name" | Remove-vCheckPlugin 257 | 258 | .EXAMPLE 259 | Remove Plugin by name 260 | Remove-vCheckPlugin "Plugin name" 261 | #> 262 | function Remove-vCheckPlugin 263 | { 264 | [CmdletBinding(DefaultParametersetName="name",SupportsShouldProcess=$true,ConfirmImpact="High")] 265 | Param 266 | ( 267 | [Parameter(parameterSetName="name",Position=0,mandatory=$true)] [String]$name, 268 | [Parameter(parameterSetName="object",Position=0,mandatory=$true,ValueFromPipeline=$true)] [PSObject]$pluginobject 269 | ) 270 | Process 271 | { 272 | if($name) 273 | { 274 | Get-vCheckPlugin $name | Remove-vCheckPlugin 275 | } 276 | elseif ($pluginObject) 277 | { 278 | Remove-Item -path $pluginObject.location -confirm:$false 279 | } 280 | } 281 | } 282 | 283 | <# 284 | .SYNOPSIS 285 | Geberates plugins XML file from local plugins 286 | 287 | .DESCRIPTION 288 | Designed to be run after plugin changes are commited, in order to generate 289 | the plugin.xml file that the plugin update check uses. 290 | 291 | .PARAMETER outputFile 292 | Path to the xml file. Defaults to temp directory 293 | #> 294 | function Get-vCheckPluginXML 295 | { 296 | param 297 | ( 298 | $outputFile = "$($env:temp)\plugins.xml" 299 | ) 300 | # create XML and root node 301 | $xml = New-Object xml 302 | $root = $xml.CreateElement("pluginlist") 303 | [void]$xml.AppendChild($root) 304 | 305 | foreach ($localPluginFile in (Get-ChildItem -Path $vCheckPath\Plugins\* -Include *.ps1 -Recurse)) 306 | { 307 | $localPluginContent = Get-Content $localPluginFile 308 | 309 | if ($localPluginContent | Select-String -SimpleMatch "title") 310 | { 311 | $localPluginName = ($localPluginContent | Select-String -SimpleMatch "Title").toString().split("`"")[1] 312 | } 313 | if($localPluginContent | Select-String -SimpleMatch "description") 314 | { 315 | $localPluginDesc = ($localPluginContent | Select-String -SimpleMatch "description").toString().split("`"")[1] 316 | } 317 | elseif ($localPluginContent | Select-String -SimpleMatch "comments") 318 | { 319 | $localPluginDesc = ($localPluginContent | Select-String -SimpleMatch "comments").toString().split("`"")[1] 320 | } 321 | if ($localPluginContent | Select-String -SimpleMatch "author") 322 | { 323 | $localPluginAuthor = ($localPluginContent | Select-String -SimpleMatch "author").toString().split("`"")[1] 324 | } 325 | if ($localPluginContent | Select-String -SimpleMatch "PluginVersion") 326 | { 327 | $localPluginVersion = @($localPluginContent | Select-String -SimpleMatch "PluginVersion")[0].toString().split(" ")[-1] 328 | } 329 | if ($localPluginContent | Select-String -SimpleMatch "PluginCategory") 330 | { 331 | $localPluginCategory = @($localPluginContent | Select-String -SimpleMatch "PluginCategory")[0].toString().split("`"")[1] 332 | } 333 | 334 | $pluginXML = $xml.CreateElement("plugin") 335 | $elem=$xml.CreateElement("name") 336 | $elem.InnerText=$localPluginName 337 | [void]$pluginXML.AppendChild($elem) 338 | 339 | $elem=$xml.CreateElement("description") 340 | $elem.InnerText=$localPluginDesc 341 | [void]$pluginXML.AppendChild($elem) 342 | 343 | $elem=$xml.CreateElement("author") 344 | $elem.InnerText=$localPluginAuthor 345 | [void]$pluginXML.AppendChild($elem) 346 | 347 | $elem=$xml.CreateElement("version") 348 | $elem.InnerText=$localPluginVersion 349 | [void]$pluginXML.AppendChild($elem) 350 | 351 | $elem=$xml.CreateElement("category") 352 | $elem.InnerText=$localPluginCategory 353 | [void]$pluginXML.AppendChild($elem) 354 | 355 | $elem=$xml.CreateElement("href") 356 | $elem.InnerText= ($pluginURL -f $localPluginCategory, $localPluginFile.Directory.Name, $localPluginFile.Name) 357 | [void]$pluginXML.AppendChild($elem) 358 | 359 | [void]$root.AppendChild($pluginXML) 360 | } 361 | 362 | $xml.save($outputFile) 363 | } 364 | 365 | <# 366 | .SYNOPSIS 367 | Returns settings from vCheck plugins. 368 | 369 | .DESCRIPTION 370 | Get-PluginSettings will return an array of settings contained 371 | within a supplied plugin. Used by Export-vCheckSettings. 372 | 373 | .PARAMETER filename 374 | Full path to plugin file 375 | #> 376 | Function Get-PluginSettings { 377 | Param 378 | ( 379 | [Parameter(mandatory=$true)] [String]$filename 380 | ) 381 | $psettings = @() 382 | $file = Get-Content $filename 383 | $OriginalLine = ($file | Select-String -SimpleMatch "# Start of Settings").LineNumber 384 | $EndLine = ($file | Select-String -SimpleMatch "# End of Settings").LineNumber 385 | if (!(($OriginalLine +1) -eq $EndLine)) { 386 | $Line = $OriginalLine 387 | do { 388 | $Question = $file[$Line] 389 | $Line ++ 390 | $Split= ($file[$Line]).Split("=") 391 | $Var = $Split[0] 392 | $CurSet = $Split[1] 393 | $settings = @{} 394 | $settings.filename = $filename 395 | $settings.question = $Question 396 | $settings.var = $CurSet.Trim() 397 | $currentsetting = New-Object -TypeName PSObject -Prop $settings 398 | $psettings += $currentsetting 399 | $Line ++ 400 | } Until ( $Line -ge ($EndLine -1) ) 401 | } 402 | $psettings 403 | } 404 | 405 | <# 406 | .SYNOPSIS 407 | Applies settings to vCheck plugins. 408 | 409 | .DESCRIPTION 410 | Set-PluginSettings will apply settings supplied to a given vCheck plugin. 411 | Used by Export-vCheckSettings. 412 | 413 | .PARAMETER filename 414 | Full path to plugin file 415 | 416 | .PARAMETER settings 417 | Array of settings to apply to plugin 418 | 419 | .PARAMETER GB 420 | Switch to disable Setup Wizard when processing GlobalVariables.ps1 421 | #> 422 | Function Set-PluginSettings { 423 | Param 424 | ( 425 | [Parameter(mandatory=$true)] [String]$filename, 426 | [Parameter(mandatory=$false)] [Array]$settings, 427 | [Parameter(mandatory=$false)] [Switch]$GB 428 | ) 429 | $file = Get-Content $filename 430 | $OriginalLine = ($file | Select-String -SimpleMatch "# Start of Settings").LineNumber 431 | $EndLine = ($file | Select-String -SimpleMatch "# End of Settings").LineNumber 432 | $PluginName = ($filename.split("\")[-1]).split(".")[0] 433 | Write-Host "`nProcessing - $PluginName" -foreground $host.PrivateData.WarningForegroundColor -background $host.PrivateData.WarningBackgroundColor 434 | if (!(($OriginalLine +1) -eq $EndLine)) { 435 | $Array = @() 436 | $Line = $OriginalLine 437 | do { 438 | $Question = $file[$Line] 439 | $Found = $false 440 | $Line ++ 441 | $Split= ($file[$Line]).Split("=") 442 | $Var = $Split[0] 443 | $CurSet = $Split[1].Trim() 444 | Foreach ($setting in $settings) { 445 | If ($question -eq $setting.question) { 446 | $NewSet = $setting.var 447 | $Found = $true 448 | } 449 | } 450 | If (!$Found) { 451 | # Check if the current setting is in speech marks 452 | $String = $false 453 | if ($CurSet -match '"') { 454 | $String = $true 455 | $CurSet = $CurSet.Replace('"', '').Trim() 456 | } 457 | $NewSet = Read-Host "$Question [$CurSet]" 458 | If (-not $NewSet) { 459 | $NewSet = $CurSet 460 | } 461 | If ($String) { 462 | $NewSet = "`"$NewSet`"" 463 | } 464 | } 465 | $Array += $Question 466 | $Array += "$Var= $NewSet" 467 | $Line ++ 468 | } Until ( $Line -ge ($EndLine -1) ) 469 | $Array += "# End of Settings" 470 | 471 | $out = @() 472 | $out = $File[0..($OriginalLine -1)] 473 | $out += $Array 474 | $out += $File[$Endline..($file.count -1)] 475 | If ($GB) { 476 | $Setup = ($file | Select-String -SimpleMatch '# Set the following to true to enable the setup wizard for first time run').LineNumber 477 | $SetupLine = $Setup ++ 478 | $out[$SetupLine] = '$SetupWizard = $False' 479 | } 480 | $out | Out-File -Encoding ASCII $filename 481 | } 482 | } 483 | 484 | <# 485 | .SYNOPSIS 486 | Retrieves configured vCheck plugin settings and exports them to CSV. 487 | 488 | .DESCRIPTION 489 | Export-vCheckSettings will retrieve the settings from each plugin and export them to a CSV file. 490 | By default, the CSV file will be created in the vCheck folder named vCheckSettings.csv. 491 | You can also specify a custom path using -outfile. 492 | Once the export has been created the settings can then be imported via Import-vCheckSettings. 493 | 494 | .PARAMETER outfile 495 | Full path to CSV file 496 | 497 | .EXAMPLE 498 | Export-vCheckSettings 499 | Creates vCheckSettings.csv file in default location (vCheck folder) 500 | 501 | .EXAMPLE 502 | Export-vCheckSettings -outfile "E:\vCheck-vCenter01.csv" 503 | Creates CSV file in custom location E:\vCheck-vCenter01.csv 504 | #> 505 | Function Export-vCheckSettings { 506 | Param 507 | ( 508 | [Parameter(mandatory=$false)] [String]$outfile = "$vCheckPath\vCheckSettings.csv" 509 | ) 510 | 511 | $Export = @() 512 | $GlobalVariables = "$vCheckPath\GlobalVariables.ps1" 513 | $Export = Get-PluginSettings -Filename $GlobalVariables 514 | Foreach ($plugin in (Get-ChildItem -Path $vCheckPath\Plugins\* -Include *.ps1, *.ps1.disabled -Recurse)) { 515 | $Export += Get-PluginSettings -Filename $plugin.Fullname 516 | } 517 | $Export | Select filename, question, var | Export-Csv -NoTypeInformation $outfile 518 | } 519 | 520 | <# 521 | .SYNOPSIS 522 | Retreives settings from CSV and applies them to vCheck. 523 | 524 | .DESCRIPTION 525 | Import-vCheckSettings will retrieve the settings exported via Export-vCheckSettings and apply them to the 526 | current vCheck folder. 527 | By default, the CSV file is expected to be located in the vCheck folder named vCheckSettings.csv. 528 | You can also specify a custom path using -csvfile. 529 | If the CSV file is not found you will be asked to provide the path. 530 | The Setup Wizard will be disabled. 531 | You will be asked any questions not found in the export. This would occur for new settings introduced 532 | enabling a quick update between versions. 533 | 534 | .PARAMETER csvfile 535 | Full path to CSV file 536 | 537 | .EXAMPLE 538 | Import-vCheckSettings 539 | Imports settings from vCheckSettings.csv file in default location (vCheck folder) 540 | 541 | .EXAMPLE 542 | Import-vCheckSettings -outfile "E:\vCheck-vCenter01.csv" 543 | Imports settings from CSV file in custom location E:\vCheck-vCenter01.csv 544 | #> 545 | Function Import-vCheckSettings { 546 | Param 547 | ( 548 | [Parameter(mandatory=$false)] [String]$csvfile = "$vCheckPath\vCheckSettings.csv" 549 | ) 550 | 551 | If (!(Test-Path $csvfile)) { 552 | $csvfile = Read-Host "Enter full path to settings CSV file you want to import" 553 | } 554 | $Import = Import-Csv $csvfile 555 | $GlobalVariables = "$vCheckPath\GlobalVariables.ps1" 556 | $settings = $Import | Where {($_.filename).Split("\")[-1] -eq ($GlobalVariables).Split("\")[-1]} 557 | Set-PluginSettings -Filename $GlobalVariables -Settings $settings -GB 558 | Foreach ($plugin in (Get-ChildItem -Path $vCheckPath\Plugins\* -Include *.ps1, *.ps1.disabled -Recurse)) { 559 | $settings = $Import | Where {($_.filename).Split("\")[-1] -eq ($plugin.Fullname).Split("\")[-1]} 560 | Set-PluginSettings -Filename $plugin.Fullname -Settings $settings 561 | } 562 | Write-Host "`nImport Complete!`n" -foreground $host.PrivateData.WarningForegroundColor -background $host.PrivateData.WarningBackgroundColor 563 | } 564 | 565 | Function Get-vCheckCommand { 566 | Get-Command *vCheck* 567 | } 568 | 569 | Get-vCheckCommand 570 | 571 | # Below is a set of functions to make upgrading a vCheck directory easier 572 | 573 | <# 574 | .SYNOPSIS 575 | Lists the variables in vCheck plugin files. 576 | 577 | .DESCRIPTION 578 | Plugin file will be scanned as a text file and any variables in between the "Start of Settings" 579 | and "End of Settings" section markers will be sent out the pipeline. Files can be sent in 580 | via the pipeline or individually with a loop. If using the "-Verbose" option for troubleshooting 581 | then using a loop is recommended. 582 | 583 | .PARAMETER PluginFile 584 | The file to be processed. Can be passed as text, a file object, or a set of files, such as 585 | "Get-ChildItem *.ps1". 586 | 587 | .EXAMPLE 588 | Simple 589 | Get-vCheckVariablesSettings -PluginFile "c:\scripts\vcheck6\vcenter\Plugins\20 Cluster\75 DRS Rules.ps1" 590 | 591 | Recursed 592 | Get-ChildItem -Path E:\vCheckLatestTesting -File -Filter *.ps1 -Recurse | % { Get-vCheckVariablesSettings -PluginFile $_.FullName } 593 | 594 | .INPUTS 595 | System.IO.FileInfo 596 | 597 | .OUTPUTS 598 | File Selected.System.Management.Automation.PSCustomObject The 'Fullname' property from the plugin file. 599 | Variable Selected.System.Management.Automation.PSCustomObject The text of the variable assignment from the plugin file. 600 | 601 | .NOTES 602 | With multiple vCheck directories to upgrade I needed an easy to pull the variables used 603 | in the old vCheck installation to go into the new version. 604 | 605 | .LINK 606 | https://github.com/alanrenouf/vCheck-vSphere 607 | 608 | Recent Comment History 609 | 20150127 cmonahan Initial release. 610 | 611 | #> 612 | 613 | Function Get-vCheckVariablesSettings { 614 | 615 | [CmdletBinding()] 616 | param ( 617 | [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)] $PluginFile 618 | ) 619 | 620 | #begin { 621 | Write-Verbose "Started $PluginFile" 622 | 623 | if (Test-Path $PluginFile) { 624 | $PluginFile = Get-ChildItem $PluginFile 625 | $contents = Get-Content $PluginFile 626 | $end = $contents.length } 627 | else { throw "Value passed to File parameter is not a file." } 628 | 629 | $i=0 630 | 631 | #} # end begin block 632 | 633 | #process { 634 | 635 | while ( ($i -lt $end) -and ($contents[$i] -notmatch "Start of Settings") ) { $i++ } 636 | 637 | while ( ($i -lt $end) -and ($contents[$i] -notmatch "End of Settings") ) { 638 | if ($contents[$i] -match "`=") { "" | select @{n='File';e={$PluginFile.fullname}},@{n='Variable';e={$contents[$i]}}; $i++ } 639 | else { $i++ } 640 | } 641 | 642 | #} #end process block 643 | 644 | #end { 645 | Write-Verbose "Ended $PluginFile" 646 | #} #end end block 647 | 648 | <# 649 | Recent Comment History 650 | 20150127 cmonahan Initial release. 651 | #> 652 | 653 | } # end function 654 | 655 | <# 656 | .SYNOPSIS 657 | Matches the disabled plugins in a target directory with those in a source directory. 658 | 659 | .DESCRIPTION 660 | I wrote it for when I'm upgrading vCheck. This will go through the old directory and 661 | any plugin marked as disabled there will be marked as disabled in the new directory. 662 | 663 | .PARAMETER OldVcheckDir 664 | What you you think it is. 665 | 666 | .PARAMETER NewVcheckDir 667 | No tricks here. 668 | 669 | .EXAMPLE 670 | Sync-vCheckDisabledPlugins -OldVcheckDir c:\scripts\vcheck6\vccenter_old_20150218_163057 -NewVcheckDir c:\scripts\vcheck6\vcenter 671 | 672 | .LINK 673 | https://github.com/alanrenouf/vCheck-vSphere 674 | 675 | 676 | Recent Comment History 677 | 20150128 cmonahan Initial release. 678 | 679 | #> 680 | 681 | function Sync-vCheckDisabledPlugins { 682 | [cmdletbinding(SupportsShouldProcess=$True)] 683 | 684 | param ( 685 | [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$false)] $OldVcheckDir, 686 | [Parameter(Position=1,Mandatory=$true,ValueFromPipeline=$false)] $NewVcheckDir 687 | ) 688 | 689 | # $WhatIfPreference 690 | 691 | $OldVcheckPluginsDir = (Get-ChildItem "$($OldVcheckDir)\Plugins").PsParentPath 692 | $NewVcheckPluginsDir = (Get-ChildItem "$($NewVcheckDir)\Plugins").PsParentPath 693 | 694 | $OldDisabled = Get-ChildItem $OldVcheckDir -Recurse | ? { $_ -like "*.disabled" } #| select -First 1 695 | $OldDisabled 696 | 697 | foreach ($file in $OldDisabled) { 698 | Get-ChildItem $NewVcheckDir -Recurse | Where-Object { $_ -match $file.Name } | Select-Object FullName 699 | Get-ChildItem $NewVcheckDir -Recurse -Filter $file.BaseName | ForEach-Object { Move-Item -Path $_.FullName -Destination ($_.FullName -replace("ps1","ps1.disabled")) } 700 | } 701 | 702 | <# Comment History 703 | 20150128 cmonahan Initial release. 704 | #> 705 | 706 | } # end function 707 | 708 | 709 | <# 710 | .SYNOPSIS 711 | Lists the disabled plugins in a target directory. 712 | 713 | .DESCRIPTION 714 | Essentially a stripped down version of Sync-vCheckDisabledPlugins I threw it in 715 | in case someone found it useful. 716 | 717 | .PARAMETER VcheckDir 718 | What you you think it is. 719 | 720 | .EXAMPLE 721 | Get-vCheckDisabledPlugins -VcheckDir c:\scripts\vcheck6\vcenter 722 | 723 | .LINK 724 | https://github.com/alanrenouf/vCheck-vSphere 725 | 726 | 727 | Recent Comment History 728 | 20150128 cmonahan Initial release. 729 | 730 | #> 731 | 732 | function Get-vCheckDisabledPlugins { 733 | 734 | param ( [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)] $vCheckDir ) 735 | 736 | Get-ChildItem (Get-ChildItem "$($vCheckDir)\Plugins").PsParentPath -Recurse | ? { $_ -like "*.disabled" } #| select -First 1 737 | 738 | <# Comment History 739 | 20150128 cmonahan Initial release. 740 | #> 741 | 742 | } # end function 743 | 744 | <# 745 | .SYNOPSIS 746 | Does most of the work upgrading to a new version of vCheck. 747 | 748 | .DESCRIPTION 749 | This function will: 750 | -Backup the current directory by renaming it with the current date 751 | -Copy in the new version from a directory you've specified 752 | -Save all the variable settings to a text file 753 | -Set the same disabled plugins in the new version 754 | -Opens the saved variable settings in Notepad 755 | 756 | Then run vCheck.ps1 for the first time to configure it using the 757 | variables listing open in Notepad as a reference. 758 | 759 | .PARAMETER CurrentvCheckPath 760 | Directory to be upgraded. 761 | 762 | .PARAMETER NewvCheckSource 763 | Location of the new version downloaded from GitHub. 764 | 765 | .EXAMPLE 766 | Upgrade-vCheckDirectory -CurrentvCheckPath c:\scripts\vcheck6\vcenter -NewvCheckSource c:\scripts\vcheck6\vcenter\vCheck-vSphere-master 767 | 768 | .NOTES 769 | If you have multiple directories and some settings like smtp server 770 | are the same for them all you could upgrade the file(s) in the new 771 | vCheck version directory and they'll be copied out with each upgrade. 772 | 773 | This is my process for upgrading vCheck with this function. 774 | 1. Extract a new, unmodified version of the vCheck to a directory. For this example "C:\Scripts\vCheck\vCheck-vSphere-master". 775 | 2. Load the utility - ". C:\Scripts\vCheck\vCheck-vSphere-master\vCheckUtils.ps1" . 776 | 3. Upgrade-vCheckDirectory –CurrentvCheckPath C:\Scripts\vcheck\vcenterprod -NewvCheckSource C:\Scripts\vcheck6\vCheck-vSphere-master 777 | 4. The list of plugin variable values is automatically opened in Notepad. 778 | 5. Change directory to C:\Scripts\vcheck\vcenterprod . 779 | 6. Run vCheck.ps1 . Input all the prompts for variable values with the ones in the file opened by Notepad. For the global variable “ $EmailFrom = "vcheck-vcenter@monster.com" ” I use my own email address until after I done a test run. Then I change it back to the group email address. 780 | 7. After all the variable have been entered vCheck will run. 781 | 8. Review the PowerShell console for script errors and the vCheck email report for any problems. 782 | 9. If there are not problems set the “EmailFrom” variable in “GlobalVariables.ps1” back to it’s original value. 783 | 784 | .LINK 785 | https://github.com/alanrenouf/vCheck-vSphere 786 | 787 | Recent Comment History 788 | 20150127 cmonahan Initial release. 789 | 790 | #> 791 | 792 | Function Upgrade-vCheckDirectory { 793 | 794 | param ( 795 | [Parameter(Position=0,Mandatory=$true)] $CurrentvCheckPath, 796 | [Parameter(Position=1,Mandatory=$true)] $NewvCheckSource 797 | ) 798 | 799 | function Get-Now { (get-date -uformat %Y%m%d) + "_" + (get-date -uformat %H%M%S) } 800 | $TS = Get-Now # TS means time stamp 801 | 802 | # Test that directories exist 803 | if ( !(Test-Path -Path $CurrentvCheckPath) ) { break } 804 | if ( !(Test-Path -Path $NewvCheckSource) ) { break } 805 | $OldvCheckPath = "$($CurrentvCheckPath)_old_$($TS)" 806 | $OldvCheckVariables = "$($OldvCheckPath)\vCheckVariables_$($TS).txt" 807 | 808 | # Backup current directory and setup new directory 809 | Move-Item -Path $CurrentvCheckPath -Destination $OldvCheckPath 810 | mkdir $CurrentvCheckPath 811 | robocopy $NewvCheckSource $CurrentvCheckPath /s /e /z /xj /r:2 /w:5 /np 812 | 813 | # Save variable settings 814 | Get-ChildItem -Path $OldvCheckPath -Filter *.ps1 -Recurse | % { Get-vCheckVariablesSettings -PluginFile $_.FullName } | Format-Table -AutoSize | Out-File -FilePath $OldvCheckVariables 815 | 816 | # Make the disabled plugins match 817 | Sync-vCheckDisabledPlugins -OldVcheckDir $OldvCheckPath -NewVcheckDir $CurrentvCheckPath 818 | 819 | # Configure it 820 | notepad $OldvCheckVariables 821 | Write-Output "Locally on the server hosting the vCheck script run vCheck.ps1" 822 | 823 | <# Comment History 824 | 20150128 cmonahan Initial release. 825 | #> 826 | 827 | } # end function 828 | 829 | Function Get-vCheckLogData { 830 | param( 831 | [string] $vCheckFile, 832 | [string] $Section 833 | ) 834 | 835 | # Find the comment above the specified section table and grab the Post context for 6 lines beyond the comment 836 | # The HTML is stored within the next 6 lines. 837 | # line 1:
 
838 | # line 2:
839 | $ContextInfo = Select-String "Plugin Start - $Section" $vCheckFile -context 0,6 840 | 841 | # lines 3-6 are the data we want. 842 | $table = $ContextInfo.Context.PostContext | select -last 4 843 | 844 | # The table actually ends on line 7. But line 6 looks like this: 845 | # Back To Top 846 | # There is no ending 847 | # Line 7: 848 | # So add these missing tags back in. 849 | $table += "" 850 | try { 851 | # Convert to XML for easier parsing 852 | $xmlObj = [xml]$table 853 | } catch { 854 | # This catches any instances where there are no matches in the file, and then the only data is the ending tags. 855 | # just in case you want to see it, Write-Verbose 856 | Write-Verbose "$vCheckFile : $table" 857 | } 858 | 859 | # There is a sub table with the data - so get the TR that contains a sub table 860 | $ParentTR = $xmlObj.table.tr | ? { $_.td.table } 861 | # Get the TD 862 | $ParentTD = $ParentTR.td 863 | # Get the table 864 | $SubTable = $ParentTD.Table 865 | 866 | # Use the TH to get the header names 867 | $th = $subTable.tr.th 868 | 869 | # Create a hash table that stores all the header names, and use the index as the key. We'll use this as a lookup when we get to the TD 870 | $thHash = @{} 871 | for ($i=0;$i -lt $th.count; $i++) { 872 | $thHash.Add($i,$th[$i]) 873 | } 874 | 875 | # Loop through each TR containing the log data 876 | for ($i=1; $i -lt $subTable.tr.count; $i++) { 877 | # Get the TDs under the TR, and loop through those 878 | $td = $subTable.tr[$i].td 879 | 880 | # build a hash table pulling the column name from the TH hash table, and the value from the TD 881 | $tdHash = @{} 882 | for ($j=0; $j -lt $td.count;$j++) { 883 | $tdHash.Add($thHash[$j],$td[$j]) 884 | } 885 | # Return this as an object 886 | New-Object -Type PSObject -Prop $tdHash 887 | } 888 | } 889 | --------------------------------------------------------------------------------