Friday Fun: 50 Shades of PowerShell HTML Reports

happyreport I’ve been working on a project for a client that includes creating an HTML report, generated by PowerShell. I originally thought I would include a certain feature but decided against it. However, this is so cool I thought I’d share it with you as a Friday Fun article. I’ve done alot this year with some advanced HTML scripting techniques and this one might come in handy.

I’m always looking for ways to add visual reinforcement to my HTML reports. And since keeping track of disk space is a common IT Pro task, I figured it would be nice to have a visual representation on disk utilization. So I created a short proof of concept script that generates an HTML report like this:


What do you think? Here’s how I did it.

The key element here is the addition of a gradient. In the script you can see that I’ve defined a here string for the gradient. The string includes code to support just about any browser, that’s why you see all the background-image lines. The here string also has place holders, {0} and {1} for the starting and ending percentages. I’ll explain how that works in a moment.

Using Get-CIMInstance, the script gets fixed logical disks. Now, instead of simply creating an HTML fragment, I create the fragment as an XML document. This allows me to add a caption to the table, using the computer name. Then I iterate through the table node, skipping the first row which is the table header. I create an attribute called Style.

Next, I get the value of the last cell, which is the PercentFree value. That’s one of the reasons I used an ordered hashtable so I could guarantee that the last cell would always be the PercentFree property. I grab the value and make sure it is an integer.

I can use this value and plug it in to my gradient here string using the -f operator.

I append the style attribute to each node. This allows me to set different values for each drive.

After going through the table rows, all that remains is to add the modified HTML, which is the InnerXML property to my array of fragments and create the final report.

The gradient isn’t absolute but it gives you a rough visual approximation of how much free space is on each drive. By the way, you can also use the gradient in the body element of a style sheet if you want to jazz up the background of your report.

I included plenty of comments in my code which I hope helps. If not, please leave a comment. Enjoy!

Getting Top Level Folder Report in PowerShell

One of the sessions I presented recently at TechDays San Francisco was on file share management with PowerShell. One of the scripts I demonstrated was for a function to get information for top level folders. This is the type of thing that could be handy to run say against the root of your shared users folder. Or the root of a group share where each subfolder is a share that belongs to a different group. My function takes advantage of a new feature for Get-ChildItem that makes it much easier to retrieve only file or directories. Here’s my Get-FolderSize function.

The function defaults to the local path and gets a collection of all of the top level folders, that is, those found directly in the root. The function then takes the collection of folders and pipes them to ForEach-Object. Most of the time we only use the Process scriptblock with ForEach-Object, but I want to take advantage of the Begin and End blocks as well. In the Begin scriptblock I measure all of the files in the root of the parent path and create a custom object that shows the number of files and total size in bytes. I’m going to get this same information for each child folder as well.

The process scriptblock does just that for each top level folder. This version of my function uses Write-Progress to display progress and in the End script block I have code to complete the progress bar, although It works just fine without it.

Other techniques I’d like to point out are the use of splatting and error handling. You’ll notice that I’m using the common -ErrorVariable parameter. After exploring the different types of exceptions I decided I could easily display any errors and the paths In the Catch block. I’m using Write-Warning, but this could just as easily be written to a text file.

The function writes an object like this for every folder.

Here’s an example of complete output:

foldersizeBecause I’ve written objects to the pipeline, I could pipe this to Out-Gridview, export to a CSV file or create an HTML report.

This is just a taste of what you can accomplish with some basic PowerShell commands.


PowerShell Morning Report with Credentials

I had an email about trying to use my Morning Report script to connect to machines that required alternate credentials. For example, you might have non-domain systems in a DMZ. Fair enough. Since most of the report script uses WMI, it wasn’t too hard to add a Credential parameter and modify the WMI code to use it. I tweaked the code a bit to use hashtables to splat parameters.

#region define a parameter hashtable

if ($credential) {
If ($OK) {

Try {
#get Operating system information from WMI
$os = Get-WmiObject @paramhash

I’m a little mixed on using splatting in the script. On one hand it makes it easier to wrap up parameters but the actual command might be a little confusing. Hopefully the comments make it clear.

So that handled all the WMI parts. The event log section is using Get-Eventlog which doesn’t have a -Credential parameter. I could have tried to rewrite the section using WMI, but that seemed like a lot of work. So I made the assumption that the computers you are querying are running PowerShell 2 or later with remoting enabled. That means I can use Invoke-Command to run Get-Eventlog ON the remote computer. As an additional benefit this seems to run a little faster, at least in my testing.

The tricky part was passing all the parameter values to Get-EventLog and Invoke-Command. I ended up with some complicated nesting but it works.

#Event log errors and warnings in the last $Hours hours
#define a hash table of parameters to splat to Get-Eventlog
$GetEventLogParam = @{

#System Log
Write-Host "...System Event Log Error/Warning since $last" -ForegroundColor Cyan
#hashtable of optional parameters for Invoke-Command
$InvokeCommandParam = @{
ScriptBlock = {Param ($params) Get-EventLog @params }

if ($Credential) { $InvokeCommandParam.Add("Credential",$Credential) }

$syslog = Invoke-Command @InvokeCommandParam

$syslogdata = $syslog | Select TimeGenerated,EventID,Source,Message

#Application Log
Write-Host "...Application Event Log Error/Warning since $last" -ForegroundColor Cyan
#update the hashtable

#update invoke-command parameters
$InvokeCommandParam.ArgumentList = $GetEventLogParam

$applog = Invoke-Command @InvokeCommandParam
$applogdata = $applog | Select TimeGenerated,EventID,Source,Message

Now you can run the script using -Credential, specifying either a saved credential object or the user name which will give you the Get-Credential prompt. I also made some slight tweaks to the embedded style and layout.

morning report

If you missed the original and related posts, you might want to read:

Download the latest version of the MorningReport.

Event Log Morning Report

The Morning Report script I published earlier this yeas was quite popular. One of the changes I made in it was to skip getting event log entries to speed up the overall process. But I received an inquiry today about how to create the report with only event log information. The only way really is to revise the script.

So I did.

Well, actually I wrote a new script based on the last version. This script, EventlogMorningReport.ps1 works basically the same as the regular morning report except it only gets event log information. Although I added a section to also get audit failures from the Security event log. You run it the same way. You might even want to use a background job.

PS E:\> start-job {get-content computers.txt | c:\scripts\EventLogMorningReport.ps1 -verbose -html | out-file E:\temp\evtlog.htm}

I suppose the better approach would have been to modify the Morning Report script and make each of the different sections optional. And perhaps I’ll address that at some point. But time was tight this morning and it was quicker to simply strip out the bits I didn’t need.

The underlying code hasn’t really changed much so I won’t post it. Feel free to download EventLogMorningReport and give it a spin.