One of the strength that makes PowerShell one of the best scripting languages is the flexibility and simplicity it offers in working with objects. That flexibility can be demonstrated by the cmdlets available to convert objects to and from a different type. There is a plethora of conversions that can be done natively in PowerShell.

PS D:\> Get-Command Convert*
Function Name
Function ConvertFrom-SddlString
Cmdlet ConvertFrom-CIPolicy
Cmdlet ConvertFrom-Csv
Cmdlet ConvertFrom-Json
Cmdlet ConvertFrom-SecureString
Cmdlet ConvertFrom-String
Cmdlet ConvertFrom-StringData
Cmdlet Convert-Path
Cmdlet Convert-String
Cmdlet ConvertTo-Csv
Cmdlet ConvertTo-Html
Cmdlet ConvertTo-Json
Cmdlet ConvertTo-SecureString
Cmdlet ConvertTo-TpmOwnerAuth
Cmdlet ConvertTo-Xml

As you can see one of them is called “ConvertTo-Html” which we will use to produce reports that can be displayed in a web browser. You can find the complete help page of this cmdlet in the Microsoft documentation.

Like any other Convert cmdlet, it can be used by specifying the input object as an argument or by piping it into the convert command. Usually the latter is used as it is easier and quicker that way.

The output of the “ConvertTo-Html” cmdlet is of type string so it can be stored in a file.

PS D:\> get-process | ConvertTo-Html | Get-Member
TypeName: System.String

Creating a basic HTML report in PowerShell

You can create a basic report of the processes running on your machine in a file called Process.Html with the following command.

Download Banner
PS D:\> get-process | ConvertTo-Html | Out-File Process.Html

If you look into the Html file you will see all your processes in the form of an HTML table.


If you open this file in a web browser you will see the same output as if you run “Get-Process | Select * | format-Table” in PowerShell (provided you have an incredibly wide screen).

As you can see, the info is here but not very exciting visually speaking. We will see later on how to make it look more appealing.


Narrowing down the output

You most likely do not want to collect every single property of the objects as they are not all relevant to your use case. You can use the ” -Property” parameter of the “ConvertTo-Html” cmdlet rather than piping it into “Select”. The output is the same and it does not make the command run faster but I personally find it cleaner to use the parameters offered by the cmdlet and it shortens the command overall. The shorter the command the cleaner.

PS D:\> get-process | ConvertTo-Html -Property Name,cpu,Id,StartTime | out-file .\Process.Html

Once the set of properties have been specified to the command, the output is narrowed down to those only.


Content customization parameters

Before cracking on with customization, I figured it would be best to review the parameters offered by the “ConvertTo-Html” cmdlet. The parameters listed below are the main ones that you will need to make good reports.

Parameter Description
-Body Specifies the text to add after the opening tag. By default, there is no text in that position.
-CssUri Specifies the Uniform Resource Identifier (URI) of the cascading style sheet (CSS) that is applied to the HTML file. The URI is included in a style sheet link in the output.
-Fragment Generates only an HTML table. The HTML, HEAD, TITLE, and BODY tags are omitted.
-Head Specifies the content of the HEAD tag. The default is title HTML TABLE. If you use the Head parameter, the Title parameter is ignored.
-PostContent Specifies text to add after the closing TABLE tag. By default, there is no text in that position.
-PreContent Specifies text to add before the opening TABLE tag. By default, there is no text in that position.
-Title Specifies a title for the HTML file, that is, the text that appears between the TITLE tags.


As seen previously, the default layout of the Html table is not so great for a report and the whole thing looks unsatisfactory at best.

In this chapter, we are going to customize the report. We will do a few basic changes but if you want to go further, the website will be your friend as it provides all the needed information about Html and Css syntax.


There are 2 main ways for performing the customization of an Html file. In both cases the styling instructions will be the same, it is where we specify them that will change. Which one to choose will depend on your use case. There is also a third way called Inline Styles which consists of placing the styling inside the tag of the object to style. We will not describe this method here, it may be suitable in some cases but it is not flexible enough and not very clean I think.

Note that you can combine different methods but a priority applies. 1 being the highest priority:

  1. Inline style (inside an HTML element)
  2. External and internal style sheets (in the head section)
  3. Browser default

External Style Sheet (Customization done in a CSS file)

Allows for complex customization while keeping the Html clean.

Centralized configuration, recommended when multiple reports require the same formatting.

If you are planning on producing multiple reports or use many styling options, it will be a good idea to consolidate the customization of your documents into a CSS file (Cascading Style Sheets). It means that instead of configuring the style in each HTML file, you only specify a path to the CSS file which will apply its styles to the report. It also brings the benefit of being able to ensure consistency across reports by editing a single file.

In order to apply a CSS style sheet, we can use the ” -CssUri” parameter of the “ConvertTo-Html” cmdlet. I placed a few processes in a $Process variable to shorten the command and make it easier to read.

PS D:\> $process | ConvertTo-Html -CssUri “..\MyStyleSheet.Css” | Out-File .\Process.Html

Now if we inspect the Html file, we notice that the MyStyleSheet.Css file is configured.


As an example, we are adding basic borders to the table by configuring in the Css file.


The table now looks as follows:


Internal style sheet (Customization done in the HTML file)

Good for basic formatting of a single report.

Simpler as it does not require interacting with an external Css file.

In this section we are not using a Css file, meaning we specify the Css code within the Html file. I like this approach for basic reports that require a unique style or if I have only a couple of them. I won’t bother with a CSS file unless it brings something useful to the table.

As a best practice, internal styles are defined within the < style > element, inside the head section of an Html page. To achieve this, we are going to use the ” -Head” parameter of the “ConvertTo-Html” cmdlet to include our styles. Whatever is passed to the parameter will be placed right below the Head tag. In short, you populate this variable with what would have been the content of the Css file. To demonstrate this we will add borders to our sad table as we did earlier.

PS D:\> $Style = “< style >table, th, td {border: 1px solid;}< /style >”

PS D:\> $process | ConvertTo-Html -Head $Style | Out-File .\Process.Html

The Html file now includes our styles as pictured below.


The table now looks the exact same as in the previous example.


Title and extra content

If you want your reports to look as professional as possible, there are a few little tweaks that will make a difference to the end result. These are especially useful when there is a collection of reports being produced to keep them ordered.


To add a title, simply use the ” -Title” argument of the “ConvertTo-Html” cmdlet. Just like any other command, you can work with variables to make it relevant.

$process | ConvertTo-Html -Title “$env:COMPUTERNAME Processes” | Out-File .\Process.Html

The title is added to the Html content.


The title now appears in the browser tab.


Add content

Along with the table, you may want to add some text or other content to your reports. One use case could be a description of what is depicted in the table and how to react in accordance to it. But then again, your imagination is the limit, you could, for instance, use the content of the given table to set conditions to adapt the text to the situation.

To add extra content to an Html report, we are using the ” -PreContent” and ” -PostContent” parameters of the “ConvertTo-Html” cmdlet. In the example below we list the Chrome processes.

PS D:\> $chrome = $process | where name -eq “chrome”

PS D:\> $precontent = “List of processes on $env:COMPUTERNAME.”

PS D:\> $postcontent= “There are $($chrome.count) chrome processes running.”

PS D:\> $process | ConvertTo-Html -CssUri .\MyStyleSheet.Css -PostContent $postcontent -PreContent $precontent | Out-File .\Process.Html

The PreContent is added after the body tag and the PostContent is added before the /body tag.


The report obviously shows these new changes in the browser.


Now, this was a very simple example but one can think of various usages for those. For instance, you could imagine adding the content of a log file or appending another table to the existing one (more on the “-Fragment” parameter below).

Change the color of the first row

I wanted to add this section to demonstrate how to change the color of the first row of the table. Although it may sound gimmicky I find it quite useful to know the status of a report at a glance and it is fairly easy to do with a tiny bit of Css. Note that if you use internal style sheets (which we are about to do) you can use it in emails.

For this example, we are listing the datastores in PowerCLI. According to the amount of free space of the most used datastore, it will change the top row to blue, yellow or red. The PowerShell code below is self-explanatory to understand how this works. As you can see we use the style section to change the color of the top row according to whether a datastore is found with less than 30% (red), 40% (yellow). If no datastore exist in either, the color will be blue. These thresholds are that high to make it go red, you are fine with 30%.

$yellow = 40

$red = 30

$PreContent = “Free space thresholds: Yellow 40% ; Red 30%”

$Datastore = Get-Datastore

if ($Datastore | where { ($_.freespacegb / $_.capacitygb * 100) -lt $red }) {$color = “red”}
elseif ($Datastore | where { ($_.freespacegb / $_.capacitygb * 100) -lt $yellow }) {$color = “yellow”}
else {$color = “blue”}

$style = “< style > table th { background: $color; } table, th, td { border: 1px solid; } < /style >”

$Html = $Datastore | ConvertTo-Html -Property name,freespacegb,capacitygb -Head $style -PreContent $PreContent

$Html | Out-File Datastore-Report.Html

Here is what it looks like when a datastore is found with less than 30% of free space in this instance. In this case “Datastore-X3”.


The “Fragment” parameter

I decided to dedicate a whole chapter to this parameter because I find it very useful and can be used in multiple cases. The purpose of this parameter is to exclude the HTML, HEAD, TITLE, and BODY tags from the output. Which means only the Html code of the table itself will be in the output. In this section, we are going to review a few of these use cases and see what we can do with it.

Multiple tables in one report

In the below example we are stacking a table of processes and a table of services. We use the ” -PostContent” parameter to append the service table with a br tag to leave some space in between.

PS D:\> $Service = Get-Service | select -fi 5 | ConvertTo-Html -Property status,name,displayname

PS D:\> $process | ConvertTo-Html -CssUri .\MyStyleSheet.Css -PostContent (“$Service”) | Out-File .\Process.Html

The result in the browser is as follows.


Using an Html template file

This method is a little less obvious but can be useful if you want to place the table inside an Html file that you use as a template. Typically you would have a complete Html file with all the bells and whistles that you need, for example, there could be a link back to an index file listing all the reports and pictures of cats. The pros of this method is that, like with a Css file for the styling, you can modify the layout of all your reports from a single location.

In this “Template” file you place a special string of characters that you will then replace with the table generated by “ConvertTo-Html”. The ” -Fragment” parameter is useful here because the Html is already built and only the table is missing.

  1. Import content of Html template in a Powershell variable
  2. Replace the special string with the Html table in the variable
  3. Output the content of the variable into the Html file of the report. New or existing with ” -Force”

Let’s call the Html template file “Report-Template.Html” which will look something similar to this. It will be a more advanced template in an actual use case but it is simplified for the sake of the argument. The special string mentioned earlier is “REPLACE-ME” in the example below.


The PowerShell code to generate a report based on this template will look like so. There is no need for more as a link to the Css file is already in the template. Note that you may also need to use the same replacement method for the title tag as it is located in the Head tag, but it depends on how you build the template.

$Template = “.\Report-Template.Html”
$ReplaceString = “REPLACE-ME”

$Report = Get-Content $Template

$Table = Get-Process | ConvertTo-Html -Fragment

$Report = $Report -replace $ReplaceString,$Table

$Report | Out-File Processes.Html


Now that you have a nice web report it would not be nice to leave it somewhere on your computer and open it locally. Well, it’s actually ok but we can do better.

Web server

Once you are ready to use PowerShell for actual reporting purpose, a good idea is to use a dedicated server with a scheduled task per PowerShell scripts. You can then write your scripts in such a way that they store the Html file in a folder served by IIS to have them available from anywhere. This will allow you to have your reports run automatically and have them ready when you come to work in the morning.


If you don’t want to bother opening a browser and clicking on the bookmark, which is totally understandable, you may want to have the report brought to you in an email. No problem with that. Instead of storing the Html code generated by PowerShell in a file, you just need to pass it as the ” -Body” parameter of the “Send-MailMessage” and set the ” -BodyAsHtml” switch. Like so. Note that what was described in the previous chapters also applies here.

$Table = Get-Process | ConvertTo-Html

Send-MailMessage -Body $Table -BodyAsHtml -From “$” -To “” -SmtpServer “” -subject “Processes report”

Note that you can add attachments with this cmdlet. This could be useful to attach a log file or the full report and only display the top 10 rows if it is too long for an email.


Reporting and monitoring are two areas of the data center that can make a real difference in your response time to an event, be it reactive to proactive. Being able to make html reports in PowerShell will open a world of possibilities in these regards.

There is so much more to be said about this topic that I could go on about it for hours. If you are interested in more advanced reports with filter, sort and all kind of other features I recommend that you check out the Tablesorter plugin for JQuery.

It is now up to you to make your own use cases and start increasing the reporting in your environment.

Follow our Twitter and Facebook feeds for new releases, updates, insightful posts and more.

Like what you read? Rate us
Guide on how to make Html reports in PowerShell
Rate this post