Creating HTML Reports in PowerShell, Study Guides, Projects, Research of Computer Science

Learn to properly use ConvertTo-HTML to produce multi-section, well-formed HTML reports – but then go further with a custom EnhancedHTML module! Produce beautiful, color-coded, dynamic, multi-section reports easily and quickly

Typology: Study Guides, Projects, Research

2016/2017

Uploaded on 08/24/2017

kukucz
kukucz 🇵🇱

4.7

(3)

7 documents

1 / 26

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a

Partial preview of the text

Download Creating HTML Reports in PowerShell and more Study Guides, Projects, Research Computer Science in PDF only on Docsity!

Table of Contents

ReadMe

Introduction

HTML Report Basics

Gathering the Information

Building the HTML

Combining HTML Reports and a GUI Application

Contacting Me

Creating HTML Reports in PowerShell

By Don Jones

Cover design by Nathan Vonnahme

Learn to properly use ConvertTo-HTML to produce multi-section, well-formed HTML reports

  • but then go further with a custom EnhancedHTML module! Produce beautiful, color-coded,

dynamic, multi-section reports easily and quickly. By Don Jones.

This guide is released under the Creative Commons Attribution-NoDerivs 3.0 Unported

License. The authors encourage you to redistribute this file as widely as possible, but ask

that you do not modify the document.

Getting the Code The EnhancedHTML2 module mentioned in this book can be found in the

https://www.powershellgallery.com/packages/EnhancedHTML2/. That page includes

download instructions. PowerShellGet is requires, and can be obtained from

PowerShellGallery.com

Was this book helpful? The author(s) kindly ask(s) that you make a tax-deductible (in the

US; check your laws if you live elsewhere) donation of any amount to The DevOps

Collective to support their ongoing work.

Check for Updates! Our ebooks are often updated with new and corrected content. We

make them available in three ways:

Our main, authoritative GitHub organization, with a repo for each book. Visit

https://github.com/devops-collective-inc/

Our GitBook page, where you can browse books online, or download as PDF, EPUB, or

MOBI. Using the online reader, you can link to specific chapters. Visit

https://www.gitbook.com/@devopscollective

On LeanPub, where you can download as PDF, EPUB, or MOBI (login required), and

"purchase" the books to make a donation to DevOps Collective. You can also choose to

be notified of updates. Visit https://leanpub.com/u/devopscollective

GitBook and LeanPub have slightly different PDF formatting output, so you can choose the

one you prefer. LeanPub can also notify you when we push updates. Our main GitHub repo

is authoritative; repositories on other sites are usually just mirrors used for the publishing

Introduction 4

process. GitBook will usually contain our latest version, including not-yet-finished bits;

LeanPub always contains the most recent "public release" of any book.

Introduction 5

wrong. Once you start getting fancy with reports, you'll figure out pretty quickly that this

approach is painful. It isn't PowerShell's fault; you're just not following the rules. Hence this

guide!

You'll notice that the HTML consists of a lot of other tags, too: , , , and

so on. Most of these are paired , meaning they come in an opening tag like and a

closing tag like . The tag represents a table cell, and everything between those

tags is considered the contents of that cell.

The section is important. What's inside there isn't normally visible in the browser;

instead, the browser focuses on what's in the section. The section provides

additional meta-data, like what the title of the page will be (as displayed in the browser's

window title bar or tab, not in the page itself), any style sheets or scripts that are attached to

the page, and so on. We're going to do some pretty awesome stuff with the section,

trust me.

You'll also notice that this HTML is pretty "clean," as opposed to, say, the HTML output by

Microsoft Word. This HTML doesn't have a lot of visual information embedded in it, like

colors or fonts. That's good, because it follows correct HTML practices of separating

formatting information from the document structure. It's disappointing at first, because your

HTML pages look really, really boring. But we're going to fix that, also.

In order to help the narrative in this book stay focused, I'm going to start with a single

example. In that example, we're going to retrieve multiple bits of information about a remote

computer, and format it all into a pretty, dynamic HTML report. Hopefully, you'll be able to

focus on the techniques I'm showing you, and adapt those to your own specific needs.

In my example, I want the report to have five sections, each with the following information:

Computer Information

The computer's operating system version, build number, and service pack version.

Hardware info: the amount of installed RAM and number of processes, along with the

manufacturer and model.

An list of all processes running on the machine.

A list of all services which are set to start automatically, but which aren't running.

Information about all physical network adapters in the computer. Not IP addresses,

necessarily - hardware information like MAC address.

I realize this isn't a universally-interesting set of information, but these sections will allow be

to demonstrate some specific techniques. Again, I'm hoping that you can adapt these to your

precise needs.

HTML Report Basics 7

  • HTML Report Basics

Very similar to the last one. You'll notice here that I'm using the -f formatting operator with

the RAM property, so that I get a value in gigabytes with 2 decimal places. The native value

is in bytes, which isn't useful for me.

function Get-InfoBadService { [CmdletBinding()] param( [Parameter(Mandatory=$True)][string]$ComputerName ) $svcs = Get-WmiObject -class Win32_Service -ComputerName $ComputerName ` -Filter "StartMode='Auto' AND State<>'Running'" foreach ($svc in $svcs) { $props = @{'ServiceName'=$svc.name; 'LogonAccount'=$svc.startname; 'DisplayName'=$svc.displayname} New-Object -TypeName PSObject -Property $props } }

Here, I've had to recognize that I'll be getting back more than one object from WMI, so I

have to enumerate through them using a ForEach construct. Again, I'm primarily just

renaming properties. I absolutely could have done that with a Select-Object command, but I

like to keep the overall function structure similar to my other functions. Just a personal

preference that helps me include fewer bugs, since I'm used to doing things this way.

function Get-InfoProc { [CmdletBinding()] param( [Parameter(Mandatory=$True)][string]$ComputerName ) $procs = Get-WmiObject -class Win32_Process -ComputerName $ComputerName foreach ($proc in $procs) { $props = @{'ProcName'=$proc.name; 'Executable'=$proc.ExecutablePath} New-Object -TypeName PSObject -Property $props } }

Very similar to the function for services. You can probably start to see how using this same

structure makes a certain amount of copy-and-paste pretty effective when I create a new

function.

Gathering the Information 10

function Get-InfoNIC { [CmdletBinding()] param( [Parameter(Mandatory=$True)][string]$ComputerName ) $nics = Get-WmiObject -class Win32_NetworkAdapter -ComputerName $ComputerName ` -Filter "PhysicalAdapter=True" foreach ($nic in $nics) { $props = @{'NICName'=$nic.servicename; 'Speed'=$nic.speed / 1MB -as [int]; 'Manufacturer'=$nic.manufacturer; 'MACAddress'=$nic.macaddress} New-Object -TypeName PSObject -Property $props } }

The main thing of note here is how I've converted the speed property, which is natively in

bytes, to megabytes. Because I don't care about decimal places here (I want a whole

number), casting the value as an integer, by using the -as operator, is easier for me than the

-f formatting operator. Also, it gives me a chance to show you this technique!

Note that, for the purposes of this book, I'm going to be putting these functions into the same

script file as the rest of my code, which actually generates the HTML. I don't normally do

that. Normally, info-retrieval functions go into a script module, and I then write my HTML-

generation script to load that module. Having the functions in a module makes them easier

to use elsewhere, if I want to. I'm skipping the module this time just to keep things simpler

for this demonstration. If you want to learn more about script modules, pick up Learn

PowerShell Toolmaking in a Month of Lunches or PowerShell in Depth, both of which are

available from Manning.com.

Gathering the Information 11

The above section tells us that this is an "advanced script," meaning it uses PowerShell's

cmdlet binding. You can specify one or more computer names to report from, and you must

specify a folder path (not a filename) in which to store the final reports.

BEGIN {

Remove-Module EnhancedHTML Import-Module EnhancedHTML }

The BEGIN block can technically be removed. I use this demo to test the module, so it's

important that it unload any old version from memory and then re-load the revised version. In

production you don't need to do the removal. In fact, PowerShell v3 and later won't require

the import, either, if the module is properly located in

\Documents\WindowsPowerShell\Modules\EnhancedHTML.

PROCESS {

$style = @"

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; }

.sectionheader { cursor:pointer; }

.sectionheader:hover { color:red; }

.grid { width:100% }

.red { color:red; font-weight:bold; }

"@

That's called a Cascading Style Sheet, or CSS. There are a few cool things to pull out from

this:

I've jammed the entire section into a here-string , and stored that in the

variable $style. That'll make it easy to refer to this later.

Notice that I've defined styling for several HTML tags, such as H1, H2, BODY, and TH.

Those style definitions list the tag name without a preceding period or hash sign. Inside curly

brackets, you define the style elements you care about, such as font size, text alignment,

and so on. Tags like H1 and H2 already have predefined styles set by your browser, like their

font size; anything you put in the CSS will override the browser defaults.

Styles also inherit. The entire body of the HTML page is contained within the

tags, so whatever you assign to the BODY tag in the CSS will also apply to everything in the

page. My body sets a font family and a font color; H1 and H2 tags will use the same font and

color.

You'll also see style definitions preceded by a period. Those are called class styles, and I

made them up out of thin air. These are sort of reusable style templates that can be applied

to any element within the page. The ".paginate" ones are actually used by the JavaScript I

Building the HTML 14

$procs = Get-WmiObject -class Win32_Process -ComputerName $ComputerName foreach ($proc in $procs) { $props = @{'ProcName'=$proc.name; 'Executable'=$proc.ExecutablePath} New-Object -TypeName PSObject -Property $props } }

function Get-InfoNIC { [CmdletBinding()] param( [Parameter(Mandatory=$True)][string]$ComputerName ) $nics = Get-WmiObject -class Win32_NetworkAdapter -ComputerName $ComputerName ` -Filter "PhysicalAdapter=True" foreach ($nic in $nics) { $props = @{'NICName'=$nic.servicename; 'Speed'=$nic.speed / 1MB -as [int]; 'Manufacturer'=$nic.manufacturer; 'MACAddress'=$nic.macaddress} New-Object -TypeName PSObject -Property $props } }

function Get-InfoDisk { [CmdletBinding()] param( [Parameter(Mandatory=$True)][string]$ComputerName ) $drives = Get-WmiObject -class Win32_LogicalDisk -ComputerName $ComputerName ` -Filter "DriveType=3" foreach ($drive in $drives) { $props = @{'Drive'=$drive.DeviceID; 'Size'=$drive.size / 1GB -as [int]; 'Free'="{0:N2}" -f ($drive.freespace / 1GB); 'FreePct'=$drive.freespace / $drive.size * 100 -as [int]} New-Object -TypeName PSObject -Property $props } }

The preceding six functions do nothing but retrieve data from a single computer (notice that

their -ComputerName parameter is defined as [string] , accepting one value, rather than

[string[]] which would accept multiples). If you can't figure out how these work... you

probably need to step back a bit!

For formatting purposes in this book, you're seeing me use the back tick character (like after

-ComputerName $ComputerName ). That escapes the carriage return right after it, turning it into a

kind of line-continuation character. I point it out because it's easy to miss, being such a tiny

character.

Building the HTML 16

foreach ($computer in $computername) { try { $everything_ok = $true Write-Verbose "Checking connectivity to $computer" Get-WmiObject -class Win32_BIOS -ComputerName $Computer -EA Stop | Out-Null } catch { Write-Warning "$computer failed" $everything_ok = $false }

The above kicks off the main body of my demo script. It's taking whatever computer names

were passed to the script's -ComputerName parameter, and going through them one at a

time. It's making a call to Get-WmiObject as a test - if this fails, I don't want to do anything

with the current computer name at all. The remainder of the script only runs if that WMI call

succeeds.

if ($everything_ok) { $filepath = Join-Path -Path $Path -ChildPath "$computer.html"

Remember that this script's other parameter is -Path. I'm using Join-Path to combine

$Path with a filename. Join-Path ensures the right number of backslashes, so that if -

Path is "C:" or "C:" I'll get a valid file path. The filename will be the current computer's

name, followed by the .html filename extension.

$params = @{'As'='List'; 'PreContent'='OS'} $html_os = Get-InfoOS -ComputerName $computer | ConvertTo-EnhancedHTMLFragment @params

Here's my first use of the EnhancedHTML2 module: The ConvertTo-

EnhancedHTMLFragment. Notice what I'm doing:

1. I'm using a hashtable to define the command parameters, including both -As List and -

PreContent ' OS ' as parameters and their values. This specifies a list-style

output (vs. a table), preceded by the heading "OS" in the H2 style. Glance back at the

CSS, and you'll see I've applied a top border to all element, which will help

visually separate my report sections.

2. I'm running my Get-InfoOS command, passing in the current computer name. The

output is being piped to...

3. ConvertTo-EnhancedHTMLFragment, which is being given my hashtable of parameters.

The result will be a big string of HTML, which will be stored in $html_os.

Building the HTML 17

Last up is the -Properties parameter. This works a lot like the -Properties

parameters of Select-Object and Format-Table. The parameter accepts a comma-

separated list of properties. The first, Drive, is already being produced by Get-

InfoDisk. The next three are special: they're hashtables, creating custom columns just

like Format-Table would do. Within the hashtable, you can use the following keys:

n (or name, or l, or label) specifies the column header - I'm using "Size(GB),"

"Free(GB)", and "Free(%)" as column headers.

e (or expression) is a script block, which defines what the table cell will contain.

Within it, you can use $_ to refer to the piped-in object. In this example, the piped-in

object comes from Get-InfoDisk, so I'm referring to the object's Size, Free, and

FreePct properties.

css (or cssClass) is also a script block. While the rest of the keys work the same as

they do with Select-Object or Format-Table, css (or cssClass) is unique to

ConvertTo-EnhancedHTMLFragment. It accepts a script block, which is expected to

produce either a string, or nothing at all. In this case, I'm checking to see if the

piped-in object's FreePct property is less than 80 or not. If it is, I output the string

"red." That string will be added as a CSS class of the table cell. Remember, back in

my CSS I defined the class ".red" and this is where I'm attaching that class to table

cells.

As a note, I realize it's silly to color it red when the disk free percent is less than

80%. It's just a good example to play with. You could easily have a more complex

formula, like if ($\ .FreePct -lt 20) { 'red' } elseif ($_.FreePct -lt 40) { 'yellow' } else {

'green' }_ - that would assume you'd defined the classes ".red" and ".yellow" and

".green" in your CSS.

$params = @{'As'='Table'; 'PreContent'='♦ Processes'; 'MakeTableDynamic'=$true; 'TableCssClass'='grid'} $html_pr = Get-InfoProc -ComputerName $computer | ConvertTo-EnhancedHTMLFragment @params

$params = @{'As'='Table'; 'PreContent'='♦ Services to Check'; 'EvenRowCssClass'='even'; 'OddRowCssClass'='odd'; 'MakeHiddenSection'=$true; 'TableCssClass'='grid'}

$html_sv = Get-InfoBadService -ComputerName $computer | ConvertTo-EnhancedHTMLFragment @params

Building the HTML 19

More of the same in the above two examples, with just one new parameter: -

MakeHiddenSection. This will cause that section of the report to be collapsed by default,

displaying only the -PreContent string. Clicking on the string will expand and collapse the

report section.

Notice way back in my CSS that, for the class .sectionHeader, I set the cursor to a pointer

icon, and made the section text color red when the mouse hovers over it. That helps cue the

user that the section header can be clicked. The EnhancedHTML2 module always adds the

CSS class "sectionheader" to the -PreContent, so by defining ".sectionheader" in your CSS,

you can further style the section headers.

$params = @{'As'='Table'; 'PreContent'='♦ NICs'; 'EvenRowCssClass'='even'; 'OddRowCssClass'='odd'; 'MakeHiddenSection'=$true; 'TableCssClass'='grid'} $html_na = Get-InfoNIC -ComputerName $Computer | ConvertTo-EnhancedHTMLFragment @params

Nothing new in the above snippet, but now we're ready to assemble the final HTML:

$params = @{'CssStyleSheet'=$style; 'Title'="System Report for $computer"; 'PreContent'="System Report for $computer"; 'HTMLFragments'=@($html_os,$html_cs,$html_dr,$html_pr,$html_sv,$html_na); 'jQueryDataTableUri'='C:\html\jquerydatatable.js'; 'jQueryUri'='C:\html\jquery.js'} ConvertTo-EnhancedHTML @params | Out-File -FilePath $filepath

<# $params = @{'CssStyleSheet'=$style; 'Title'="System Report for $computer"; 'PreContent'="System Report for $computer"; 'HTMLFragments'=@($html_os,$html_cs,$html_dr,$html_pr,$html_sv,$html_na)} ConvertTo-EnhancedHTML @params | Out-File -FilePath $filepath #> } }

}

The uncommented code and commented code both do the same thing. The first one,

uncommented, sets a local file path for the two required JavaScript files. The commented

one doesn't specify those parameters, so the final HTML defaults to pulling the JavaScript

Building the HTML 20