#
.SYNOPSIS
vCheck is a PowerShell HTML framework script, 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.
.DESCRIPTION
vCheck Daily Report for vSphere
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.
This script picks on the key known issues and potential issues scripted as
plugins for various technologies written as powershell scripts and reports
it all in one place so all you do in the morning is check your email.
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.
.NOTES
File Name : vCheck.ps1
Author : Alan Renouf - @alanrenouf
Version : 6.25
Thanks to all who have commented on my blog to help improve this project
all beta testers and previous contributors to this script.
.LINK
http://www.virtu-al.net/vcheck-pluginsheaders/vcheck
.LINK
https://github.com/alanrenouf/vCheck-vSphere/
.INPUTS
No inputs required
.OUTPUTS
HTML formatted email, Email with attachment, HTML File
.PARAMETER config
If this switch is set, run the setup wizard
.PARAMETER Outputpath
This parameter specifies the output location for files.
.PARAMETER job
This parameter lets you specify an xml config file for this invokation
.PARAMETER VMFolder
This parameter lets you filter VMs based on a certain vCenter folder
#>
#Requires -Version 3.0
[CmdletBinding()]
param (
[Switch]$config,
[Switch]$GUIConfig,
[ValidateScript({ Test-Path $_ -PathType 'Container' })]
[string]$Outputpath=$Env:TEMP,
[ValidateScript({ Test-Path $_ -PathType 'Leaf' })]
[string]$job,
[string]$VMFolder
)
$vCheckVersion = "6.25"
$Date = Get-Date
# Setup all paths required for script to run
$ScriptPath = (Split-Path ((Get-Variable MyInvocation).Value).MyCommand.Path)
$PluginsFolder = $ScriptPath + "\Plugins\"
#region Internationalization
################################################################################
# Internationalization #
################################################################################
# Default language en-US
Import-LocalizedData -BaseDirectory ($ScriptPath + '\Lang') -BindingVariable lang -UICulture en-US -ErrorAction SilentlyContinue
# Override the default (en-US) if it exists in lang directory
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-OutPut "[$($LogDate)] $Details"
}
<# Placeholder for now, just return the setting passed to it. Eventually this
will be used for new settings handling #>
function Get-vCheckSetting
{
param
(
[string]$Module,
[string]$Setting,
$default
)
return $default
}
<# 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)
}
}
<# 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 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 = $OriginalLine = $EndLine = $null
$file = Get-Content $filename
$OriginalLine = ($file | Select-String -Pattern "# Start of Settings").LineNumber
$EndLine = ($file | Select-String -Pattern "# End of Settings").LineNumber
if ($EndLine) { $EndLineActual = $file[$EndLine - 1] }
if (!(($OriginalLine + 1) -eq $EndLine)) {
$Array = @()
$Line = $OriginalLine + 1
$PluginName = (Get-PluginID $Filename).Title
If ($PluginName.EndsWith(".ps1", 1)) {
$PluginName = ($PluginName.split("\")[-1]).split(".")[0]
} # end if
Write-Warning -Message "`n$PluginName"
do {
$NoQuestion = $Question = $Split = $VarWS = $Var = $CurSet = $null
if ($file[$Line - 1] -match "^\s*#") {
$Question = $file[$Line - 1]
$Line++
} else {
$NoQuestion = $true
}
$Split = ($file[$Line - 1]).Split("=",2)
$VarWS = if ($Split[0] -match "^\s*") { $matches[0] } else { "" }
$Var = $Split[0].Trim()
$CurSet = $Split[1].Trim()
if ($null -eq $Question) { $Question = $Var }
# Check if the current setting is quoted
$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) {
if (-not $NoQuestion) { $Array += $Question }
$Array += "${VarWS}${Var} = `"$NewSet`""
} Else {
if (-not $NoQuestion) { $Array += $Question }
$Array += "${VarWS}${Var} = $NewSet"
} # end if/else
$Line++
} Until ($Line -ge $EndLine)
$Array += $EndLineActual
$out = @()
$out = $File[0..($OriginalLine - 1)]
$out += $Array
if ($EndLine -lt $file.count) { $out += $File[$Endline..($file.count - 1)] }
if ($GB) {
$out[$SetupLine] = '$SetupWizard = $False'
} # end if
$out | Set-Content $Filename
} # end if
} # end PROCESS block
} # end Function Invoke-Settings
Function Invoke-HTMLSettings {
<#
.DESCRIPTION
Run through settings for specified file, expects question on one line, and variable/value on following line.
Outputs settings to HTML file, which accepts input, and can create a configuration file.
.NOTES
Updated: 20160830
Updated By: David Seibel
Update Notes:
- Initial creation
#>
[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)) {
$Line = $OriginalLine
$PluginInfo = Get-PluginID $Filename
$PluginName = $PluginInfo.Title
$htmlOutput = ""
If ($PluginName.EndsWith(".ps1", 1)) {
$PluginName = ($PluginName.split("\")[-1]).split(".")[0]
} # end if
$htmlOutput += "
"
$PluginConfig += New-Object PSObject -Property @{
"Details" = $htmlOutput;
"Header" = $PluginName;
"PluginID" = $PluginName;
}
return $PluginConfig
} # end if
} # end PROCESS block
} # end Function Invoke-HTMLSettings
<# 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
}
<# 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.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-Object -ExpandProperty Name -First 1).ToString()
}
# 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]]) {
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"""
}
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
}
# 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-Object -ExpandProperty Name -First 1).ToString()
}
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
}
<# 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
}
}
<# 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))
}
<# 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));
}
}
<# Adds a resource to the resource array, to be included in report.
At the moment, only "File" types are supported- this will be expanded to include
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)++
}
}
Function Set-ReportResource {
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
if (((Get-Command get-content).Parameters).Keys -contains "AsByteStream") {
$imgData = Get-Content ($data[1]) -AsByteStream
} else {
$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(Convert-Path $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;
}
}
}
}
function Get-ConfigScripts {
return "function createCSV() {
var inputs = document.getElementsByTagName('input');
var strsplit = null
//var output = 'filename,question,var\n'
var output = '\n'
for (var i = 0; i < inputs.length; i += 1) {
strsplit = inputs[i].name.split('|')
output += '\t\n'
output += '\t\t'
output += strsplit[0]
output += '\n'
output += '\t\t'
output += strsplit[1]
output += '\n'
output += '\t\t'
output += strsplit[2]
output += '\n'
output += '\t\t""'
output += inputs[i].value
output += '""\n'
output += '\t\n'
}
output += ''
downloadFile('vCheckSettings.xml', output)
}
function downloadFile(filename, rows) {
var fileContent = '';
for (var i = 0; i < rows.length; i++) {
fileContent += rows[i];
}
var blob = new Blob([fileContent], { type: 'text/xml;charset=utf-8;' });
if (navigator.msSaveBlob) { // IE 10+
navigator.msSaveBlob(blob, filename);
} else {
var link = document.createElement('a');
if (link.download !== undefined) { // feature detection
// Browsers that support HTML5 download attribute
var url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', filename);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
}"
}
<# Prompts for and stores vCenter credentials in a secure manner.
The username is saved in cleartext. The password is saved as a SecureString.
The output file is XML.
#>
function Set-vCenterCredentials ($OutputFile)
{
$NewCredential = Get-Credential -Message @"
Enter the username and password for vCenter server '$Server'.
These credentials will be stored securely at '$OutputFile'."
"@
if ($NewCredential -eq $null) {
Write-Warning "No credentials were provided! Exiting."
Exit 1
}
$export = "" | Select-Object Username, EncryptedPassword
$export.Username = $NewCredential.Username
$export.EncryptedPassword = $NewCredential.Password | ConvertFrom-SecureString
$export | Export-Clixml $OutputFile
Write-Host -foregroundcolor green "Credentials saved to: $OutputFile"
Return $NewCredential
}
<# Retrieves the securely stored vCenter credentials from a file on disk. #>
function Get-vCenterCredentials ($InputFile)
{
$credentials = Import-Clixml $InputFile
$import = "" | Select-Object Username, Password
$import.Username = $credentials.Username
$import.Password = $credentials.EncryptedPassword | ConvertTo-SecureString
Return $import
}
#endregion functions
#region initialization
################################################################################
# Initialization #
################################################################################
# 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-Object -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-Object FullName
}
} else {
$ToNatural = { [regex]::Replace($_, '\d+', { $args[0].Value.PadLeft(20) }) }
$vCheckPlugins = @(Get-ChildItem -Path $PluginsFolder -filter "*.ps1" -Recurse | Where-Object { $_.Directory -match "initialize" } | Sort-Object $ToNatural)
$PluginsSubFolder = Get-ChildItem -Path $PluginsFolder | Where-Object { ($_.PSIsContainer) -and ($_.Name -notmatch "initialize") -and ($_.Name -notmatch "finish") }
$vCheckPlugins += $PluginsSubFolder | % { Get-ChildItem -Path $_.FullName -filter "*.ps1" | Sort-Object $ToNatural }
$vCheckPlugins += Get-ChildItem -Path $PluginsFolder -filter "*.ps1" -Recurse | Where-Object { $_.Directory -match "finish" } | Sort-Object $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++
$SetupSetting = Invoke-Expression (($file[$SetupLine]).Split("="))[1]
## 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)
}
}
# Create empty array of resources (i.e. Images)
$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)"
}
# Import the Style
. ("$($StylePath)\Style.ps1")
if ($SetupSetting -or $config -or $GUIConfig) {
#Clear-Host
($lang.GetEnumerator() | Where-Object { $_.Name -match "setupMsg[0-9]*" } | Sort-Object Name) | ForEach-Object {
Write-Warning -Message "$($_.value)"
}
if ($GUIConfig) {
$PluginResult = @()
# Set the output filename
if (-not (Test-Path -PathType Container $Outputpath)) { New-Item $Outputpath -type directory | Out-Null }
$Filename = ("{0}{1}{2}_vCheck-Config_{3}.html" -f $Outputpath, [System.IO.Path]::DirectorySeparatorChar, $Server, (Get-Date -Format "yyyyMMdd_HHmm"))
#$configHTML = ""
#$configHTML += Invoke-HTMLSettings -Filename $GlobalVariables
$PluginResult += Invoke-HTMLSettings -Filename $GlobalVariables
Foreach ($plugin in $vCheckPlugins) {
#$configHTML += Invoke-HTMLSettings -Filename $plugin.Fullname
$PluginResult += Invoke-HTMLSettings -Filename $plugin.Fullname
}
# Run Style replacement
$MyConfig = Get-ReportHTML
# Always generate the report with embedded images
$embedConfig = $MyConfig
# Loop over all CIDs and replace them
Foreach ($cid in $global:ReportResources.Keys) {
$embedConfig = $embedConfig -replace ("cid:{0}" -f $cid), (Get-ReportResource $cid -ReturnType "embed")
}
$embedConfig | Out-File $Filename
Invoke-Item $Filename
($lang.GetEnumerator() | Where-Object { $_.Name -match "configMsg[0-9]*" } | Sort-Object Name) | ForEach-Object {
Write-Warning -Message "$($_.value)"
}
} elseif ($SetupSetting -or $config) {
Invoke-Settings -Filename $GlobalVariables -GB $true
Foreach ($plugin in $vCheckPlugins) {
Invoke-Settings -Filename $plugin.Fullname
}
}
}
#endregion initialization
if (-not $GUIConfig) {
#region scriptlogic
################################################################################
# Script logic #
################################################################################
# Start generating the report
$PluginResult = @()
Write-Warning -Message $lang.pluginBegin
# Loop over all enabled plugins
$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;
}
}
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-Object -ExpandProperty FullName) -Contains $plugin.FullName)
}
}
if ($ListEnabledPluginsFirst) {
$Plugins = $Plugins | Sort-Object -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-Object { $_.TimeToRun -gt $PluginSeconds } | Select-Object 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 }
default { $pr.Details = $null }
}
$pr | Add-Member -Type NoteProperty -Name pluginID -Value "plugin-$p"
$p++
}
if ($pr.Details -ne $null)
{
$emptyReport = $false
}
}
# Run Style replacement
$MyReport = Get-ReportHTML
# 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, $VIServer, (Get-Date -Format "yyyyMMdd_HHmm"))
# 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 | Out-File -encoding ASCII -filepath $Filename
# Display to screen
if ($DisplayToScreen -and (!($emptyReport -and !$DisplayReportEvenIfEmpty))) {
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 (($SMTPSRV -split ":")[1]) {
$smtpClient.Port = ($SMTPSRV -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")
}
#endregion output
}