I have been having a lot of fun with this series of articles demonstrating a variety of tips, tricks, and techniques to generate killer HTML reports with PowerShell and ConvertTo-HTML. By all means, use as little or as much as you want. If you missed the previous articles, get caught up before continuing. I will not explain the previously covered material.

I will confess right up front that I am not a web developer or even close. I know some basic HTML techniques and like you, have gleaned ideas from searching the Internet. One of those techniques is the use of javascript. While I probably could not write a javascript function from scratch, I do know how to use it in my PowerShell scripts. At some point in the past, I found some functions to create expandable sections. This is great for a long HTML document because you can collapse one or more sections.

In my header here string, where I define my CSS, I am going to insert this code.



1 2 3 4 5 6 7 8 9 10 11 12 13 <script type = 'text/javascript' src = 'https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js' > </script> <script type = 'text/javascript' > function toggleDiv ( divId ) { ` $ ( "#" + divId ) . toggle ( ) ; } function toggleAll ( ) { var divs = document . getElementsByTagName ( 'div' ) ; for ( var i = 0 ; i < divs . length ; i ++ ) { var div = divs [ i ] ; ` $ ( "#" + div . id ) . toggle ( ) ; } }



The code assumes you will have a named DIV section that you want to collapse or expand. Let’s revise the example from the last article.





1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 $computername = $env : COMPUTERNAME $fragments = @ ( ) #insert a graphic $ImagePath = "c:\scripts\db.png" $ImageBits = [ Convert ] :: ToBase64String ( ( Get-Content $ImagePath -Encoding Byte ) ) $ImageFile = Get-Item $ImagePath $ImageType = $ImageFile . Extension . Substring ( 1 ) #strip off the leading . $ImageTag = "<Img src='data:image/$ImageType;base64,$($ImageBits)' Alt='$($ImageFile.Name)' style='float:left' width='120' height='120' hspace=10>" $top = @" <table> <tr> <td class='transparent'>$ImageTag</td><td class='transparent'><H1>System Report - $Computername</H1></td> </tr> </table> "@ $fragments += $top



Most of this should look familiar to you by now. One thing I am doing differently is putting the graphic in a table with the document title. I do not want this table to follow the same style as other tables. Therefore, I am defining a style called ‘transparent’ that sets the cell background color to the same as the document background color.



1 2 3 . transparent { background -color : #E5E4E2; }



Now, let’s add the new stuff. I want to define some section headings that when clicked, will toggle between expanded and collapsed. I also want to include a link at the beginning to toggle all sections. I will add the necessary script code to my array of fragments.



1 $fragments += "<a href='javascript:toggleAll();' title='Click to toggle all sections'>+/-</a>"



This will insert a link with the text “+/-“. When clicked, this will invoke the toggleAll javascript function. Now, we wll add the first section.



1 2 3 $Text = "Operating System" $div = $Text . Replace ( " " , "_" ) $fragments += "<a href='javascript:toggleDiv(" "$div" ");' title='click to collapse or expand this section'><h2>$Text</h2></a><div id=" "$div" ">"



I am calling the DIV section the same as the text but I need to remove any spaces for the DIV id. After this section heading, I need to insert my content and add the closing DIV tag.



1 2 3 4 $fragments += Get-Ciminstance -ClassName win32_operatingsystem -ComputerName $computername | Select @ { Name = "Operating System" ; Expression = { $_ . Caption } } , Version , InstallDate | ConvertTo-Html -Fragment -As List $fragments += "</div>"



I repeat the process as needed.



1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 $Text = "System Information" $div = $Text . Replace ( " " , "_" ) $fragments += "<a href='javascript:toggleDiv(" "$div" ");' title='click to collapse or expand this section'><h2>$Text</h2></a><div id=" "$div" ">" $fragments += Get -systeminfo -Computername $computername | ConvertTo-Html -Fragment -As List $fragments += "</div>" $Text = "Disk Information" $div = $Text . Replace ( " " , "_" ) $fragments += "<a href='javascript:toggleDiv(" "$div" ");' title='click to collapse or expand this section'><h2>$Text</h2></a><div id=" "$div" ">" #highlight low free space in red [ xml ] $html = Get-DiskInfo -Computername $computername | ConvertTo-Html -Fragment for ( $i = 1 ; $i -le $html . table . tr . count -1 ; $i ++ ) { if ( $html . table . tr [ $i ] . td [ -1 ] -le 20 ) { $class = $html . CreateAttribute ( "class" ) $class . value = 'alert' $html . table . tr [ $i ] . childnodes [ 3 ] . attributes . append ( $class ) | out-null } } $fragments += $html . InnerXml $fragments += "</div>"



When finished, I can create my final document from the collection of fragments. For your convenience, here is a script with all of my final code.



1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 [ cmdletbinding ( ) ] Param ( [ string ] $computername = $env : COMPUTERNAME , #the path to the final htm report [ string ] $Path = "d:\temp\systemreport.htm" , #set your own default graphic or delete default value [ string ] $ImagePath = "c:\scripts\db.png" ) #region helper functions Function Get-SystemInfo { [ cmdletbinding ( ) ] Param ( [ string ] $Computername = $env : COMPUTERNAME ) $cs = Get-CimInstance -ClassName Win32_computersystem #-ComputerName $Computername #this assumes a single processor $proc = Get-CimInstance -ClassName win32_processor #-ComputerName $Computername $data = [ ordered ] @ { TotalPhysicalMemGB = $cs . TotalPhysicalMemory / 1GB -as [ int ] NumProcessors = $cs . NumberOfProcessors NumLogicalProcessors = $cs . NumberOfLogicalProcessors HyperVisorPresent = $cs . HypervisorPresent DeviceID = $proc . DeviceID Name = $proc . Name MaxClock = $proc . MaxClockSpeed L2size = $proc . L2CacheSize L3Size = $proc . L3CacheSize } New-Object -TypeName PSObject -Property $data } Function Get-DiskInfo { [ cmdletbinding ( ) ] Param ( [ string ] $Computername = $env : COMPUTERNAME ) Get-CimInstance -ClassName win32_logicaldisk -filter "drivetype=3" -ComputerName $Computername | Select DeviceID , @ { Name = "SizeGB" ; Expression = { $_ . size / 1gb -as [ int ] } } , @ { Name = "FreeGB" ; Expression = { [ math ] :: round ( $_ . Freespace / 1gb , 2 ) } } , @ { Name = "PctFree" ; Expression = { [ math ] :: round ( ( $_ . freespace / $_ . size ) * 100 , 2 ) } } } #endregion $fragments = @ ( ) if ( Test-Path $ImagePath ) { #insert a graphic $ImageBits = [ Convert ] :: ToBase64String ( ( Get-Content $ImagePath -Encoding Byte ) ) $ImageFile = Get-Item $ImagePath $ImageType = $ImageFile . Extension . Substring ( 1 ) #strip off the leading . $ImageTag = "<Img src='data:image/$ImageType;base64,$($ImageBits)' Alt='$($ImageFile.Name)' style='float:left' width='120' height='120' hspace=10>" } else { Write-Warning "Could not find image file $ImagePath" } $top = @" <table> <tr> <td class='transparent'>$ImageTag</td><td class='transparent'><H1>System Report - $Computername</H1></td> </tr> </table> "@ $fragments += $top $fragments += "<a href='javascript:toggleAll();' title='Click to toggle all sections'>+/-</a>" $Text = "Operating System" $div = $Text . Replace ( " " , "_" ) $fragments += "<a href='javascript:toggleDiv(" "$div" ");' title='click to collapse or expand this section'><h2>$Text</h2></a><div id=" "$div" ">" $fragments += Get-Ciminstance -ClassName win32_operatingsystem -ComputerName $computername | Select @ { Name = "Operating System" ; Expression = { $_ . Caption } } , Version , InstallDate | ConvertTo-Html -Fragment -As List $fragments += "</div>" $Text = "System Information" $div = $Text . Replace ( " " , "_" ) $fragments += "<a href='javascript:toggleDiv(" "$div" ");' title='click to collapse or expand this section'><h2>$Text</h2></a><div id=" "$div" ">" $fragments += Get -systeminfo -Computername $computername | ConvertTo-Html -Fragment -As List $fragments += "</div>" $Text = "Disk Information" $div = $Text . Replace ( " " , "_" ) $fragments += "<a href='javascript:toggleDiv(" "$div" ");' title='click to collapse or expand this section'><h2>$Text</h2></a><div id=" "$div" ">" #highlight low free space in red [ xml ] $html = Get-DiskInfo -Computername $computername | ConvertTo-Html -Fragment for ( $i = 1 ; $i -le $html . table . tr . count -1 ; $i ++ ) { if ( $html . table . tr [ $i ] . td [ -1 ] -le 20 ) { $class = $html . CreateAttribute ( "class" ) $class . value = 'alert' $html . table . tr [ $i ] . childnodes [ 3 ] . attributes . append ( $class ) | out-null } } $fragments += $html . InnerXml $fragments += "</div>" $Text = "EventLog Info" $div = $Text . Replace ( " " , "_" ) $fragments += "<a href='javascript:toggleDiv(" "$div" ");' title='click to collapse or expand this section'><h2>$Text</h2></a><div id=" "$div" ">" [ xml ] $html = Get-Eventlog -List -ComputerName $computername | Select @ { Name = "Max(K)" ; Expression = { "{0:n0}" -f $_ . MaximumKilobytes } } , @ { Name = "Retain" ; Expression = { $_ . MinimumRetentionDays } } , OverFlowAction , @ { Name = "Entries" ; Expression = { "{0:n0}" -f $_ . entries . count } } , @ { Name = "Log" ; Expression = { $_ . LogDisplayname } } | ConvertTo-Html -Fragment for ( $i = 1 ; $i -le $html . table . tr . count -1 ; $i ++ ) { if ( $html . table . tr [ $i ] . td [ 3 ] -eq 0 ) { $class = $html . CreateAttribute ( "class" ) $class . value = 'alert' $html . table . tr [ $i ] . attributes . append ( $class ) | out-null } } $fragments += $html . InnerXml $fragments += "</div>" $fragments += "<p class='footer'>$(get-date)</p>" $head = @" <Title>System Report - $($env:computername)</Title> <style> body { background-color : #E5E4E2 ; font-family : Monospace ; font-size : 10pt ; } td, th { border : 0px solid black ; border-collapse : collapse ; white-space : pre ; } th { color : white ; background-color : black ; } table, tr, td, th { padding : 2px ; margin : 0px ; white-space : pre ; } tr:nth-child(odd) { background-color : lightgray } table { width : 95% ; margin-left : 5px ; margin-bottom : 20px ; } h2 { font-family : Tahoma ; color : #6D7B8D ; } .alert { color : red ; } .footer { color : green ; margin-left : 10px ; font-family : Tahoma ; font-size : 8pt ; font-style : italic ; } .transparent { background-color : #E5E4E2 ; } </style> <script type = 'text/javascript' src = 'https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js' > </script> <script type = 'text/javascript' > function toggleDiv ( divId ) { ` $ ( "#" + divId ) . toggle ( ) ; } function toggleAll ( ) { var divs = document . getElementsByTagName ( 'div' ) ; for ( var i = 0 ; i < divs . length ; i ++ ) { var div = divs [ i ] ; ` $ ( "#" + div . id ) . toggle ( ) ; } } </script> "@ $convertParams = @ { head = $head body = $fragments } convertto-html @ convertParams | out-file -FilePath $Path Get-Item -Path $Path



I have added my default values. You would want to change them for yourself. When I run the script, I end up with a file like this.

I have clicked the toggle all link to collapse all the sections. I can click in the individual section headings to toggle them.

What do you think? Is this something you think you would find useful? This is definitely something you want to try for yourself and feel free to experiment. Make sure you try the CSS settings, assuming you are like me, and have limited experience with it.

I hope you found this series of articles enjoyable. If there is some other HTML-related element that you would like to add to your PowerShell reports, ping me on Twitter @jeffhicks.