erstelle-HTMLReport (PowerShell-Funktion)

Die Idee

Viele meiner Scripte senden mir Daten und Auswertungen per Mail zu. Das Lesen kann je nach Datenmenge und Mailhäufigkeit schnell zum Zeitfresser werden. Daher färbe ich die Informationen gerne ein und signalisiere damit bereits beim Überfliegen der Mails, ob alles ok ist oder ob vielleicht doch Probleme aufgetreten sind. Gerade am Handy scrolle ich die Mails nur schnell nach unten. Ist alles grün, dann muss ich nicht genauer hinschauen.

Aber wie färbt man gezielt Informationen in der Darstellung? Natürlich mit HTML. Das hat zudem den Vorteil, dass gängige Mailclients den Text korrekt darstellen. Leider hat Microsoft aber kenie passenden cmdlets für meine Aufgabenstellung geliefert. Also habe ich mir eine Funktion für die Lösung erstellt. Diese möchte ich hier kurz vorstellen.

Von der Idee zum Code

Das ist mein Ziel: Bestimmte Informationen sollen sich farblich vom Rest abheben:

PowerShell-Funktion erstelle-HTMLReport

Der HTML-Text dazu sieht dann so aus:

PowerShell-Funktion erstelle-HTMLReport

Im Header definiere ich zu Beginn einige Style-Elemente. Diese kann ich dann in den einzelnen TD-Tags (also den Tabellenzeilen) verwenden. Welche Zeile welche class zugewiesen bekommt, soll durch meine Funktion gesteuert werden.

Das fertige Script

Der finale Code wurde von mir wieder in Teilbereiche untergliedert. Zusätzlich stellte ich mir noch die Anforderung der Pipeline-Fähigkeit. Daher benötigte ich die Blöcke Begin, Process und End:

PowerShell-Funktion erstelle-HTMLReport

Die Parameter habe ich in 3 Teile gegliedert. Deren Verwendung zeige ich nachher praktisch an verschiedenen Beispielen.

Im Block Begin wird der HTML-Header generiert. Natürlich nur, wenn der HTML-Text noch leer ist. So kann ich auch mehrere Tabellen in einer HTML-Datei zusammenfassen, ohne dass es dublizierte Header gibt:

PowerShell-Funktion erstelle-HTMLReport

Im Block Process werden nur die einzelnen Zeilen der Eingabetabelle gesammelt:

PowerShell-Funktion erstelle-HTMLReport

Im Block End passiert das eigentliche Rendern der HTML-Tabelle. Zuerst werden die Spaltennamen in einem TH-Tag gebildet. Diese können automatisch mit get-member ausgelesen werden oder der Anwender gibt sie als Parameterargument mit an:

PowerShell-Funktion erstelle-HTMLReport

Dann wird die Tabelle zeilenweise erstellt. Für jede Zeile können durch Bedingungen die entsprechenden CSS-Classes bestimmt werden:

PowerShell-Funktion erstelle-HTMLReport

Im dritten Teil vom End-Block wird die Ausgabe vorgenommen: Wahlweise als String oder als Datei im Dateisystem:

PowerShell-Funktion erstelle-HTMLReport

Das ist der finale Code:

function erstelle-HTMLReport {
    <#
    .NOTES
       Scriptreihe:      Hilfsscripte
       Datum:            2020-07-3
       Version:          V1.00
       Programmierer:    Stephan Walther

    .Synopsis
       Die Funktion generiert eine HTML-Tabelle aus einem PowerShell-Objekt.

    .DESCRIPTION
       Jede zweidimensionale Datenstruktur lässt sich mit ConvertTo-HTML in eine HTML-Tabelle 
       konvertieren. Diese Funktion geht weiter und erlaubt die Generierung eines vollständigen
       HTML-Textes um die Tabelle inklusive dem Einsatz von CSS und Formatierungen.

    .EXAMPLE
       $Sample1 = Get-Process | select -First 10 -Property name,ws,cpu       
       erstelle-HTMLReport -Daten $Sample1 -Headline 'Prozessliste' -Muster 

       Es wird der vollständige Text im HTML-Format ausgegeben.
    .EXAMPLE
       $Sample1 = Get-Process | select -First 10 -Property name,ws,cpu

       erstelle-HTMLReport `
               -Daten      $Sample1 `
               -Headline   'Prozessliste' `
               -Sortierung 'name','ws','cpu'  `
               -gelb       '$_.cpu -gt 10' `
               -gruen      '$_.cpu -lt 5' `
               -rot        '$_.cpu -gt 50' `
               -Dateiname  "$($env:USERPROFILE)\Desktop\Sample.htm"

       & "$($env:USERPROFILE)\Desktop\Sample.htm"

       Die Demodaten werden in einer html-Datei auf dem Desktop ausgegeben. Ist der Zellwert der
       Spalte "CPU" kleiner als 5, wird sie grün gefärbt. Zwischen 10 und 50 wird sie gelb und wenn
       der Wert größer als 50 ist, wird sie rot. Die Reihenfolge der Spalten wird angepasst.

    .EXAMPLE
       $Sample1 = Get-Process | select -First 10 -Property name,ws,cpu
       $Sample2 = Get-service | select -First 10 -Property name,Status,DisplayName
        
       $HTML = erstelle-HTMLReport `
                   -Daten      $Sample1 `
                   -Headline   'Prozessliste' `
                   -Sortierung 'name','ws','cpu'  `
                   -gelb       '$_.cpu -gt 10' `
                   -gruen      '$_.cpu -lt 5' `
                   -rot        '$_.cpu -gt 50'
       $HTML = erstelle-HTMLReport `
                   -HTML       $HTML `
                   -Daten      $Sample2 `
                   -Headline   'Serviceliste' `
                   -Sortierung 'name','Status','DisplayName'  `
                   -gelb       '$_.Status -eq "stopped"' `
                   -gruen      '$_.Status -eq "running"'

       $HTML | Out-File -FilePath "$($env:USERPROFILE)\Desktop\Sample.htm"

       & "$($env:USERPROFILE)\Desktop\Sample.htm"  
       
       Die erste Verwendung der Funktion erstellt einen fertigen HTML-Text aus der Tabelle 
       $Sample1. Der Text wird im zweiten Aufruf als Argument an den Parameter -HTML übergeben.
       Dadurch wird die Tabelle $Sample2 in den HTML-Text integriert. Der finale Text wird
       mit out-file gespeichert und im Browser geöffnet.   
       
    .EXAMPLE
       Get-Process | 
           erstelle-HTMLReport -Dateiname "$($env:USERPROFILE)\Desktop\Sample.htm"
    
       & "$($env:USERPROFILE)\Desktop\Sample.htm"        

       Hier wird die Funktion über die Pipeline aufgerufen.
    
    .PARAMETER Daten
       An diesen Parameter wird die zweidimensionale Datentabelle übergeben. Sie kann aber auch 
       durch die Pipeline referenziert werden. Der Parameter ist pflicht.

    .PARAMETER HTML
       Wenn mehrere Tabellen in einem gemeinsamen HTML-Text untergebracht werden sollen, dann 
       kann die Funktion erneut aufgerufen werden. Der Text aus dem ersten Aufruf wird dann
       über den Parameter HTML übergeben. So werden mehrfache Header vermieden. Wenn nur eine 
       Tabelle gerendert werden soll, dann kann das Argument leer bleiben.

    .PARAMETER Muster
       Mit diesem Schalter wird eine abwechselnde Hintergrundfarbe der Tabellenzeilen aktiviert.
       Das erhöht die Lesbarkeit. Der Parameter schließt die gleichzeitige Verwendung der 
       anderen Farbparameter aus.

    .PARAMETER gruen
       Der Farbparameter kann nur ohne den Parameter -Muster verwendet werden. Kombinationen 
       mit den anderen Farbparametern sind möglich. Es muss als Argument eine Bedingung 
       formuliert werden. Die Syntax entspricht der eines Where-Object. Ist die Bedingung 
       erfüllt, dann wird die Tabellenzeile die angegebene Parameterfarbe als Hintergrundfarbe
       erhalten.

       get-process | where-object {$_.ws -gt 100MB}    
       # ==> liefert alle Prozesse mit mehr als 100MB RAM

       get-process | erstelle-HTML -rot '$_.ws -gt 100MB'
       # ==> in der HTML-Tabelle werden alle Prozesszeilen mit mehr als 100MB RAM rot gefärbt

    .PARAMETER gelb
       Der Farbparameter kann nur ohne den Parameter -Muster verwendet werden. Kombinationen 
       mit den anderen Farbparametern sind möglich. Es muss als Argument eine Bedingung 
       formuliert werden. Die Syntax entspricht der eines Where-Object. Ist die Bedingung 
       erfüllt, dann wird die Tabellenzeile die angegebene Parameterfarbe als Hintergrundfarbe
       erhalten.

       get-process | where-object {$_.ws -gt 100MB}    
       # ==> liefert alle Prozesse mit mehr als 100MB RAM

       get-process | erstelle-HTML -rot '$_.ws -gt 100MB'
       # ==> in der HTML-Tabelle werden alle Prozesszeilen mit mehr als 100MB RAM rot gefärbt

    .PARAMETER rot
       Der Farbparameter kann nur ohne den Parameter -Muster verwendet werden. Kombinationen 
       mit den anderen Farbparametern sind möglich. Es muss als Argument eine Bedingung 
       formuliert werden. Die Syntax entspricht der eines Where-Object. Ist die Bedingung 
       erfüllt, dann wird die Tabellenzeile die angegebene Parameterfarbe als Hintergrundfarbe
       erhalten.

       get-process | where-object {$_.ws -gt 100MB}    
       # ==> liefert alle Prozesse mit mehr als 100MB RAM

       get-process | erstelle-HTML -rot '$_.ws -gt 100MB'
       # ==> in der HTML-Tabelle werden alle Prozesszeilen mit mehr als 100MB RAM rot gefärbt

    .PARAMETER blau
       Der Farbparameter kann nur ohne den Parameter -Muster verwendet werden. Kombinationen 
       mit den anderen Farbparametern sind möglich. Es muss als Argument eine Bedingung 
       formuliert werden. Die Syntax entspricht der eines Where-Object. Ist die Bedingung 
       erfüllt, dann wird die Tabellenzeile die angegebene Parameterfarbe als Hintergrundfarbe
       erhalten.

       get-process | where-object {$_.ws -gt 100MB}    
       # ==> liefert alle Prozesse mit mehr als 100MB RAM

       get-process | erstelle-HTML -rot '$_.ws -gt 100MB'
       # ==> in der HTML-Tabelle werden alle Prozesszeilen mit mehr als 100MB RAM rot gefärbt

    .PARAMETER Sortierung
       Ohne den Parameter -Sortierung werden die Tabellenspalten mit einem get-member automatisch
       ermittelt. Dabei kann aber die Sortierung verändert werden. Stattdessen können die Namen
       der Spalten in der richtigen Reihenfolge als Argument angegeben werden. Damit können auch
       Spalten ausgelassen werden.
       
    .PARAMETER Dateiname
       Ohne den Parameter -Dateiname werden die HTML-Daten als Text zurückgegeben. Alternativ kann
       auch ein Dateipfad als Argument übergeben werden. Dann speichert die Funktion den HTML-Text
       direkt in der Datei.

    #>
    param(
        [cmdletbinding()]
        [parameter(Mandatory=$true,ValueFromPipeline=$true)]  [Object[]] $Daten      = @(),
        [parameter(Mandatory=$false)] [string[]] $HTML       = @(),        
        [parameter(Mandatory=$false)] [string]   $Headline   = '',
                
        [parameter(Mandatory=$false)] [Switch]   $Muster     = $false,
        [parameter(Mandatory=$false)] [string]   $gruen      = '',
        [parameter(Mandatory=$false)] [string]   $gelb       = '',
        [parameter(Mandatory=$false)] [string]   $rot        = '',
        [parameter(Mandatory=$false)] [string]   $blau       = '',        

        [parameter(Mandatory=$false)] [string[]] $Sortierung = '',
        [parameter(Mandatory=$false)] [string]   $Dateiname  = ''
    )

    begin {
        #region Variablen
            $Ausgabe = @()
            $Tabelle = @()
        #endregion
            
        #region erzeuge HTML-Header
            if ($HTML.count -eq 0) {
                $Ausgabe += "<HTML>"
                $Ausgabe += "<STYLE>"
                $Ausgabe += "   BODY{font-family: Arial; font-size: 8pt;}"
                $Ausgabe += "   H1{font-size: 16px;}"
                $Ausgabe += "   H2{font-size: 14px;}"
                $Ausgabe += "   H3{font-size: 12px;}"
                $Ausgabe += "   TABLE{border: 1px solid black; border-collapse: collapse; font-size: 8pt;}"
                $Ausgabe += "   TH{border: 1px solid black; background: #dddddd; padding: 5px; color: #000000;}"
                $Ausgabe += "   TD{border: 1px solid black; padding: 5px; }"
                $Ausgabe += "   td.pass{background: #7FFF00;}"
                $Ausgabe += "   td.warn{background: #FFE600;}"
                $Ausgabe += "   td.fail{background: #FF0000; color: #ffffff;} "
                $Ausgabe += "   td.info{background: #85D4FF;}"
                $Ausgabe += "   td.inf2{background: #B2F0FF;}"
                $Ausgabe += "</STYLE>"
                $Ausgabe += "<BODY>"
            } else {
                $Ausgabe += $HTML -split "`r`n" | Select-Object -SkipLast 1
            }
        #endregion

        #region erzeuge Headline
            if ($Headline -ne '') {$Ausgabe += "<h3>$Headline</h3>"}
        #endregion
    }

    process {
        #region Daten sammeln
            $Tabelle += $Daten
        #endregion
    }

    end {
        #region generiere Kopfzeile der Ausgabetabelle
            $Spaltennamen = ""
            if (($Tabelle).length -ne 0) {
                if ($Sortierung[0] -eq '') {                        
                    $Spaltennamen = ($Tabelle | 
                        Get-Member -MemberType Properties | 
                            Select-Object -ExpandProperty name) -join '</TH><TH align="left">'
                } else { 
                    $Spaltennamen = $Sortierung  -join '</TH><TH align="left">'
                } 
            }
        #endregion

        #region erzeuge Zeilen der Ausgabetabelle        
            if (($Tabelle).length -ne 0) {
                $Ausgabe += "   <TABLE>"
                $Ausgabe += '      <TH align="left">' + $Spaltennamen + "</TH>" 
            
                $Tabelle | ForEach-Object {
                    $DatenZeile = $_                 
                    $Tabellenzeile = '      <TR>'
                    $stat = 'leer'

                    if ($Muster) {
                        if ($FarbeAn) {
                            $stat = "inf2"
                            $FarbeAn = $false
                        } else {
                            $stat = "leer"
                            $FarbeAn = $true
                        }                    
                    } else {
                        if ($blau  -ne '') { Invoke-Expression ('if (' + $blau  + ' ) {$stat = "info"}') }
                        if ($gruen -ne '') { Invoke-Expression ('if (' + $gruen + ' ) {$stat = "pass"}') }
                        if ($gelb  -ne '') { Invoke-Expression ('if (' + $gelb  + ' ) {$stat = "warn"}') }
                        if ($rot   -ne '') { Invoke-Expression ('if (' + $rot   + ' ) {$stat = "fail"}') }
                    }

                    if ($Sortierung[0] -eq '') {
                        $Tabelle | Get-Member -MemberType Properties | ForEach-Object {
                            $Spalte = $_.Name
                            $Tabellenzeile = $Tabellenzeile + '<TD class="' + $stat + '">' + $DatenZeile.$Spalte + '</TD>'
                        }
                    } else {
                        $Sortierung | ForEach-Object {
                            $Spalte = $_                
                            $Tabellenzeile = $Tabellenzeile + '<TD class="' + $stat + '">' + $DatenZeile.$Spalte + '</TD>'
                        }
                    }           
                    $Ausgabe += $Tabellenzeile + '</TR>' 
                }
                $Ausgabe += '   </TABLE>'
            } 
            $Ausgabe += '</BODY></HTML>'
        #endregion

        #region Ausgabe
            if ($Dateiname -eq '') {
                RETURN ($Ausgabe -join "`r`n")
            } else {        
                ($Ausgabe -join "`r`n") | Out-File -FilePath $Dateiname -Encoding default
            }
        #endregion 
    }   
}

Mögliche Aufrufe

Und so könnten die Szenarien der Verwendung aussehen. Hier übergebe ich ein paar Zeilen aus der Prozessliste an die Funktion. Mit dem Parameter -Muster generiere ich abwechselnde Hintergrundfarben für die Zeilen. Der Text wird als Ergebnis ausgegeben:

PowerShell-Funktion erstelle-HTMLReport

Im nächsten Beispiel verwende ich Bedingungen für die gezielte Einfärbung der Zeilen. Die Syntax entspricht dabei der vom Cmdlet Where-Object:

PowerShell-Funktion erstelle-HTMLReport

Das dritte Beispiel zeigt die Verbindung von zwei PowerShell-Tabellen in einem HTML-Text. Das Ergebnis des ersten Aufrufs wird in der Variable $HTML gespeichert und beim zweiten Aufruf als Argument übergeben. Die zweite Tabelle wird dabei an die richtige Stelle im HTML-Text eingebettet:

PowerShell-Funktion erstelle-HTMLReport
PowerShell-Funktion erstelle-HTMLReport

Mein 4. Beispiel zeigt die Pipeline-Fähigkeit. Ebenso kann natürlich auch auf die Einfärbung verzichtet werden:

PowerShell-Funktion erstelle-HTMLReport

Zusammenfassung

Die Funktion habe ich bereits in vielen meiner Scripte verbaut. So spare ich mir viel Zeit beim Lesen der unzähligen Logmails pro Tag. Und etwas Farbe kann man auch so immer gut gebrauchen.

Das Script mit Beispielen könnt ihr hier als zip-Archiv herunterladen.

Stay tuned!

Kommentar hinterlassen