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:
- Fetch the data. Need to grab the data somehow
- Convert. Lets assume this data comes in some type of format and we most likely need to convert it some other form.
- 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:
- Call
Invoke-RestMethod
. - Followed by
ConvertTo-Html
. - 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? :)
- Start
pwsh
in the console. - Create a file web-report.ps1
- Add the following code:
Invoke-RestMethod -URI https://swapi.dev/api/people/1/ |
ConvertTo-Html |
Out-File report.htm
- 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>
- Show this file in a browser with
Invoke_Item
:
Invoke-Item ./report.htm
This should start up a browser and you should see something like:
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.
- 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"
)
- 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/
- 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>
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:
- Let's add
Select-Object
like so:
Select-Object name, age, planet |
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
- Let's invoke it again:
./web-report.ps1 -URL https://swapi.dev/api/people/1/
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>
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
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.
- 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}
- Update the script to take
-CssUri report.css
onConvertTo-Html
- Run it again, you should see this in the browser:
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