diff --git a/EnhancedHTML2.psd1 b/EnhancedHTML2.psd1 index c7638bb..5432598 100644 --- a/EnhancedHTML2.psd1 +++ b/EnhancedHTML2.psd1 @@ -1,666 +1,96 @@ -$script = '' -function ConvertTo-EnhancedHTML { -<# -.SYNOPSIS -Provides an enhanced version of the ConvertTo-HTML command that includes -inserting an embedded CSS style sheet, JQuery, and JQuery Data Tables for -interactivity. Intended to be used with HTML fragments that are produced -by ConvertTo-EnhancedHTMLFragment. This command does not accept pipeline -input. +# +# Module manifest for module 'EnhancedHTML2' +# +# Generated by: Don Jones +# +# Generated on: 9/10/2013 +# +@{ -.PARAMETER jQueryURI -A Uniform Resource Indicator (URI) pointing to the location of the -jQuery script file. You can download jQuery from www.jquery.com; you should -host the script file on a local intranet Web server and provide a URI -that starts with http:// or https://. Alternately, you can also provide -a file system path to the script file, although this may create security -issues for the Web browser in some configurations. +# Script module or binary module file associated with this manifest. +RootModule = '.\EnhancedHTML2.psm1' +# Version number of this module. +ModuleVersion = '2.1.0.1' -Tested with v1.8.2. +# ID used to uniquely identify this module +GUID = '49ec726c-f962-4d61-896c-bcb11147c382' +# Author of this module +Author = 'Don Jones' -Defaults to http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.2.min.js, which -will pull the file from Microsoft's ASP.NET Content Delivery Network. +# Company or vendor of this module +CompanyName = 'PowerShell.org' +# Copyright statement for this module +Copyright = '(c) 2013 Don Jones. All rights reserved.' -.PARAMETER jQueryDataTableURI -A Uniform Resource Indicator (URI) pointing to the location of the -jQuery Data Table script file. You can download this from www.datatables.net; -you should host the script file on a local intranet Web server and provide a URI -that starts with http:// or https://. Alternately, you can also provide -a file system path to the script file, although this may create security -issues for the Web browser in some configurations. +# Description of the functionality provided by this module +Description = 'EnhancedHTML2 module from free ebook Creating HTML Reports in PowerShell' +# Minimum version of the Windows PowerShell engine required by this module +PowerShellVersion = '2.0' -Tested with jQuery DataTable v1.9.4 +# Name of the Windows PowerShell host required by this module +# PowerShellHostName = '' +# Minimum version of the Windows PowerShell host required by this module +# PowerShellHostVersion = '' -Defaults to http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.3/jquery.dataTables.min.js, -which will pull the file from Microsoft's ASP.NET Content Delivery Network. +# Minimum version of the .NET Framework required by this module +# DotNetFrameworkVersion = '' +# Minimum version of the common language runtime (CLR) required by this module +# CLRVersion = '' -.PARAMETER CssStyleSheet -The CSS style sheet content - not a file name. If you have a CSS file, -you can load it into this parameter as follows: +# Processor architecture (None, X86, Amd64) required by this module +# ProcessorArchitecture = '' +# Modules that must be imported into the global environment prior to importing this module +# RequiredModules = @() - -CSSStyleSheet (Get-Content MyCSSFile.css) +# Assemblies that must be loaded prior to importing this module +# RequiredAssemblies = @() +# Script files (.ps1) that are run in the caller's environment prior to importing this module. +# ScriptsToProcess = @() -Alternately, you may link to a Web server-hosted CSS file by using the --CssUri parameter. +# Type files (.ps1xml) to be loaded when importing this module +# TypesToProcess = @() +# Format files (.ps1xml) to be loaded when importing this module +# FormatsToProcess = @() -.PARAMETER CssUri -A Uniform Resource Indicator (URI) to a Web server-hosted CSS file. -Must start with either http:// or https://. If you omit this, you -can still provide an embedded style sheet, which makes the resulting -HTML page more standalone. To provide an embedded style sheet, use -the -CSSStyleSheet parameter. +# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess +# NestedModules = @() +# Functions to export from this module +FunctionsToExport = '*' -.PARAMETER Title -A plain-text title that will be displayed in the Web browser's window -title bar. Note that not all browsers will display this. +# Cmdlets to export from this module +CmdletsToExport = 'ConvertTo-EnhancedHTML','ConvertTo-EnhancedHTMLFragment' +# Variables to export from this module +VariablesToExport = '*' -.PARAMETER PreContent -Raw HTML to insert before all HTML fragments. Use this to specify a main -title for the report: +# Aliases to export from this module +AliasesToExport = '*' +# List of all modules packaged with this module. +# ModuleList = @() - -PreContent "

My HTML Report

" +# List of all files packaged with this module +# FileList = @() +# Private data to pass to the module specified in RootModule/ModuleToProcess +# PrivateData = '' -.PARAMETER PostContent -Raw HTML to insert after all HTML fragments. Use this to specify a -report footer: +# HelpInfo URI of this module +# HelpInfoURI = '' +# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. +# DefaultCommandPrefix = '' - -PostContent "Created on $(Get-Date)" - - -.PARAMETER HTMLFragments -One or more HTML fragments, as produced by ConvertTo-EnhancedHTMLFragment. - - - -HTMLFragments $part1,$part2,$part3 -.EXAMPLE -For examples, please see the free ebooks, "Creating HTML Reports in PowerShell," -available at http://powershell.org/ebooks. - - - -#> - [CmdletBinding()] - param( - [string]$jQueryURI = 'http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.2.min.js', - [string]$jQueryDataTableURI = 'http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.3/jquery.dataTables.min.js', - [Parameter(ParameterSetName='CSSContent')][string[]]$CssStyleSheet, - [Parameter(ParameterSetName='CSSURI')][string[]]$CssUri, - [string]$Title = 'Report', - [string]$PreContent, - [string]$PostContent, - [Parameter(Mandatory=$True)][string[]]$HTMLFragments - ) - - - <# - Add CSS style sheet. If provided in -CssUri, add a element. - If provided in -CssStyleSheet, embed in the section. - Note that BOTH may be supplied - this is legitimate in HTML. - #> - Write-Verbose "Making CSS style sheet" - $stylesheet = "" - if ($PSBoundParameters.ContainsKey('CssUri')) { - $stylesheet = "" - } - if ($PSBoundParameters.ContainsKey('CssStyleSheet')) { - $stylesheet = "" | Out-String - } - - - <# - Create the HTML tags for the page title, and for - our main javascripts. - #> - Write-Verbose "Creating and <SCRIPT> tags" - $titletag = "" - if ($PSBoundParameters.ContainsKey('title')) { - $titletag = "<title>$title" - } - $script += "`n" - - - <# - Render supplied HTML fragments as one giant string - #> - Write-Verbose "Combining HTML fragments" - $body = $HTMLFragments | Out-String - - - <# - If supplied, add pre- and post-content strings - #> - Write-Verbose "Adding Pre and Post content" - if ($PSBoundParameters.ContainsKey('precontent')) { - $body = "$PreContent`n$body" - } - if ($PSBoundParameters.ContainsKey('postcontent')) { - $body = "$body`n$PostContent" - } - - - <# - Add a final script that calls the datatable code - We dynamic-ize all tables with the .enhancedhtml-dynamic-table - class, which is added by ConvertTo-EnhancedHTMLFragment. - #> - Write-Verbose "Adding interactivity calls" - $datatable = "" - $datatable = "" - - - <# - Datatables expect a section containing the - table header row; ConvertTo-HTML doesn't produce that - so we have to fix it. - #> - Write-Verbose "Fixing table HTML" - $body = $body -replace '','' - $body = $body -replace '','' - - - <# - Produce the final HTML. We've more or less hand-made - the amd sections, but we let ConvertTo-HTML - produce the other bits of the page. - #> - Write-Verbose "Producing final HTML" - ConvertTo-HTML -Head "$stylesheet`n$titletag`n$script`n$datatable" -Body $body - Write-Debug "Finished producing final HTML" - - -} - - -function ConvertTo-EnhancedHTMLFragment { -<# -.SYNOPSIS -Creates an HTML fragment (much like ConvertTo-HTML with the -Fragment switch -that includes CSS class names for table rows, CSS class and ID names for the -table, and wraps the table in a
tag that has a CSS class and ID name. - - -.PARAMETER InputObject -The object to be converted to HTML. You cannot select properties using this -command; precede this command with Select-Object if you need a subset of -the objects' properties. - - -.PARAMETER EvenRowCssClass -The CSS class name applied to even-numbered tags. Optional, but if you -use it you must also include -OddRowCssClass. - - -.PARAMETER OddRowCssClass -The CSS class name applied to odd-numbered tags. Optional, but if you -use it you must also include -EvenRowCssClass. - - -.PARAMETER TableCssID -Optional. The CSS ID name applied to the tag. - - -.PARAMETER DivCssID -Optional. The CSS ID name applied to the
tag which is wrapped around the table. - - -.PARAMETER TableCssClass -Optional. The CSS class name to apply to the
tag. - - -.PARAMETER DivCssClass -Optional. The CSS class name to apply to the wrapping
tag. - - -.PARAMETER As -Must be 'List' or 'Table.' Defaults to Table. Actually produces an HTML -table either way; with Table the output is a grid-like display. With -List the output is a two-column table with properties in the left column -and values in the right column. - - -.PARAMETER Properties -A comma-separated list of properties to include in the HTML fragment. -This can be * (which is the default) to include all properties of the -piped-in object(s). In addition to property names, you can also use a -hashtable similar to that used with Select-Object. For example: - - - Get-Process | ConvertTo-EnhancedHTMLFragment -As Table ` - -Properties Name,ID,@{n='VM'; - e={$_.VM}; - css={if ($_.VM -gt 100) { 'red' } - else { 'green' }}} - - -This will create table cell rows with the calculated CSS class names. -E.g., for a process with a VM greater than 100, you'd get: - - -
- -You can use this feature to specify a CSS class for each table cell -based upon the contents of that cell. Valid keys in the hashtable are: - - - n, name, l, or label: The table column header - e or expression: The table cell contents - css or csslcass: The CSS class name to apply to the section containing the + table header row; ConvertTo-HTML doesn't produce that + so we have to fix it. + #> + Write-Verbose "Fixing table HTML" + $body = $body -replace '','' + + + <# + Produce the final HTML. We've more or less hand-made + the amd sections, but we let ConvertTo-HTML + produce the other bits of the page. + #> + Write-Verbose "Producing final HTML" + ConvertTo-HTML -Head "$stylesheet`n$titletag`n$script`n$customHeader`n$datatable" -Body $body + Write-Debug "Finished producing final HTML" + + +} + + +function ConvertTo-EnhancedHTMLFragment { +<# +.SYNOPSIS +Creates an HTML fragment (much like ConvertTo-HTML with the -Fragment switch +that includes CSS class names for table rows, CSS class and ID names for the +table, and wraps the table in a
tag that has a CSS class and ID name. + + +.PARAMETER InputObject +The object to be converted to HTML. You cannot select properties using this +command; precede this command with Select-Object if you need a subset of +the objects' properties. + + +.PARAMETER EvenRowCssClass +The CSS class name applied to even-numbered
tags. Optional, but if you +use it you must also include -OddRowCssClass. + + +.PARAMETER OddRowCssClass +The CSS class name applied to odd-numbered tags. Optional, but if you +use it you must also include -EvenRowCssClass. + + +.PARAMETER TableCssID +Optional. The CSS ID name applied to the
475858 tag - -Another example: - - - @{n='Free(MB)'; - e={$_.FreeSpace / 1MB -as [int]}; - css={ if ($_.FreeSpace -lt 100) { 'red' } else { 'blue' }} - -This example creates a column titled "Free(MB)". It will contain -the input object's FreeSpace property, divided by 1MB and cast -as a whole number (integer). If the value is less than 100, the -table cell will be given the CSS class "red." If not, the table -cell will be given the CSS class "blue." The supplied cascading -style sheet must define ".red" and ".blue" for those to have any -effect. - - -.PARAMETER PreContent -Raw HTML content to be placed before the wrapping
tag. -For example: - - - -PreContent "

Section A

" - - -.PARAMETER PostContent -Raw HTML content to be placed after the wrapping
tag. -For example: - - - -PostContent "
" - - -.PARAMETER MakeHiddenSection -Used in conjunction with -PreContent. Adding this switch, which -needs no value, turns your -PreContent into clickable report -section header. The section will be hidden by default, and clicking -the header will toggle its visibility. - - -When using this parameter, consider adding a symbol to your -PreContent -that helps indicate this is an expandable section. For example: - - - -PreContent '

♦ My Section

' - - -If you use -MakeHiddenSection, you MUST provide -PreContent also, or -the hidden section will not have a section header and will not be -visible. - - -.PARAMETER MakeTableDynamic -When using "-As Table", makes the table dynamic. Will be ignored -if you use "-As List". Dynamic tables are sortable, searchable, and -are paginated. - - -You should not use even/odd styling with tables that are made -dynamic. Dynamic tables automatically have their own even/odd -styling. You can apply CSS classes named ".odd" and ".even" in -your CSS to style the even/odd in a dynamic table. - - -.EXAMPLE - $fragment = Get-WmiObject -Class Win32_LogicalDisk | - Select-Object -Property PSComputerName,DeviceID,FreeSpace,Size | - ConvertTo-HTMLFragment -EvenRowClass 'even' ` - -OddRowClass 'odd' ` - -PreContent '

Disk Report

' ` - -MakeHiddenSection ` - -MakeTableDynamic - - - You will usually save fragments to a variable, so that multiple fragments - (each in its own variable) can be passed to ConvertTo-EnhancedHTML. - -.NOTES -Consider adding the following to your CSS when using dynamic tables -(replace the * with .): - - - *paginate_enabled_next, .paginate_enabled_previous { - cursor:pointer; - border:1px solid #222222; - background-color:#dddddd; - padding:2px; - margin:4px; - border-radius:2px; - } - *paginate_disabled_previous, .paginate_disabled_next { - color:#666666; - cursor:pointer; - background-color:#dddddd; - padding:2px; - margin:4px; - border-radius:2px; - } - *dataTables_info { margin-bottom:4px; } - - -This applies appropriate coloring to the next/previous buttons, -and applies a small amount of space after the dynamic table. - - -If you choose to make sections hidden (meaning they can be shown -and hidden by clicking on the section header), consider adding -the following to your CSS (replace the * with .): - - - *sectionheader { cursor:pointer; } - *sectionheader:hover { color:red; } - - -This will apply a hover-over color, and change the cursor icon, -to help visually indicate that the section can be toggled. -#> - [CmdletBinding()] - param( - [Parameter(Mandatory=$True,ValueFromPipeline=$True)] - [object[]]$InputObject, - - - [string]$EvenRowCssClass, - [string]$OddRowCssClass, - [string]$TableCssID, - [string]$DivCssID, - [string]$DivCssClass, - [string]$TableCssClass, - - - [ValidateSet('List','Table')] - [string]$As = 'Table', - - - [object[]]$Properties = '*', - - - [string]$PreContent, - - - [switch]$MakeHiddenSection, - - - [switch]$MakeTableDynamic, - - - [string]$PostContent - ) - BEGIN { - <# - Accumulate output in a variable so that we don't - produce an array of strings to the pipeline, but - instead produce a single string. - #> - $out = '' - - - <# - Add the section header (pre-content). If asked to - make this section of the report hidden, set the - appropriate code on the section header to toggle - the underlying table. Note that we generate a GUID - to use as an additional ID on the
, so that - we can uniquely refer to it without relying on the - user supplying us with a unique ID. - #> - Write-Verbose "Precontent" - if ($PSBoundParameters.ContainsKey('PreContent')) { - if ($PSBoundParameters.ContainsKey('MakeHiddenSection')) { - [string]$tempid = [System.Guid]::NewGuid() - $out += "$PreContent`n" - } else { - $out += $PreContent - $tempid = '' - } - } - - - <# - The table will be wrapped in a
tag for styling - purposes. Note that THIS, not the table per se, is what - we hide for -MakeHiddenSection. So we will hide the section - if asked to do so. - #> - Write-Verbose "DIV" - if ($PSBoundParameters.ContainsKey('DivCSSClass')) { - $temp = " class=`"$DivCSSClass`"" - } else { - $temp = "" - } - if ($PSBoundParameters.ContainsKey('MakeHiddenSection')) { - $temp += " id=`"$tempid`" style=`"display:none;`"" - } else { - $tempid = '' - } - if ($PSBoundParameters.ContainsKey('DivCSSID')) { - $temp += " id=`"$DivCSSID`"" - } - $out += "
" - - - <# - Create the table header. If asked to make the table dynamic, - we add the CSS style that ConvertTo-EnhancedHTML will look for - to dynamic-ize tables. - #> - Write-Verbose "TABLE" - $_TableCssClass = '' - if ($PSBoundParameters.ContainsKey('MakeTableDynamic') -and $As -eq 'Table') { - $_TableCssClass += 'enhancedhtml-dynamic-table ' - } - if ($PSBoundParameters.ContainsKey('TableCssClass')) { - $_TableCssClass += $TableCssClass - } - if ($_TableCssClass -ne '') { - $css = "class=`"$_TableCSSClass`"" - } else { - $css = "" - } - if ($PSBoundParameters.ContainsKey('TableCSSID')) { - $css += "id=`"$TableCSSID`"" - } else { - if ($tempid -ne '') { - $css += "id=`"$tempid`"" - } - } - $out += "" - - - <# - We're now setting up to run through our input objects - and create the table rows - #> - $fragment = '' - $wrote_first_line = $false - $even_row = $false - - - if ($properties -eq '*') { - $all_properties = $true - } else { - $all_properties = $false - } - - - } - PROCESS { - - - foreach ($object in $inputobject) { - Write-Verbose "Processing object" - $datarow = '' - $headerrow = '' - - - <# - Apply even/odd row class. Note that this will mess up the output - if the table is made dynamic. That's noted in the help. - #> - if ($PSBoundParameters.ContainsKey('EvenRowCSSClass') -and $PSBoundParameters.ContainsKey('OddRowCssClass')) { - if ($even_row) { - $row_css = $OddRowCSSClass - $even_row = $false - Write-Verbose "Even row" - } else { - $row_css = $EvenRowCSSClass - $even_row = $true - Write-Verbose "Odd row" - } - } else { - $row_css = '' - Write-Verbose "No row CSS class" - } - - - <# - If asked to include all object properties, get them. - #> - if ($all_properties) { - $properties = $object | Get-Member -MemberType Properties | Select -ExpandProperty Name - } - - - <# - We either have a list of all properties, or a hashtable of - properties to play with. Process the list. - #> - foreach ($prop in $properties) { - Write-Verbose "Processing property" - $name = $null - $value = $null - $cell_css = '' - - - <# - $prop is a simple string if we are doing "all properties," - otherwise it is a hashtable. If it's a string, then we - can easily get the name (it's the string) and the value. - #> - if ($prop -is [string]) { - Write-Verbose "Property $prop" - $name = $Prop - $value = $object.($prop) - } elseif ($prop -is [hashtable]) { - Write-Verbose "Property hashtable" - <# - For key "css" or "cssclass," execute the supplied script block. - It's expected to output a class name; we embed that in the "class" - attribute later. - #> - if ($prop.ContainsKey('cssclass')) { $cell_css = $Object | ForEach $prop['cssclass'] } - if ($prop.ContainsKey('css')) { $cell_css = $Object | ForEach $prop['css'] } - - - <# - Get the current property name. - #> - if ($prop.ContainsKey('n')) { $name = $prop['n'] } - if ($prop.ContainsKey('name')) { $name = $prop['name'] } - if ($prop.ContainsKey('label')) { $name = $prop['label'] } - if ($prop.ContainsKey('l')) { $name = $prop['l'] } - - - <# - Execute the "expression" or "e" key to get the value of the property. - #> - if ($prop.ContainsKey('e')) { $value = $Object | ForEach $prop['e'] } - if ($prop.ContainsKey('expression')) { $value = $Object | ForEach $prop['expression'] } - - - <# - Make sure we have a name and a value at this point. - #> - if ($name -eq $null -or $value -eq $null) { - Write-Error "Hashtable missing Name and/or Expression key" - } - } else { - <# - We got a property list that wasn't strings and - wasn't hashtables. Bad input. - #> - Write-Warning "Unhandled property $prop" - } - - - <# - When constructing a table, we have to remember the - property names so that we can build the table header. - In a list, it's easier - we output the property name - and the value at the same time, since they both live - on the same row of the output. - #> - if ($As -eq 'table') { - Write-Verbose "Adding $name to header and $value to row" - $headerrow += "" - $datarow += "$value" - } else { - $wrote_first_line = $true - $headerrow = "" - $datarow = "$name :$value" - $out += "$datarow" - } - } - - - <# - Write the table header, if we're doing a table. - #> - if (-not $wrote_first_line -and $as -eq 'Table') { - Write-Verbose "Writing header row" - $out += "$headerrow" - $wrote_first_line = $true - } - - - <# - In table mode, write the data row. - #> - if ($as -eq 'table') { - Write-Verbose "Writing data row" - $out += "$datarow" - } - } - } - END { - <# - Finally, post-content code, the end of the table, - the end of the
, and write the final string. - #> - Write-Verbose "PostContent" - if ($PSBoundParameters.ContainsKey('PostContent')) { - $out += "`n$PostContent" - } - Write-Verbose "Done" - $out += "
$name
" - Write-Output $out - } } diff --git a/EnhancedHTML2.psm1 b/EnhancedHTML2.psm1 index 5432598..bed3ace 100644 --- a/EnhancedHTML2.psm1 +++ b/EnhancedHTML2.psm1 @@ -1,96 +1,690 @@ -# -# Module manifest for module 'EnhancedHTML2' -# -# Generated by: Don Jones -# -# Generated on: 9/10/2013 -# +$script = '' +function ConvertTo-EnhancedHTML { +<# +.SYNOPSIS +Provides an enhanced version of the ConvertTo-HTML command that includes +inserting an embedded CSS style sheet, JQuery, and JQuery Data Tables for +interactivity. Intended to be used with HTML fragments that are produced +by ConvertTo-EnhancedHTMLFragment. This command does not accept pipeline +input. -@{ -# Script module or binary module file associated with this manifest. -RootModule = '.\EnhancedHTML2.psm1' +.PARAMETER jQueryURI +A Uniform Resource Indicator (URI) pointing to the location of the +jQuery script file. You can download jQuery from www.jquery.com; you should +host the script file on a local intranet Web server and provide a URI +that starts with http:// or https://. Alternately, you can also provide +a file system path to the script file, although this may create security +issues for the Web browser in some configurations. -# Version number of this module. -ModuleVersion = '2.1.0.1' -# ID used to uniquely identify this module -GUID = '49ec726c-f962-4d61-896c-bcb11147c382' +Tested with v1.8.2. -# Author of this module -Author = 'Don Jones' -# Company or vendor of this module -CompanyName = 'PowerShell.org' +Defaults to http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.2.min.js, which +will pull the file from Microsoft's ASP.NET Content Delivery Network. -# Copyright statement for this module -Copyright = '(c) 2013 Don Jones. All rights reserved.' -# Description of the functionality provided by this module -Description = 'EnhancedHTML2 module from free ebook Creating HTML Reports in PowerShell' +.PARAMETER jQueryDataTableURI +A Uniform Resource Indicator (URI) pointing to the location of the +jQuery Data Table script file. You can download this from www.datatables.net; +you should host the script file on a local intranet Web server and provide a URI +that starts with http:// or https://. Alternately, you can also provide +a file system path to the script file, although this may create security +issues for the Web browser in some configurations. -# Minimum version of the Windows PowerShell engine required by this module -PowerShellVersion = '2.0' -# Name of the Windows PowerShell host required by this module -# PowerShellHostName = '' +Tested with jQuery DataTable v1.9.4 -# Minimum version of the Windows PowerShell host required by this module -# PowerShellHostVersion = '' -# Minimum version of the .NET Framework required by this module -# DotNetFrameworkVersion = '' +Defaults to http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.3/jquery.dataTables.min.js, +which will pull the file from Microsoft's ASP.NET Content Delivery Network. -# Minimum version of the common language runtime (CLR) required by this module -# CLRVersion = '' -# Processor architecture (None, X86, Amd64) required by this module -# ProcessorArchitecture = '' +.PARAMETER CssStyleSheet +The CSS style sheet content - not a file name. If you have a CSS file, +you can load it into this parameter as follows: -# Modules that must be imported into the global environment prior to importing this module -# RequiredModules = @() -# Assemblies that must be loaded prior to importing this module -# RequiredAssemblies = @() + -CSSStyleSheet (Get-Content MyCSSFile.css) -# Script files (.ps1) that are run in the caller's environment prior to importing this module. -# ScriptsToProcess = @() -# Type files (.ps1xml) to be loaded when importing this module -# TypesToProcess = @() +Alternately, you may link to a Web server-hosted CSS file by using the +-CssUri parameter. -# Format files (.ps1xml) to be loaded when importing this module -# FormatsToProcess = @() -# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess -# NestedModules = @() +.PARAMETER CssUri +A Uniform Resource Indicator (URI) to a Web server-hosted CSS file. +Must start with either http:// or https://. If you omit this, you +can still provide an embedded style sheet, which makes the resulting +HTML page more standalone. To provide an embedded style sheet, use +the -CSSStyleSheet parameter. -# Functions to export from this module -FunctionsToExport = '*' -# Cmdlets to export from this module -CmdletsToExport = 'ConvertTo-EnhancedHTML','ConvertTo-EnhancedHTMLFragment' +.PARAMETER customHeader +A valid well formed raw HTML list of pointers to external stylers\frameworks +to customize page. Refer to external components documentation about the +HTML code required to import components + + $externalStyle=@" + + + "@ + -customHeader $externalStyle -# Variables to export from this module -VariablesToExport = '*' -# Aliases to export from this module -AliasesToExport = '*' +.PARAMETER Title +A plain-text title that will be displayed in the Web browser's window +title bar. Note that not all browsers will display this. -# List of all modules packaged with this module. -# ModuleList = @() -# List of all files packaged with this module -# FileList = @() +.PARAMETER PreContent +Raw HTML to insert before all HTML fragments. Use this to specify a main +title for the report: -# Private data to pass to the module specified in RootModule/ModuleToProcess -# PrivateData = '' -# HelpInfo URI of this module -# HelpInfoURI = '' + -PreContent "

My HTML Report

" -# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. -# DefaultCommandPrefix = '' +.PARAMETER PostContent +Raw HTML to insert after all HTML fragments. Use this to specify a +report footer: + + + -PostContent "Created on $(Get-Date)" + + +.PARAMETER HTMLFragments +One or more HTML fragments, as produced by ConvertTo-EnhancedHTMLFragment. + + + -HTMLFragments $part1,$part2,$part3 +.EXAMPLE +For examples, please see the free ebooks, "Creating HTML Reports in PowerShell," +available at http://powershell.org/ebooks. + + + +#> + [CmdletBinding()] + param( + [string]$jQueryURI = 'http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.2.min.js', + [string]$jQueryDataTableURI = 'http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.3/jquery.dataTables.min.js', + [Parameter(ParameterSetName='CSSContent')][string[]]$CssStyleSheet, + [Parameter(ParameterSetName='CSSURI')][string[]]$CssUri, + [string[]]$customHeader="", + [string]$Title = 'Report', + [string]$PreContent, + [string]$PostContent, + [Parameter(Mandatory=$True)][string[]]$HTMLFragments + ) + + + <# + Add CSS style sheet. If provided in -CssUri, add a element. + If provided in -CssStyleSheet, embed in the section. + Note that BOTH may be supplied - this is legitimate in HTML. + #> + Write-Verbose "Making CSS style sheet" + $stylesheet = "" + if ($PSBoundParameters.ContainsKey('CssUri')) { + $stylesheet = "" + } + if ($PSBoundParameters.ContainsKey('CssStyleSheet')) { + $stylesheet = "" | Out-String + } + + + <# + Create the HTML tags for the page title, and for + our main javascripts. + #> + Write-Verbose "Creating and <SCRIPT> tags" + $titletag = "" + if ($PSBoundParameters.ContainsKey('title')) { + $titletag = "<title>$title" + } + $script += "`n" + + + <# + Render supplied HTML fragments as one giant string + #> + Write-Verbose "Combining HTML fragments" + $body = $HTMLFragments | Out-String + + + <# + If supplied, add pre- and post-content strings + #> + Write-Verbose "Adding Pre and Post content" + if ($PSBoundParameters.ContainsKey('precontent')) { + $body = "$PreContent`n$body" + } + if ($PSBoundParameters.ContainsKey('postcontent')) { + $body = "$body`n$PostContent" + } + + + <# + Add a final script that calls the datatable code + We dynamic-ize all tables with the .enhancedhtml-dynamic-table + class, which is added by ConvertTo-EnhancedHTMLFragment. + #> + Write-Verbose "Adding interactivity calls" + $datatable = "" + $datatable = "" + + + <# + Datatables expect a
','
' + $body = $body -replace '
tag. + + +.PARAMETER DivCssID +Optional. The CSS ID name applied to the
tag which is wrapped around the table. + + +.PARAMETER TableCssClass +Optional. The CSS class name to apply to the
tag. + + +.PARAMETER DivCssClass +Optional. The CSS class name to apply to the wrapping
tag. + + +.PARAMETER As +Must be 'List' or 'Table.' Defaults to Table. Actually produces an HTML +table either way; with Table the output is a grid-like display. With +List the output is a two-column table with properties in the left column +and values in the right column. + + +.PARAMETER Properties +A comma-separated list of properties to include in the HTML fragment. +This can be * (which is the default) to include all properties of the +piped-in object(s). In addition to property names, you can also use a +hashtable similar to that used with Select-Object. For example: + + + Get-Process | ConvertTo-EnhancedHTMLFragment -As Table ` + -Properties Name,ID,@{n='VM'; + e={$_.VM}; + css={if ($_.VM -gt 100) { 'red' } + else { 'green' }}} + + +This will create table cell rows with the calculated CSS class names. +E.g., for a process with a VM greater than 100, you'd get: + + +
+ +You can use this feature to specify a CSS class for each table cell +based upon the contents of that cell. Valid keys in the hashtable are: + + + n, name, l, or label: The table column header + e or expression: The table cell contents + css or csslcass: The CSS class name to apply to the
475858 tag + +Another example: + + + @{n='Free(MB)'; + e={$_.FreeSpace / 1MB -as [int]}; + css={ if ($_.FreeSpace -lt 100) { 'red' } else { 'blue' }} + +This example creates a column titled "Free(MB)". It will contain +the input object's FreeSpace property, divided by 1MB and cast +as a whole number (integer). If the value is less than 100, the +table cell will be given the CSS class "red." If not, the table +cell will be given the CSS class "blue." The supplied cascading +style sheet must define ".red" and ".blue" for those to have any +effect. + + +.PARAMETER PreContent +Raw HTML content to be placed before the wrapping
tag. +For example: + + + -PreContent "

Section A

" + + +.PARAMETER PostContent +Raw HTML content to be placed after the wrapping
tag. +For example: + + + -PostContent "
" + + +.PARAMETER MakeHiddenSection +Used in conjunction with -PreContent. Adding this switch, which +needs no value, turns your -PreContent into clickable report +section header. The section will be hidden by default, and clicking +the header will toggle its visibility. + + +When using this parameter, consider adding a symbol to your -PreContent +that helps indicate this is an expandable section. For example: + + + -PreContent '

♦ My Section

' + + +If you use -MakeHiddenSection, you MUST provide -PreContent also, or +the hidden section will not have a section header and will not be +visible. + + +.PARAMETER MakeTableDynamic +When using "-As Table", makes the table dynamic. Will be ignored +if you use "-As List". Dynamic tables are sortable, searchable, and +are paginated. + + +You should not use even/odd styling with tables that are made +dynamic. Dynamic tables automatically have their own even/odd +styling. You can apply CSS classes named ".odd" and ".even" in +your CSS to style the even/odd in a dynamic table. + + +.EXAMPLE + $fragment = Get-WmiObject -Class Win32_LogicalDisk | + Select-Object -Property PSComputerName,DeviceID,FreeSpace,Size | + ConvertTo-HTMLFragment -EvenRowClass 'even' ` + -OddRowClass 'odd' ` + -PreContent '

Disk Report

' ` + -MakeHiddenSection ` + -MakeTableDynamic + + + You will usually save fragments to a variable, so that multiple fragments + (each in its own variable) can be passed to ConvertTo-EnhancedHTML. + +.NOTES +Consider adding the following to your CSS when using dynamic tables +(replace the * with .): + + + *paginate_enabled_next, .paginate_enabled_previous { + cursor:pointer; + border:1px solid #222222; + background-color:#dddddd; + padding:2px; + margin:4px; + border-radius:2px; + } + *paginate_disabled_previous, .paginate_disabled_next { + color:#666666; + cursor:pointer; + background-color:#dddddd; + padding:2px; + margin:4px; + border-radius:2px; + } + *dataTables_info { margin-bottom:4px; } + + +This applies appropriate coloring to the next/previous buttons, +and applies a small amount of space after the dynamic table. + + +If you choose to make sections hidden (meaning they can be shown +and hidden by clicking on the section header), consider adding +the following to your CSS (replace the * with .): + + + *sectionheader { cursor:pointer; } + *sectionheader:hover { color:red; } + + +This will apply a hover-over color, and change the cursor icon, +to help visually indicate that the section can be toggled. +#> + [CmdletBinding()] + param( + [Parameter(Mandatory=$True,ValueFromPipeline=$True)] + [object[]]$InputObject, + + + [string]$EvenRowCssClass, + [string]$OddRowCssClass, + [string]$TableCssID, + [string]$DivCssID, + [string]$DivCssClass, + [string]$TableCssClass, + + + [ValidateSet('List','Table')] + [string]$As = 'Table', + + + [object[]]$Properties = '*', + + + [string]$PreContent, + + + [switch]$MakeHiddenSection, + + + [switch]$MakeTableDynamic, + + + [string]$PostContent + ) + BEGIN { + <# + Accumulate output in a variable so that we don't + produce an array of strings to the pipeline, but + instead produce a single string. + #> + $out = '' + + + <# + Add the section header (pre-content). If asked to + make this section of the report hidden, set the + appropriate code on the section header to toggle + the underlying table. Note that we generate a GUID + to use as an additional ID on the
, so that + we can uniquely refer to it without relying on the + user supplying us with a unique ID. + #> + Write-Verbose "Precontent" + if ($PSBoundParameters.ContainsKey('PreContent')) { + if ($PSBoundParameters.ContainsKey('MakeHiddenSection')) { + [string]$tempid = [System.Guid]::NewGuid() + $out += "$PreContent`n" + } else { + $out += $PreContent + $tempid = '' + } + } + + + <# + The table will be wrapped in a
tag for styling + purposes. Note that THIS, not the table per se, is what + we hide for -MakeHiddenSection. So we will hide the section + if asked to do so. + #> + Write-Verbose "DIV" + if ($PSBoundParameters.ContainsKey('DivCSSClass')) { + $temp = " class=`"$DivCSSClass`"" + } else { + $temp = "" + } + if ($PSBoundParameters.ContainsKey('MakeHiddenSection')) { + $temp += " id=`"$tempid`" style=`"display:none;`"" + } else { + $tempid = '' + } + if ($PSBoundParameters.ContainsKey('DivCSSID')) { + $temp += " id=`"$DivCSSID`"" + } + $out += "
" + + + <# + Create the table header. If asked to make the table dynamic, + we add the CSS style that ConvertTo-EnhancedHTML will look for + to dynamic-ize tables. + #> + Write-Verbose "TABLE" + $_TableCssClass = '' + if ($PSBoundParameters.ContainsKey('MakeTableDynamic') -and $As -eq 'Table') { + $_TableCssClass += 'enhancedhtml-dynamic-table ' + } + if ($PSBoundParameters.ContainsKey('TableCssClass')) { + $_TableCssClass += $TableCssClass + } + if ($_TableCssClass -ne '') { + $css = "class=`"$_TableCSSClass`"" + } else { + $css = "" + } + if ($PSBoundParameters.ContainsKey('TableCSSID')) { + $css += "id=`"$TableCSSID`"" + } else { + if ($tempid -ne '') { + $css += "id=`"$tempid`"" + } + } + $out += "" + + + <# + We're now setting up to run through our input objects + and create the table rows + #> + $fragment = '' + $wrote_first_line = $false + $even_row = $false + + + if ($properties -eq '*') { + $all_properties = $true + } else { + $all_properties = $false + } + + + } + PROCESS { + + + foreach ($object in $inputobject) { + Write-Verbose "Processing object" + $datarow = '' + $headerrow = '' + + + <# + Apply even/odd row class. Note that this will mess up the output + if the table is made dynamic. That's noted in the help. + #> + if ($PSBoundParameters.ContainsKey('EvenRowCSSClass') -and $PSBoundParameters.ContainsKey('OddRowCssClass')) { + if ($even_row) { + $row_css = $OddRowCSSClass + $even_row = $false + Write-Verbose "Even row" + } else { + $row_css = $EvenRowCSSClass + $even_row = $true + Write-Verbose "Odd row" + } + } else { + $row_css = '' + Write-Verbose "No row CSS class" + } + + + <# + If asked to include all object properties, get them. + #> + if ($all_properties) { + $properties = $object | Get-Member -MemberType Properties | Select -ExpandProperty Name + } + + + <# + We either have a list of all properties, or a hashtable of + properties to play with. Process the list. + #> + foreach ($prop in $properties) { + Write-Verbose "Processing property" + $name = $null + $value = $null + $cell_css = '' + + + <# + $prop is a simple string if we are doing "all properties," + otherwise it is a hashtable. If it's a string, then we + can easily get the name (it's the string) and the value. + #> + if ($prop -is [string]) { + Write-Verbose "Property $prop" + $name = $Prop + $value = $object.($prop) + } elseif ($prop -is [hashtable]) { + Write-Verbose "Property hashtable" + <# + For key "css" or "cssclass," execute the supplied script block. + It's expected to output a class name; we embed that in the "class" + attribute later. + #> + if ($prop.ContainsKey('cssclass')) { $cell_css = $Object | ForEach $prop['cssclass'] } + if ($prop.ContainsKey('css')) { $cell_css = $Object | ForEach $prop['css'] } + + + <# + Get the current property name. + #> + if ($prop.ContainsKey('n')) { $name = $prop['n'] } + if ($prop.ContainsKey('name')) { $name = $prop['name'] } + if ($prop.ContainsKey('label')) { $name = $prop['label'] } + if ($prop.ContainsKey('l')) { $name = $prop['l'] } + + + <# + Execute the "expression" or "e" key to get the value of the property. + #> + if ($prop.ContainsKey('e')) { $value = $Object | ForEach $prop['e'] } + if ($prop.ContainsKey('expression')) { $value = $Object | ForEach $prop['expression'] } + + + <# + Make sure we have a name and a value at this point. + #> + if ($name -eq $null -or $value -eq $null) { + Write-Error "Hashtable missing Name and/or Expression key" + } + } else { + <# + We got a property list that wasn't strings and + wasn't hashtables. Bad input. + #> + Write-Warning "Unhandled property $prop" + } + + + <# + When constructing a table, we have to remember the + property names so that we can build the table header. + In a list, it's easier - we output the property name + and the value at the same time, since they both live + on the same row of the output. + #> + if ($As -eq 'table') { + Write-Verbose "Adding $name to header and $value to row" + $headerrow += "" + $datarow += "$value" + } else { + $wrote_first_line = $true + $headerrow = "" + $datarow = "$name :$value" + $out += "$datarow" + } + } + + + <# + Write the table header, if we're doing a table. + #> + if (-not $wrote_first_line -and $as -eq 'Table') { + Write-Verbose "Writing header row" + $out += "$headerrow" + $wrote_first_line = $true + } + + + <# + In table mode, write the data row. + #> + if ($as -eq 'table') { + Write-Verbose "Writing data row" + $out += "$datarow" + } + } + } + END { + <# + Finally, post-content code, the end of the table, + the end of the
, and write the final string. + #> + Write-Verbose "PostContent" + if ($PSBoundParameters.ContainsKey('PostContent')) { + $out += "`n$PostContent" + } + Write-Verbose "Done" + <# + If input was list type there is no
opened, + so no need to close it, or generally speaking + no open, no closure. + #> + if ($out -match "") { + $out += "
$name
" + } + else { + $out += "
" + } + + Write-Output $out + } }