diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1f16d6fa..f0ec2ceb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,7 +31,7 @@ If the changes are related to an existing GitHub issue, please reference the iss 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 ;)* +*We are at least 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. @@ -111,7 +111,7 @@ 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. +* When writing Markdown, if a paragraph includes more than one sentence, 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. diff --git a/GlobalVariables.ps1 b/GlobalVariables.ps1 index bd3a1329..9719f3f5 100644 --- a/GlobalVariables.ps1 +++ b/GlobalVariables.ps1 @@ -32,7 +32,7 @@ $EmailReportEvenIfEmpty = $true # If you would prefer the HTML file as an attachment then enable the following: $SendAttachment = $false # Set the style template to use. -$Style = "VMware" +$Style = "Clarity" # Do you want to include plugin details in the report? $reportOnPlugins = $true # List Enabled plugins first in Plugin Report? diff --git a/Lang/en-GB/79 Find VMs in Uncontrolled Snapshot Mode.psd1 b/Lang/en-GB/79 Find VMs in Uncontrolled Snapshot Mode.psd1 index 7ca13a50..259c0e3a 100644 --- a/Lang/en-GB/79 Find VMs in Uncontrolled Snapshot Mode.psd1 +++ b/Lang/en-GB/79 Find VMs in Uncontrolled Snapshot Mode.psd1 @@ -1,6 +1,6 @@ -# culture="en-US" -ConvertFrom-StringData @' - pluginActivity = Searching Datastores - pluginStatus = [{0} of {1}] {2} - Complete = completed -'@ +# culture="en-US" +ConvertFrom-StringData @' + pluginActivity = Searching Datastores + pluginStatus = [{0} of {1}] {2} + Complete = completed +'@ diff --git a/Lang/en-GB/vCheck.psd1 b/Lang/en-GB/vCheck.psd1 index 494f54eb..f007d9b6 100644 --- a/Lang/en-GB/vCheck.psd1 +++ b/Lang/en-GB/vCheck.psd1 @@ -1,36 +1,36 @@ -# culture="en-US" -ConvertFrom-StringData @' - setupMsg01 = - setupMsg02 = Welcome to vCheck by Virtu-Al http://virtu-al.net - setupMsg03 = ================================================= - setupMsg04 = This is the first time you have run this script or you have re-enabled the setup wizard. - setupMsg05 = - setupMsg06 = To re-run this wizard in the future please use vCheck.ps1 -Config - setupMsg07 = To get usage information, please use Get-Help vCheck.ps1 - setupMsg08 = - setupMsg09 = Please complete the following questions or hit Enter to accept the current setting - setupMsg10 = After completing this wizard the vCheck report will be displayed on the screen. - setupMsg11 = - configMsg01 = After you have exported the new settings from the configuration interface, - configMsg02 = import the settings CSV file using Import-vCheckSettings -csvfile C:\\path\\to\\vCheckSettings.csv - configMsg03 = NOTE: If vCheckSettings.csv is stored in the vCheck folder, simply run Import-vCheckSettings - resFileWarn = Image File not found for {0}! - pluginInvalid = Plugin does not exist: {0} - pluginpathInvalid = Plugin path "{0}" is invalid, defaulting to {1} - gvInvalid = Global Variables path invalid in job specification, defaulting to {0} - varUndefined = Variable `${0} is not defined in GlobalVariables.ps1 - pluginActivity = Evaluating plugins - pluginStatus = [{0} of {1}] {2} - Complete = Complete - pluginBegin = \nBegin Plugin Processing - pluginStart = ..start calculating {0} by {1} v{2} [{3} of {4}] - pluginEnd = ..finished calculating {0} by {1} v{2} [{3} of {4}] - repTime = This report took {0} minutes to run all checks, completing on {1} at {2} - repPRTitle = Plugin Report - repTTRTitle = Time to Run - slowPlugins = The following plugins took longer than {0} seconds to run, there may be a way to optimize these or remove them if not needed - emailSend = ..Sending Email - emailAtch = vCheck attached to this email - HTMLdisp = ..Displaying HTML results -'@ - +# culture="en-US" +ConvertFrom-StringData @' + setupMsg01 = + setupMsg02 = Welcome to vCheck by Virtu-Al http://virtu-al.net + setupMsg03 = ================================================= + setupMsg04 = This is the first time you have run this script or you have re-enabled the setup wizard. + setupMsg05 = + setupMsg06 = To re-run this wizard in the future please use vCheck.ps1 -Config + setupMsg07 = To get usage information, please use Get-Help vCheck.ps1 + setupMsg08 = + setupMsg09 = Please complete the following questions or hit Enter to accept the current setting + setupMsg10 = After completing this wizard the vCheck report will be displayed on the screen. + setupMsg11 = + configMsg01 = After you have exported the new settings from the configuration interface, + configMsg02 = import the settings CSV file using Import-vCheckSettings -csvfile C:\\path\\to\\vCheckSettings.csv + configMsg03 = NOTE: If vCheckSettings.csv is stored in the vCheck folder, simply run Import-vCheckSettings + resFileWarn = Image File not found for {0}! + pluginInvalid = Plugin does not exist: {0} + pluginpathInvalid = Plugin path "{0}" is invalid, defaulting to {1} + gvInvalid = Global Variables path invalid in job specification, defaulting to {0} + varUndefined = Variable `${0} is not defined in GlobalVariables.ps1 + pluginActivity = Evaluating plugins + pluginStatus = [{0} of {1}] {2} + Complete = Complete + pluginBegin = \nBegin Plugin Processing + pluginStart = ..start calculating {0} by {1} v{2} [{3} of {4}] + pluginEnd = ..finished calculating {0} by {1} v{2} [{3} of {4}] + repTime = This report took {0} minutes to run all checks, completing on {1} at {2} + repPRTitle = Plugin Report + repTTRTitle = Time to Run + slowPlugins = The following plugins took longer than {0} seconds to run, there may be a way to optimize these or remove them if not needed + emailSend = ..Sending Email + emailAtch = vCheck attached to this email + HTMLdisp = ..Displaying HTML results +'@ + diff --git a/Lang/en-US/00 Connection Plugin for vCenter.psd1 b/Lang/en-US/00 Connection Plugin for vCenter.psd1 index 6e298075..9644e449 100644 --- a/Lang/en-US/00 Connection Plugin for vCenter.psd1 +++ b/Lang/en-US/00 Connection Plugin for vCenter.psd1 @@ -1,21 +1,21 @@ -# culture="en-US" - -ConvertFrom-StringData @' - connReuse = Re-using connection to VI Server - connOpen = Connecting to VI Server - connError = Unable to connect to vCenter, please ensure you have altered the vCenter server address correctly. To specify a username and password edit the connection string in the file $GlobalVariables - custAttr = Adding Custom properties - collectVM = Collecting VM Objects - collectHost = Collecting VM Host Objects - collectCluster = Collecting Cluster Objects - collectDatastore = Collecting Datastore Objects - collectDVM = Collecting Detailed VM Objects - collectTemplate = Collecting Template Objects - collectDVIO = Collecting Detailed VI Objects - collectAlarm = Collecting Detailed Alarm Objects - collectDHost = Collecting Detailed VMHost Objects - collectDCluster = Collecting Detailed Cluster Objects - collectDDatastore = Collecting Detailed Datastore Objects - collectDDatastoreCluster = Collecting Detailed Datastore Cluster Objects - collectAlarms = Collecting Alarm Definitions +# culture="en-US" + +ConvertFrom-StringData @' + connReuse = Re-using connection to VI Server + connOpen = Connecting to VI Server + connError = Unable to connect to vCenter, please ensure you have altered the vCenter server address correctly. To specify a username and password edit the connection string in the file $GlobalVariables + custAttr = Adding Custom properties + collectVM = Collecting VM Objects + collectHost = Collecting VM Host Objects + collectCluster = Collecting Cluster Objects + collectDatastore = Collecting Datastore Objects + collectDVM = Collecting Detailed VM Objects + collectTemplate = Collecting Template Objects + collectDVIO = Collecting Detailed VI Objects + collectAlarm = Collecting Detailed Alarm Objects + collectDHost = Collecting Detailed VMHost Objects + collectDCluster = Collecting Detailed Cluster Objects + collectDDatastore = Collecting Detailed Datastore Objects + collectDDatastoreCluster = Collecting Detailed Datastore Cluster Objects + collectAlarms = Collecting Alarm Definitions '@ \ No newline at end of file diff --git a/Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 b/Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 index 07f01636..d8340353 100644 --- a/Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 +++ b/Plugins/00 Initialize/00 Connection Plugin for vCenter.ps1 @@ -110,8 +110,8 @@ switch ($platform.OSFamily) { Get-Module -ListAvailable PowerCLI* | Import-Module } "Linux" { - $Outputpath = $templocation $templocation = "/tmp" + $Outputpath = $templocation Get-Module -ListAvailable PowerCLI* | Import-Module } "Windows" { @@ -437,10 +437,10 @@ PS> Get-Datastore | Get-HttpDatastoreItem -Credential $cred -Recurse [cmdletbinding()] param( [VMware.VimAutomation.ViCore.Types.V1.VIServer]$Server = $global:DefaultVIServer, - [parameter(Mandatory=$true,ValueFromPipelineByPropertyName,ParameterSetName=�Datastore�)] + [parameter(Mandatory=$true,ValueFromPipelineByPropertyName,ParameterSetName="Datastore")] [Alias('Name')] [string]$Datastore, - [parameter(Mandatory=$true,ParameterSetName=�Path�)] + [parameter(Mandatory=$true,ParameterSetName="Path")] [string]$Path = '', [PSCredential]$Credential, [Switch]$Recurse = $false, @@ -545,9 +545,12 @@ PS> Get-Datastore | Get-HttpDatastoreItem -Credential $cred -Recurse $tSize = $_.Groups['Filesize'].Value if($Unit.IsPresent){ $friendly = $tSize | Get-FriendlyUnit - $obj.Add('Size',[Math]::Round($friendly.Value,0)) $obj.Add('Unit',$friendly.Unit) } + $obj.Add('Size',[Math]::Round($friendly.Value,0)) + $obj.Add('Unit',$friendly.Unit) + } else{ - $obj.Add('Size',$tSize) } + $obj.Add('Size',$tSize) + } } else{ $obj.Add('Size','') @@ -572,4 +575,4 @@ PS> Get-Datastore | Get-HttpDatastoreItem -Credential $cred -Recurse } Write-Verbose -Message "$(Get-Date) $($s = Get-PSCallStack;" $null - $VMName = $matches[2] - $eachVM = $FullVM | Where-Object {$_.Name -eq $VMName} - if (!$eachVM.snapshot) - { - # Only process VMs without snapshots - New-Object -TypeName PSObject -Property @{ - VM = $eachVM.Name - Datacenter = $eachDS.Datacenter - Path = $vmFile.FullName - } - } - } -} -Write-Progress -ID 1 -Activity $pLang.pluginActivity -Status $pLang.Complete -Completed \ No newline at end of file +#region Internationalization +################################################################################ +# Internationalization # +################################################################################ +# Default language en-US +Import-LocalizedData -BaseDirectory ($ScriptPath + '\lang') -BindingVariable pLang -UICulture en-US -ErrorAction SilentlyContinue + +# Override the default (en-US) if it exists in lang directory +Import-LocalizedData -BaseDirectory ($ScriptPath + "\lang") -BindingVariable pLang -ErrorAction SilentlyContinue + +#endregion Internationalization + +$Title = "VMs in uncontrolled snapshot mode" +$Header = "VMs in uncontrolled snapshot mode: [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, Dan Rowe" +$PluginVersion = 1.5 +$PluginCategory = "vSphere" + +# Start of Settings +# End of Settings + +$i=0; +foreach ($eachDS in ($Datastores | Where-Object {$_.State -eq "Available"})) { + Write-Progress -ID 2 -Parent 1 -Activity $pLang.pluginActivity -Status ($pLang.pluginStatus -f $i, $Datastores.count, $eachDS.Name) -PercentComplete ($i*100/$Datastores.count) + + $FilePath = $eachDS.DatastoreBrowserPath + '\*\*delta.vmdk*' + $fileList = @(Get-ChildItem -Path "$FilePath" | Select-Object Name, FolderPath, FullName) + $FilePath = $eachDS.DatastoreBrowserPath + '\*\-*-flat.vmdk' + $fileList += Get-ChildItem -Path "$FilePath" | Select-Object Name, FolderPath, FullName + + $i++ + + foreach ($vmFile in $filelist | Sort-Object FolderPath) + { + $vmFile.FolderPath -match '^\[([^\]]+)\] ([^/]+)' > $null + $VMName = $matches[2] + $eachVM = $FullVM | Where-Object {$_.Name -eq $VMName} + if (!$eachVM.snapshot) + { + # Only process VMs without snapshots + New-Object -TypeName PSObject -Property @{ + VM = $eachVM.Name + Datacenter = $eachDS.Datacenter + Path = $vmFile.FullName + } + } + } +} +Write-Progress -ID 1 -Activity $pLang.pluginActivity -Status $pLang.Complete -Completed diff --git a/README.md b/README.md index 94fd9406..fcde71e0 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ [Join the VMware Code and #vCheck channel on slack and ask questions here!](https://code.vmware.com/slack/) -![Alt text](https://vcheck.report/samples/vCheck-vSphere_Screenshot.PNG "vCheck Sample") +![Alt text](http://www.virtu-al.net/wp-content/uploads/2017/10/vCheck_Clarity.jpg "vCheck Sample") |Navigation| |-----------------| @@ -23,7 +23,6 @@ |[Features](#Features)| |[Installing](#Installing)| |[Enhancements](#Enhancements)| -|[Release Notes](#ReleaseNotes)| |[Contributing](#Contributing)| |[Plugins](#Plugins)| |[Styles](#Styles)| @@ -103,10 +102,13 @@ The following items are included as part of the vCheck vSphere download, they ar - Inaccessible VMs - Much, Much more....... + + # Installing [*Back to top*](#Title) -Copy the vCheck files to the desired location. Run the script `vCheckUtils.ps1` and call the function `Schedule-vCheck`. Answer the prompts to configure the scheduled job. +Copy the vCheck files to the desired location. Run the script 'vCheck.ps1' -Config to do initial configuration of the script. + @@ -121,47 +123,6 @@ Copy the vCheck files to the desired location. Run the script `vCheckUtils.ps1` In the meantime, don't hesitate to pop over to the [#vCheck channel on slack](https://code.vmware.com/slack/) and join in on active conversations about anything you see- or don't see- here! - - -# Release Notes -[*Back to top*](#Title) - -* 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. -* 6.19 - Bugfixes. -* 6.18 - Added Job parameter to allow job specifications via XML file -* 6.17 - Basic Internationalization (i18n) support -* 6.16 - Table formatting rules -* 6.15 - Added Category to all plugins and features to vCheckUtils script for Categorys. -* 6.14 - Fixed a bug where a plugin was resetting the $VM variable so later plugins were not working :( -* 6.13 - Fixed issue with plugins 63 and 65 not using the days -* 6.12 - Changed Version to PluginVersion in each Plugin as the word Version is very hard to isolate! -* 6.11 - Fixed a copy and paste mistake and plugin issues. -* 6.10 - Fixed multiple spelling mistakes and small plugin issues -* 6.9 - Fixed VMKernel logs but had to remove date/Time parser due to inconsistent VMKernel Log entries -* 6.8 - Added Creator of snapshots back in due to popular demand -* 6.7 - Added Multiple plugins from contributors - Thanks! -* 6.6 - Tech Support Mode Plugin fixed to work with 5.0 hosts -* 6.5 - HW Version plugin fixed due to string output -* 6.4 - Added a 00 plugin and VeryLastPlugin for vCenter connection info to separate the report entirely from VMware if needed. -* 6.3 - Changed the format of each Plugin so you can include a count for each header and altered plugin layout for each plugin. -* 6.2 - Added Time to Run section based on TimeToBuild by Frederic Martin -* 6.1 - Bug fixes, filter for ps1 files only in the plugins folder so other files can be kept in the plugins folder. -* 6.0 - Moved plugins into seperate scripts to make it easier to expand vCheck and fixed issues + lots lots more ! -* 5.1 - Code Fixes and ability to change colour for title text to fix issue with Outlook 2007/10 not displaying correctly -* 5.0 - Changed the order and a few titles etc, tidy up ! -* 4.9 - Added Inacessable VMs -* 4.8 - Added HA VM restarts and resets -* 4.7 - VMTools Issues -* 4.6 - Added VCB Garbage -* 4.5 - Added Host config issues -* 4.4 - Added Disk Overcommit check -* 4.3 - Added vSwitch free ports check -* 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 diff --git a/Styles/Clarity/Header-vmware.png b/Styles/Clarity/Header-vmware.png new file mode 100644 index 00000000..eb7f4bb0 Binary files /dev/null and b/Styles/Clarity/Header-vmware.png differ diff --git a/Styles/Clarity/Header.jpg b/Styles/Clarity/Header.jpg new file mode 100644 index 00000000..86b90374 Binary files /dev/null and b/Styles/Clarity/Header.jpg differ diff --git a/Styles/Clarity/Style.ps1 b/Styles/Clarity/Style.ps1 new file mode 100644 index 00000000..07e84bb9 --- /dev/null +++ b/Styles/Clarity/Style.ps1 @@ -0,0 +1,11712 @@ +# Start of Settings +# Show table of centents in report? +$ShowTOC = $true +# Number of columns in table of contents +$ToCColumns = 1 +# End of Settings + +$StyleVersion = 1.4 + +# Define Chart Colours +$ChartColours = @("377C2B", "0A77BA", "1D6325", "89CBE1") +$ChartBackground = "FFFFFF" + +# Set Chart dimensions (WidthxHeight) +$ChartSize = "200x200" + +# Header Images +Add-ReportResource "Header-vCheck" ($StylePath + "\Header.jpg") -Used $true +Add-ReportResource "Header-VMware" ($StylePath + "\Header-vmware.png") -Used $true + +# Hash table of key/value replacements +if ($GUIConfig) { + $StyleReplace = @{"_HEADER_" = ("'$reportHeader'"); + "_SCRIPT_" = "Get-ConfigScripts"; + "_CONTENT_" = "Get-ReportContentHTML"; + "_CONFIGEXPORT_" = ("'
'") + "_TOC_" = ("''")} +} else { + $StyleReplace = @{"_HEADER_" = ("'$reportHeader'"); + "_CONTENT_" = "Get-ReportContentHTML"; + "_CONFIGEXPORT_" = ("''") + "_TOC_" = "Get-ReportTOC"} +} + +#region Function Definitions +<# + Get-ReportHTML - *REQUIRED* + Returns the HTML for the report +#> +function Get-ReportHTML { + foreach ($replaceKey in $StyleReplace.Keys.GetEnumerator()) { + $ReportHTML = $ReportHTML -replace $replaceKey, (Invoke-Expression $StyleReplace[$replaceKey]) + } + + return $reportHTML +} + +<# + Get-ReportContentHTML + Called to replace the content section of the HTML template +#> +function Get-ReportContentHTML { + $ContentHTML = "" + + foreach ($pr in $PluginResult) { + if ($pr.Details) { + $ContentHTML += Get-PluginHTML $pr + } + } + return $ContentHTML +} +<# + Get-PluginHTML + Called to populate the plugin content in the report +#> +function Get-PluginHTML { + param ($PluginResult) + + $FinalHTML = $PluginHTML -replace "_TITLE_", $PluginResult.Header + $FinalHTML = $FinalHTML -replace "_COMMENTS_", $PluginResult.Comments + $PluginResult.Details = $PluginResult.Details -replace "", "" + $PluginResult.Details = $PluginResult.Details -replace "", "" + $FinalHTML = $FinalHTML -replace "_PLUGINCONTENT_", $PluginResult.Details + $FinalHTML = $FinalHTML -replace "_PLUGINID_", $PluginResult.PluginID + + return $FinalHTML +} + +<# + Get-ReportTOC + Generate table of contents +#> +function Get-ReportTOC { + if ($ShowTOC) { + $TOCHTML = @" +
" + + return $TOCHTML + } +} +#endregion + +# Report HTML structure +$ReportHTML = @" + + + + _HEADER_ + + + + + +
+
+
_HEADER_
+
+
+
+ + + + + +
+ vCheck + + VMware +
+ _CONTENT_ + _CONFIGEXPORT_ +
+ _TOC_ +
+ +
 
+
+

vCheck v$($vCheckVersion) by Alan Renouf generated on $($ENV:Computername) on $($Date.ToLongDateString()) at $($Date.ToLongTimeString())

+
+
+ + +"@ + +# Structure of each Plugin +$PluginHTML = @" + +
 
+ + + + +
_TITLE_
_COMMENTS_
_PLUGINCONTENT_
+ +"@ diff --git a/Styles/CleanGreen/Style.ps1 b/Styles/CleanGreen/Style.ps1 index 1a3c86ad..5286eb65 100644 --- a/Styles/CleanGreen/Style.ps1 +++ b/Styles/CleanGreen/Style.ps1 @@ -71,13 +71,15 @@ function Get-PluginHTML { Generate table of contents #> function Get-ReportTOC { - $TOCHTML = "" - - return $TOCHTML } #endregion diff --git a/Styles/VMware/Style.ps1 b/Styles/VMware/Style.ps1 index 32bac19e..66f35b4d 100644 --- a/Styles/VMware/Style.ps1 +++ b/Styles/VMware/Style.ps1 @@ -79,26 +79,28 @@ function Get-PluginHTML { Generate table of contents #> function Get-ReportTOC { - $TOCHTML = "" - - $i = 0 - foreach ($pr in ($PluginResult | Where-Object {$_.Details})) { - $TOCHTML += ("" -f $pr.PluginID, $pr.Title) - - $i++ - # We have hit the end of the line - if ($i%$ToCColumns -eq 0) { - $TOCHTML +="" + if ($ShowTOC) { + $TOCHTML = "
{1}
" + + $i = 0 + foreach ($pr in ($PluginResult | Where-Object {$_.Details})) { + $TOCHTML += ("" -f $pr.PluginID, $pr.Title) + + $i++ + # We have hit the end of the line + if ($i%$ToCColumns -eq 0) { + $TOCHTML +="" + } + } + # If the row is unfinished, need to pad it out with a cell + if ($i%$ToCColumns -gt 0) { + $TOCHTML += ("" -f ($ToCColumns-($i%$ToCColumns))) } - } - # If the row is unfinished, need to pad it out with a cell - if ($i%$ToCColumns -gt 0) { - $TOCHTML += ("" -f ($ToCColumns-($i%$ToCColumns))) - } - $TOCHTML += "
{1}
  
" + $TOCHTML += "" - return $TOCHTML + return $TOCHTML + } } #endregion diff --git a/Using-Culture.ps1 b/Using-Culture.ps1 index 6c44be2a..0fd8b161 100644 --- a/Using-Culture.ps1 +++ b/Using-Culture.ps1 @@ -1,51 +1,51 @@ -<# -.NOTES - Use-Culture.ps1 - - This script allows you to test your internationalisation files. - With this script there is no need to switch your complete OS to another language. - Run the script from the folder where the vCheck.ps1 file is located. - - The script is based on following blog posts: - - Using-Culture -Culture culture -Script {scriptblock} - https://blogs.msdn.microsoft.com/powershell/2006/04/25/using-culture-culture-culture-script-scriptblock/ - - Windows PowerShell 2.0 String Localization - https://rkeithhill.wordpress.com/2009/10/21/windows-powershell-2-0-string-localization/ - - Use a language code from https://msdn.microsoft.com/en-us/library/ee825488(v=cs.20).aspx - - Examples: # Run vCheck with the French language code - Using-Culture -culture fr-FR -script {.\vCheck.ps1} - - # Run vCheck with the Spanish language code - Using-Culture -culture es-ES -script {.\vCheck.ps1} - - Changelog - ============================ - 1.0 - Luc Dekens - - initial version -#> - -function Using-Culture ([System.Globalization.CultureInfo]$culture =(throw "USAGE: Using-Culture -Culture culture -Script {scriptblock}"), - [ScriptBlock]$script=(throw "USAGE: Using-Culture -Culture culture -Script {scriptblock}")) -{ - $OldCulture = [System.Threading.Thread]::CurrentThread.CurrentCulture - $OldUICulture = [System.Threading.Thread]::CurrentThread.CurrentUICulture - try { - [System.Threading.Thread]::CurrentThread.CurrentCulture = $culture - [System.Threading.Thread]::CurrentThread.CurrentUICulture = $culture - Invoke-Command $script - } - finally { - [System.Threading.Thread]::CurrentThread.CurrentCulture = $OldCulture - [System.Threading.Thread]::CurrentThread.CurrentUICulture = $OldUICulture - } -} - -Using-Culture -culture en-US -script {.\vCheck.ps1} - -#Using-Culture -culture af-ZA -script {.\vCheck.ps1} -#Using-Culture -culture de-DE -script {.\vCheck.ps1} -#Using-Culture -culture fr-FR -script {.\vCheck.ps1} +<# +.NOTES + Use-Culture.ps1 + + This script allows you to test your internationalisation files. + With this script there is no need to switch your complete OS to another language. + Run the script from the folder where the vCheck.ps1 file is located. + + The script is based on following blog posts: + + Using-Culture -Culture culture -Script {scriptblock} + https://blogs.msdn.microsoft.com/powershell/2006/04/25/using-culture-culture-culture-script-scriptblock/ + + Windows PowerShell 2.0 String Localization + https://rkeithhill.wordpress.com/2009/10/21/windows-powershell-2-0-string-localization/ + + Use a language code from https://msdn.microsoft.com/en-us/library/ee825488(v=cs.20).aspx + + Examples: # Run vCheck with the French language code + Using-Culture -culture fr-FR -script {.\vCheck.ps1} + + # Run vCheck with the Spanish language code + Using-Culture -culture es-ES -script {.\vCheck.ps1} + + Changelog + ============================ + 1.0 - Luc Dekens + - initial version +#> + +function Using-Culture ([System.Globalization.CultureInfo]$culture =(throw "USAGE: Using-Culture -Culture culture -Script {scriptblock}"), + [ScriptBlock]$script=(throw "USAGE: Using-Culture -Culture culture -Script {scriptblock}")) +{ + $OldCulture = [System.Threading.Thread]::CurrentThread.CurrentCulture + $OldUICulture = [System.Threading.Thread]::CurrentThread.CurrentUICulture + try { + [System.Threading.Thread]::CurrentThread.CurrentCulture = $culture + [System.Threading.Thread]::CurrentThread.CurrentUICulture = $culture + Invoke-Command $script + } + finally { + [System.Threading.Thread]::CurrentThread.CurrentCulture = $OldCulture + [System.Threading.Thread]::CurrentThread.CurrentUICulture = $OldUICulture + } +} + +Using-Culture -culture en-US -script {.\vCheck.ps1} + +#Using-Culture -culture af-ZA -script {.\vCheck.ps1} +#Using-Culture -culture de-DE -script {.\vCheck.ps1} +#Using-Culture -culture fr-FR -script {.\vCheck.ps1} diff --git a/vCheck.ps1 b/vCheck.ps1 index 0e423536..02f6a47a 100644 --- a/vCheck.ps1 +++ b/vCheck.ps1 @@ -1,1063 +1,1063 @@ -<# -.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.24 - - 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 -#> -#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 -) - -$vCheckVersion = "6.24" -$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 = 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 - -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 += "" - - do { - $Question = $file[$Line] - $QuestionWithoutHash = $Question.Replace("# ", "") - $Line++ - $Split = ($file[$Line]).Split("=") - $Var = $Split[0].Trim() - if ($Split.count -gt 1) { - $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 - - $htmlOutput += "`n" - } - } Until ($Line -ge ($EndLine - 1)) - - $htmlOutput += "
$QuestionWithoutHash
" - $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 - $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; - } - } - } -} - - -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); - } - } - }" -} -#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}_vCheck-Config_{2}.html" -f $Outputpath, $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 ($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 -} +<# +.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 +#> +#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 +) + +$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 = 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 + +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 += "
" + + do { + $Question = $file[$Line] + $QuestionWithoutHash = $Question.Replace("# ", "") + $Line++ + $Split = ($file[$Line]).Split("=") + $Var = $Split[0].Trim() + if ($Split.count -gt 1) { + $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 + + $htmlOutput += "`n" + } + } Until ($Line -ge ($EndLine - 1)) + + $htmlOutput += "
$QuestionWithoutHash
" + $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 + $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; + } + } + } +} + + +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); + } + } + }" +} +#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}_vCheck-Config_{2}.html" -f $Outputpath, $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 ($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 +}