Building a Web Report in PowerShell, use the -Force Luke

Chris Noring - Oct 7 '21 - - Dev Community

TLDR; The idea of this article is to show how to build a web report. I will show the usage of several commands that you can connect that does all the heavy lifting for you

The scenario - create a report from some remote data

Ok, here you are, you are looking to read data from one place and present that as a web report. The data is remote, you need to fetch it somehow, you also probably need to think about how to convert the incoming data and lastly create that web report. If you are a developer, you probably think that oh ok, this is probably a few moving parts, 10-20 lines of code. But you've heard of PowerShell, it's supposed to be powerful, do a lot of heavy lifting, so why not give it a spin.

The steps we will take

To achieve our task, we need to plan it out. Carry it out sequentially, and who knows, maybe this is something we can reuse? So what steps:

  1. Fetch the data. Need to grab the data somehow
  2. Convert. Lets assume this data comes in some type of format and we most likely need to convert it some other form.
  3. Present. So we fetched the data, massaged it into a suitable format, now what? Now, we want to present it as a web report, HTML, CSS etc.

Getting to work

We have a game plan. Now let's see if we can find the commands we need. A good starting point is the Microsoft.PowerShell.Utility section. In this section, there are tons of commands that does a lot of heavy lifting for you.

Grabbing the data

First things first, we need to grab some data remotely, so what's our options?

  • Invoke-WebRequest this seems to let us call a URL, send a body, credentials etc, seems promising.
  • Invoke-RestMethod. What about this one, how is it different? This sentence right here:

PowerShell formats the response based to the data type. For an RSS or ATOM feed, PowerShell returns the Item or Entry XML nodes. For JavaScript Object Notation (JSON) or XML, PowerShell converts, or deserializes, the content into [PSCustomObject] objects.

It takes a JSON response and turns that into a PSCustomObject, nice. Well let's see with our other commands before we make a decision.

Presenting the data

This is the last thing we need to do but we need to understand if there's a command that helps us with report creation and most importantly, what input it takes. I think we found it:

  • ConvertTo-Html Converts .NET objects into HTML that can be displayed in a Web browser.

Yea, that reminds us of something, Invoke-RestMethod. Why? Cause Invoke-RestMethod produces custom objects, i.e .NET objects.

How do we save the report to a file though, so we can store that somewhere and let it be hosted by a web server? Oh, here's an example, pipe it to Out-File, like so ConvertTo-Html | Out-File aliases.htm

Overview of the solution

So we have a theory on how to do this:

  1. Call Invoke-RestMethod.
  2. Followed by ConvertTo-Html.
  3. Followed by Out-File.

Build the solution

Seems almost too easy. Ah well, let's give it whirl. First things first, lets choose a data source, SWAPI, the Star Wars API, cause use the -Force, am I right? :)

  1. Start pwsh in the console.
  2. Create a file web-report.ps1
  3. Add the following code:
   Invoke-RestMethod -URI https://swapi.dev/api/people/1/ |
     ConvertTo-Html |
       Out-File report.htm
Enter fullscreen mode Exit fullscreen mode
  1. Run ./web-report.ps1 (or .\web-report.ps1 for Windows folks)

It created a report.htm file for us. Ok, let's have a look:

   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <title>HTML TABLE</title>
    </head><body>
    <table>
    <colgroup><col/><col/><col/><col/><col/><col/><col/><col/><col/><col/><col/><col/><col/><col/><col/><col/></colgroup>
    <tr><th>name</th><th>height</th><th>mass</th><th>hair_color</th><th>skin_color</th><th>eye_color</th><th>birth_year</th><th>gender</th><th>homeworld</th><th>films</th><th>species</th><th>vehicles</th><th>starships</th><th>created</th><th>edited</th><th>url</th></tr>
    <tr><td>Luke Skywalker</td><td>172</td><td>77</td><td>blond</td><td>fair</td><td>blue</td><td>19BBY</td><td>male</td><td>https://swapi.dev/api/planets/1/</td><td>System.Object[]</td><td>System.Object[]</td><td>System.Object[]</td><td>System.Object[]</td><td>09/12/2014 13:50:51</td><td>20/12/2014 21:17:56</td><td>https://swapi.dev/api/people/1/</td></tr>
    </table>
    </body></html>
Enter fullscreen mode Exit fullscreen mode
  1. Show this file in a browser with Invoke_Item:
   Invoke-Item ./report.htm
Enter fullscreen mode Exit fullscreen mode

This should start up a browser and you should see something like:

Browser with report

Ok, you could be done here, or we can make it more flexible. We don't like hardcoded values, right? RIGHT?

Making it flexible

I thought so, now, let's add some parameters for, URL, and report name.

  1. Add the following code to the top part of our script web-report.ps1:
   Param(
      [String] $URL = "https://swapi.dev/api/people/1/",
      [String] $Report = "report.htm"
    )
Enter fullscreen mode Exit fullscreen mode
  1. Lets invoke it again, this time with a new URL "https://swapi.dev/api/people/2/":
   ./web-report.ps1 -URL https://swapi.dev/api/people/2/
Enter fullscreen mode Exit fullscreen mode
  1. Lets check the response in report.htm. :
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <title>HTML TABLE</title>
    </head><body>
    <table>
    <colgroup><col/><col/><col/><col/><col/><col/><col/><col/><col/><col/><col/><col/><col/><col/><col/><col/></colgroup>
    <tr><th>name</th><th>height</th><th>mass</th><th>hair_color</th><th>skin_color</th><th>eye_color</th><th>birth_year</th><th>gender</th><th>homeworld</th><th>films</th><th>species</th><th>vehicles</th><th>starships</th><th>created</th><th>edited</th><th>url</th></tr>
    <tr><td>C-3PO</td><td>167</td><td>75</td><td>n/a</td><td>gold</td><td>yellow</td><td>112BBY</td><td>n/a</td><td>https://swapi.dev/api/planets/1/</td><td>System.Object[]</td><td>System.Object[]</td><td>System.Object[]</td><td>System.Object[]</td><td>10/12/2014 15:10:51</td><td>20/12/2014 21:17:50</td><td>https://swapi.dev/api/people/2/</td></tr>
    </table>
    </body></html>
Enter fullscreen mode Exit fullscreen mode

This time we have C-3PO, yup, definitely not Luke, it seems to be working :)

Improve the response

So, so far we had a ton of columns coming back, maybe we just need a few fields from the response, like name, planet and height. Yea let's do that, and justt pick what we need from the response:

  1. Let's add Select-Object like so:
   Select-Object name, age, planet |
Enter fullscreen mode Exit fullscreen mode

with the full code looking like so:

   Param(
      [String] $URL = "https://swapi.dev/api/people/1/",
      [String] $Report = "report.htm"
    )
    Invoke-RestMethod -URI $URL |
      ConvertTo-Html -Property name, height, homeworld |
        Out-File $Report
Enter fullscreen mode Exit fullscreen mode
  1. Let's invoke it again:
   ./web-report.ps1 -URL https://swapi.dev/api/people/1/
Enter fullscreen mode Exit fullscreen mode

and our report now looks like:

   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <title>HTML TABLE</title>
    </head><body>
    <table>
    <colgroup><col/><col/><col/></colgroup>
    <tr><th>name</th><th>height</th><th>homeworld</th></tr>
    <tr><td>Luke Skywalker</td><td>172</td><td>https://swapi.dev/api/planets/1/</td></tr>
    </table>
    </body></html>
Enter fullscreen mode Exit fullscreen mode

Much better :) In fact, reading up a bit, we can just use -Property on ConvertTo-Html, so we get:

   Invoke-RestMethod -URI $URL |
      ConvertTo-Html -Property name, height, homeworld |
        Out-File $Report
Enter fullscreen mode Exit fullscreen mode

Make it pretty

In all honesty, this report is bad, no colors, no nothing. Surely, we must be able to pass a CSS file to ConvertTo-Html?

Ah yes, looking through the docs there's the parameter -CssUri that takes a file path. Let's create a CSS file then.

  1. Create report.css and add the following CSS:
   table {
      border: solid 1px black;
      padding: 10px;
      border-collapse: collapse;
    }

    tr:nth-child(even) {background: #CCC}
    tr:nth-child(odd) {background: #FFF}
Enter fullscreen mode Exit fullscreen mode
  1. Update the script to take -CssUri report.css on ConvertTo-Html
  2. Run it again, you should see this in the browser:

Table with CSS

Summary

In summary, we learned that we could use just a few commands, Invoke-RestMethod, ConvertTo-Html and Out-File and boom, we've created ourselves a report.

Full code:

Param(
  [String] $URL = "https://swapi.dev/api/people/1/",
  [String] $Report = "report.htm"
)
Invoke-RestMethod -URI $URL |
  ConvertTo-Html -CssUri report.css -Title "Web report" -Property name, height, homeworld |
    Out-File $Report
Enter fullscreen mode Exit fullscreen mode
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player