From 018a4fce4dbed62db4378ee13b9c219795db80d6 Mon Sep 17 00:00:00 2001 From: Kevin Kirkpatrick Date: Mon, 27 Apr 2015 23:48:44 -0400 Subject: [PATCH 01/50] Added specific values for basic PowerShell file types Added specific values for basic PowerShell file types --- .gitAttributes | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.gitAttributes b/.gitAttributes index df7acb38..cc82df9a 100644 --- a/.gitAttributes +++ b/.gitAttributes @@ -1,6 +1,14 @@ # Set default behaviour, in case users don't have core.autocrlf set. * text=auto +# Custom for PowerShell +*.ps1 text eol=crlf +*.psm1 text eol=crlf +*.psd1 text eol=crlf + +# Misc/Others +*.xml text eol=crlf + # Ignore plugins.xml in exports plugins.xml export-ignore .gitAttributes export-ignore From 771f12b7d3534b3a74a37b090cd628c6eaddce88 Mon Sep 17 00:00:00 2001 From: Kevin Kirkpatrick Date: Mon, 27 Apr 2015 23:50:00 -0400 Subject: [PATCH 02/50] README.md - Fixed text encoding issue Some of the apostrophes were displaying incorrectly/were not being translated correctly in the markdown file --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 233f2543..f6bd9e86 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ This script picks on the key known issues and potential issues scripted as plugi 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. -This script is not to be confused with an Audit script, although the reporting framework can also be used for auditing scripts too. I dont 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 dont want to read that kind of information unless you need it, this script will only tell you about problem areas with your infrastructure. +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 CPU's 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. What is checked for in the vSphere version ? ============================================ From ca842684becbb8764132671887e8f8bf1a59c094 Mon Sep 17 00:00:00 2001 From: Kevin Kirkpatrick Date: Tue, 28 Apr 2015 00:18:55 -0400 Subject: [PATCH 03/50] Removed Write-Host; updated CBH [X] Add cmdletbinding/param [X] Move comments into proper comment based help block so it can be reviewed via standard PS help system (EX: C:\PS>.\Get-Help Select-Plugins.ps1 -ShowWindow) [X] Remove Write-Host; converted to Write-Warning --- Select-Plugins.ps1 | 57 ++++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/Select-Plugins.ps1 b/Select-Plugins.ps1 index 646df424..711ad5dd 100644 --- a/Select-Plugins.ps1 +++ b/Select-Plugins.ps1 @@ -1,27 +1,43 @@ -# Select-Plugins.ps1 +<# +.NOTES + Select-Plugins.ps1 -# selectively enable / disable vCheck Plugins + selectively enable / disable vCheck Plugins -# presents a list of plugins whose names match *.ps1 or *.ps1.disabled -# -# disabled plugins will be renamed as appropriate to .ps1.disabled -# enabled plugins will be renamed as appropriate to .ps1 + presents a list of plugins whose names match *.ps1 or *.ps1.disabled -# To use, run from the vCheck directory -# or, if you wish to be perverse, copy to the plugins directory and rename to -# "ZZ Select Plugins for Next Run.ps1" and run vCheck as normal. + disabled plugins will be renamed as appropriate to .ps1.disabled + enabled plugins will be renamed as appropriate to .ps1 -# Great for testing plugins. When done, untick it... + To use, run from the vCheck directory or, if you wish to be perverse, copy to the plugins + directory and rename to "ZZ Select Plugins for Next Run.ps1" and run vCheck as normal. -# If run as a plugin, it will affect the next vCheck run, not the current one, -# as vCheck has already collected its list of plugins when it is invoked -# so make it the very last plugin executed to avoid counter-intuitive behaviour + Great for testing plugins. When done, untick it... -# based on code from Select-GraphicalFilteredObject.ps1 in -# "Windows Powershell Cookbook" by Lee Holmes. -# Copyright 2007 Lee Holmes. -# Published by O'Reilly ISBN 978-0-596-528492 -# and used under the 'free use' provisions specified on Preface page xxv + If run as a plugin, it will affect the next vCheck run, not the current one, + as vCheck has already collected its list of plugins when it is invoked + so make it the very last plugin executed to avoid counter-intuitive behaviour + + based on code from Select-GraphicalFilteredObject.ps1 in + "Windows Powershell Cookbook" by Lee Holmes. + Copyright 2007 Lee Holmes. + Published by O'Reilly ISBN 978-0-596-528492 + and used under the 'free use' provisions specified on Preface page xxv + + Changelog + ============================ + 2.2 - Kevin Kirkpatrick + [X] Add cmdletbinding/param + [X] Move comments into proper comment based help block so it can be reviewed via standard + PS help system (EX: C:\PS>.\Get-Help Select-Plugins.ps1 -ShowWindow) + [X] Remove Write-Host; converted to Write-Warning + + 2.1 - Phil Randal + [X] Added Select All/Deselect All buttons - Changed sort to numeric +#> + +[cmdletbinding()] +param() $Title = "Plugin Selection Plugin" $Author = "Phil Randal" @@ -33,9 +49,6 @@ $Display = "None" # Start of Settings # End of Settings -# Changelog -## 2.1 : Added Select All/Deselect All buttons - Changed sort to numeric - $PluginPath = (Split-Path ((Get-Variable MyInvocation).Value).MyCommand.Path) If ($PluginPath -notmatch 'plugins$') { $PluginPath += "\Plugins" @@ -139,7 +152,7 @@ if($result -eq "OK") { $newname = $plugin.Plugin + $(If ($listbox.GetItemChecked($i)) {'.ps1'} else {'.ps1.disabled'}) If ($newname -ne $oldname) { If (Test-Path (($plugin.FullName | Split-Path) + "\" + $newname)) { - Write-Host "Attempting to rename ""$oldname"" to ""$newname"", which already exists - please delete or rename the superfluous file and try again" + Write-Warning "Attempting to rename ""$oldname"" to ""$newname"", which already exists - please delete or rename the superfluous file and try again" } Else { Rename-Item (($plugin.FullName | Split-Path) + "\" + $oldname) $newname } From 5b97187a8141522bdb86f8ce27eeaaafc0808c89 Mon Sep 17 00:00:00 2001 From: Sneddo Date: Tue, 28 Apr 2015 15:19:28 +1000 Subject: [PATCH 04/50] Added Gitter link --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f6bd9e86..1f126063 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ ![Alt text](http://www.virtu-al.net/wp-content/uploads/2014/02/vCheck619.jpg "vCheck Sample") -[![Stories in Ready](http://badge.waffle.io/alanrenouf/vCheck-vSphere.png)](http://waffle.io/alanrenouf/vCheck-vSphere) vCheck-vSphere ============== +[![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) [![Stories in Ready](http://badge.waffle.io/alanrenouf/vCheck-vSphere.png)](http://waffle.io/alanrenouf/vCheck-vSphere) + vCheck Daily Report for vSphere @@ -12,7 +13,7 @@ This script picks on the key known issues and potential issues scripted as plugi 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. -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 CPU's 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. +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. What is checked for in the vSphere version ? ============================================ From 5ee19bddc9fe155eb8439cf35d31e817a601fa56 Mon Sep 17 00:00:00 2001 From: Kevin Kirkpatrick Date: Wed, 29 Apr 2015 01:05:01 -0400 Subject: [PATCH 05/50] See Desc. See pull request details. --- CONTRIBUTING.md | 112 +++++ EndScript .ps1 | 1 + README.md | 258 +++++++++- vCheck.ps1 | 1242 +++++++++++++++++++++++++---------------------- 4 files changed, 1014 insertions(+), 599 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 EndScript .ps1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..0abc89b3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,112 @@ +# Contribute to the vCheck-vSphere repository + +Hi! We can't thank you enough for wanting to contribute; the community is what keeps the wheels moving on this awesome project. +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? + +## Using GitHub, Git, and this repository + +We are working on more detailed instructions that outline the basics. + +## Contributing to the existing vCheck-vSphere repository + +### Forks and Pull Requests + +GitHub fosters collaboration through the notion of [pull requests](https://help.github.com/articles/using-pull-requests/). +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. +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". + +#### Lifecycle of a pull reqeust + +* **Always create pull requests to the `dev` branch of a repository**. +For more information, learn about the [branch structure](#branch-structure) that we are using. + +![PR-Dev.png](https://github.com/vScripter/vCheck-vSphere/blob/dev/Images/PR-Dev.PNG) + +* When you create a pull request, fill out the description with a summary of what's included in your changes. +If the changes are related to an existing GitHub issue, please reference the issue in your description. +* Once the PR is submitted, we will review your code +* Once the code review is done, and all merge conflicts are resolved, a maintainer will merge your changes. + +### Contributing to documentation +One of the easiest ways to contribute to a PowerShell project is by helping to write and edit documentation. +All of our documentation hosted on GitHub is written using [GitHub Flavored Markdown](https://help.github.com/articles/github-flavored-markdown/) + +*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 ;)* + +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. +GitHub will automatically create your own fork of our repository where you can make your changes. +Once you're finished, save your edits and submit a pull request to get your changes merged upstream. + +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. +If no one seems to be working on what you have planned: +* Open a new issue tagged as "Documentation" to tell others what you're working on +* Create a fork of our repository and start adding new Markdown-based documentation to it +* When you're ready to contribute your documentation, submit a pull request to the *dev* branch + + +#### GitHub Flavored Markdown (GFM) + +All of the articles in this repository use [GitHub Flavored Markdown (GFM)](https://help.github.com/articles/github-flavored-markdown/). + +If you are looking for a good editor, try [Markdown Pad](http://markdownpad.com/) or +GitHub also provides a web interface for Markdown editing with syntax highlighting and the ability to preview changes. + +Some of the more basic GFM syntax includes: + +* **Line breaks vs. paragraphs:** In Markdown there is no HTML `
` or `

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

` and `###` = ```

```). +* **Numbered lists:** To make a numbered (ordered) list start the line with `1. `. +If you want multiple elements within a single list element, format your list as follows: +``` +1. For the first element (like this one), insert a tab stop after the 1. + + To include a second element (like this one), insert a line break after the first and align indentations. +``` +to get this output: + +1. For the first element (like this one), insert a tab stop after the 1. + + To include a second element (like this one), insert a line break after the first and align indentations. + +* **Bulleted lists:** Bulleted (unordered) lists are almost identical to ordered lists except that the `1. ` is replaced with either `* `, `- `, or `+ `. +Multiple element lists work the same way as with ordered lists. +* **Links:** The syntax for a hyperlink is `[visible link text](link url)`. +Links can also have references, which will be discussed in the "Link and Image References" section below. + +## Editing an existing plugin + +We are in the process of adding/consolidating more detailed documentation around this. + +## Creating a new plugin + +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. + +## Gitter & Waffle + +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. + +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). + + +## Style guidelines + +When contributing to this repository, please follow the following guidelines: + +* For all indentation, use 4 spaces instead of tab stops +* Make sure all files are encoding using UTF-8. +* When writing Markdown, if a paragraph includes more than one setence, end each sentence with a newline. +GitHub will still render the sentences as a single paragraph, but the readability of `git diff` will be greatly improved. + + +## Branch structure + +We are using a [git flow](http://nvie.com/posts/a-successful-git-branching-model/) model for development. +We recommend that you create local working branches that target a specific scope of change. +Each branch should be limited to a single feature/bugfix both to streamline workflows and reduce the possibility of merge conflicts. +![git flow picture](http://nvie.com/img/git-model@2x.png) diff --git a/EndScript .ps1 b/EndScript .ps1 new file mode 100644 index 00000000..25a15ecb --- /dev/null +++ b/EndScript .ps1 @@ -0,0 +1 @@ +# Everything in this script will run at the end of vCheck diff --git a/README.md b/README.md index 1f126063..e8730c8e 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,24 @@ -![Alt text](http://www.virtu-al.net/wp-content/uploads/2014/02/vCheck619.jpg "vCheck Sample") + +# vCheck Daily Report for vSphere +[![Stories in Ready](http://badge.waffle.io/alanrenouf/vCheck-vSphere.png)](http://waffle.io/alanrenouf/vCheck-vSphere)      [![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) -vCheck-vSphere -============== -[![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) [![Stories in Ready](http://badge.waffle.io/alanrenouf/vCheck-vSphere.png)](http://waffle.io/alanrenouf/vCheck-vSphere) +![Alt text](http://www.virtu-al.net/wp-content/uploads/2014/02/vCheck619.jpg "vCheck Sample") +|Navigation| +|-----------------| +|[About](#About)| +|[Features](#Features)| +|[Enhancements](#Enhancements)| +|[Release Notes](#ReleaseNotes)| +|[Contributing](#Contributing)| +|[Plugins](#Plugins)| +|[Styles](#Styles)| +|[Jobs & Settings](#JobsSettings)| +|[More Info](#More)| -vCheck Daily Report for vSphere + +# About +[*Back to top*](#Title) 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. @@ -13,10 +26,11 @@ This script picks on the key known issues and potential issues scripted as plugi 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. -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. +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 CPU's 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. -What is checked for in the vSphere version ? -============================================ + +# What is checked for in the vSphere version ? +[*Back to top*](#Title) The following items are included as part of the vCheck vSphere 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. @@ -72,15 +86,22 @@ The following items are included as part of the vCheck vSphere download, they ar - Inaccessible VMs - Much, Much more....... -More Info -========= + +# Enhancements +[*Back to top*](#Title) -For more information please read here: http://www.virtu-al.net/vcheck-pluginsheaders/vcheck/ +* **Unit Testing / CI** - We are working on full support for [Pester](https://github.com/pester/Pester/blob/master/README.md) tests, which will help automate code validation. We will start small and work to provide as much documentation as we can to help with integration. -For an example vSphere output (doesnt contain all info) click here http://virtu-al.net/Downloads/vCheck/vCheck.htm +* **Module Support** - We are looking at our options to convert some, or all of the plugins to PowerShell modules. This will make things much easier to version and track, individually. Additionally, if we convert vCheck, itself, to a module, we open our options to support publishing to the [PowerShell Gallery](https://www.powershellgallery.com/), or at least providing users and organizations a standard platform to distrubute it. Again, these options are currently under review. + +* **Settings GUI** - A settings GUI would be a basic form that would allow a user to view/set/change current vCheck configuration settings, without the complexity of settings values from within a file. This initiative is currently in development. + +In the meantime, don't hessitate to pop over to [![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) and join in on active conversations about anything you see, or don't see, here! + + +# Release Notes +[*Back to top*](#Title) -Changelog -========= * 6.22 - Fixes to VMware style. Consolidating plugins. Updates to style handling. * 6.21 - Added support for charts. New plugins. Support non-standard vCenter Ports. Bugfixes * 6.20 - First tagged release. Bugfixes. Email resource support added. @@ -116,3 +137,212 @@ Changelog * 4.2 - Added General Capacity Information based on CPU and MEM ussage per cluster * 4.1 - Added the ability to change the colours of the report. * 4.0 - HTML Tidy up, comments added for each item and the ability to enable/disable comments. + + +# Contributing +[*Back to top*](#Title) + +See out [Contributions](CONTRIBUTING.md) guidelines + + +# Plugins +[*Back to top*](#Title) + +## Plugin Structure +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. + +### Settings +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. + +**Examples** + +No Settings + ``` + # Start of Settings + # End of Settings + ``` + +Settings to define two variables + ``` + # Start of Settings + # Comment - presented as part of the setup wizard + $variable = "value" + # Second variable + $variable2 = "value2" + ... + # End of Settings + ``` + +### Required variables +Each plugin **must** define the following variables: +$Title - The display name of the plugin +$Header - the header of the plugin in the report +$Display - The format of the plugin (See Content section) +$Author - The author's name +$PluginVersion - Version of the plugin +$PluginCategory - The Category of the plugin + +### Content +#### Report output +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. + +#### $Display variable +- List +- Table +- Chart - Not currently merged to master + +#### Plugin Template + ``` + # Start of Settings + # End of Settings + + # generate your report content here. Simple placeholder hashtable for the sake of example + @{"Plugin"="Awesome"} + + $Title = "Plugin Template" + $Header = "Plugin Template" + $Comments = "Comment about this awesome plugin" + $Display = "List" + $Author = "Plugin Author" + $PluginVersion = 1.0 + $PluginCategory = "vSphere" + ``` +## Table Formatting +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. + +### Using Formatting Rules + +To use formatting rules, a `$TableFormat` variable must be defined in the module. + +The `$TableFormat` variable is a Hastable, with the key being the "column" of the table that the rule should apply to. + +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. + +### Formatting options + +The Formatting options are made up of two comma-separated values: + 1. The scope of the formatting rule - "Row" to apply to the entire row, or "Cell" to only apply to that particular cell. + 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. + +### Examples + +#### Example 1 + +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: + +`$TableFormat = @{"Compliant" = @(@{ "-eq $true" = "Cell,class|green"; }, @{ "-eq$false" = "Cell,class|red" })}` + +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. +element). The second rule applies when the compliant column is equal to $false, and applied the "red" class. + +#### Example 2 + +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. + +To achieve this, you could use the following: +``` +$TableFormat = @{"PercentFree" = @(@{ "-le 25" = "Row,class|warning"; }, @{ "-le 15" = "Row,class|critical" }); "CapacityGB" = @(@{ "-lt 500" = "Cell,style|background-color: silver"})} + ``` +Here we see the rules that apply to two different columns, with rules applied to the values in a fashion similar to Example 1. + + +# Styles +[*Back to top*](#Title) + +Each style *must* implement a function named Get-ReportHTML, as this is what is called by vCheck to generate the HTML report. + +An array of plugin results is passed to Get-ReportHTML, which contains the following properties: +* Title +* Author +* Version +* Details +* Display +* TableFormat +* Header +* Comments +* TimeToRun + +Additionally, if the style is to define colours to be used by charts, the following variables need to be defined: +* `[string[]] $ChartColours` - Array containing HTML colours without the hash e.g. $ChartColours = @("377C2B", "0A77BA", "1D6325", "89CBE1") +* `[string] $ChartBackground` - HTML colour without the hash. e.g. "FFFFFF" +* `[string] $ChartSize` - YYYxZZZ formatted string - where YYY is horizontal size, and ZZZ is height. E.g. "200x200" + +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. + + +# Jobs & Settings +[*Back to top*](#Title) + +## Job XML Specifications + +In order to use the `-Job` parameter, an XML configuration file is used. + +The root element is ``, under this there are two elements: +* `` element specifies the path to the file containing the vCheck settings (by default globalVariables.ps1) +* `` element has a semi-colon separated attribute name path, which contains the path(s) to search for plugins contained in child `` elements. + +Each `` element contains the plugin name. + +### Config Example + ``` + + GlobalVariables.ps1 + + 00 Connection Plugin for vCenter.ps1 + 03 Datastore Information.ps1 + 11 VMs with over CPU Count LOL WRONG PATH.ps1 + 99 VeryLastPlugin Used to Disconnect.ps1 + + + ``` +## Export/Import Settings +This section describes how to import and export your vCheck settings between builds. + +These functions were added to vCheckUtils.ps1 in June '14 (first release build TBD) + +You can copy a newer version of vCheckUtils.ps1 to your existing build in order to use the new functions. + +To utilize the new functions, simply dot source the vCheckUtils.ps1 file in a PowerShell console: +``` +PS E:\scripts\vCheck-vSphere> . .\vCheckUtils.ps1 +``` +This should load and list the functions available to you. +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. + +### Example +Lets assume we have an existing build located at +`E:\Scripts\vCheck-vSphere` + +First lets rename the folder +`E:\Scripts\vCheck-vSphere-old` + +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-vSphere-old` and `vCheck-vSphere` + +Next we'll export the settings from the old build - using PowerShell navigate to `E:\Scripts\vCheck-vSphere-old` and dot source `vCheckUtils.ps1` + +### Export Settings +Running `Export-vCheckSettings` will by default create a CSV file named `vCheckSettings.csv` in the current directory. +You can also specify a settings file +``` +PS E:\scripts\vCheck-vSphere-old> Export-vCheckSettings -outfile E:\MyvCheckSettings.csv +``` + +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. + +### Import Settings +To import your vCheck settings, in PowerShell navigate to the new build at `E:\Scripts\vCheck-vSphere` and dot source `vCheckUtils.ps1` once again. + +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. +The second option is to specify the path to the settings CSV file when running Import-vCheckSettings +``` +PS E:\scripts\vCheck-vSphere> Import-vCheckSettings -csvfile E:\MyvCheckSettings.csv +``` +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. + + +# More Info +[*Back to top*](#Title) + +For more information please read here: http://www.virtu-al.net/vcheck-pluginsheaders/vcheck/ + +For an example vSphere output (doesnt contain all info) click here http://virtu-al.net/Downloads/vCheck/vCheck.htm diff --git a/vCheck.ps1 b/vCheck.ps1 index b3e80bb7..51c59a6b 100644 --- a/vCheck.ps1 +++ b/vCheck.ps1 @@ -56,20 +56,24 @@ #> [CmdletBinding()] param ( - [Switch]$config, - [ValidateScript({Test-Path $_ -PathType 'Container'})] - [string]$Outputpath, - [ValidateScript({Test-Path $_ -PathType 'Leaf'})] - [string]$job + [Switch]$config, + + [ValidateScript({ Test-Path $_ -PathType 'Container' })] + [string]$Outputpath, + + [ValidateScript({ Test-Path $_ -PathType 'Leaf' })] + [string]$job ) + $vCheckVersion = "6.23-alpha-3" $Date = Get-Date +#region Internationalization ################################################################################ # Internationalization # ################################################################################ $lang = DATA { - ConvertFrom-StringData @' + ConvertFrom-StringData @' setupMsg01 = setupMsg02 = Welcome to vCheck by Virtu-Al http://virtu-al.net setupMsg03 = ================================================= @@ -104,332 +108,383 @@ $lang = DATA { Import-LocalizedData -BaseDirectory ($ScriptPath + "\lang") -BindingVariable lang -ErrorAction SilentlyContinue +#endregion Internationalization + +#region functions ################################################################################ # Functions # ################################################################################ <# Write timestamped output to screen #> -function Write-CustomOut ($Details){ - $LogDate = Get-Date -Format "HH:mm:ss" - Write-Host "[$($LogDate)] $Details" +function Write-CustomOut ($Details) { + $LogDate = Get-Date -Format "HH:mm:ss" + Write-OutPut "[$($LogDate)] $Details" } <# Search $file_content for name/value pair with ID_Name and return value #> -Function Get-ID-String ($file_content,$ID_name) { - if ($file_content | Select-String -Pattern "\$+$ID_name\s*=") { - $value = (($file_content | Select-String -pattern "\$+${ID_name}\s*=").toString().split("=")[1]).Trim(' "') - return ( $value ) - } +Function Get-ID-String ($file_content, $ID_name) { + if ($file_content | Select-String -Pattern "\$+$ID_name\s*=") { + $value = (($file_content | Select-String -pattern "\$+${ID_name}\s*=").toString().split("=")[1]).Trim(' "') + return ($value) + } } <# Get basic information abount a plugin #> -Function Get-PluginID ($Filename){ - # Get the identifying information for a plugin script - $file = Get-Content $Filename - $Title = Get-ID-String $file "Title" - if ( !$Title ) { $Title = $Filename } - $PluginVersion = Get-ID-String $file "PluginVersion" - $Author = Get-ID-String $file "Author" - $Ver = "{0:N1}" -f $PluginVersion - - return @{"Title"=$Title; "Version"=$Ver; "Author"=$Author } +Function Get-PluginID ($Filename) { + # Get the identifying information for a plugin script + $file = Get-Content $Filename + $Title = Get-ID-String $file "Title" + if (!$Title) { $Title = $Filename } + $PluginVersion = Get-ID-String $file "PluginVersion" + $Author = Get-ID-String $file "Author" + $Ver = "{0:N1}" -f $PluginVersion + + return @{ "Title" = $Title; "Version" = $Ver; "Author" = $Author } } -<# Run through settings for specified file, expects question on one line, and variable/value on following line #> -Function Invoke-Settings ($Filename, $GB) { - $file = Get-Content $filename - $OriginalLine = ($file | Select-String -Pattern "# Start of Settings").LineNumber - $EndLine = ($file | Select-String -Pattern "# End of Settings").LineNumber - if (!(($OriginalLine +1) -eq $EndLine)) { - $Array = @() - $Line = $OriginalLine - $PluginName = (Get-PluginID $Filename).Title - If ($PluginName.EndsWith(".ps1",1)) { - $PluginName = ($PluginName.split("\")[-1]).split(".")[0] - } - Write-Host "`n$PluginName" -foreground $host.PrivateData.WarningForegroundColor -background $host.PrivateData.WarningBackgroundColor - do { - $Question = $file[$Line] - $Line ++ - $Split= ($file[$Line]).Split("=") - $Var = $Split[0] - $CurSet = $Split[1].Trim() - - # Check if the current setting is in speech marks - $String = $false - if ($CurSet -match '"') { - $String = $true - $CurSet = $CurSet.Replace('"', '').Trim() - } - $NewSet = Read-Host "$Question [$CurSet]" - If (-not $NewSet) { - $NewSet = $CurSet - } - If ($String) { - $Array += $Question - $Array += "$Var= `"$NewSet`"" - } Else { - $Array += $Question - $Array += "$Var= $NewSet" - } - $Line ++ - } Until ( $Line -ge ($EndLine -1) ) - $Array += "# End of Settings" - - $out = @() - $out = $File[0..($OriginalLine -1)] - $out += $array - $out += $File[$Endline..($file.count -1)] - if ($GB) { $out[$SetupLine] = '$SetupWizard = $False' } - $out | Out-File $Filename - } -} + +Function Invoke-Settings { + + <# + .DESCRIPTION + Run through settings for specified file, expects question on one line, and variable/value on following line + .NOTES + Updated: 20150428 + Updated By: Kevin Kirkpatrick (@vScripter - Twitter/GitHub) + Update Notes: + - Remove Write-Host in favor of Write-Warning; this was based on setting the color of Write-Host to 'warning' colors + - converted function to advanced function + - moved parameters out of function declaration and into the param declaration + - moved all code into the PROCESS block + - improved code spacing for improved readability + - added comment based help section for notes/comments + #> + + [CmdletBinding(PositionalBinding = $true)] + param ( + [parameter(Position = 0)] + $Filename, + [parameter(Position = 1)] + $GB + ) + + PROCESS { + + $file = Get-Content $filename + $OriginalLine = ($file | Select-String -Pattern "# Start of Settings").LineNumber + $EndLine = ($file | Select-String -Pattern "# End of Settings").LineNumber + + if (!(($OriginalLine + 1) -eq $EndLine)) { + + $Array = @() + $Line = $OriginalLine + $PluginName = (Get-PluginID $Filename).Title + + If ($PluginName.EndsWith(".ps1", 1)) { + + $PluginName = ($PluginName.split("\")[-1]).split(".")[0] + + } # end if + + Write-Warning -Message "`n$PluginName" + + do { + + $Question = $file[$Line] + $Line++ + $Split = ($file[$Line]).Split("=") + $Var = $Split[0] + $CurSet = $Split[1].Trim() + + # Check if the current setting is in speech marks + $String = $false + if ($CurSet -match '"') { + $String = $true + $CurSet = $CurSet.Replace('"', '').Trim() + } # end if + + $NewSet = Read-Host "$Question [$CurSet]" + + If (-not $NewSet) { + $NewSet = $CurSet + } # end if + + If ($String) { + $Array += $Question + $Array += "$Var= `"$NewSet`"" + } Else { + $Array += $Question + $Array += "$Var= $NewSet" + } # end if/else + + $Line++ + + } Until ($Line -ge ($EndLine - 1)) + + $Array += "# End of Settings" + + $out = @() + $out = $File[0..($OriginalLine - 1)] + $out += $array + $out += $File[$Endline..($file.count - 1)] + + if ($GB) { + $out[$SetupLine] = '$SetupWizard = $False' + } # end if + + $out | Out-File $Filename + + } # end if + + } # end PROCESS block + +} # end Function Invoke-Settings <# Replace HTML Entities in string. Used to stop
tags from being mangled in tables #> function Format-HTMLEntities { - param ([string]$content) - - $replace = @{"<" = "<"; - ">" = ">"; } - - foreach ($r in $replace.Keys.GetEnumerator()) { - $content = $content -replace $r, $replace[$r] - } - return $content + param ([string]$content) + + $replace = @{ + "<" = "<"; + ">" = ">"; + } + + foreach ($r in $replace.Keys.GetEnumerator()) { + $content = $content -replace $r, $replace[$r] + } + return $content } <# Takes an array of content, and optional formatRules and generated HTML table #> Function Get-HTMLTable { - param($Content, $FormatRules) - - # Use an XML object for ease of use - $XMLTable = [xml]($content | ConvertTo-Html -Fragment) - $XMLTable.table.RemoveChild($XMLTable.table.colgroup) | out-null - $XMLTable.table.SetAttribute("width","100%") - - # If format rules are specified - if ($FormatRules) { - # Check each cell to see if there are any format rules - for ($RowN = 1; $RowN -lt $XMLTable.table.tr.count; $RowN++) { - for ($ColN = 0; $ColN -lt $XMLTable.table.tr[$RowN].td.count; $ColN++) { - if ( $FormatRules.keys -contains $XMLTable.table.tr[0].th[$ColN]) { - # Current cell has a rule, test to see if they are valid - foreach ( $rule in $FormatRules[$XMLTable.table.tr[0].th[$ColN]] ) { - $value = $XMLTable.table.tr[$RowN].td[$ColN] - if ($value -notmatch "^[0-9.]+$") { - $value = """$value""" - } - if ( Invoke-Expression ("{0} {1}" -f $value, [string]$rule.Keys) ) { - # Find what to - $RuleScope = ([string]$rule.Values).split(",")[0] - $RuleActions = ([string]$rule.Values).split(",")[1].split("|") - - switch ($RuleScope) { - "Row" { - for ($TRColN = 0; $TRColN -lt $XMLTable.table.tr[$RowN].td.count; $TRColN++) { - $XMLTable.table.tr[$RowN].selectSingleNode("td[$($TRColN+1)]").SetAttribute($RuleActions[0], $RuleActions[1]) - } - } - "Cell" { - if ($RuleActions[0] -eq "cid") { - # Do Image - create new XML node for img and clear #text - $XMLTable.table.tr[$RowN].selectSingleNode("td[$($ColN+1)]")."#text" = "" - $elem = $XMLTable.CreateElement("img") - $elem.SetAttribute("src", ("cid:{0}" -f $RuleActions[1])) - # Add img size if specified - if ($RuleActions[2] -match "(\d+)x(\d+)") { - $elem.SetAttribute("width", $Matches[1]) - $elem.SetAttribute("height", $Matches[2]) - } - - $XMLTable.table.tr[$RowN].selectSingleNode("td[$($ColN+1)]").AppendChild($elem) | Out-Null - # Increment usage counter (so we don't have .bin attachments) - Set-ReportResource $RuleActions[1] - } - else { - $XMLTable.table.tr[$RowN].selectSingleNode("td[$($ColN+1)]").SetAttribute($RuleActions[0], $RuleActions[1]) - } - } - } - } - } - } - } - } - } - return (Format-HTMLEntities ([string]($XMLTable.OuterXml))) + param ($Content, $FormatRules) + + # Use an XML object for ease of use + $XMLTable = [xml]($content | ConvertTo-Html -Fragment) + $XMLTable.table.RemoveChild($XMLTable.table.colgroup) | out-null + $XMLTable.table.SetAttribute("width", "100%") + + # If format rules are specified + if ($FormatRules) { + # Check each cell to see if there are any format rules + for ($RowN = 1; $RowN -lt $XMLTable.table.tr.count; $RowN++) { + for ($ColN = 0; $ColN -lt $XMLTable.table.tr[$RowN].td.count; $ColN++) { + if ($FormatRules.keys -contains $XMLTable.table.tr[0].th[$ColN]) { + # Current cell has a rule, test to see if they are valid + foreach ($rule in $FormatRules[$XMLTable.table.tr[0].th[$ColN]]) { + $value = $XMLTable.table.tr[$RowN].td[$ColN] + if ($value -notmatch "^[0-9.]+$") { + $value = """$value""" + } + if (Invoke-Expression ("{0} {1}" -f $value, [string]$rule.Keys)) { + # Find what to + $RuleScope = ([string]$rule.Values).split(",")[0] + $RuleActions = ([string]$rule.Values).split(",")[1].split("|") + + switch ($RuleScope) { + "Row" { + for ($TRColN = 0; $TRColN -lt $XMLTable.table.tr[$RowN].td.count; $TRColN++) { + $XMLTable.table.tr[$RowN].selectSingleNode("td[$($TRColN + 1)]").SetAttribute($RuleActions[0], $RuleActions[1]) + } + } + "Cell" { + if ($RuleActions[0] -eq "cid") { + # Do Image - create new XML node for img and clear #text + $XMLTable.table.tr[$RowN].selectSingleNode("td[$($ColN + 1)]")."#text" = "" + $elem = $XMLTable.CreateElement("img") + $elem.SetAttribute("src", ("cid:{0}" -f $RuleActions[1])) + # Add img size if specified + if ($RuleActions[2] -match "(\d+)x(\d+)") { + $elem.SetAttribute("width", $Matches[1]) + $elem.SetAttribute("height", $Matches[2]) + } + + $XMLTable.table.tr[$RowN].selectSingleNode("td[$($ColN + 1)]").AppendChild($elem) | Out-Null + # Increment usage counter (so we don't have .bin attachments) + Set-ReportResource $RuleActions[1] + } else { + $XMLTable.table.tr[$RowN].selectSingleNode("td[$($ColN + 1)]").SetAttribute($RuleActions[0], $RuleActions[1]) + } + } + } + } + } + } + } + } + } + return (Format-HTMLEntities ([string]($XMLTable.OuterXml))) } <# Takes an array of content, and returns HTML table with header column #> Function Get-HTMLList { - param ([array]$content) - - if ($content.count -gt 0 ) { - # Create XML doc from HTML. Remove colgroup and header row - if ($content.count -gt 1 ) { - [xml]$XMLTable = $content | ConvertTo-HTML -Fragment - $XMLTable.table.RemoveChild($XMLTable.table.colgroup) | out-null - $XMLTable.table.RemoveChild($XMLTable.table.tr[0]) | out-null - $XMLTable.table.SetAttribute("width","100%") - } - else { - [xml]$XMLTable = $content | ConvertTo-HTML -Fragment -As List - } - - # Replace the first column td with th - for ($i = 0; $i -lt $XMLTable.table.tr.count; $i++) { - $node = $XMLTable.table.tr[$i].SelectSingleNode("/table/tr[$($i+1)]/td[1]") - $elem = $XMLTable.CreateElement("th") - $elem.InnerText = $node."#text" - $trNode = $XMLTable.SelectSingleNode("/table/tr[$($i+1)]") - $trNode.ReplaceChild($elem, $node) | Out-Null - } - return (Format-HTMLEntities ([string]($XMLTable.OuterXml))) - } + param ([array]$content) + + if ($content.count -gt 0) { + # Create XML doc from HTML. Remove colgroup and header row + if ($content.count -gt 1) { + [xml]$XMLTable = $content | ConvertTo-HTML -Fragment + $XMLTable.table.RemoveChild($XMLTable.table.colgroup) | out-null + $XMLTable.table.RemoveChild($XMLTable.table.tr[0]) | out-null + $XMLTable.table.SetAttribute("width", "100%") + } else { + [xml]$XMLTable = $content | ConvertTo-HTML -Fragment -As List + } + + # Replace the first column td with th + for ($i = 0; $i -lt $XMLTable.table.tr.count; $i++) { + $node = $XMLTable.table.tr[$i].SelectSingleNode("/table/tr[$($i + 1)]/td[1]") + $elem = $XMLTable.CreateElement("th") + $elem.InnerText = $node."#text" + $trNode = $XMLTable.SelectSingleNode("/table/tr[$($i + 1)]") + $trNode.ReplaceChild($elem, $node) | Out-Null + } + return (Format-HTMLEntities ([string]($XMLTable.OuterXml))) + } } <# Returns HTML fragment for chart. Calls Get-ChartResource to generate chart image #> function Get-HTMLChart { - param ( - [string]$cidbase, - [Object[]]$ChartObjs - ) - $html = "" - $i = 0 - foreach ($ChartObj in $ChartObjs) { - $i++ - $base64 = Get-ChartResource $ChartObj - $cid = $cidbase+"-"+$i - Add-ReportResource -cid $cid -ResourceData $Base64 -Type "Base64" -Used $true - $html += "" - } - return $html + param ( + [string]$cidbase, + [Object[]]$ChartObjs + ) + $html = "" + $i = 0 + foreach ($ChartObj in $ChartObjs) { + $i++ + $base64 = Get-ChartResource $ChartObj + $cid = $cidbase + "-" + $i + Add-ReportResource -cid $cid -ResourceData $Base64 -Type "Base64" -Used $true + $html += "" + } + return $html } <# Create a new Chert object, this will get fed back down the output stream as part of plugin processing. This allows us to keep the same interface for plugins content #> function New-Chart { - param ( - [int]$height, - [int]$width, - [Parameter(Mandatory=$true)] - [Hashtable[]]$data, - [string]$title, - [string]$titleX, - [string]$titleY, - [ValidateSet("Area", "Bar", "BoxPlot", "Bubble", "Candlestick", "Column", "Doughnut", "ErrorBar", "FastLine", - "FastPoint", "Funnel", "Kagi", "Line", "Pie", "Point", "PointAndFigure", "Polar", "Pyramid", - "Radar", "Range", "RangeBar", "RangeColumn", "Renko", "Spline", "SplineArea", "SplineRange", - "StackedArea", "StackedArea100", "StackedBar", "StackedBar100", "StackedColumn", - "StackedColumn100", "StepLine", "Stock", "ThreeLineBreak")] - $ChartType="bar" - ) - - # If chartsize is specified in style, use it unless explicitly set - if ($ChartSize -and (-not $height -and -not $width)) { - if ($ChartSize -match "(\d+)x(\d+)") { - $height = $Matches[1] - $width = $Matches[2] - } - } - # if size not set in style or function call, default to 400x400 (maybe make this a globalVariable?) - if (-not $ChartSize -and (-not $height -and -not $width)) { - $height = 400 - $width = 400 - } - - return New-Object PSObject -Property @{"height" = $height; - "width" = $width; - "data" = $data; - "title" = $title; - "titleX" = $titleX; - "titleY" = $titleY; - "ChartType" = $ChartType - } + param ( + [int]$height, + [int]$width, + [Parameter(Mandatory = $true)] + [Hashtable[]]$data, + [string]$title, + [string]$titleX, + [string]$titleY, + [ValidateSet("Area", "Bar", "BoxPlot", "Bubble", "Candlestick", "Column", "Doughnut", "ErrorBar", "FastLine", + "FastPoint", "Funnel", "Kagi", "Line", "Pie", "Point", "PointAndFigure", "Polar", "Pyramid", + "Radar", "Range", "RangeBar", "RangeColumn", "Renko", "Spline", "SplineArea", "SplineRange", + "StackedArea", "StackedArea100", "StackedBar", "StackedBar100", "StackedColumn", + "StackedColumn100", "StepLine", "Stock", "ThreeLineBreak")] + $ChartType = "bar" + ) + + # If chartsize is specified in style, use it unless explicitly set + if ($ChartSize -and (-not $height -and -not $width)) { + if ($ChartSize -match "(\d+)x(\d+)") { + $height = $Matches[1] + $width = $Matches[2] + } + } + # if size not set in style or function call, default to 400x400 (maybe make this a globalVariable?) + if (-not $ChartSize -and (-not $height -and -not $width)) { + $height = 400 + $width = 400 + } + + return New-Object PSObject -Property @{ + "height" = $height; + "width" = $width; + "data" = $data; + "title" = $title; + "titleX" = $titleX; + "titleY" = $titleY; + "ChartType" = $ChartType + } } <# Creates a chart Image #> function Get-ChartResource { - param ( - $ChartDef - ) - [void][Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms.DataVisualization") - - # Create a new chart object - $Chart = New-object System.Windows.Forms.DataVisualization.Charting.Chart - $Chart.Width = $ChartDef.width - $Chart.Height = $ChartDef.height - $Chart.AntiAliasing = "All" - - # Create a chartarea to draw on and add to chart - $ChartArea = New-Object System.Windows.Forms.DataVisualization.Charting.ChartArea - $Chart.ChartAreas.Add($ChartArea) - - # Set title and axis labels - if ($ChartDef.title) { - $titleRef = $Chart.Titles.Add($ChartDef.title) - } - if ($ChartDef.titleX) { - $ChartArea.AxisX.Title = $ChartDef.titleX - } - if ($ChartDef.titleY) { - $ChartArea.AxisY.Title = $ChartDef.titleY - } - - # change chart colours - if ($ChartBackground) { - $Chart.BackColor = Get-ChartColours $ChartBackground - $ChartArea.BackColor = Get-ChartColours $ChartBackground - } - else { - $Chart.BackColor = [System.Drawing.Color]::Transparent - $ChartArea.BackColor = [System.Drawing.Color]::Transparent - } - # If we have style - if ($ChartColours) { - $Chart.PaletteCustomColors = Get-ChartColours $ChartColours - $Chart.Palette = [System.Windows.Forms.DataVisualization.Charting.ChartColorPalette]::None - } - - if ($ChartFontColour) { - $Chart.ForeColor = Get-ChartColours $ChartFontColour - } - - # Add data to chart and set chart type - for ($i = 0; $i -lt $ChartDef.data.count; $i++) { - [void]$Chart.Series.Add("Data$i") - $Chart.Series["Data$i"].Points.DataBindXY($ChartDef.data[$i].Keys, $ChartDef.data[$i].Values) - $Chart.Series["Data$i"].ChartType = [System.Windows.Forms.DataVisualization.Charting.SeriesChartType]::($ChartDef.ChartType) - } - - # Do some funky work to increase the DPI so charts look nice. Default 96 DPI looks terrible :( - [void][System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") - - $bmp = New-Object System.Drawing.Bitmap(($ChartDef.width), ($ChartDef.height)) - $bmp.SetResolution(384, 384); - if ($ChartArea.BackColor -eq [System.Drawing.Color]::Transparent) { - $bmp.MakeTransparent() - } - $chart.DrawToBitmap($bmp, (new-object System.Drawing.Rectangle(0, 0, $ChartDef.width, $ChartDef.height))) - $ms = new-Object IO.MemoryStream - $bmp.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png); - $ms.Seek(0, [System.IO.SeekOrigin]::Begin) | Out-Null - $byte = New-Object byte[] $ms.Length - $ms.read($byte, 0, $ms.length) | Out-Null - - return ("png|{0}" -f [System.Convert]::ToBase64String($byte)) + param ( + $ChartDef + ) + [void][Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms.DataVisualization") + + # Create a new chart object + $Chart = New-object System.Windows.Forms.DataVisualization.Charting.Chart + $Chart.Width = $ChartDef.width + $Chart.Height = $ChartDef.height + $Chart.AntiAliasing = "All" + + # Create a chartarea to draw on and add to chart + $ChartArea = New-Object System.Windows.Forms.DataVisualization.Charting.ChartArea + $Chart.ChartAreas.Add($ChartArea) + + # Set title and axis labels + if ($ChartDef.title) { + $titleRef = $Chart.Titles.Add($ChartDef.title) + } + if ($ChartDef.titleX) { + $ChartArea.AxisX.Title = $ChartDef.titleX + } + if ($ChartDef.titleY) { + $ChartArea.AxisY.Title = $ChartDef.titleY + } + + # change chart colours + if ($ChartBackground) { + $Chart.BackColor = Get-ChartColours $ChartBackground + $ChartArea.BackColor = Get-ChartColours $ChartBackground + } else { + $Chart.BackColor = [System.Drawing.Color]::Transparent + $ChartArea.BackColor = [System.Drawing.Color]::Transparent + } + # If we have style + if ($ChartColours) { + $Chart.PaletteCustomColors = Get-ChartColours $ChartColours + $Chart.Palette = [System.Windows.Forms.DataVisualization.Charting.ChartColorPalette]::None + } + + if ($ChartFontColour) { + $Chart.ForeColor = Get-ChartColours $ChartFontColour + } + + # Add data to chart and set chart type + for ($i = 0; $i -lt $ChartDef.data.count; $i++) { + [void]$Chart.Series.Add("Data$i") + $Chart.Series["Data$i"].Points.DataBindXY($ChartDef.data[$i].Keys, $ChartDef.data[$i].Values) + $Chart.Series["Data$i"].ChartType = [System.Windows.Forms.DataVisualization.Charting.SeriesChartType]::($ChartDef.ChartType) + } + + # Do some funky work to increase the DPI so charts look nice. Default 96 DPI looks terrible :( + [void][System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") + + $bmp = New-Object System.Drawing.Bitmap(($ChartDef.width), ($ChartDef.height)) + $bmp.SetResolution(384, 384); + if ($ChartArea.BackColor -eq [System.Drawing.Color]::Transparent) { + $bmp.MakeTransparent() + } + $chart.DrawToBitmap($bmp, (new-object System.Drawing.Rectangle(0, 0, $ChartDef.width, $ChartDef.height))) + $ms = new-Object IO.MemoryStream + $bmp.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png); + $ms.Seek(0, [System.IO.SeekOrigin]::Begin) | Out-Null + $byte = New-Object byte[] $ms.Length + $ms.read($byte, 0, $ms.length) | Out-Null + + return ("png|{0}" -f [System.Convert]::ToBase64String($byte)) } <# Takes Array of HTML colour codes and returns Color object #> function Get-ChartColours { - param ( - [string[]]$ChartColours - ) - - foreach ($colour in $ChartColours) { - [System.Drawing.Color]::FromArgb([Convert]::ToInt32($colour.Substring(0,2), 16), - [Convert]::ToInt32($colour.Substring(2,2), 16), - [Convert]::ToInt32($colour.Substring(4,2), 16)); - } + param ( + [string[]]$ChartColours + ) + + foreach ($colour in $ChartColours) { + [System.Drawing.Color]::FromArgb([Convert]::ToInt32($colour.Substring(0, 2), 16), + [Convert]::ToInt32($colour.Substring(2, 2), 16), + [Convert]::ToInt32($colour.Substring(4, 2), 16)); + } } <# Adds a resource to the resource array, to be included in report. @@ -437,109 +492,113 @@ function Get-ChartColours { SystemIcons and raw byte data (so images can be packaged completely in styles if desired #> function Add-ReportResource { - param ( - $cid, - $ResourceData, - [ValidateSet("File","SystemIcons","Base64")] - $Type="File", - $Used=$false - ) - - # If cid does not exist, add it - if ($global:ReportResources.Keys -notcontains $cid) { - $global:ReportResources.Add($cid, @{"Data" = ("{0}|{1}" -f $Type, $ResourceData); - "Uses" = 0 }) - } - - # Update uses count if $Used set (Should normally be incremented with Set-ReportResource) - # Useful for things like headers where they are always required. - if ($Used) { - ($global:ReportResources[$cid].Uses)++ - } + param ( + $cid, + $ResourceData, + [ValidateSet("File", "SystemIcons", "Base64")] + $Type = "File", + $Used = $false + ) + + # If cid does not exist, add it + if ($global:ReportResources.Keys -notcontains $cid) { + $global:ReportResources.Add($cid, @{ + "Data" = ("{0}|{1}" -f $Type, $ResourceData); + "Uses" = 0 + }) + } + + # Update uses count if $Used set (Should normally be incremented with Set-ReportResource) + # Useful for things like headers where they are always required. + if ($Used) { + ($global:ReportResources[$cid].Uses)++ + } } Function Set-ReportResource { - param ( - $cid - ) - - # Increment use - ($global:ReportResources[$cid].Uses)++ + param ( + $cid + ) + + # Increment use + ($global:ReportResources[$cid].Uses)++ } <# Gets a resource in the specified ReturnType (eventually support both a base64 encoded string, and Linked Resource for email #> function Get-ReportResource { - param ( - $cid, - [ValidateSet("embed","linkedresource")] - $ReturnType="embed" - ) - - $data = $global:ReportResources[$cid].Data.Split("|") - - # Process each resource type differently - switch ($data[0]) { - "File" { - # Check the path exists - if (Test-Path $data[1] -ErrorAction SilentlyContinue) { - if ($ReturnType -eq "embed") { - # return a MIME/Base64 combo for embedding in HTML - $imgData = Get-Content ($data[1]) -Encoding Byte - $type = $data[1].substring($data[1].LastIndexOf(".")+1) - return ("data:image/{0};base64,{1}" -f $type, [System.Convert]::ToBase64String($imgData)) - } - if ($ReturnType -eq "linkedresource") { - # return a linked resource to be added to mail message - $lr = New-Object system.net.mail.LinkedResource($data[1]) - $lr.ContentId=$cid - return $lr; - } - } - else { - Write-Warning ($lang.resFileWarn -f $cid) - } - } - "SystemIcons" { - # Take the SystemIcon Name - see http://msdn.microsoft.com/en-us/library/system.drawing.systemicons(v=vs.110).aspx - # Load the image into a MemoryStream in PNG format (to preserve transparency) - [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") - $bmp = ([System.Drawing.SystemIcons]::($data[1])).toBitmap() - $bmp.MakeTransparent() - $ms = new-Object IO.MemoryStream - $bmp.Save($ms, [System.Drawing.Imaging.ImageFormat]::PNG) - $ms.Seek(0, [System.IO.SeekOrigin]::Begin) | Out-Null - - if ($ReturnType -eq "embed") { - # return a MIME/Base64 combo for embedding in HTML - $byte = New-Object byte[] $ms.Length - $ms.read($byte, 0, $ms.length) | Out-Null - return ("data:image/png;base64,"+[System.Convert]::ToBase64String($byte)) - } - if ($ReturnType -eq "linkedresource") { - # return a linked resource to be added to mail message - $lr = New-Object system.net.mail.LinkedResource($ms) - $lr.ContentId=$cid - return $lr; - } - } - "Base64" { - if ($ReturnType -eq "embed") { - return ("data:image/{0};base64,{1}" -f $data[1], $data[2]) - } - if ($ReturnType -eq "linkedresource") { - $w = [system.convert]::FromBase64String($data[2]) - $ms = new-Object IO.MemoryStream - $ms.Write($w, 0 , $w.Length); - $ms.Seek(0, [System.IO.SeekOrigin]::Begin) | Out-Null - $lr = New-Object system.net.mail.LinkedResource($ms) - $lr.ContentId=$cid - return $lr; - } - } - } + param ( + $cid, + [ValidateSet("embed", "linkedresource")] + $ReturnType = "embed" + ) + + $data = $global:ReportResources[$cid].Data.Split("|") + + # Process each resource type differently + switch ($data[0]) { + "File" { + # Check the path exists + if (Test-Path $data[1] -ErrorAction SilentlyContinue) { + if ($ReturnType -eq "embed") { + # return a MIME/Base64 combo for embedding in HTML + $imgData = Get-Content ($data[1]) -Encoding Byte + $type = $data[1].substring($data[1].LastIndexOf(".") + 1) + return ("data:image/{0};base64,{1}" -f $type, [System.Convert]::ToBase64String($imgData)) + } + if ($ReturnType -eq "linkedresource") { + # return a linked resource to be added to mail message + $lr = New-Object system.net.mail.LinkedResource($data[1]) + $lr.ContentId = $cid + return $lr; + } + } else { + Write-Warning ($lang.resFileWarn -f $cid) + } + } + "SystemIcons" { + # Take the SystemIcon Name - see http://msdn.microsoft.com/en-us/library/system.drawing.systemicons(v=vs.110).aspx + # Load the image into a MemoryStream in PNG format (to preserve transparency) + [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") + $bmp = ([System.Drawing.SystemIcons]::($data[1])).toBitmap() + $bmp.MakeTransparent() + $ms = new-Object IO.MemoryStream + $bmp.Save($ms, [System.Drawing.Imaging.ImageFormat]::PNG) + $ms.Seek(0, [System.IO.SeekOrigin]::Begin) | Out-Null + + if ($ReturnType -eq "embed") { + # return a MIME/Base64 combo for embedding in HTML + $byte = New-Object byte[] $ms.Length + $ms.read($byte, 0, $ms.length) | Out-Null + return ("data:image/png;base64," + [System.Convert]::ToBase64String($byte)) + } + if ($ReturnType -eq "linkedresource") { + # return a linked resource to be added to mail message + $lr = New-Object system.net.mail.LinkedResource($ms) + $lr.ContentId = $cid + return $lr; + } + } + "Base64" { + if ($ReturnType -eq "embed") { + return ("data:image/{0};base64,{1}" -f $data[1], $data[2]) + } + if ($ReturnType -eq "linkedresource") { + $w = [system.convert]::FromBase64String($data[2]) + $ms = new-Object IO.MemoryStream + $ms.Write($w, 0, $w.Length); + $ms.Seek(0, [System.IO.SeekOrigin]::Begin) | Out-Null + $lr = New-Object system.net.mail.LinkedResource($ms) + $lr.ContentId = $cid + return $lr; + } + } + } } +#endregion functions + +#region initialization ################################################################################ # Initialization # ################################################################################ @@ -549,199 +608,211 @@ $PluginsFolder = $ScriptPath + "\Plugins\" # if we have the job parameter set, get the paths from the config file. if ($job) { - [xml]$jobConfig = Get-Content $job - - # Use GlobalVariables path if it is valid, otherwise use default - if (Test-Path $jobConfig.vCheck.globalVariables) { - $GlobalVariables = (Get-Item $jobConfig.vCheck.globalVariables).FullName - } - else { - $GlobalVariables = $ScriptPath + "\GlobalVariables.ps1" - Write-Warning ($lang.gvInvalid -f $GlobalVariables) - } - - # Get Plugin paths - $PluginPaths = @() - if ($jobConfig.vCheck.plugins.path) { - foreach ($PluginPath in ($jobConfig.vCheck.plugins.path -split ";")) { - if (Test-Path $PluginPath) { - $PluginPaths += (Get-Item $PluginPath).Fullname - $PluginPaths += Get-Childitem $PluginPath -Recurse | ?{ $_.PSIsContainer } | Select -ExpandProperty FullName - } - else { - $PluginPaths += $ScriptPath + "\Plugins" - Write-Warning ($lang.pluginpathInvalid -f $PluginPath, ($ScriptPath + "\Plugins")) - } - } - $PluginPaths = $PluginPaths | Sort-Object -unique - - # Get all plugins and test they are correct - $vCheckPlugins = @() - foreach ($plugin in $jobConfig.vCheck.plugins.plugin) { - $testedPaths = 0 - foreach ($PluginPath in $PluginPaths) { - $testedPaths++ - if (Test-Path ("{0}\{1}" -f $PluginPath, $plugin)) { - $vCheckPlugins += Get-Item ("{0}\{1}" -f $PluginPath, $plugin) - break; - } - # Plugin not found in any search path - elseif ($testedPaths -eq $PluginPaths.Count) { - Write-Warning ($lang.pluginInvalid -f $plugin) - } - } - } - } - # if no valid plugins specified, fall back to default - if (!$vCheckPlugins) { - $vCheckPlugins = Get-ChildItem -Path $PluginsFolder -filter "*.ps1" -Recurse | Sort FullName - } -} -else { - $ToNatural = { [regex]::Replace($_, '\d+', { $args[0].Value.PadLeft(20) }) } - $vCheckPlugins = @(Get-ChildItem -Path $PluginsFolder -filter "*.ps1" -Recurse | where {$_.Directory -match "initialize"} | Sort $ToNatural) - $PluginsSubFolder = Get-ChildItem -Path $PluginsFolder | where {($_.PSIsContainer) -and ($_.Name -notmatch "initialize") -and ($_.Name -notmatch "finish")} - $vCheckPlugins += $PluginsSubFolder | % {Get-ChildItem -Path $_.FullName -filter "*.ps1" | Sort $ToNatural} - $vCheckPlugins += Get-ChildItem -Path $PluginsFolder -filter "*.ps1" -Recurse | where {$_.Directory -match "finish"} | Sort $ToNatural - $GlobalVariables = $ScriptPath + "\GlobalVariables.ps1" + [xml]$jobConfig = Get-Content $job + + # Use GlobalVariables path if it is valid, otherwise use default + if (Test-Path $jobConfig.vCheck.globalVariables) { + $GlobalVariables = (Get-Item $jobConfig.vCheck.globalVariables).FullName + } else { + $GlobalVariables = $ScriptPath + "\GlobalVariables.ps1" + Write-Warning ($lang.gvInvalid -f $GlobalVariables) + } + + # Get Plugin paths + $PluginPaths = @() + if ($jobConfig.vCheck.plugins.path) { + foreach ($PluginPath in ($jobConfig.vCheck.plugins.path -split ";")) { + if (Test-Path $PluginPath) { + $PluginPaths += (Get-Item $PluginPath).Fullname + $PluginPaths += Get-Childitem $PluginPath -Recurse | ?{ $_.PSIsContainer } | Select -ExpandProperty FullName + } else { + $PluginPaths += $ScriptPath + "\Plugins" + Write-Warning ($lang.pluginpathInvalid -f $PluginPath, ($ScriptPath + "\Plugins")) + } + } + $PluginPaths = $PluginPaths | Sort-Object -unique + + # Get all plugins and test they are correct + $vCheckPlugins = @() + foreach ($plugin in $jobConfig.vCheck.plugins.plugin) { + $testedPaths = 0 + foreach ($PluginPath in $PluginPaths) { + $testedPaths++ + if (Test-Path ("{0}\{1}" -f $PluginPath, $plugin)) { + $vCheckPlugins += Get-Item ("{0}\{1}" -f $PluginPath, $plugin) + break; + } + # Plugin not found in any search path + elseif ($testedPaths -eq $PluginPaths.Count) { + Write-Warning ($lang.pluginInvalid -f $plugin) + } + } + } + } + # if no valid plugins specified, fall back to default + if (!$vCheckPlugins) { + $vCheckPlugins = Get-ChildItem -Path $PluginsFolder -filter "*.ps1" -Recurse | Sort FullName + } +} else { + $ToNatural = { [regex]::Replace($_, '\d+', { $args[0].Value.PadLeft(20) }) } + $vCheckPlugins = @(Get-ChildItem -Path $PluginsFolder -filter "*.ps1" -Recurse | where { $_.Directory -match "initialize" } | Sort $ToNatural) + $PluginsSubFolder = Get-ChildItem -Path $PluginsFolder | where { ($_.PSIsContainer) -and ($_.Name -notmatch "initialize") -and ($_.Name -notmatch "finish") } + $vCheckPlugins += $PluginsSubFolder | % { Get-ChildItem -Path $_.FullName -filter "*.ps1" | Sort $ToNatural } + $vCheckPlugins += Get-ChildItem -Path $PluginsFolder -filter "*.ps1" -Recurse | where { $_.Directory -match "finish" } | Sort $ToNatural + $GlobalVariables = $ScriptPath + "\GlobalVariables.ps1" } ## Determine if the setup wizard needs to run $file = Get-Content $GlobalVariables $Setup = ($file | Select-String -Pattern '# Set the following to true to enable the setup wizard for first time run').LineNumber -$SetupLine = $Setup ++ +$SetupLine = $Setup++ $SetupSetting = Invoke-Expression (($file[$SetupLine]).Split("="))[1] if ($SetupSetting -or $config) { - Clear-Host - ($lang.GetEnumerator() | where {$_.Name -match "setupMsg[0-9]*"} | Sort-Object Name) | Foreach { - Write-Host -foreground $host.PrivateData.WarningForegroundColor -background $host.PrivateData.WarningBackgroundColor $_.value - } - - Invoke-Settings -Filename $GlobalVariables -GB $true - Foreach ($plugin in $vCheckPlugins) { - Invoke-Settings -Filename $plugin.Fullname - } + + Clear-Host + + ($lang.GetEnumerator() | Where-Object { $_.Name -match "setupMsg[0-9]*" } | Sort-Object Name) | ForEach-Object { + Write-Warning -Message "$($_.value)" + } + + Invoke-Settings -Filename $GlobalVariables -GB $true + Foreach ($plugin in $vCheckPlugins) { + Invoke-Settings -Filename $plugin.Fullname + } } ## Include GlobalVariables and validate settings (at the moment just check they exist) . $GlobalVariables -$vcvars = @("SetupWizard" , "reportHeader" , "SMTPSRV" , "EmailFrom" , "EmailTo" , "EmailSubject", "DisplaytoScreen" , "SendEmail" , "SendAttachment", "TimeToRun" , "PluginSeconds" , "Style" , "Date") -foreach($vcvar in $vcvars) { - if (!($(Get-Variable -Name "$vcvar" -Erroraction 'SilentlyContinue'))) { - Write-Error ($lang.varUndefined -f $vcvar) - } +$vcvars = @("SetupWizard", "reportHeader", "SMTPSRV", "EmailFrom", "EmailTo", "EmailSubject", "DisplaytoScreen", "SendEmail", "SendAttachment", "TimeToRun", "PluginSeconds", "Style", "Date") +foreach ($vcvar in $vcvars) { + if (!($(Get-Variable -Name "$vcvar" -Erroraction 'SilentlyContinue'))) { + Write-Error ($lang.varUndefined -f $vcvar) + } } # Create empty array of resources (i.e. Images) -$global:ReportResources = @{} +$global:ReportResources = @{ } ## Set the StylePath and include it $StylePath = $ScriptPath + "\Styles\" + $Style -if(!(Test-Path ($StylePath))) { - # The path is not valid - # Use the default style - Write-Warning "Style path ($($StylePath)) is not valid" - $StylePath = $ScriptPath + "\Styles\VMware" - Write-Warning "Using $($StylePath)" +if (!(Test-Path ($StylePath))) { + # The path is not valid + # Use the default style + Write-Warning "Style path ($($StylePath)) is not valid" + $StylePath = $ScriptPath + "\Styles\VMware" + Write-Warning "Using $($StylePath)" } # Import the Style . ("$($StylePath)\Style.ps1") +#endregion initialization + +#region scriptlogic ################################################################################ # Script logic # ################################################################################ # Start generating the report $PluginResult = @() -Write-Host $lang.pluginBegin -foreground $host.PrivateData.WarningForegroundColor -background $host.PrivateData.WarningBackgroundColor +Write-Warning -Message $lang.pluginBegin + # Loop over all enabled plugins -$p = 0 +$p = 0 $vCheckPlugins | Foreach { - $TableFormat = $null - $PluginInfo = Get-PluginID $_.Fullname - $p++ - Write-CustomOut ($lang.pluginStart -f $PluginInfo["Title"], $PluginInfo["Author"], $PluginInfo["Version"], $p, $vCheckPlugins.count) - $pluginStatus = ($lang.pluginStatus -f $p, $vCheckPlugins.count, $_.Name) - Write-Progress -ID 1 -Activity $lang.pluginActivity -Status $pluginStatus -PercentComplete (100*$p/($vCheckPlugins.count)) - $TTR = [math]::round((Measure-Command {$Details = . $_.FullName}).TotalSeconds, 2) - - Write-CustomOut ($lang.pluginEnd -f $PluginInfo["Title"], $PluginInfo["Author"], $PluginInfo["Version"], $p, $vCheckPlugins.count) - # Do a replacement for {count} for number of items returned in $header - $Header = $Header -replace "\[count\]", $Details.count - - $PluginResult += New-Object PSObject -Property @{"Title" = $Title; - "Author" = $PluginInfo["Author"]; - "Version" = $PluginInfo["Version"]; - "Details" = $Details; - "Display" = $Display; - "TableFormat" = $TableFormat; - "Header" = $Header; - "Comments" = $Comments; - "TimeToRun" = $TTR; } + $TableFormat = $null + $PluginInfo = Get-PluginID $_.Fullname + $p++ + Write-CustomOut ($lang.pluginStart -f $PluginInfo["Title"], $PluginInfo["Author"], $PluginInfo["Version"], $p, $vCheckPlugins.count) + $pluginStatus = ($lang.pluginStatus -f $p, $vCheckPlugins.count, $_.Name) + Write-Progress -ID 1 -Activity $lang.pluginActivity -Status $pluginStatus -PercentComplete (100 * $p/($vCheckPlugins.count)) + $TTR = [math]::round((Measure-Command { $Details = . $_.FullName }).TotalSeconds, 2) + + Write-CustomOut ($lang.pluginEnd -f $PluginInfo["Title"], $PluginInfo["Author"], $PluginInfo["Version"], $p, $vCheckPlugins.count) + # Do a replacement for {count} for number of items returned in $header + $Header = $Header -replace "\[count\]", $Details.count + + $PluginResult += New-Object PSObject -Property @{ + "Title" = $Title; + "Author" = $PluginInfo["Author"]; + "Version" = $PluginInfo["Version"]; + "Details" = $Details; + "Display" = $Display; + "TableFormat" = $TableFormat; + "Header" = $Header; + "Comments" = $Comments; + "TimeToRun" = $TTR; + } } Write-Progress -ID 1 -Activity $lang.pluginActivity -Status $lang.Complete -Completed # Add report on plugins if ($reportOnPlugins) { - $Comments = "Plugins in numerical order" - $Plugins = @() - foreach ($Plugin in (Get-ChildItem $PluginsFolder -Include *.ps1, *.ps1.disabled -Recurse)) { - $Plugins += New-Object PSObject -Property @{"Name" = (Get-PluginID $Plugin.FullName).Title; - "Enabled" = (($vCheckPlugins | Select -ExpandProperty FullName) -Contains $plugin.FullName) } - } - - if ($ListEnabledPluginsFirst) { - $Plugins = $Plugins | Sort -property @{Expression="Enabled";Descending=$true} - $Comments = "Plugins in numerical order, enabled plugins listed first" - } - - $PluginResult += New-Object PSObject -Property @{"Title" = $lang.repPRTitle; - "Author" = "vCheck"; - "Version" = $vCheckVersion; - "Details" = $Plugins; - "Display" = "Table"; - "TableFormat" = $null; - "Header" = $lang.repPRTitle; - "Comments" = $Comments; - "TimeToRun" = 0; - } + $Comments = "Plugins in numerical order" + $Plugins = @() + foreach ($Plugin in (Get-ChildItem $PluginsFolder -Include *.ps1, *.ps1.disabled -Recurse)) { + $Plugins += New-Object PSObject -Property @{ + "Name" = (Get-PluginID $Plugin.FullName).Title; + "Enabled" = (($vCheckPlugins | Select -ExpandProperty FullName) -Contains $plugin.FullName) + } + } + + if ($ListEnabledPluginsFirst) { + $Plugins = $Plugins | Sort -property @{ Expression = "Enabled"; Descending = $true } + $Comments = "Plugins in numerical order, enabled plugins listed first" + } + + $PluginResult += New-Object PSObject -Property @{ + "Title" = $lang.repPRTitle; + "Author" = "vCheck"; + "Version" = $vCheckVersion; + "Details" = $Plugins; + "Display" = "Table"; + "TableFormat" = $null; + "Header" = $lang.repPRTitle; + "Comments" = $Comments; + "TimeToRun" = 0; + } } # Add Time to Run detail for plugins - if specified in GlobalVariables.ps1 if ($TimeToRun) { - $Finished = Get-Date - $PluginResult += New-Object PSObject -Property @{"Title" = $lang.repTTRTitle; - "Author" = "vCheck"; - "Version" = $vCheckVersion; - "Details" = ($PluginResult | Where { $_.TimeToRun -gt $PluginSeconds } | Select Title, TimeToRun | Sort-Object TimeToRun -Descending); - "Display" = "List"; - "TableFormat" = $null; - "Header" = ($lang.repTime -f [math]::round(($Finished - $Date).TotalMinutes,2), ($Finished.ToLongDateString()), ($Finished.ToLongTimeString())); - "Comments" = ($lang.slowPlugins -f $PluginSeconds); - "TimeToRun" = 0; - } + $Finished = Get-Date + $PluginResult += New-Object PSObject -Property @{ + "Title" = $lang.repTTRTitle; + "Author" = "vCheck"; + "Version" = $vCheckVersion; + "Details" = ($PluginResult | Where { $_.TimeToRun -gt $PluginSeconds } | Select Title, TimeToRun | Sort-Object TimeToRun -Descending); + "Display" = "List"; + "TableFormat" = $null; + "Header" = ($lang.repTime -f [math]::round(($Finished - $Date).TotalMinutes, 2), ($Finished.ToLongDateString()), ($Finished.ToLongTimeString())); + "Comments" = ($lang.slowPlugins -f $PluginSeconds); + "TimeToRun" = 0; + } } +#endregion scriptlogic + +#region output ################################################################################ # Output # ################################################################################ # Loop over plugin results and generate HTML from style $emptyReport = $true -$p=1 -Foreach ( $pr in $PluginResult) { - If ($pr.Details) { - $emptyReport = $false - switch ($pr.Display) { - "List" { $pr.Details = Get-HTMLList $pr.Details } - "Table" { $pr.Details = Get-HTMLTable $pr.Details $pr.TableFormat } - "Chart" { $pr.Details = Get-HTMLChart "plugin$($p)" $pr.Details } - } - $pr | Add-Member -Type NoteProperty -Name pluginID -Value "plugin-$p" - $p++ - } +$p = 1 +Foreach ($pr in $PluginResult) { + If ($pr.Details) { + $emptyReport = $false + switch ($pr.Display) { + "List" { $pr.Details = Get-HTMLList $pr.Details } + "Table" { $pr.Details = Get-HTMLTable $pr.Details $pr.TableFormat } + "Chart" { $pr.Details = Get-HTMLChart "plugin$($p)" $pr.Details } + } + $pr | Add-Member -Type NoteProperty -Name pluginID -Value "plugin-$p" + $p++ + } } # Run Style replacement @@ -749,78 +820,79 @@ $MyReport = Get-ReportHTML # Set the output filename - if one is specified use it, otherwise just use temp if ($Outputpath) { - $DateHTML = Get-Date -Format "yyyyMMddHH" - $ArchiveFilePath = $Outputpath + "\Archives\" + $VIServer - if (-not (Test-Path -PathType Container $ArchiveFilePath)) { New-Item $ArchiveFilePath -type directory | Out-Null } - $Filename = $ArchiveFilePath + "\" + $VIServer + "_vCheck_" + $DateHTML + ".htm" -} -else { - $Filename = $Env:TEMP + "\" + $Server + "_vCheck_" + $Date.Day + "-" + $Date.Month + "-" + $Date.Year + ".htm" + $DateHTML = Get-Date -Format "yyyyMMddHH" + $ArchiveFilePath = $Outputpath + "\Archives\" + $VIServer + if (-not (Test-Path -PathType Container $ArchiveFilePath)) { New-Item $ArchiveFilePath -type directory | Out-Null } + $Filename = $ArchiveFilePath + "\" + $VIServer + "_vCheck_" + $DateHTML + ".htm" +} else { + $Filename = $Env:TEMP + "\" + $Server + "_vCheck_" + $Date.Day + "-" + $Date.Month + "-" + $Date.Year + ".htm" } # Always generate the report with embedded images $embedReport = $MyReport # Loop over all CIDs and replace them Foreach ($cid in $global:ReportResources.Keys) { - $embedReport = $embedReport -replace ("cid:{0}" -f $cid), (Get-ReportResource $cid -ReturnType "embed") + $embedReport = $embedReport -replace ("cid:{0}" -f $cid), (Get-ReportResource $cid -ReturnType "embed") } $embedReport | Out-File -encoding ASCII -filepath $Filename # Display to screen if ($DisplayToScreen -and (!($emptyReport -and !$DisplayReportEvenIfEmpty))) { - Write-CustomOut $lang.HTMLdisp - Invoke-Item $Filename + Write-CustomOut $lang.HTMLdisp + Invoke-Item $Filename } # Generate email if ($SendEmail -and (!($emptyReport -and !$EmailReportEvenIfEmpty))) { - Write-CustomOut $lang.emailSend - $msg = New-Object System.Net.Mail.MailMessage ($EmailFrom,$EmailTo) - # If CC address specified, add - If ($EmailCc -ne "") { - $msg.CC.Add($EmailCc) - } - $msg.subject = $EmailSubject - - # if send attachment, just send plaintext email with HTML report attached - If ($SendAttachment) { - $msg.Body = $lang.emailAtch - $attachment = new-object System.Net.Mail.Attachment $Filename - $msg.Attachments.Add($attachment) - } - # Otherwise send the HTML email - else { - $msg.IsBodyHtml = $true; - $html = [System.Net.Mail.AlternateView]::CreateAlternateViewFromString($MyReport,$null,'text/html') - $msg.AlternateViews.Add($html) - - # Loop over all CIDs and replace them - Foreach ($cid in $global:ReportResources.Keys) { - if ($global:ReportResources[$cid].Uses -gt 0) { - $lr = (Get-ReportResource $cid -ReturnType "linkedresource") - $html.LinkedResources.Add($lr); - } - } - } - # Send the email - $smtpClient = New-Object System.Net.Mail.SmtpClient - - # Find the VI Server and port from the global settings file - $smtpClient.Host = ($SMTPSRV -Split ":")[0] - if (($server -split ":")[1]) { - $smtpClient.Port = ($server -split ":")[1] - } - - if ($EmailSSL -eq $true) { - $smtpClient.EnableSsl = $true - } - $smtpClient.UseDefaultCredentials = $true; - $smtpClient.Send($msg) - If ($SendAttachment) { $attachment.Dispose() } - $msg.Dispose() + Write-CustomOut $lang.emailSend + $msg = New-Object System.Net.Mail.MailMessage ($EmailFrom, $EmailTo) + # If CC address specified, add + If ($EmailCc -ne "") { + $msg.CC.Add($EmailCc) + } + $msg.subject = $EmailSubject + + # if send attachment, just send plaintext email with HTML report attached + If ($SendAttachment) { + $msg.Body = $lang.emailAtch + $attachment = new-object System.Net.Mail.Attachment $Filename + $msg.Attachments.Add($attachment) + } + # Otherwise send the HTML email + else { + $msg.IsBodyHtml = $true; + $html = [System.Net.Mail.AlternateView]::CreateAlternateViewFromString($MyReport, $null, 'text/html') + $msg.AlternateViews.Add($html) + + # Loop over all CIDs and replace them + Foreach ($cid in $global:ReportResources.Keys) { + if ($global:ReportResources[$cid].Uses -gt 0) { + $lr = (Get-ReportResource $cid -ReturnType "linkedresource") + $html.LinkedResources.Add($lr); + } + } + } + # Send the email + $smtpClient = New-Object System.Net.Mail.SmtpClient + + # Find the VI Server and port from the global settings file + $smtpClient.Host = ($SMTPSRV -Split ":")[0] + if (($server -split ":")[1]) { + $smtpClient.Port = ($server -split ":")[1] + } + + if ($EmailSSL -eq $true) { + $smtpClient.EnableSsl = $true + } + $smtpClient.UseDefaultCredentials = $true; + $smtpClient.Send($msg) + If ($SendAttachment) { $attachment.Dispose() } + $msg.Dispose() } # Run EndScript once everything else is complete if (Test-Path ($ScriptPath + "\EndScript.ps1")) { - . ($ScriptPath + "\EndScript.ps1") + . ($ScriptPath + "\EndScript.ps1") } + +#endregion output \ No newline at end of file From 2922e2205aae49cdb94d02293f111a7e3f0ed5bf Mon Sep 17 00:00:00 2001 From: Kevin Kirkpatrick Date: Wed, 29 Apr 2015 01:28:52 -0400 Subject: [PATCH 06/50] prepare for merge --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index f5ddc151..f10451b5 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,6 @@ ![Alt text](http://www.virtu-al.net/wp-content/uploads/2014/02/vCheck619.jpg "vCheck Sample") -<<<<<<< HEAD |Navigation| |-----------------| |[About](#About)| From 075b95605a2e159da77239eab6e3e35fb10378f3 Mon Sep 17 00:00:00 2001 From: Kevin Kirkpatrick Date: Wed, 29 Apr 2015 11:14:13 -0400 Subject: [PATCH 07/50] fixed link ref for Waffle badge --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index f10451b5..e8cb27ed 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
# vCheck Daily Report for vSphere -[![Stories in Ready](http://badge.waffle.io/alanrenouf/vCheck-vSphere.png)](http://waffle.io/alanrenouf/vCheck-vSphere)      [![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) +[![Stories in Ready](http://badge.waffle.io/alanrenouf/vCheck-vSphere.png?label=Ready)](http://waffle.io/alanrenouf/vCheck-vSphere)      [![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) ![Alt text](http://www.virtu-al.net/wp-content/uploads/2014/02/vCheck619.jpg "vCheck Sample") @@ -15,7 +15,6 @@ |[Styles](#Styles)| |[Jobs & Settings](#JobsSettings)| |[More Info](#More)| -======= # About From 532696cf52f3476fa9b582e6c717c42593b4ccfc Mon Sep 17 00:00:00 2001 From: Sneddo Date: Mon, 4 May 2015 15:47:24 +1000 Subject: [PATCH 08/50] Fix Issue #292 Added type check- slots are only valid for failover hosts. --- Plugins/20 Cluster/18 Cluster Slot Sizes.ps1 | 31 ++++++++++---------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/Plugins/20 Cluster/18 Cluster Slot Sizes.ps1 b/Plugins/20 Cluster/18 Cluster Slot Sizes.ps1 index 1fce7645..17193964 100644 --- a/Plugins/20 Cluster/18 Cluster Slot Sizes.ps1 +++ b/Plugins/20 Cluster/18 Cluster Slot Sizes.ps1 @@ -2,21 +2,22 @@ # Number of slots available in a cluster $numslots = 10 # End of Settings - + If ($vSphere){ - $SlotInfo = @() - Foreach ($Cluster in ($Clusters)){ - If ($Cluster.ExtensionData.Configuration.DasConfig.Enabled -eq $true){ - $SlotDetails = $Cluster.ExtensionData.RetrieveDasAdvancedRuntimeInfo() - $Details = "" | Select Cluster, TotalSlots, UsedSlots, AvailableSlots - $Details.Cluster = $Cluster.Name - $Details.TotalSlots = $SlotDetails.TotalSlots - $Details.UsedSlots = $SlotDetails.UsedSlots - $Details.AvailableSlots = $SlotDetails.UnreservedSlots - $SlotInfo += $Details - } - } - $SlotCHK = @($SlotInfo | Where { $_.AvailableSlots -lt $numslots}) + $SlotInfo = @() + Foreach ($Cluster in ($Clusters)){ + If ($Cluster.ExtensionData.Configuration.DasConfig.Enabled -eq $true -and + $Cluster.ExtensionData.Configuration.DasConfig.AdmissionControlPolicy.getType() -eq [VMware.Vim.ClusterFailoverLevelAdmissionControlPolicy]){ + $SlotDetails = $Cluster.ExtensionData.RetrieveDasAdvancedRuntimeInfo() + $Details = "" | Select Cluster, TotalSlots, UsedSlots, AvailableSlots + $Details.Cluster = $Cluster.Name + $Details.TotalSlots = $SlotDetails.TotalSlots + $Details.UsedSlots = $SlotDetails.UsedSlots + $Details.AvailableSlots = $SlotDetails.UnreservedSlots + $SlotInfo += $Details + } + } + $SlotCHK = @($SlotInfo | Where { $_.AvailableSlots -lt $numslots}) } $SlotCHK @@ -26,5 +27,5 @@ $Header = "Clusters with less than $numslots Slot Sizes : $(@($SlotCHK).count)" $Comments = "Slot sizes in the below cluster are less than is specified, this may cause issues with creating new VMs, for more information click here: Yellow-Bricks HA Deep Dive" $Display = "Table" $Author = "Alan Renouf" -$PluginVersion = 1.1 +$PluginVersion = 1.2 $PluginCategory = "vSphere" From 78f47e4b1a54caa2ddd5a6572176fa831f67c559 Mon Sep 17 00:00:00 2001 From: Sneddo Date: Tue, 5 May 2015 07:45:43 +1000 Subject: [PATCH 09/50] Update 18 Cluster Slot Sizes.ps1 Casting to a proper PSObject --- Plugins/20 Cluster/18 Cluster Slot Sizes.ps1 | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Plugins/20 Cluster/18 Cluster Slot Sizes.ps1 b/Plugins/20 Cluster/18 Cluster Slot Sizes.ps1 index 17193964..acd3ef97 100644 --- a/Plugins/20 Cluster/18 Cluster Slot Sizes.ps1 +++ b/Plugins/20 Cluster/18 Cluster Slot Sizes.ps1 @@ -9,11 +9,13 @@ If ($vSphere){ If ($Cluster.ExtensionData.Configuration.DasConfig.Enabled -eq $true -and $Cluster.ExtensionData.Configuration.DasConfig.AdmissionControlPolicy.getType() -eq [VMware.Vim.ClusterFailoverLevelAdmissionControlPolicy]){ $SlotDetails = $Cluster.ExtensionData.RetrieveDasAdvancedRuntimeInfo() - $Details = "" | Select Cluster, TotalSlots, UsedSlots, AvailableSlots - $Details.Cluster = $Cluster.Name - $Details.TotalSlots = $SlotDetails.TotalSlots - $Details.UsedSlots = $SlotDetails.UsedSlots - $Details.AvailableSlots = $SlotDetails.UnreservedSlots + + $Details = [PSCustomObject] @{ + Cluster = $Cluster.Name + TotalSlots = $SlotDetails.TotalSlots + UsedSlots = $SlotDetails.UsedSlots + AvailableSlots = $SlotDetails.UnreservedSlots + } $SlotInfo += $Details } } From e3bf6344530725df9f59eff5d865600efc82e232 Mon Sep 17 00:00:00 2001 From: Sneddo Date: Tue, 5 May 2015 13:47:34 +1000 Subject: [PATCH 10/50] Update 71 Capacity Planning.ps1 - [X] Added Datacenter column - Issue #342 - [X] Added condition for "empty" clusters - Issue #234 - [X] Convert to use proper PSObject --- Plugins/20 Cluster/71 Capacity Planning.ps1 | 102 +++++++++++--------- 1 file changed, 54 insertions(+), 48 deletions(-) diff --git a/Plugins/20 Cluster/71 Capacity Planning.ps1 b/Plugins/20 Cluster/71 Capacity Planning.ps1 index d4451076..cba1c072 100644 --- a/Plugins/20 Cluster/71 Capacity Planning.ps1 +++ b/Plugins/20 Cluster/71 Capacity Planning.ps1 @@ -6,61 +6,67 @@ $limitResourceMEMClusNonHA = 0.6 # End of Settings $capacityinfo = @() -foreach ($cluv in ($clusviews | Sort Name)) { - $clucapacity = "" |Select ClusterName, "Estimated Num VM Left (CPU)", "Estimated Num VM Left (MEM)", "vCPU/pCPU ratio", "VM/VMHost ratio" - if ( $cluv.Configuration.DasConfig.Enabled -eq $true ) { - $DasRealCpuCapacity = $cluv.Summary.EffectiveCpu - (($cluv.Summary.EffectiveCpu * $cluv.Configuration.DasConfig.FailoverLevel)/$cluv.Summary.NumHosts) - $DasRealMemCapacity = $cluv.Summary.EffectiveMemory - (($cluv.Summary.EffectiveMemory * $cluv.Configuration.DasConfig.FailoverLevel)/$cluv.Summary.NumHosts) - } else { - $DasRealCpuCapacity = $cluv.Summary.EffectiveCpu * $limitResourceCPUClusNonHA - $DasRealMemCapacity = $cluv.Summary.EffectiveMemory * $limitResourceMEMClusNonHA - } - - $cluvmlist = $VM | where { $cluv.Host -contains $_.VMHost.Id } - - #CPU - $CluCpuUsage = (get-view $cluv.ResourcePool).Summary.runtime.cpu.OverallUsage - $CluCpuUsageAvg = $CluCpuUsage - if ($cluvmlist -and $cluv.host -and $CluCpuUsageAvg -gt 0){ - $VmCpuAverage = $CluCpuUsageAvg/($cluvmlist.count) - $CpuVmLeft = [math]::round(($DasRealCpuCapacity-$CluCpuUsageAvg)/$VmCpuAverage,0) - } - elseif ($CluCpuUsageAvg -eq 0) {$CpuVmLeft = "N/A"} - else {$CpuVmLeft = 0} - - - #MEM - $CluMemUsage = (get-view $cluv.ResourcePool).Summary.runtime.memory.OverallUsage - $CluMemUsageAvg = $CluMemUsage/1MB - if ($cluvmlist -and $cluv.host -and $CluMemUsageAvg -gt 100){ - $VmMemAverage = $CluMemUsageAvg/(Get-Cluster $cluv.name|Get-VM).count - $MemVmLeft = [math]::round(($DasRealMemCapacity-$CluMemUsageAvg)/$VmMemAverage,0) - } - elseif ($CluMemUsageAvg -lt 100) {$CluMemUsageAvg = "N/A"} - else{$MemVmLeft = 0} - - $clucapacity.ClusterName = $cluv.name - $clucapacity."Estimated Num VM Left (CPU)" = $CpuVmLeft - $clucapacity."Estimated Num VM Left (MEM)" = $MemVmLeft - if ($cluvmlist){ - $vCPUpCPUratio = [math]::round(($cluvmlist|Measure-Object -Sum -Property NumCpu).sum / $cluv.summary.NumCpuThreads,0) - $clucapacity."vCPU/pCPU ratio" = $vCPUpCPUratio - } - else {$clucapacity."vCPU/pCPU ratio" = "0 (vCPU < pCPU)"} - if ($cluvmlist){ - $clucapacity."VM/VMHost ratio" = [math]::round(($cluvmlist).count/$cluv.Summary.NumHosts,0) - } - else {$clucapacity."VM/VMHost ratio" = 0} +foreach ($cluv in ($clusviews | Where {$_.Summary.NumHosts -gt 0 } | Sort Name)) { + + if ( $cluv.Configuration.DasConfig.Enabled -eq $true ) { + $DasRealCpuCapacity = $cluv.Summary.EffectiveCpu - (($cluv.Summary.EffectiveCpu * $cluv.Configuration.DasConfig.FailoverLevel)/$cluv.Summary.NumHosts) + $DasRealMemCapacity = $cluv.Summary.EffectiveMemory - (($cluv.Summary.EffectiveMemory * $cluv.Configuration.DasConfig.FailoverLevel)/$cluv.Summary.NumHosts) + } else { + $DasRealCpuCapacity = $cluv.Summary.EffectiveCpu * $limitResourceCPUClusNonHA + $DasRealMemCapacity = $cluv.Summary.EffectiveMemory * $limitResourceMEMClusNonHA + } + + $cluvmlist = $VM | where { $cluv.Host -contains $_.VMHost.Id } - $capacityinfo += $clucapacity + #CPU + $CluCpuUsage = (get-view $cluv.ResourcePool).Summary.runtime.cpu.OverallUsage + $CluCpuUsageAvg = $CluCpuUsage + if ($cluvmlist -and $cluv.host -and $CluCpuUsageAvg -gt 0){ + $VmCpuAverage = $CluCpuUsageAvg/($cluvmlist.count) + $CpuVmLeft = [math]::round(($DasRealCpuCapacity-$CluCpuUsageAvg)/$VmCpuAverage,0) + } + elseif ($CluCpuUsageAvg -eq 0) {$CpuVmLeft = "N/A"} + else {$CpuVmLeft = 0} + + #MEM + $CluMemUsage = (get-view $cluv.ResourcePool).Summary.runtime.memory.OverallUsage + $CluMemUsageAvg = $CluMemUsage/1MB + if ($cluvmlist -and $cluv.host -and $CluMemUsageAvg -gt 100){ + $VmMemAverage = $CluMemUsageAvg/(Get-Cluster $cluv.name|Get-VM).count + $MemVmLeft = [math]::round(($DasRealMemCapacity-$CluMemUsageAvg)/$VmMemAverage,0) + } + elseif ($CluMemUsageAvg -lt 100) {$CluMemUsageAvg = "N/A"} + else{$MemVmLeft = 0} + + # vCPU to pCPU ratio + if ($cluvmlist){ + $vCPUpCPUratio = [math]::round(($cluvmlist|Measure-Object -Sum -Property NumCpu).sum / $cluv.summary.NumCpuThreads,0) + } + else { $vCPUpCPUratio = "0 (vCPU < pCPU)"} + + if ($cluvmlist){ + $VMVMHostRatio = [math]::round(($cluvmlist).count/$cluv.Summary.NumHosts,0) + } + else {$VMVMHostRatio = 0} + + $clucapacity = [PSCustomObject] @{ + Datacenter = (Get-VIObjectByView -MoRef $cluv.Parent).Parent.Name + ClusterName = $cluv.name + "Estimated Num VM Left (CPU)" = $CpuVmLeft + "Estimated Num VM Left (MEM)" = $MemVmLeft + "vCPU/pCPU ratio" = $vCPUpCPUratio + "VM/VMHost ratio" = $VMVMHostRatio + } + + $capacityinfo += $clucapacity } -$capacityinfo | Sort ClusterName +$capacityinfo | Sort Datacenter, ClusterName $Title = "QuickStats Capacity Planning" $Header = "QuickStats Capacity Planning" $Comments = "The following gives brief capacity information for each cluster based on QuickStats CPU/Mem usage and counting for HA failover requirements" $Display = "Table" $Author = "Raphael Schitz, Frederic Martin" -$PluginVersion = 1.4 +$PluginVersion = 1.5 $PluginCategory = "vSphere" From fc554b81ad3ca95686461c4c4827eebaeaca6cd9 Mon Sep 17 00:00:00 2001 From: Sneddo Date: Wed, 6 May 2015 08:22:48 +1000 Subject: [PATCH 11/50] Fixed incorrect cmdlet Get-VIObjectByVIView not Get-VIObjectByView... --- Plugins/20 Cluster/71 Capacity Planning.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/20 Cluster/71 Capacity Planning.ps1 b/Plugins/20 Cluster/71 Capacity Planning.ps1 index cba1c072..ab7ca461 100644 --- a/Plugins/20 Cluster/71 Capacity Planning.ps1 +++ b/Plugins/20 Cluster/71 Capacity Planning.ps1 @@ -50,7 +50,7 @@ foreach ($cluv in ($clusviews | Where {$_.Summary.NumHosts -gt 0 } | Sort Name)) else {$VMVMHostRatio = 0} $clucapacity = [PSCustomObject] @{ - Datacenter = (Get-VIObjectByView -MoRef $cluv.Parent).Parent.Name + Datacenter = (Get-VIObjectByVIView -MoRef $cluv.Parent).Parent.Name ClusterName = $cluv.name "Estimated Num VM Left (CPU)" = $CpuVmLeft "Estimated Num VM Left (MEM)" = $MemVmLeft From b6116c91c22039250b92a68b842a70a37dba0b2c Mon Sep 17 00:00:00 2001 From: Sneddo Date: Tue, 19 May 2015 14:04:42 +1000 Subject: [PATCH 12/50] Delete EndScript .ps1 --- EndScript .ps1 | 1 - 1 file changed, 1 deletion(-) delete mode 100644 EndScript .ps1 diff --git a/EndScript .ps1 b/EndScript .ps1 deleted file mode 100644 index 25a15ecb..00000000 --- a/EndScript .ps1 +++ /dev/null @@ -1 +0,0 @@ -# Everything in this script will run at the end of vCheck From f3f0af755c36a60c2994f4e9a9f89b09753ec492 Mon Sep 17 00:00:00 2001 From: Sneddo Date: Tue, 19 May 2015 14:05:24 +1000 Subject: [PATCH 13/50] Delete EndScript .ps1 Duplicate file --- EndScript .ps1 | 1 - 1 file changed, 1 deletion(-) delete mode 100644 EndScript .ps1 diff --git a/EndScript .ps1 b/EndScript .ps1 deleted file mode 100644 index 25a15ecb..00000000 --- a/EndScript .ps1 +++ /dev/null @@ -1 +0,0 @@ -# Everything in this script will run at the end of vCheck From 25a1deaf325cdf5e4b6837123bd764a2f7edce81 Mon Sep 17 00:00:00 2001 From: Sneddo Date: Tue, 19 May 2015 14:21:46 +1000 Subject: [PATCH 14/50] Fix #385 If $Outputpath is specified, we should use this path. Changed the default to $env:temp to simplify the code a little. Set date format to ISO8601 as well. --- vCheck.ps1 | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/vCheck.ps1 b/vCheck.ps1 index 51c59a6b..ff7d129f 100644 --- a/vCheck.ps1 +++ b/vCheck.ps1 @@ -59,7 +59,7 @@ param ( [Switch]$config, [ValidateScript({ Test-Path $_ -PathType 'Container' })] - [string]$Outputpath, + [string]$Outputpath=$Env:TEMP, [ValidateScript({ Test-Path $_ -PathType 'Leaf' })] [string]$job @@ -818,15 +818,9 @@ Foreach ($pr in $PluginResult) { # Run Style replacement $MyReport = Get-ReportHTML -# Set the output filename - if one is specified use it, otherwise just use temp -if ($Outputpath) { - $DateHTML = Get-Date -Format "yyyyMMddHH" - $ArchiveFilePath = $Outputpath + "\Archives\" + $VIServer - if (-not (Test-Path -PathType Container $ArchiveFilePath)) { New-Item $ArchiveFilePath -type directory | Out-Null } - $Filename = $ArchiveFilePath + "\" + $VIServer + "_vCheck_" + $DateHTML + ".htm" -} else { - $Filename = $Env:TEMP + "\" + $Server + "_vCheck_" + $Date.Day + "-" + $Date.Month + "-" + $Date.Year + ".htm" -} +# Set the output filename +if (-not (Test-Path -PathType Container $Outputpath)) { New-Item $Outputpath -type directory | Out-Null } +$Filename = ("{0}\{1}_vCheck_{2}.htm" -f $Outputpath, $Server, (Get-Date -Format "yyyyMMdd_HHmm")) # Always generate the report with embedded images $embedReport = $MyReport @@ -895,4 +889,4 @@ if (Test-Path ($ScriptPath + "\EndScript.ps1")) { . ($ScriptPath + "\EndScript.ps1") } -#endregion output \ No newline at end of file +#endregion output From e9f7d2d76caefaba272f37abe25d0348aaf569f7 Mon Sep 17 00:00:00 2001 From: Sneddo Date: Thu, 21 May 2015 13:02:05 +1000 Subject: [PATCH 15/50] Fix #384 This line appears to cause issues in certain circumstances. From memory this line was added to combat a style problem prior to the style changes and does not appear to have any impact removing it. --- vCheck.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/vCheck.ps1 b/vCheck.ps1 index ff7d129f..7eca3dcd 100644 --- a/vCheck.ps1 +++ b/vCheck.ps1 @@ -260,7 +260,6 @@ Function Get-HTMLTable { # Use an XML object for ease of use $XMLTable = [xml]($content | ConvertTo-Html -Fragment) - $XMLTable.table.RemoveChild($XMLTable.table.colgroup) | out-null $XMLTable.table.SetAttribute("width", "100%") # If format rules are specified From ae9c5b9cbaffbf486f96d8d5f3f09fa175880430 Mon Sep 17 00:00:00 2001 From: Tim Williams Date: Mon, 22 Jun 2015 10:52:11 +0800 Subject: [PATCH 16/50] Create 200 VMs on ephemeral portgroup.ps1 Report all VMs attached to vDS Portgroups with ephemeral binding configured --- .../60 VM/200 VMs on ephemeral portgroup.ps1 | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 Plugins/60 VM/200 VMs on ephemeral portgroup.ps1 diff --git a/Plugins/60 VM/200 VMs on ephemeral portgroup.ps1 b/Plugins/60 VM/200 VMs on ephemeral portgroup.ps1 new file mode 100644 index 00000000..f19117c1 --- /dev/null +++ b/Plugins/60 VM/200 VMs on ephemeral portgroup.ps1 @@ -0,0 +1,21 @@ +$EphemeralReport = @() + +$EphemeralPG = Get-VDSwitch | Get-VDPortgroup | where {$_.PortBinding -eq "Ephemeral"} +$vNetworkAdapter = $VM | Get-NetworkAdapter | where {$_.NetworkName -contains $EphemeralPG} + +ForEach ($v in $vNetworkAdapter) + { + $vDSSummary = "" | Select VMName, Portgroup + $vDSSummary.Portgroup = $v.NetworkName + $vDSSummary.VMName = $v.parent + $EphemeralReport += $vDSSummary + } +$EphemeralReport + +Title = "VMs on Ephemeral Portgroup" +$Header = "VMs on Ephemeral Portgroup: $(@($EphemeralReport).Count)" +$Comments = "...." +$Display = "Table" +$Author = "Tim Williams" +$PluginVersion = 1.0 +$PluginCategory = "vSphere" From 826d759ee0bf28657ec6da6b9d7ab7cd4a2c23a0 Mon Sep 17 00:00:00 2001 From: D'Haese Willem Date: Mon, 22 Jun 2015 10:26:02 +0200 Subject: [PATCH 17/50] Check if PowerCLI module is already added if (!(get-module -name VMware.VimAutomation.Core -erroraction silentlycontinue)) --- .../00 Initialize/00 Connection Plugin for vCenter.ps1 | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 b/Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 index 32356e3e..f785da53 100644 --- a/Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 +++ b/Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 @@ -50,9 +50,11 @@ else # Path to credentials file which is automatically created if needed $Credfile = $ScriptPath + "\Windowscreds.xml" -# Adding PowerCLI core snapin -if (!(get-pssnapin -name VMware.VimAutomation.Core -erroraction silentlycontinue)) { - add-pssnapin VMware.VimAutomation.Core +# Adding PowerCLI core snapin, also check if powerCLI module is alsready added +if (!(get-module -name VMware.VimAutomation.Core -erroraction silentlycontinue)) { + if (!(get-pssnapin -name VMware.VimAutomation.Core -erroraction silentlycontinue)) { + add-pssnapin VMware.VimAutomation.Core -erroraction silentlycontinue + } } $OpenConnection = $global:DefaultVIServers | where { $_.Name -eq $VIServer } From 8932608b42bbd58e0202b16112f927afd13d1138 Mon Sep 17 00:00:00 2001 From: Dan Rowe Date: Thu, 27 Aug 2015 14:28:43 -0500 Subject: [PATCH 18/50] Update 79 Find VMs in Uncontrolled Snapshot Mode.ps1 --- ...Find VMs in Uncontrolled Snapshot Mode.ps1 | 48 ++++++++----------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/Plugins/60 VM/79 Find VMs in Uncontrolled Snapshot Mode.ps1 b/Plugins/60 VM/79 Find VMs in Uncontrolled Snapshot Mode.ps1 index 926b8167..4fbdbc95 100644 --- a/Plugins/60 VM/79 Find VMs in Uncontrolled Snapshot Mode.ps1 +++ b/Plugins/60 VM/79 Find VMs in Uncontrolled Snapshot Mode.ps1 @@ -2,38 +2,32 @@ # End of Settings $VMFolder = @() -foreach ($eachVM in $FullVM) { - if (!$eachVM.snapshot) { # Only process VMs without snapshots - $eachVM.Summary.Config.VmPathName -match '^\[([^\]]+)\] ([^/]+)' > $null - $Datastore = $matches[1] - $VMPath = $matches[2] - $DC = Get-Datacenter -VM $eachVM.Name - if ($DC.ParentFolder.Parent) { #Check if Datacenter has a parent folder - $DCPath = $DC.ParentFolder.Name - } - else { - $DCPath = '' - } - $gciloc = (Get-ChildItem vmstores: | Select -first 1).Name - $fileList = Get-ChildItem "vmstores:\$gciloc\$DCPath\$DC\$Datastore\$VMPath" - foreach ($file in $fileList) { - if ($file.Name -like '*delta.vmdk*' -or $file -like '-*-flat.vmdk') { - $Details = "" | Select-Object VM, Datacenter, Path - $Details.VM = $eachVM.Name - $Details.Datacenter = $DC.Name - $Details.Path = $Datastore + '/' + $VMPath + '/' + $file.Name - $VMFolder += $Details - break - } +foreach ($eachDS in $Datastores) { + $FilePath = $eachDS.DatastoreBrowserPath + '\*\*delta.vmdk*' + $fileList = Get-ChildItem -Path "$FilePath" | Select Name, FolderPath, FullName + $FilePath = $eachDS.DatastoreBrowserPath + '\*\-*-flat.vmdk' + $fileList += Get-ChildItem -Path "$FilePath" | Select Name, FolderPath, FullName + + foreach ($vmFile in $filelist | sort FolderPath) { + $vmFile.FolderPath -match '^\[([^\]]+)\] ([^/]+)' > $null + $VMName = $matches[2] + $eachVM = $FullVM | where {$_.Name -eq $VMName} + if (!$eachVM.snapshot) { # Only process VMs without snapshots + $Details = "" | Select-Object VM, Datacenter, Path + $Details.VM = $eachVM.Name + $Details.Datacenter = $eachDS.Datacenter + $Details.Path = $vmFile.FullName + $VMFolder += $Details + } } - } } -$VMFolder +$Results = $VMFolder | sort VM +$Results $Title = "VMs in uncontrolled snapshot mode" $Header = "VMs in uncontrolled snapshot mode: $(@($Result).Count)" $Comments = "The following VMs are in snapshot mode, but vCenter isn't aware of it. See http://kb.vmware.com/kb/1002310" $Display = "Table" -$Author = "Rick Glover, Matthias Koehler" -$PluginVersion = 1.3 +$Author = "Rick Glover, Matthias Koehler, Dan Rowe" +$PluginVersion = 1.4 $PluginCategory = "vSphere" From f77f05921506bc7c51352241caa894a938f97b0d Mon Sep 17 00:00:00 2001 From: Sneddo Date: Mon, 5 Oct 2015 09:20:55 +1100 Subject: [PATCH 19/50] Updated with changes from pull #391 --- Plugins/20 Cluster/75 DRS Rules.ps1 | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Plugins/20 Cluster/75 DRS Rules.ps1 b/Plugins/20 Cluster/75 DRS Rules.ps1 index 20f964fd..7de1cd7f 100644 --- a/Plugins/20 Cluster/75 DRS Rules.ps1 +++ b/Plugins/20 Cluster/75 DRS Rules.ps1 @@ -5,10 +5,13 @@ $ShowVMAffinity = $true $ShowVMAntiAffinity = $true # Display HOSTaffinity rules? $ShowHostAffinity = $true +# Set DRS Rule name exception (regex) +$excludeName = "ExcludeMe" # End of Settings # Changelog ## 1.0 : Initial Version +## 1.1 : Add filter option (Pull #391 - @mtehonica) # Add pretty icons Add-ReportResource -cid "Error" -Type "SystemIcons" -ResourceData "Error" @@ -21,7 +24,7 @@ if ($ShowVMAntiAffinity) { $Types += "VMAntiAffinity"} if ($ShowHostAffinity) { $Types += "VMHostAffinity"} $Clusters | Foreach { - Get-DrsRule -Cluster $_ -Type $Types | + Get-DrsRule -Cluster $_ -Type $Types | Where { $_.Name -notmatch $excludeName } | Select Cluster, Enabled, Name, Type, @{N="VM";E={(Get-View $_.VMIDS | Select -ExpandProperty Name) -join "
"}}, @{N="Rule Host";E={(Get-View $_.AffineHostIds | Select -ExpandProperty Name) -join "
" }}, @{N="Running on";E={(Get-View (Get-View $_.VMIDS | %{$_.Runtime.Host}) | Select -ExpandProperty Name) -join "
"}} @@ -32,7 +35,7 @@ $Header = "DRS Rules" $Comments = ("Contains all DRS rules defined in this vCenter - {0}" -f ($types -join ",")) $Display = "Table" $Author = "John Sneddon" -$PluginVersion = 1.0 +$PluginVersion = 1.1 $PluginCategory = "vSphere" # Table formatting rules From 3db2eb3abb1552702cf88e7b432378d515639c56 Mon Sep 17 00:00:00 2001 From: Chris Monahan Date: Sun, 15 Nov 2015 23:58:50 -0500 Subject: [PATCH 20/50] Update 01 General Information.ps1 [16:11:13] ..start calculating General Information by Alan Renouf, Frederic Martin v1.2 [2 of 99] The variable '$MigrationQuery2' cannot be retrieved because it has not been set. At E:\vCheckLatestTesting\Plugins\00 Initialize\01 General Information.ps1:45 char:96 + ... rations for last $($SDRSMigrateAge) Days" (@($MigrationQuery2 | Where ... + ~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (MigrationQuery2:String) [], RuntimeException + FullyQualifiedErrorId : VariableIsUndefined [16:11:15] ..finished calculating General Information by Alan Renouf, Frederic Martin v1.2 [2 of 99] ========== In "01 General Information.ps1" line 19 $MigrationQuery2 could have zero results and not be created. Added test that $MigrationQuery2 exists. line 44 -- if ( ($MigrationQuery2) -and ($SDRSMigrateAge -gt 0) ) { --- Plugins/00 Initialize/01 General Information.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/00 Initialize/01 General Information.ps1 b/Plugins/00 Initialize/01 General Information.ps1 index 48e3b830..cc14d78e 100644 --- a/Plugins/00 Initialize/01 General Information.ps1 +++ b/Plugins/00 Initialize/01 General Information.ps1 @@ -41,7 +41,7 @@ if ($DRSMigrateAge -gt 0) { # Adding vSphere 5 informations if ($VIVersion -ge 5) { $Info | Add-Member Noteproperty "Number of Datastore Clusters" $(@($DatastoreClustersView).Count) - if ($SDRSMigrateAge -gt 0) { + if (($MigrationQuery2) -and ($SDRSMigrateAge -gt 0)) { $Info | Add-Member Noteproperty "Storage DRS Migrations for last $($SDRSMigrateAge) Days" (@($MigrationQuery2 | Where {$_.FullFormattedMessage -imatch "(Storage vMotion){1}.*(DRS){1}"}).Count) } } From b93f1e97a53e658878a5c06bba6efe3cd7d5e355 Mon Sep 17 00:00:00 2001 From: Sneddo Date: Tue, 8 Dec 2015 10:10:22 +1100 Subject: [PATCH 21/50] Fix #429 Removed string comparison and code cleanup --- Plugins/60 VM/22 Checking VM Hardware Version.ps1 | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Plugins/60 VM/22 Checking VM Hardware Version.ps1 b/Plugins/60 VM/22 Checking VM Hardware Version.ps1 index 01572285..6e34dff8 100644 --- a/Plugins/60 VM/22 Checking VM Hardware Version.ps1 +++ b/Plugins/60 VM/22 Checking VM Hardware Version.ps1 @@ -5,13 +5,12 @@ $HWVers = 8 $vmIgnore = "vShield*|dsva*" # End of Settings -$HWver = @($VM | Where-Object {$_.Name -notmatch $vmIgnore} | Select-Object Name, HWVersion | Where-Object {[INT]($_.HWVersion).ToString() -lt $HWVers}) -$HWVer +@($VM | Where-Object {$_.Name -notmatch $vmIgnore} | Select-Object Name, HWVersion | Where-Object {[INT]($_.HWVersion)-lt $HWVers}) $Title = "Checking VM Hardware Version" -$Header = "VMs with old hardware: $(@($HWVer).Count)" +$Header = "VMs with old hardware: [count]" $Comments = "The following VMs are not at the latest hardware version, you may gain performance enhancements if you convert them to the latest version" $Display = "Table" $Author = "Alan Renouf" -$PluginVersion = 1.1 +$PluginVersion = 1.2 $PluginCategory = "vSphere" From e6d1b23e6cb897cbb1cd2ce75c02f28ebae7d949 Mon Sep 17 00:00:00 2001 From: jeffgreenca Date: Tue, 8 Dec 2015 08:21:39 -0800 Subject: [PATCH 22/50] support PowerCLI 6.x The VMware.VimAutomation.Vds module does not always auto-load on PowerCLI 6.x. Added code to check for and import the module-based cmdlets for newer versions of PowerCLI --- .../80 DvPG with less than x Ports Free.ps1 | 6 +++--- Plugins/50 Network/98 vSwitch Security.ps1 | 14 +++++++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Plugins/50 Network/80 DvPG with less than x Ports Free.ps1 b/Plugins/50 Network/80 DvPG with less than x Ports Free.ps1 index 9678b8b5..c4f2f733 100644 --- a/Plugins/50 Network/80 DvPG with less than x Ports Free.ps1 +++ b/Plugins/50 Network/80 DvPG with less than x Ports Free.ps1 @@ -3,9 +3,9 @@ $DvSwitchLeft = 10 # End of Settings -if (Get-PSSnapin VMware.VimAutomation.Vds -ErrorAction SilentlyContinue) -{ - if ($vdspg = Get-VDSwitch | Sort-Object -Property Name | Get-VDPortgroup) +if ((Get-PSSnapin VMware.VimAutomation.Vds -ErrorAction SilentlyContinue) -or (Get-Module VMware.VimAutomation.Vds -ErrorAction SilentlyContinue)) +{ + if ($vdspg = Get-VDSwitch | Sort-Object -Property Name | Get-VDPortgroup) { $ImpactedDVS = @() diff --git a/Plugins/50 Network/98 vSwitch Security.ps1 b/Plugins/50 Network/98 vSwitch Security.ps1 index 3d44813f..73d4ef90 100644 --- a/Plugins/50 Network/98 vSwitch Security.ps1 +++ b/Plugins/50 Network/98 vSwitch Security.ps1 @@ -19,9 +19,17 @@ $VersionOK = $false if (((Get-PowerCLIVersion) -match "VMware vSphere PowerCLI (.*) build ([0-9]+)")) { if ([int]($Matches[2]) -ge 1012425) { $VersionOK = $true - # Add required Snap-In - if (!(Get-PSSnapin -name VMware.VimAutomation.Vds -ErrorAction SilentlyContinue)) { - Add-PSSnapin VMware.VimAutomation.Vds + if ([int]($Matches[2]) -ge 2548067) { + #PowerCLI 6+ + if(!(Get-Module -Name VMware.VimAutomation.Vds -ErrorAction SilentlyContinue)) { + Import-Module VMware.VimAutomation.Vds + } + } + else { + # Add required Snap-In + if (!(Get-PSSnapin -name VMware.VimAutomation.Vds -ErrorAction SilentlyContinue)) { + Add-PSSnapin VMware.VimAutomation.Vds + } } } } From e2605193950304af858b2aa2d60935c8a8c82e13 Mon Sep 17 00:00:00 2001 From: Sneddo Date: Fri, 4 Mar 2016 08:42:33 +1100 Subject: [PATCH 23/50] Finx incorrect variable Found in issue #436 --- vCheck.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vCheck.ps1 b/vCheck.ps1 index 7eca3dcd..afed28e0 100644 --- a/vCheck.ps1 +++ b/vCheck.ps1 @@ -870,8 +870,8 @@ if ($SendEmail -and (!($emptyReport -and !$EmailReportEvenIfEmpty))) { # Find the VI Server and port from the global settings file $smtpClient.Host = ($SMTPSRV -Split ":")[0] - if (($server -split ":")[1]) { - $smtpClient.Port = ($server -split ":")[1] + if (($SMTPSRV -split ":")[1]) { + $smtpClient.Port = ($SMTPSRV -split ":")[1] } if ($EmailSSL -eq $true) { From 60017fbdd7b37155104c18f18b35835d38c6f342 Mon Sep 17 00:00:00 2001 From: Sneddo Date: Fri, 4 Mar 2016 08:52:00 +1100 Subject: [PATCH 24/50] Fix #393 Updated with extra info --- Plugins/60 VM/13 Invalid or inaccessible VMs.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Plugins/60 VM/13 Invalid or inaccessible VMs.ps1 b/Plugins/60 VM/13 Invalid or inaccessible VMs.ps1 index 275832c1..fb00217a 100644 --- a/Plugins/60 VM/13 Invalid or inaccessible VMs.ps1 +++ b/Plugins/60 VM/13 Invalid or inaccessible VMs.ps1 @@ -1,13 +1,13 @@ # Start of Settings # End of Settings -$BlindedVM = $FullVM | Where {$_.Runtime.ConnectionState -eq "invalid" -or $_.Runtime.ConnectionState -eq "inaccessible"} | sort name |select name -$BlindedVM +$FullVM | Where {$_.Runtime.ConnectionState -eq "invalid" -or $_.Runtime.ConnectionState -eq "inaccessible"} | Sort Name | ` + Select Name, @{Name="ConnectionState";e={$_.Runtime.ConnectionState}}, @{Name="PowerState";e={$_.Runtime.PowerState}}, @{Name="IP_Address";e={$_.Guest.IpAddress}} $Title = "Invalid or inaccessible VM" -$Header = "VM invalid or inaccessible : $(@($BlindedVM).count)" +$Header = "VM invalid or inaccessible: [count]]" $Comments = "The following VMs are marked as inaccessible or invalid" $Display = "Table" $Author = "Alan Renouf" -$PluginVersion = 1.1 +$PluginVersion = 1.2 $PluginCategory = "vSphere" From 6e001397222d6fe00baffdc257a8464d4a36341a Mon Sep 17 00:00:00 2001 From: Sneddo Date: Fri, 4 Mar 2016 13:06:47 +1100 Subject: [PATCH 25/50] Added plugin for datastores with alarms disabled Fix #387 --- .../70 Datastores with Alarms Disabled.ps1 | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 Plugins/40 Datastore/70 Datastores with Alarms Disabled.ps1 diff --git a/Plugins/40 Datastore/70 Datastores with Alarms Disabled.ps1 b/Plugins/40 Datastore/70 Datastores with Alarms Disabled.ps1 new file mode 100644 index 00000000..ec694457 --- /dev/null +++ b/Plugins/40 Datastore/70 Datastores with Alarms Disabled.ps1 @@ -0,0 +1,12 @@ +# Start of Settings +# End of Settings + +$Datastores | where {!$_.ExtensionData.AlarmActionsEnabled} | Select Name, @{n='AlarmActionsEnabled'; e={$_.ExtensionData.AlarmActionsEnabled}} + +$Title = "Datastores with Alarms Disabled" +$Header = "Datastores with Alarms Disabled" +$Comments = "Datastores with Alarms Disabled" +$Display = "Table" +$Author = "John Sneddon" +$PluginVersion = 1.0 +$PluginCategory = "vSphere" From 08a1221ebc89bf8d177e12c030e2ac651ffa11f6 Mon Sep 17 00:00:00 2001 From: Sneddo Date: Fri, 4 Mar 2016 13:08:47 +1100 Subject: [PATCH 26/50] Add default to no output Issue #389 --- vCheck.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/vCheck.ps1 b/vCheck.ps1 index afed28e0..73c7b799 100644 --- a/vCheck.ps1 +++ b/vCheck.ps1 @@ -808,6 +808,7 @@ Foreach ($pr in $PluginResult) { "List" { $pr.Details = Get-HTMLList $pr.Details } "Table" { $pr.Details = Get-HTMLTable $pr.Details $pr.TableFormat } "Chart" { $pr.Details = Get-HTMLChart "plugin$($p)" $pr.Details } + default { $pr.Details = $null } } $pr | Add-Member -Type NoteProperty -Name pluginID -Value "plugin-$p" $p++ From dc0b1d925f5a19c27d2d11596ce4c7403ea5300e Mon Sep 17 00:00:00 2001 From: Sneddo Date: Fri, 4 Mar 2016 13:09:39 +1100 Subject: [PATCH 27/50] Fix for Init plugin creating output Forgot this in last commit. Issue #389 --- Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 b/Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 index f785da53..9f611e82 100644 --- a/Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 +++ b/Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 @@ -3,7 +3,7 @@ $Author = "Alan Renouf" $PluginVersion = 1.5 $Header = "Connection Settings" $Comments = "Connection Plugin for connecting to vSphere" -$Display = "List" +$Display = "None" $PluginCategory = "vSphere" # Start of Settings From f591b00c1119df6e42e89813cdb1fd65838d9f6e Mon Sep 17 00:00:00 2001 From: Sneddo Date: Fri, 4 Mar 2016 14:05:32 +1100 Subject: [PATCH 28/50] More accurate ratios Discussed in #396, added more accurate ratios and refactoring --- Plugins/20 Cluster/71 Capacity Planning.ps1 | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Plugins/20 Cluster/71 Capacity Planning.ps1 b/Plugins/20 Cluster/71 Capacity Planning.ps1 index ab7ca461..3ecbeeaf 100644 --- a/Plugins/20 Cluster/71 Capacity Planning.ps1 +++ b/Plugins/20 Cluster/71 Capacity Planning.ps1 @@ -40,14 +40,14 @@ foreach ($cluv in ($clusviews | Where {$_.Summary.NumHosts -gt 0 } | Sort Name)) # vCPU to pCPU ratio if ($cluvmlist){ - $vCPUpCPUratio = [math]::round(($cluvmlist|Measure-Object -Sum -Property NumCpu).sum / $cluv.summary.NumCpuThreads,0) + $vCPUpCPUratio = ("1:{0}" -f [math]::round(($cluvmlist|Measure-Object -Sum -Property NumCpu).sum / $cluv.summary.NumCpuThreads,1)) + $VMVMHostRatio = ("1:{0}" -f [math]::round(($cluvmlist).count/$cluv.Summary.NumHosts,1)) } - else { $vCPUpCPUratio = "0 (vCPU < pCPU)"} - - if ($cluvmlist){ - $VMVMHostRatio = [math]::round(($cluvmlist).count/$cluv.Summary.NumHosts,0) + else + { + $vCPUpCPUratio = "0 (vCPU < pCPU)" + $VMVMHostRatio = 0 } - else {$VMVMHostRatio = 0} $clucapacity = [PSCustomObject] @{ Datacenter = (Get-VIObjectByVIView -MoRef $cluv.Parent).Parent.Name @@ -68,5 +68,5 @@ $Header = "QuickStats Capacity Planning" $Comments = "The following gives brief capacity information for each cluster based on QuickStats CPU/Mem usage and counting for HA failover requirements" $Display = "Table" $Author = "Raphael Schitz, Frederic Martin" -$PluginVersion = 1.5 +$PluginVersion = 1.7 $PluginCategory = "vSphere" From a8878dd45720291334e1472028764c36537ede28 Mon Sep 17 00:00:00 2001 From: Sneddo Date: Fri, 4 Mar 2016 14:15:04 +1100 Subject: [PATCH 29/50] Filter out powered off VMs Issue #401 --- Plugins/30 Host/07 Hosts Overcommit State.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Plugins/30 Host/07 Hosts Overcommit State.ps1 b/Plugins/30 Host/07 Hosts Overcommit State.ps1 index e070963c..97cf8edd 100644 --- a/Plugins/30 Host/07 Hosts Overcommit State.ps1 +++ b/Plugins/30 Host/07 Hosts Overcommit State.ps1 @@ -18,7 +18,7 @@ $VMHCount = $VMH | Measure Foreach ($VMHost in $VMH) { Write-Progress -ID 2 -Parent 1 -Activity $plang.pluginActivity -Status $VMHost.Name -PercentComplete ((100*$i)/$VMHCount.Count) if ($VMMem) { Clear-Variable VMMem } - $VM | ?{$_.VMHost.Name -eq $VMHost.Name} | Foreach { + $VM | ?{$_.VMHost.Name -eq $VMHost.Name -and $_.PowerState -ne "PoweredOff"} | Foreach { [INT]$VMMem += $_.MemoryMB } @@ -53,9 +53,9 @@ $OverCommit | Select Host, "TotalMem$Units", "TotalAssignedMem$Units", "TotalUse $Title = "Hosts Overcommit state" -$Header = "Hosts overcommitting memory : $(@($OverCommit).count)" +$Header = "Hosts overcommitting memory: [count]]" $Comments = "Overcommitted hosts may cause issues with performance if memory is not issued when needed, this may cause ballooning and swapping" $Display = "Table" $Author = "Alan Renouf" -$PluginVersion = 1.3 +$PluginVersion = 1.4 $PluginCategory = "vSphere" From e0860b8aaf844d756975875a329993a99e6f4676 Mon Sep 17 00:00:00 2001 From: Sneddo Date: Thu, 12 May 2016 10:48:20 +1000 Subject: [PATCH 30/50] Update rounding on Datastore PercentFree Fix #451 --- Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 b/Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 index 9f611e82..790a51d8 100644 --- a/Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 +++ b/Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 @@ -1,6 +1,6 @@ $Title = "Connection settings for vCenter" $Author = "Alan Renouf" -$PluginVersion = 1.5 +$PluginVersion = 1.6 $Header = "Connection Settings" $Comments = "Connection Plugin for connecting to vSphere" $Display = "None" @@ -104,7 +104,7 @@ New-VIProperty -Name LastPoweredOnDate -ObjectType VirtualMachine -Value {(Get-V New-VIProperty -Name PercentFree -ObjectType Datastore -Value { param($ds) - [math]::Round(((100 * ($ds.FreeSpaceMB)) / ($ds.CapacityMB)),0) + [math]::Round(((100 * ($ds.FreeSpaceMB)) / ($ds.CapacityMB)),2) } -Force | Out-Null New-VIProperty -Name "HWVersion" -ObjectType VirtualMachine -Value { From 6dd7d90a6270593118590b9200e4349ad6e74f3d Mon Sep 17 00:00:00 2001 From: Sneddo Date: Thu, 12 May 2016 10:56:52 +1000 Subject: [PATCH 31/50] Add VMName column Pull #452 --- Plugins/60 VM/10 Removed VMs.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/60 VM/10 Removed VMs.ps1 b/Plugins/60 VM/10 Removed VMs.ps1 index 39a5a139..e076d378 100644 --- a/Plugins/60 VM/10 Removed VMs.ps1 +++ b/Plugins/60 VM/10 Removed VMs.ps1 @@ -3,12 +3,12 @@ $VMsNewRemovedAge = 5 # End of Settings -@(Get-VIEventPlus -Start ((get-date).adddays(-$VMsNewRemovedAge)) -EventType "VmRemovedEvent" | Select CreatedTime, UserName, fullFormattedMessage) +@(Get-VIEventPlus -Start ((get-date).adddays(-$VMsNewRemovedAge)) -EventType "VmRemovedEvent" | Select @{Name="VMName";Expression={$_.vm.name}}, CreatedTime, UserName, fullFormattedMessage) $Title = "Removed VMs" $Header = "VMs Removed (Last $VMsNewRemovedAge Day(s)) : [count]" $Comments = "The following VMs have been removed/deleted over the last $($VMsNewRemovedAge) days" $Display = "Table" $Author = "Alan Renouf" -$PluginVersion = 1.2 +$PluginVersion = 1.3 $PluginCategory = "vSphere" From cfd082ddfb945d12a0214d57f327dd4327f20b8d Mon Sep 17 00:00:00 2001 From: Sneddo Date: Tue, 17 May 2016 14:16:41 +1000 Subject: [PATCH 32/50] Update to fix table formatting If more than one rule matches, the second rule will never apply as the cell value changes from String to XmlElement. Fix #445 --- vCheck.ps1 | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/vCheck.ps1 b/vCheck.ps1 index 73c7b799..f1ff4bd3 100644 --- a/vCheck.ps1 +++ b/vCheck.ps1 @@ -270,7 +270,14 @@ Function Get-HTMLTable { if ($FormatRules.keys -contains $XMLTable.table.tr[0].th[$ColN]) { # Current cell has a rule, test to see if they are valid foreach ($rule in $FormatRules[$XMLTable.table.tr[0].th[$ColN]]) { - $value = $XMLTable.table.tr[$RowN].td[$ColN] + if ($XMLTable.table.tr[$RowN].td[$ColN]."#text") + { + $value = $XMLTable.table.tr[$RowN].td[$ColN]."#text" + } + else + { + $value = $XMLTable.table.tr[$RowN].td[$ColN] + } if ($value -notmatch "^[0-9.]+$") { $value = """$value""" } From 16ab5235ef634ba524260ab55d6d5539b2167495 Mon Sep 17 00:00:00 2001 From: Sneddo Date: Wed, 18 May 2016 10:51:57 +1000 Subject: [PATCH 33/50] Fix tables with single column or row - Force $details to be an array, to prevent issues with plugins returning single row results and [count] replacement - Fix table creation when only a single column. For some reason ConvertTo-Html strips out header name and replaces with asterisk Issue #456 --- vCheck.ps1 | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/vCheck.ps1 b/vCheck.ps1 index f1ff4bd3..13cfa990 100644 --- a/vCheck.ps1 +++ b/vCheck.ps1 @@ -262,6 +262,12 @@ Function Get-HTMLTable { $XMLTable = [xml]($content | ConvertTo-Html -Fragment) $XMLTable.table.SetAttribute("width", "100%") + # If only one column, fix up the table header + if (($content | Get-Member -MemberType Properties).count -eq 1) + { + $XMLTable.table.tr[0].th = (($content | Get-Member -MemberType Properties) | Select -ExpandProperty Name -First 1).ToString() + } + # If format rules are specified if ($FormatRules) { # Check each cell to see if there are any format rules @@ -734,10 +740,10 @@ $vCheckPlugins | Foreach { Write-CustomOut ($lang.pluginStart -f $PluginInfo["Title"], $PluginInfo["Author"], $PluginInfo["Version"], $p, $vCheckPlugins.count) $pluginStatus = ($lang.pluginStatus -f $p, $vCheckPlugins.count, $_.Name) Write-Progress -ID 1 -Activity $lang.pluginActivity -Status $pluginStatus -PercentComplete (100 * $p/($vCheckPlugins.count)) - $TTR = [math]::round((Measure-Command { $Details = . $_.FullName }).TotalSeconds, 2) + $TTR = [math]::round((Measure-Command { $Details = @(. $_.FullName)}).TotalSeconds, 2) Write-CustomOut ($lang.pluginEnd -f $PluginInfo["Title"], $PluginInfo["Author"], $PluginInfo["Version"], $p, $vCheckPlugins.count) - # Do a replacement for {count} for number of items returned in $header + # Do a replacement for [count] for number of items returned in $header $Header = $Header -replace "\[count\]", $Details.count $PluginResult += New-Object PSObject -Property @{ From eef8533c6da20c2231b1df66eea8f6778db00794 Mon Sep 17 00:00:00 2001 From: Sneddo Date: Fri, 20 May 2016 09:38:04 +1000 Subject: [PATCH 34/50] Applied fix for List view header Issue #456, header is replaced with asterisk when only a single column of data is supplied. --- vCheck.ps1 | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/vCheck.ps1 b/vCheck.ps1 index 13cfa990..68fedc84 100644 --- a/vCheck.ps1 +++ b/vCheck.ps1 @@ -350,6 +350,13 @@ Function Get-HTMLList { $trNode = $XMLTable.SelectSingleNode("/table/tr[$($i + 1)]") $trNode.ReplaceChild($elem, $node) | Out-Null } + + # If only one column, fix up the table header + if (($content | Get-Member -MemberType Properties).count -eq 1) + { + $XMLTable.table.tr[0].th = (($content | Get-Member -MemberType Properties) | Select -ExpandProperty Name -First 1).ToString() + } + return (Format-HTMLEntities ([string]($XMLTable.OuterXml))) } } From b987577f5ab36afd96181f6813ef6fae526b3f07 Mon Sep 17 00:00:00 2001 From: Markus Fischbacher Date: Thu, 28 Jul 2016 09:10:43 +0200 Subject: [PATCH 35/50] Update 10 HA Configuration Issues.ps1 Make HAIssues configurable by user --- .../20 Cluster/10 HA Configuration Issues.ps1 | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Plugins/20 Cluster/10 HA Configuration Issues.ps1 b/Plugins/20 Cluster/10 HA Configuration Issues.ps1 index ccb5c947..bd78aad0 100644 --- a/Plugins/20 Cluster/10 HA Configuration Issues.ps1 +++ b/Plugins/20 Cluster/10 HA Configuration Issues.ps1 @@ -1,14 +1,20 @@ # Start of Settings # HA Configuration Issues, do not report on any Clusters that are defined here $ClustersDoNotInclude = "Example_Cluster_*|Test_Cluster_*" +# HA should be set to ... +$CLusterHAShouldBeEnabled = $true +# HA host monitoring should be set to ... +$ClusterHAHostMonitoringShouldBeEnabled = $true +# HA Admission Control should be set to ... +$ClusterHAAdmissionControlShouldBeEnabled = $true # End of Settings # Setup plugin-specific language table $pLang = DATA { ConvertFrom-StringData @' - HADisabled = HA Disabled on this cluster. - HAMonDisabled = Host Monitoring disabled. - HAACDisabled = HA Admission Control disabled. + HADisabled = HA config not compliant on this cluster. + HAMonDisabled = Host Monitoring config not compliant. + HAACDisabled = HA Admission Control config not compliant. '@ } # Override the default (en) if it exists in lang directory @@ -16,15 +22,15 @@ Import-LocalizedData -BaseDirectory ($ScriptPath + "\lang") -BindingVariable pLa # Clusters with HA disabled $HAIssues = @() -$HAIssues += $Clusters | Where-Object {$_.Name -notmatch $ClustersDoNotInclude -and -not $_.HAEnabled} | +$HAIssues += $Clusters | Where-Object {$_.Name -notmatch $ClustersDoNotInclude -and $_.HAEnabled -ne $CLusterHAShouldBeEnabled } | Select-Object @{Name="Cluster";Expression={$_.Name}},@{Name="Configuration Issue";Expression={$pLang.HADisabled}} # Clusters with host monitoring disabled -$HAIssues += $clusviews | where {$_.Name -notmatch $ClustersDoNotInclude -and $_.Configuration.DasConfig.HostMonitoring -eq "disabled"} | +$HAIssues += $clusviews | where {$_.Name -notmatch $ClustersDoNotInclude -and ( $_.Configuration.DasConfig.HostMonitoring -eq "enabled" ) -ne $ClusterHAHostMonitoringShouldBeEnabled } | Select-Object @{Name="Cluster";Expression={$_.Name}}, @{N="Configuration Issue";E={$pLang.HAMonDisabled}} # Clusters with admission Control Disabled -$HAIssues += $Clusters | Where-Object {$_.Name -notmatch $ClustersDoNotInclude -and -not $_.HAAdmissionControlEnabled} | +$HAIssues += $Clusters | Where-Object {$_.Name -notmatch $ClustersDoNotInclude -and $_.HAAdmissionControlEnabled -ne $ClusterHAAdmissionControlShouldBeEnabled } | Select-Object @{Name="Cluster";Expression={$_.Name}},@{Name="Configuration Issue";Expression={$pLang.HAACDisabled}} From d5697368b18a5341cc053f27b75a436b4b293d3d Mon Sep 17 00:00:00 2001 From: Markus Fischbacher Date: Thu, 28 Jul 2016 09:25:36 +0200 Subject: [PATCH 36/50] Update 00 Connection Plugin for vCenter.ps1 Display/Output Servername ($Server) for more clarity --- Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 b/Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 index 790a51d8..e9e8ca6a 100644 --- a/Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 +++ b/Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 @@ -59,10 +59,10 @@ if (!(get-module -name VMware.VimAutomation.Core -erroraction silentlycontinue)) $OpenConnection = $global:DefaultVIServers | where { $_.Name -eq $VIServer } if($OpenConnection.IsConnected) { - Write-CustomOut $pLang.connReuse + Write-CustomOut ( "{0}: {1}" -f $pLang.connReuse, $Server ) $VIConnection = $OpenConnection } else { - Write-CustomOut $pLang.connOpen + Write-CustomOut ( "{0}: {1}" -f $pLang.connOpen, $Server ) $VIConnection = Connect-VIServer -Server $VIServer -Port $Port } From 9e95529de3c3740cfc92892be43fdb596928df10 Mon Sep 17 00:00:00 2001 From: Sneddo Date: Thu, 11 Aug 2016 13:55:03 +1000 Subject: [PATCH 37/50] Actually use $SnapshotAge $SnapshotAge wasn't being used in the filter, instead a static value of 5 was. --- Plugins/60 VM/02 Snapshot Information.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/60 VM/02 Snapshot Information.ps1 b/Plugins/60 VM/02 Snapshot Information.ps1 index dbdd1961..4515d4f6 100644 --- a/Plugins/60 VM/02 Snapshot Information.ps1 +++ b/Plugins/60 VM/02 Snapshot Information.ps1 @@ -69,7 +69,7 @@ function Get-SnapshotExtra ($snap){ $filter = New-Object VMware.Vim.TaskFilterSpec $filter.Time = New-Object VMware.Vim.TaskFilterSpecByTime - $filter.Time.beginTime = (($snap.Created).AddDays(-5)) + $filter.Time.beginTime = (($snap.Created).AddDays(-$SnapshotAge)) $filter.Time.timeType = "startedTime" # Added filter to only view for the selected VM entity. Massive speed up. # Entity name check could be removed in line 91. @@ -117,5 +117,5 @@ $Header = "Snapshots (Over $SnapshotAge Days Old) : $(@($snapshots).count)" $Comments = "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." $Display = "Table" $Author = "Alan Renouf, Raphael Schitz" -$PluginVersion = 1.4 +$PluginVersion = 1.5 $PluginCategory = "vSphere" From e2a71ff68d777658fbb8df7a831f30041f775478 Mon Sep 17 00:00:00 2001 From: Sneddo Date: Thu, 11 Aug 2016 14:38:42 +1000 Subject: [PATCH 38/50] Fixes for module errors Number of issues with this module- missing settings section, wrong variable being piped to Get-NetworkAdapter, and a lot of redundant code. --- .../60 VM/200 VMs on ephemeral portgroup.ps1 | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/Plugins/60 VM/200 VMs on ephemeral portgroup.ps1 b/Plugins/60 VM/200 VMs on ephemeral portgroup.ps1 index f19117c1..4a9af812 100644 --- a/Plugins/60 VM/200 VMs on ephemeral portgroup.ps1 +++ b/Plugins/60 VM/200 VMs on ephemeral portgroup.ps1 @@ -1,20 +1,12 @@ -$EphemeralReport = @() +# Start of Settings +# End of Settings $EphemeralPG = Get-VDSwitch | Get-VDPortgroup | where {$_.PortBinding -eq "Ephemeral"} -$vNetworkAdapter = $VM | Get-NetworkAdapter | where {$_.NetworkName -contains $EphemeralPG} +@($VM | Get-NetworkAdapter | where {$_.NetworkName -contains $EphemeralPG} | Select @{Name="VMName"; Expression={$_.parent}}, @{Name="Portgroup"; Expression={$_.NetworkName}}) -ForEach ($v in $vNetworkAdapter) - { - $vDSSummary = "" | Select VMName, Portgroup - $vDSSummary.Portgroup = $v.NetworkName - $vDSSummary.VMName = $v.parent - $EphemeralReport += $vDSSummary - } -$EphemeralReport - -Title = "VMs on Ephemeral Portgroup" -$Header = "VMs on Ephemeral Portgroup: $(@($EphemeralReport).Count)" -$Comments = "...." +$Title = "VMs on Ephemeral Portgroup" +$Header = "VMs on Ephemeral Portgroup: [count]" +$Comments = "" $Display = "Table" $Author = "Tim Williams" $PluginVersion = 1.0 From 3c019b1eba9df1daca8125e5b5af03606a6dc8bd Mon Sep 17 00:00:00 2001 From: Sneddo Date: Thu, 11 Aug 2016 15:09:35 +1000 Subject: [PATCH 39/50] Duplicate of plugin 201 Other plugin structure is a bit cleaner --- .../103 VMs CPU MEM Add Hot Disabled.ps1 | 21 ------------------- 1 file changed, 21 deletions(-) delete mode 100644 Plugins/60 VM/103 VMs CPU MEM Add Hot Disabled.ps1 diff --git a/Plugins/60 VM/103 VMs CPU MEM Add Hot Disabled.ps1 b/Plugins/60 VM/103 VMs CPU MEM Add Hot Disabled.ps1 deleted file mode 100644 index 5f977dcc..00000000 --- a/Plugins/60 VM/103 VMs CPU MEM Add Hot Disabled.ps1 +++ /dev/null @@ -1,21 +0,0 @@ -# Start of Settings -# End of Settings - -$ListCPUMemDisabled = @() -foreach ($vmguest in $FullVM) { - $myObj = "" | select Name,OS,CPU,MEM - $myObj.Name = $vmguest.name - $myObj.OS = $vmguest.Config.GuestFullName - $myObj.CPU = $vmguest.Config.cpuhotaddenabled - $myObj.MEM = $vmguest.Config.memoryhotaddenabled - $ListCPUMemDisabled += $myObj -} -$ListCPUMemDisabled | where {$_.CPU -match $false -or $_.Mem -match $false} | Sort Name - -$Title = "CPU/Mem HotPlug" -$Header = "CPU/Mem HotPlug Disabled found" -$Comments = "VMs needs to be shutdown to modify CPU/Mem HotPlug in case this settings is disabled." -$Display = "Table" -$Author = "Eric Lannier" -$PluginVersion = 1.0 -$PluginCategory = "vSphere" From a5497e37d1ec3b9241b16614854cf4b4aaee2f11 Mon Sep 17 00:00:00 2001 From: Sneddo Date: Thu, 11 Aug 2016 15:16:40 +1000 Subject: [PATCH 40/50] Add option to report on disabled or enabled Plugin can now be used to report if CPU/MEM hot plug is enabled or disabled. --- Plugins/60 VM/201 VMs Hot Add Configuration.ps1 | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Plugins/60 VM/201 VMs Hot Add Configuration.ps1 b/Plugins/60 VM/201 VMs Hot Add Configuration.ps1 index 274d3c1a..65a714e0 100644 --- a/Plugins/60 VM/201 VMs Hot Add Configuration.ps1 +++ b/Plugins/60 VM/201 VMs Hot Add Configuration.ps1 @@ -1,14 +1,21 @@ # Start of Settings +# Should CPU hot plug be enabled +$CPUHotAdd = $true +# Should Memory hot add be enabled +$MEMHotAdd = $true # End of Settings -$HotAdd = @($VM | Select-Object Name, @{N="CPU Hot Plug Enabled"; E={$_.ExtensionData.config.CpuHotAddEnabled}}, @{N="Memory Hot Add Enabled"; E={$_.ExtensionData.config.MemoryHotAddEnabled}}) -$HotAdd +@($VMs | Select-Object Name, @{N="CPU Hot Plug Enabled"; E={$_.ExtensionData.config.CpuHotAddEnabled}}, @{N="Memory Hot Add Enabled"; E={$_.ExtensionData.config.MemoryHotAddEnabled}}) | Where {$_."CPU Hot Plug Enabled" -ne $CPUHotAdd -or $_."Memory Hot Add Enabled" -ne $MEMHotAdd} + +# Create variables with unexpected values, for use in the plugin comment +$CPUNotExpected = if ($CPUHotAdd) { "disabled" } else { "enabled" } +$MEMNotExpected = if ($MEMHotAdd) { "disabled" } else { "enabled" } $Title = "VMs Memory/CPU Hot Add configuration" $Header = "VMs Memory/CPU Hot Add configuration" -$Comments = "The following lists all VMs and they Hot Add / Hot Plug feature configuration" +$Comments = ("The following lists all VMs with CPU hot plug {0} or Memory hot add {1}" -f $CPUNotExpected, $MEMNotExpected) $Display = "Table" $Author = "Marc Bouchard" -$PluginVersion = 1.0 +$PluginVersion = 1.1 $PluginCategory = "vSphere" From 245f1dbec07969115dc44134eef7b7360d652277 Mon Sep 17 00:00:00 2001 From: Sneddo Date: Mon, 15 Aug 2016 10:53:27 +1000 Subject: [PATCH 41/50] Update to use Get-VIEventPlus function Previously this plugin was rolling its own search. Issue #477 --- Plugins/20 Cluster/15 HA VMs restarted.ps1 | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/Plugins/20 Cluster/15 HA VMs restarted.ps1 b/Plugins/20 Cluster/15 HA VMs restarted.ps1 index 09272863..2c06f834 100644 --- a/Plugins/20 Cluster/15 HA VMs restarted.ps1 +++ b/Plugins/20 Cluster/15 HA VMs restarted.ps1 @@ -1,23 +1,14 @@ -# Start of Settings +# Start of Settings # HA VM restart day(s) number $HAVMrestartold = 5 # End of Settings -$EventFilterSpec = New-Object VMware.Vim.EventFilterSpec -$EventFilterSpec.Category = "warning" -$EventFilterSpec.eventTypeId = "com.vmware.vc.ha.VmRestartedByHAEvent" -$EventFilterSpec.Time = New-Object VMware.Vim.EventFilterSpecByTime -$EventFilterSpec.Time.beginTime = (Get-Date).AddDays(-$HAVMrestartold) - -$HAVMrestartlist = @((get-view (get-view ServiceInstance -Property Content.EventManager).Content.EventManager).QueryEvents($EventFilterSpec) | select CreatedTime,FullFormattedMessage |sort CreatedTime -Descending) -$HAVMrestartlist +@(Get-VIEventPlus -EventType "com.vmware.vc.ha.VmRestartedByHAEvent" -Start (Get-Date).AddDays(-$HAVMrestartold) | Select-Object CreatedTime, FullFormattedMessage | Sort-Object CreatedTime -Descending) $Title = "HA VMs restarted" -$Header = ("HA: VM restart (Last {0} Day(s)) : {1}" -f $HAVMrestartold, @($HAVMrestartlist).count) +$Header = ("HA: VM restart (Last {0} Day(s)) : [count]" -f $HAVMrestartold) $Comments = "The following VMs have been restarted by HA in the last $HAVMresetold days" $Display = "Table" $Author = "Alan Renouf" -$PluginVersion = 1.1 +$PluginVersion = 1.2 $PluginCategory = "vSphere" - -Remove-Variable HAVMrestartlist, EventFilterSpec From b64d6d6e33d8f186e9cfe7980d7e7468009110f3 Mon Sep 17 00:00:00 2001 From: Sneddo Date: Mon, 15 Aug 2016 11:09:18 +1000 Subject: [PATCH 42/50] Get-VIEventPlus default to local time Update Get-VIEventPlus to return local time by default Issue #477 --- .../00 Connection Plugin for vCenter.ps1 | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 b/Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 index e9e8ca6a..662884d7 100644 --- a/Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 +++ b/Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 @@ -1,6 +1,6 @@ $Title = "Connection settings for vCenter" $Author = "Alan Renouf" -$PluginVersion = 1.6 +$PluginVersion = 1.7 $Header = "Connection Settings" $Comments = "Connection Plugin for connecting to vSphere" $Display = "None" @@ -183,6 +183,9 @@ if ($VIVersion -ge 5) { A switch indicating if the full message shall be compiled. This switch can improve the execution speed if the full message is not needed. +.PARAMETER UseUTC + A switch indicating if the event shoukld remain in UTC or + local time. .EXAMPLE PS> Get-VIEventPlus -Entity $vm .EXAMPLE @@ -199,7 +202,8 @@ function Get-VIEventPlus { [string[]]$User, [Switch]$System, [string]$ScheduledTask, - [switch]$FullMessage = $false + [switch]$FullMessage = $false, + [switch]$UseUTC = $false ) process { @@ -250,6 +254,11 @@ function Get-VIEventPlus { } $eventCollector.DestroyCollector() } + if (-not $UseUTC) + { + $events | % { $_.createdTime = $_.createdTime.ToLocalTime() } + } + $events } } From 4347241affd1f76f8641ef3a66ff1ae8710c4a3a Mon Sep 17 00:00:00 2001 From: Sneddo Date: Tue, 16 Aug 2016 09:49:18 +1000 Subject: [PATCH 43/50] Update power on/off functions Use Get-VIEventPlus instead --- Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 b/Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 index 662884d7..ef034da9 100644 --- a/Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 +++ b/Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 @@ -78,7 +78,7 @@ function Get-VMLastPoweredOffDate { process { $Report = "" | Select-Object -Property Name,LastPoweredOffDate $Report.Name = $_.Name - $Report.LastPoweredOffDate = (Get-VIEvent -Entity $vm | ` + $Report.LastPoweredOffDate = (Get-VIEventPlus -Entity $vm | ` Where-Object { $_.Gettype().Name -eq "VmPoweredOffEvent" } | ` Select-Object -First 1).CreatedTime $Report @@ -92,8 +92,8 @@ function Get-VMLastPoweredOnDate { process { $Report = "" | Select-Object -Property Name,LastPoweredOnDate $Report.Name = $_.Name - $Report.LastPoweredOnDate = (Get-VIEvent -Entity $vm | ` - Where-Object { $_.Gettype().Name -eq "VmPoweredOnEvent" } | ` + $Report.LastPoweredOnDate = (Get-VIEventPlus -Entity $vm | ` + Where-Object { $_.Gettype().Name -match "VmPoweredOnEvent" } | ` Select-Object -First 1).CreatedTime $Report } From 4cd0c5c7d5d918187e69befaa52a8afaa2be8b23 Mon Sep 17 00:00:00 2001 From: Sneddo Date: Thu, 18 Aug 2016 13:28:06 +1000 Subject: [PATCH 44/50] Update time and code cleanup Alarms now returned in localtime. #477 --- Plugins/30 Host/36 Host Alarms.ps1 | 33 ++++++++++++------------------ 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/Plugins/30 Host/36 Host Alarms.ps1 b/Plugins/30 Host/36 Host Alarms.ps1 index d63ee998..2fe7c27c 100644 --- a/Plugins/30 Host/36 Host Alarms.ps1 +++ b/Plugins/30 Host/36 Host Alarms.ps1 @@ -1,27 +1,21 @@ # Start of Settings # End of Settings -$alarms = $alarmMgr.GetAlarm($null) -$valarms = $alarms | select value, @{N="name";E={(Get-View -Id $_).Info.Name}} -$hostsalarms = @() -foreach ($HostsView in $HostsViews){ - if ($HostsView.TriggeredAlarmState){ - $hostsTriggeredAlarms = $HostsView.TriggeredAlarmState - Foreach ($hostsTriggeredAlarm in $hostsTriggeredAlarms){ - $Details = "" | Select-Object Object, Alarm, Status, Time - $Details.Object = $HostsView.name - $Details.Alarm = ($valarms | Where {$_.value -eq ($hostsTriggeredAlarm.alarm.value)}).name - $Details.Status = $hostsTriggeredAlarm.OverallStatus - $Details.Time = $hostsTriggeredAlarm.time - $hostsalarms += $Details - } - } -} +$valarms = $alarmMgr.GetAlarm($null) | select value, @{N="name";E={(Get-View -Id $_).Info.Name}} -@($hostsalarms |sort Object) +foreach ($HostsView in ($HostsViews | Where {$_.TriggeredAlarmState} | Sort-Object Name)){ + Foreach ($hostsTriggeredAlarm in $HostsView.TriggeredAlarmState){ + New-Object PSObject -Property @{ + "Object" = $HostsView.name; + "Alarm" = ($valarms | Where {$_.value -eq ($hostsTriggeredAlarm.alarm.value)}).name; + "Status" = $hostsTriggeredAlarm.OverallStatus; + "Time" = $hostsTriggeredAlarm.time.ToLocalTime() + } | Select Object, Alarm, Status, Time + } +} $Title = "Host Alarms" -$Header = "Host(s) Alarm(s): $(@($hostsalarms).Count)" +$Header = "Host(s) Alarm(s): [count]]" $Comments = "The following alarms have been registered against hosts in vCenter" $Display = "Table" $Author = "Alan Renouf, John Sneddon" @@ -29,5 +23,4 @@ $PluginVersion = 1.2 $PluginCategory = "vSphere" $TableFormat = @{"Status" = @(@{ "-eq 'yellow'" = "Row,class|warning"; }, - @{ "-eq 'red'" = "Row,class|critical" }) - } + @{ "-eq 'red'" = "Row,class|critical" })} From 10b02fd84b9316f65b11461da9917ea683b6e4c2 Mon Sep 17 00:00:00 2001 From: Sneddo Date: Thu, 18 Aug 2016 15:00:18 +1000 Subject: [PATCH 45/50] Slight code cleanup --- ...SXi hosts which do not have Lockdown mode enabled.ps1 | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Plugins/30 Host/21 ESXi hosts which do not have Lockdown mode enabled.ps1 b/Plugins/30 Host/21 ESXi hosts which do not have Lockdown mode enabled.ps1 index 0276bcf7..945e3a7a 100644 --- a/Plugins/30 Host/21 ESXi hosts which do not have Lockdown mode enabled.ps1 +++ b/Plugins/30 Host/21 ESXi hosts which do not have Lockdown mode enabled.ps1 @@ -1,12 +1,13 @@ # Start of Settings # End of Settings -$ESXiLockDown = $VMH | Where {$_.ConnectionState -eq "Connected" -or $_.ConnectionState -eq "Maintenance"} | Where {$_.ExtensionData.Summary.Config.Product.Name -match "i"} | Select Name, @{N="LockedMode";E={$_.ExtensionData.Config.AdminDisabled}} -$Result = @($ESXiLockDown | Where { $_.LockedMode -eq $false }) -$Result +$VMH | Where {@("Connected","Maintenance") -contains $_.ConnectionState -and + $_.ExtensionData.Summary.Config.Product.Name -match "i" -and + -not $_.ExtensionData.Config.AdminDisabled} | ` + Select Name, @{N="LockedMode";E={$_.ExtensionData.Config.AdminDisabled}} $Title = "ESXi hosts which do not have Lockdown mode enabled" -$Header = "ESXi Hosts with Lockdown Mode not Enabled : $(@($Result).count)" +$Header = "ESXi Hosts with Lockdown Mode not Enabled: [count]" $Comments = "The following ESXi Hosts do not have lockdown enabled, think about using lockdown as an extra security feature." $Display = "Table" $Author = "Alan Renouf" From 56ae8ad071e776293b573e47ac11d1eaca3548bd Mon Sep 17 00:00:00 2001 From: Sneddo Date: Thu, 18 Aug 2016 15:03:03 +1000 Subject: [PATCH 46/50] Code Simplification --- Plugins/30 Host/67 Host OS Pivot Table.ps1 | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/Plugins/30 Host/67 Host OS Pivot Table.ps1 b/Plugins/30 Host/67 Host OS Pivot Table.ps1 index 276efb3b..9b799b76 100644 --- a/Plugins/30 Host/67 Host OS Pivot Table.ps1 +++ b/Plugins/30 Host/67 Host OS Pivot Table.ps1 @@ -1,27 +1,13 @@ # Start of Settings # End of Settings -# Using enterpriseadmins.org modified code -$HostOSVers = @{} -$HostsViews | % { - $HostOSVer = $_.Summary.Config.product.fullName - $HostOSVers.$HostOSVer++ -} - -$myCol = @() -foreach ( $hosname in $HostOSVers.Keys | sort) { - $MyDetails = "" | select OS, Count - $MyDetails.OS = $hosname - $MyDetails.Count = $HostOSVers.$hosname - $myCol += $MyDetails -} - -$myCol | sort Count -desc +$HostsViews | Group-Object {$_.Summary.config.product.fullname} | ` + Select @{Name="Version";Expression={$_.Name}}, Count | Sort-Object Count -Descending $Title = "Host Build versions in use" $Header = "Host Build versions in use" $Comments = "The following host builds are in use in this vCenter" $Display = "Table" $Author = "Frederic Martin" -$PluginVersion = 1.1 +$PluginVersion = 1.2 $PluginCategory = "vSphere" From 8ad63d333b998b3bbdc488df7afdfad93c20a45e Mon Sep 17 00:00:00 2001 From: Sneddo Date: Thu, 18 Aug 2016 15:11:09 +1000 Subject: [PATCH 47/50] Update to Get-VIEventPlus Use Get-VIEventPlus to convert to local time. #477 --- Plugins/30 Host/77 Lost Access to Volume.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Plugins/30 Host/77 Lost Access to Volume.ps1 b/Plugins/30 Host/77 Lost Access to Volume.ps1 index bb280ebc..a09185fd 100644 --- a/Plugins/30 Host/77 Lost Access to Volume.ps1 +++ b/Plugins/30 Host/77 Lost Access to Volume.ps1 @@ -5,8 +5,8 @@ $LostAccessVolumeAge = 1 # Changelog ## 1.1 : Correctly formatted the Start / End Settings and used $MaxSampleVIEvent which is defined in plugin 00 - -$Result = @(Get-VIEvent -Start ($Date).AddDays(-$LostAccessVolumeAge) -MaxSamples $MaxSampleVIEvent | Where-Object {$_.GetType().Name -eq "EventEx" -and $_.EventTypeId -like "esx.problem.vmfs.heartbeat.*"} | Select-Object -Property @{Name="VMHost";Expression={$_.Host.Name}},CreatedTime,FullFormattedMessage | Sort-Object -Property VMHost,CreatedTime) +## 1.2 : Update to use Get-VIEventPlus +$Result = @(Get-VIEventPlus -Start ($Date).AddDays(-$LostAccessVolumeAge) | Where-Object {$_.GetType().Name -eq "EventEx" -and $_.EventTypeId -like "esx.problem.vmfs.heartbeat.*"} | Select-Object -Property @{Name="VMHost";Expression={$_.Host.Name}},CreatedTime,FullFormattedMessage | Sort-Object -Property VMHost,CreatedTime) $Result $Title = "Lost Access to Volume" @@ -14,5 +14,5 @@ $Header = "Lost Access to Volume: $(@($Result | Where-Object {$_.FullFormattedMe $Comments = "The following hosts have lost access to a volume. This may indicate a problem with your storage solution." $Display = "Table" $Author = "Robert van den Nieuwendijk, Jonathan Medd" -$PluginVersion = 1.1 +$PluginVersion = 1.2 $PluginCategory = "vSphere" From 13c47757dad66952649fcc829d828192ff742841 Mon Sep 17 00:00:00 2001 From: Sneddo Date: Thu, 18 Aug 2016 15:42:42 +1000 Subject: [PATCH 48/50] Fix parameter Type should be Types --- Plugins/10 vCenter/23 VI Events.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/10 vCenter/23 VI Events.ps1 b/Plugins/10 vCenter/23 VI Events.ps1 index 334d69e5..f7e9b11b 100644 --- a/Plugins/10 vCenter/23 VI Events.ps1 +++ b/Plugins/10 vCenter/23 VI Events.ps1 @@ -1,7 +1,7 @@ # Start of Settings # End of Settings -$Result = @(Get-VIEvent -maxsamples $MaxSampleVIEvent -Start ($Date).AddDays(-$VCEventAge ) -Type Error | Select @{N="Host";E={$_.host.name}}, createdTime, @{N="User";E={($_.userName.split("\"))[1]}}, fullFormattedMessage) +$Result = @(Get-VIEvent -maxsamples $MaxSampleVIEvent -Start ($Date).AddDays(-$VCEventAge ) -Types Error | Select @{N="Host";E={$_.host.name}}, createdTime, @{N="User";E={($_.userName.split("\"))[1]}}, fullFormattedMessage) $Result $Title = "Checking VI Events" From 552d7af9b38f93edc460365067f24013a301b78c Mon Sep 17 00:00:00 2001 From: Sneddo Date: Fri, 19 Aug 2016 12:34:45 +1000 Subject: [PATCH 49/50] Add Get-vCheckLogData Pull request #474. Probably need to add some comments to this --- vCheckUtils.ps1 | 60 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/vCheckUtils.ps1 b/vCheckUtils.ps1 index ad8908d3..76c2ac29 100644 --- a/vCheckUtils.ps1 +++ b/vCheckUtils.ps1 @@ -826,3 +826,63 @@ Write-Output "Locally on the server hosting the vCheck script run vCheck.ps1" } # end function +Function Get-vCheckLogData { + param( + [string] $vCheckFile, + [string] $Section + ) + + # Find the comment above the specified section table and grab the Post context for 6 lines beyond the comment + # The HTML is stored within the next 6 lines. + # line 1:
 
+ # line 2: + $ContextInfo = Select-String "Plugin Start - $Section" $vCheckFile -context 0,6 + + # lines 3-6 are the data we want. + $table = $ContextInfo.Context.PostContext | select -last 4 + + # The table actually ends on line 7. But line 6 looks like this: + # Back To Top + # There is no ending + # Line 7: + # So add these missing tags back in. + $table += "" + try { + # Convert to XML for easier parsing + $xmlObj = [xml]$table + } catch { + # This catches any instances where there are no matches in the file, and then the only data is the ending tags. + # just in case you want to see it, Write-Verbose + Write-Verbose "$vCheckFile : $table" + } + + # There is a sub table with the data - so get the TR that contains a sub table + $ParentTR = $xmlObj.table.tr | ? { $_.td.table } + # Get the TD + $ParentTD = $ParentTR.td + # Get the table + $SubTable = $ParentTD.Table + + # Use the TH to get the header names + $th = $subTable.tr.th + + # 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 + $thHash = @{} + for ($i=0;$i -lt $th.count; $i++) { + $thHash.Add($i,$th[$i]) + } + + # Loop through each TR containing the log data + for ($i=1; $i -lt $subTable.tr.count; $i++) { + # Get the TDs under the TR, and loop through those + $td = $subTable.tr[$i].td + + # build a hash table pulling the column name from the TH hash table, and the value from the TD + $tdHash = @{} + for ($j=0; $j -lt $td.count;$j++) { + $tdHash.Add($thHash[$j],$td[$j]) + } + # Return this as an object + New-Object -Type PSObject -Prop $tdHash + } +} From eb029d9c1de57082be3a90767f3b3fb68ff11861 Mon Sep 17 00:00:00 2001 From: Sneddo Date: Fri, 19 Aug 2016 12:59:54 +1000 Subject: [PATCH 50/50] Update version number Probably about time we actually merged the hundreds of commits in dev to a release, and with the VMware {code} Hackathon coming up, seems as good a time as any. --- vCheck.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vCheck.ps1 b/vCheck.ps1 index 68fedc84..203faa52 100644 --- a/vCheck.ps1 +++ b/vCheck.ps1 @@ -30,7 +30,7 @@ .NOTES File Name : vCheck.ps1 Author : Alan Renouf - @alanrenouf - Version : 6.23-alpha-3 + Version : 6.23 Thanks to all who have commented on my blog to help improve this project all beta testers and previous contributors to this script. @@ -65,7 +65,7 @@ param ( [string]$job ) -$vCheckVersion = "6.23-alpha-3" +$vCheckVersion = "6.23" $Date = Get-Date #region Internationalization