Search

Basics of Creating Webpages with PowerShell

Updated: Jun 30

Creating a simple web report with PowerShell doesn't need to be a chore, there are limitations and it's definitely not a proper HTML editor. It doesn't mean the output should look shoddy.


Like many, I'm using PowerShell to analyse Windows and display the results. The screen grab below is a section of a report I'm currently working on and soon to be published. The script is a comprehensive vulnerability assessment written entirely in PowerShell and made to look pretty without trawling through copious amounts of log outputs.


This blog will cover the basics of taking PowerShell objects from various sources and creating HTLM output. It's not difficult, just fiddley, a couple of different techniques to successfully convert PowerShell to HTML may be required.


Before everyone gets critical regarding the script formatting, some are due to how ConvertTo-HTML expects the data, most are to help those that aren’t familiar with scripting. There is a conscious decision not to use aliases or abbreviations and where possible to create variables.


#Set Output Location Variables

Nothing challenging here, creates a working directory, and sets the variable for the report output. Tests the existence of the path and if doesn’t exist creates the directory structure.


$RootPath = "C:\Report"

$OutFunc = "SystemReport"

$tpSec10 = Test-Path "$RootPath \$OutFunc\"

if ($tpSec10 -eq $false)

{

New-Item -Path "$RootPath \$OutFunc\" -ItemType Directory -Force

}

$working = "$RootPath \$OutFunc\"

$Report = "$RootPath \$OutFunc\"+ "$OutFunc.html"


#HTML to Text

Keep it simple, create a variable and add some text. This is the one that ought to be straightforward and ended up being a bit of a pain. The conversion to HTML ended up producing garbage. Google gave some interesting solutions…. The fix I discovered turned out to be super simple. The fragment needs to be set as a ‘Table’ and not a ‘List’. Doh…..


$Intro = "The results in this report are a guide and not a guarantee that the tested system is not without further defects or vulnerabilities."


#Simple WMI

This is a report about Windows, had better collect some wmi attributes. There are 2 methods, dump the attributes into a variable and process them later. Or create a variable for each required attribute and hashtable the data, the latter is a lot of effort.


$hn = Get-CimInstance -ClassName win32_computersystem

$os = Get-CimInstance -ClassName win32_operatingsystem

$bios = Get-CimInstance -ClassName win32_bios

$cpu = Get-CimInstance -ClassName win32_processor

#Foreach and New-Object.

Now life starts to get interesting. The date format needs updating from “23/11/2021 00:00:00” to “23/11/2021” to maintain the formatting a ‘foreach’ is required to strip out the additional characters per line, then added to an array.


Under normal circumstances, the red code snippet would suffice.


Foreach ($hfitem in $getHF)

{

$hfid = $hfitem.hotfixid

$hfdate = ($hfitem.installedon).ToShortDateString()

$hfurl = $hfitem.caption

$newObjHF = $hfid, $hfdate,$hfurl

$HotFix += $newObjHF

}


When dealing with HTML the correct method requires the use of ‘New-Object’ command.


$HotFix=@()

$getHF = Get-HotFix | Select-Object HotFixID,InstalledOn,Caption


Foreach ($hfitem in $getHF)

{

$hfid = $hfitem.hotfixid

$hfdate = $hfitem.installedon

$hfurl = $hfitem.caption

$newObjHF = New-Object psObject

Add-Member -InputObject $newObjHF -Type NoteProperty -Name HotFixID -Value $hfid

Add-Member -InputObject $newObjHF -Type NoteProperty -Name InstalledOn -Value ($hfdate).Date.ToString("dd-MM-yyyy")

Add-Member -InputObject $newObjHF -Type NoteProperty -Name Caption -Value $hfurl

$HotFix += $newObjHF

}


#Pulling Data from the Registry

Registry keys require the ‘Get-ChildItem’ followed by ‘Get-ItemProperty’ to extract the individual settings from the Registry Hive. Each setting is then assigned to a variable.


$getUnin = Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\"

$UninChild = $getUnin.Name.Replace("HKEY_LOCAL_MACHINE","HKLM:")

$InstallApps =@()

Foreach ( $uninItem in $UninChild)

{

$getUninItem = Get-ItemProperty $uninItem


$UninDisN = $getUninItem.DisplayName -replace "$null",""

$UninDisVer = $getUninItem.DisplayVersion -replace "$null",""

$UninPub = $getUninItem.Publisher -replace "$null",""

$UninDate = $getUninItem.InstallDate -replace "$null",""

$newObjInstApps = New-Object -TypeName PSObject


Add-Member -InputObject $newObjInstApps -Type NoteProperty -Name Publisher -Value $UninPub

Add-Member -InputObject $newObjInstApps -Type NoteProperty -Name DisplayName -Value $UninDisN

Add-Member -InputObject $newObjInstApps -Type NoteProperty -Name DisplayVersion -Value $UninDisVer

Add-Member -InputObject $newObjInstApps -Type NoteProperty -Name InstallDate -Value $UninDate

$InstallApps += $newObjInstApps

}


#Cascading Style Sheets (CSS)

To apply a consistent style to each element we use a CSS containing text size, colour and font as well as spacing and background colours.


Each style, for example 'h1' has a set of properties that applies to any number of elements tagged "<h1>variable or text</span></h1>". reducing repeat lines of code required, updating the CSS and all elements receive the change.


CSS Tutorial (w3schools.com) is a good resource to learn and try out CSS.


In the example below h1, h2 and h3 set different sized fonts and colours.


$style = @"

<Style>

body

{

background-color:#250F00;

color:#B87333;

font-size:100%;

font-family:helvetica;

margin:0,0,10px,0;

word-break:normal;

word-wrap:break-word

}

table

{

border-width: 1px;

padding: 7px;

border-style: solid;

border-color:#B87333;

border-collapse:collapse;

width:auto

}

h1

{

background-color:#250F00;

color:#B87333;

font-size:150%;

font-family:helvetica;

margin:0,0,10px,0;

word-break:normal;

word-wrap:break-word

}

h2

{

background-color:#250F00;

color:#4682B4;

font-size:120%;

font-family:helvetica;

margin:0,0,10px,0;

word-break:normal;

word-wrap:break-word

}

h3

{

background-color:#250F00;

color:#B87333;

font-size:100%;

font-family:helvetica;

margin:0,0,10px,0;

word-break:normal;

word-wrap:break-w


The script references the CSS and applies to each element, in this case, variables containing text.


$header1= "This is Header One."

$header2= "This is Header Two."

$header3= "This is Header Three."

$frag_H1 = $header1 | ConvertTo-Html -as table -Fragment -PreContent "<h1> $header1</span></h1>" | Out-String

$frag_H2 = $header2 | ConvertTo-Html -as table -Fragment -PreContent "<h2> $header2</span></h2>" | Out-String

$frag_H3 = $header3 | ConvertTo-Html -as table -Fragment -PreContent "<h3> $header3</span></h3>" | Out-String


ConvertTo-Html -Head $style -Body "<h1 align=center style='text-align:center'><span style='color:#4682B4;'>Header1 with Alt Colour</span><h1>",

$frag_H1,

$frag_H2,

$frag_H3 | out-file $Report

The result is..... triggering my love of consistency and complementary colour schemes.


The 'styles' can be overridden by applying setting directly to the element, that's why Tenaka.net is centred and blue, despite applying the <h1> style.


#Converting to HTML

Each WMI, Registry or PowerShell variable is converted with the element to HTML and stored in a variable.


Note: the WMI variables attributes are called via -property


$FragDescrip1 = $Descrip1 | ConvertTo-Html -as table -Fragment -PreContent "<h3><span>$Intro</span></h3>" | Out-String


$fragHost = $hn | ConvertTo-Html -As table -Property Name,Domain,Model -fragment -PreContent "<h2><span>Host Details</span></h2>" | Out-String


$fragOS = $OS | ConvertTo-Html -As table -property Caption,Version,OSArchitecture,InstallDate -fragment -PreContent "<h2><span>Windows Details</span></h2>" | Out-String


$fragBios = $bios | ConvertTo-Html -As table -property Name,Manufacturer,SerialNumber,SMBIOSBIOSVersion,ReleaseDate -fragment -PreContent "<h2><span>Bios Details</span></h2>" | Out-String


$fragCpu = $cpu | ConvertTo-Html -As table -property Name,MaxClockSpeed,NumberOfCores,ThreadCount -fragment -PreContent "<h2><span>Processor Details</span></h2>" | Out-String


$fragHotFix = $HotFix | ConvertTo-Html -As table -property HotFixID,InstalledOn,Caption -fragment -PreContent "<h2><span>Installed Updates</span></h2>" | Out-String


$fragInstaApps = $InstallApps | Sort-Object publisher,displayname -Unique | ConvertTo-Html -As Table -fragment -PreContent "<h2><span>Installed Applications</span></h2>" | Out-String


$HotFix output as a PowerShell variable

HotFixID InstalledOn Caption

-------- ----------- -------

KB5007292 23-11-2021 http://support.microsoft.com/?kbid=5007292

KB5004567 04-11-2021 https://support.microsoft.com/help/5004567

KB5008295 10-11-2021 https://support.microsoft.com/help/5008295

KB5007262 23-11-2021 https://support.microsoft.com/help/5007262

KB5007414 23-11-2021


$HotFix after its been converted to HTML

<h2><span>Installed Updates</span></h2>

<table>

<colgroup><col/><col/><col/></colgroup>

<tr><th>HotFixID</th><th>InstalledOn</th><th>Caption</th></tr>

<tr><td>KB5007292</td><td>23-11-2021</td><td>http://support.microsoft.com/?kbid=5007292</td></tr>

<tr><td>KB5004567</td><td>04-11-2021</td><td>https://support.microsoft.com/help/5004567</td></tr>

<tr><td>KB5008295</td><td>10-11-2021</td><td>https://support.microsoft.com/help/5008295</td></tr>

<tr><td>KB5007262</td><td>23-11-2021</td><td>https://support.microsoft.com/help/5007262</td></tr>

<tr><td>KB5007414</td><td>23-11-2021</td><td></td></tr>

</table>


#Creating the Report

One final ConvertTo-Html applying the overall look and feel with the 'body' element plus the fragments and out put as a .htm or .html file.


ConvertTo-Html -Head $style -Body "<h1 align=center style='text-align:center'><span style='color:#4682B4;'>TENAKA.NET</span><h1>",

$fragDescrip1,

$fraghost,

$fragOS,

$fragInstaApps,

$fragHotFix,

$fragbios,

$fragcpu | out-file $Report


The completed script can be found on Github (here) and will look like something like this.










14 views0 comments