Public/New-HTMLTable.ps1

function New-HTMLTable {
    <#
    .SYNOPSIS
    Creates an HTML table from an input object.
 
    .DESCRIPTION
    Creates an HTML table from an input object.
 
    .PARAMETER InputObject
    Object or array of objects as source data for HTML table.
 
    .PARAMETER Properties
    If specified, limit the table to these specific properties in the order specified.
 
    .PARAMETER SetAlternating
    Add CSS class = odd or even to each row. True by default. Be sure your CSS includes odd and even definitions.
 
    Example Output: <tr class="odd">
 
    .PARAMETER ListTableHeader
    If a list is provided, use this parameter to specify the list header (PowerShell uses * by default).
 
    .PARAMETER HTMLDecode
    If specified, sends HTML data to HtmlDecode to convert html specific characters like < or > to their character entity
 
    .PARAMETER ColumnIdPrefix
    Prefixes the id of td elements to the specified value and appends the column number.
 
    Example Output: <td id="value-0">
 
    .PARAMETER ColumnClass
    Sets the class of td elements to the specified value.
 
    Example Output: <td class="value">
 
    .PARAMETER ColumnClassPrefix
    Prefixes the class of td elements to the specified value and appends the column number.
 
    Example Output: <td class="value-0">
 
    .PARAMETER TableAttributes
    Adds any table attributes. Useful if you want to specify any additional CSS values for different tables in the same HTML data stream.
 
    .PARAMETER RemoveColumnGroup
    If specified, removes the <colgroup><col /></colgroup> tags from output.
 
    .PARAMETER AddTableTags
    If specified, adds <thead></thead>, <tbody></tbody>, <tfoot /> tags to the output.
 
    .PARAMETER NestedTable
    If specified, removes the opening <table> and closing </table> tags from the HTML output. This is useful when nesting similar tables within a container table.
 
    .PARAMETER RowIdPrefix
    Prefixes the id of tr elements to the specified value and appends the row number.
 
    .PARAMETER RowClass
    Sets the class of tr elements to the specified value.
 
    .PARAMETER RowClassPrefix
    Prefixes the class of tr elements to the specified value and appends the row number.
 
    .PARAMETER PrePendHeader
    If specified, adds string to the beginning of the <thead> section. This requires the AddTableTags parameter to inject the value of the PrePendHeader parameter.
 
    .PARAMETER RemoveHeader
    If specified, removes the th elements within a tr element as well as the enclosing tr element.
 
    .EXAMPLE
    Sample scripts can be found in the "Examples" folder off of the module's root path
 
    .NOTES
    Author: brandon said
    #>

    [CmdletBinding()]
    param (
        [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)]
        [PSObject[]]$InputObject,

        [Parameter(Mandatory = $false)]
        [String[]]$Properties,

        [Parameter(Mandatory = $false)]
        [Switch]$SetAlternating = $false,

        [Parameter(Mandatory = $false)]
        [Alias('ListTableHead')]
        [String]$ListTableHeader = $null,

        [Parameter(Mandatory = $false)]
        [Switch]$HTMLDecode = $false,

        [Parameter(Mandatory = $false)]
        [String]$ColumnIdPrefix = "",

        [Parameter(Mandatory = $false)]
        [String]$ColumnClass = "",

        [Parameter(Mandatory = $false)]
        [String]$ColumnClassPrefix = "",

        [Parameter(Mandatory = $false)]
        [String]$RowIdPrefix = "",

        [Parameter(Mandatory = $false)]
        [String]$RowClass = "",

        [Parameter(Mandatory = $false)]
        [String]$RowClassPrefix = "",

        [Parameter(Mandatory = $false)]
        [Switch]$RemoveHeader = $false,

        [Parameter(Mandatory = $false)]
        [HashTable]$TableAttributes,

        [Parameter(Mandatory = $false)]
        [Switch]$RemoveColumnGroup,

        [Parameter(Mandatory = $false)]
        [Switch]$AddTableTags,

        [Parameter(Mandatory = $false)]
        [Switch]$NestedTable
    )

    DynamicParam {
        if ($AddTableTags) {
            $Attributes = New-Object -Type System.Management.Automation.ParameterAttribute
            $Attributes.ParameterSetName = "AddTableTags"
            $Attributes.Mandatory = $false
            $AttributeCollection = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
            $AttributeCollection.Add($Attributes)

            $DynamicParameter1 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("PrependHeader", [String], $AttributeCollection)
            $DynamicParameters = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary
            $DynamicParameters.Add("PrependHeader", $DynamicParameter1)
            return $DynamicParameters
        }
    }

    begin {
        #Requires -Version 2.0
        Add-Type -AssemblyName System.Xml.Linq | Out-Null
        Add-Type -AssemblyName System.Web | Out-Null
        $Objects = New-Object System.Collections.ArrayList

        $PrependHeader = $PSBoundParameters['PrependHeader']
    }

    process {
        # Loop through inputObject, add to collection. Filter properties if specified.
        foreach ($Object in $InputObject) {
            if ($Properties) {
                [void]$Objects.Add(($Object | Select-Object $Properties))
            } else {
                [void]$Objects.Add($Object)
            }
        }
    }

    end {
        # Convert our data to x(ht)ml
        $Xml = [System.Xml.Linq.XDocument]::Parse("$($Objects | ConvertTo-Html -Fragment)")

        if ($RemoveColumnGroup) {
            $Xml.Element("table").Element("colgroup").Remove()
        }
        foreach ($Table in $($Xml.Descendants("table"))) {
            if ($null -ne $TableAttributes) {
                foreach ($Attribute in $TableAttributes.GetEnumerator()) {
                    $Table.SetAttributeValue($Attribute.Name, $Attribute.Value)
                }
            }
        }
        # Replace * as table head if specified. Note, this should only be done for a list...
        if ($ListTableHeader) {
            $Xml = [System.Xml.Linq.XDocument]::Parse($Xml.Document.ToString().Replace("<th>*</th>","<th>$ListTableHeader</th>"))
        }

        if ($AddTableTags) {
            $Xml.Descendants("tr").Where({$_.Element('th').Value}) | ForEach-Object -Begin {
                    $TableHeader = (New-Object -TypeName 'System.Xml.Linq.XElement' -ArgumentList ([System.Xml.Linq.XName]"thead"))
                    try {
                        $TableHeader.Add([System.Xml.Linq.XElement]::Parse($PrePendHeader))
                    } catch {
                        $TableHeader.Add($PrePendHeader)
                    }
                } -Process {
                    if (-not $RemoveHeader) {
                        $TableHeader.Add($_)
                    }
                $_.Remove()
                } -End {
                    if ($TableHeader.Elements("tr").Count -gt 0) {
                        $Xml.Root.Add($TableHeader)
                    }
            }

            $Xml.Descendants("tr").Where({$_.Element('td').Value}) | ForEach-Object -Begin {
                    $TableBody = (New-Object -TypeName 'System.Xml.Linq.XElement' -ArgumentList ([System.Xml.Linq.XName]"tbody"))
                } -Process {
                    $TableBody.Add($_)
                    $_.Remove()
                } -End {
                    if ($TableBody.Elements("tr").Count -gt 0) {
                        $Xml.Root.Add($TableBody)
                    }
            }
            $Xml.Root.Add((New-Object -TypeName 'System.Xml.Linq.XElement' -ArgumentList ([System.Xml.Linq.XName]"tfoot")))
        } else {
            if ($RemoveHeader) {
                $Xml.Descendants("tr").Where({$_.Element('th').Value}) | ForEach-Object {
                    $_.Remove()
                }
            }
        }

        # Loop through tr elements.
        foreach ($XmlTr in $($Xml.Descendants("tr"))) {
            # Finds rows with th elements
            if ($XmlTr.Where({$_.Element('th').Value})) {
                if ($RowIdPrefix -ne "") {
                    $XmlTr.SetAttributeValue("id", "$RowIdPrefix-$(($XmlTr.NodesBeforeSelf() | Measure-Object).Count)")
                }
                if ($RowClass -ne "") {
                    $XmlTr.SetAttributeValue("class", "$RowClass")
                }
                if ($RowClassPrefix -ne "") {
                    if ($RowClass -ne "") {
                        $XmlTr.SetAttributeValue("class", "$RowClassPrefix-$RowClass")
                    } else {
                        $XmlTr.SetAttributeValue("class", "$RowClassPrefix-$(($XmlTr.NodesBeforeSelf() | Measure-Object).Count)")
                    }
                }

                # Loop through th elements and set CSS id / class values
                foreach ($XmlTh in $($XmlTr.Descendants("th"))) {
                    if ($ColumnIdPrefix -ne "") {
                        $XmlTh.SetAttributeValue("id", "$ColumnIdPrefix-$(($XmlTh.NodesBeforeSelf() | Measure-Object).Count)")
                    }
                    if ($ColumnClassPrefix -ne "") {
                        $XmlTh.SetAttributeValue("class", "$ColumnClassPrefix-$(($XmlTh.NodesBeforeSelf() | Measure-Object).Count)")
                    }
                    if ($ColumnClass -ne "") {
                        $XmlTh.SetAttributeValue("class", "$ColumnClass".Trim())
                    }
                    if ($ColumnClassPrefix -ne "" -and $ColumnClass -ne "") {
                        $XmlTh.SetAttributeValue("class", "$($XMlTh.Attribute("class").Value) $ColumnClassPrefix-$ColumnClass".Trim())
                    }
                    if (($XmlTh.NodesBeforeSelf() | Measure-Object).Count -eq 0) {
                        $XmlTh.SetAttributeValue("class", "$($XMlTh.Attribute("class").Value) first-child".Trim())
                    } elseif (($XmlTh.NodesBeforeSelf() | Measure-Object).Count -eq ($XmlTr.Descendants("th").Value).Count - 1) {
                        $XmlTh.SetAttributeValue("class", "$($XMlTh.Attribute("class").Value) last-child".Trim())
                    }
                }
            }
            # Finds rows with td elements and set class to even or odd depening on the index value.
            if ($XmlTr.Where({$_.Element('td')})) {
                if ($RowIdPrefix -ne "") {
                    $XmlTr.SetAttributeValue("id", "$RowIdPrefix-$(($XmlTr.NodesBeforeSelf() | Measure-Object).Count)")
                }
                if ($RowClass -ne "") {
                    $XmlTr.SetAttributeValue("class", "$RowClass")
                }
                if ($RowClassPrefix -ne "") {
                    if ($RowClass -ne "") {
                        $XmlTr.SetAttributeValue("class", "$RowClassPrefix-$RowClass")
                    } else {
                        $XmlTr.SetAttributeValue("class", "$RowClassPrefix-$(($XmlTr.NodesBeforeSelf() | Measure-Object).Count)")
                    }
                }
                if ($SetAlternating) {
                    if (($XmlTr.NodesBeforeSelf() | Measure-Object).Count % 2 -eq 0) {
                        $XmlTr.SetAttributeValue("class", "even $($XMlTr.Attribute("class").Value)".Trim())
                    }
                    else {
                        $XmlTr.SetAttributeValue("class", "odd $($XMlTr.Attribute("class").Value)".Trim())
                    }
                }
                # Loop through td elements and set CSS id / class values
                foreach ($XmlTd in $($XmlTr.Descendants("td"))) {
                    $XmlTd.SetAttributeValue("valign", "top")
                    if ($ColumnIdPrefix -ne "") {
                        $XmlTd.SetAttributeValue("id", "$($XMlTd.Attribute("class").Value) $ColumnIdPrefix-$(($XmlTd.NodesBeforeSelf() | Measure-Object).Count)".Trim())
                    }
                    if ($ColumnClass -ne "") {
                        $XmlTd.SetAttributeValue("class", "$($XMlTd.Attribute("class").Value) $ColumnClass".Trim())
                    }
                    if ($ColumnClassPrefix -ne "") {
                        if ($ColumnClass -ne "") {
                            $XmlTd.SetAttributeValue("class", "$($XMlTd.Attribute("class").Value) $ColumnClassPrefix-$ColumnClass".Trim())
                        } else {
                            $XmlTd.SetAttributeValue("class", "$($XMlTd.Attribute("class").Value) $ColumnClassPrefix-$(($XmlTd.NodesBeforeSelf() | Measure-Object).Count)".Trim())
                        }
                    }
                    if (($XmlTd.NodesBeforeSelf() | Measure-Object).Count -eq 0) {
                        $XmlTd.SetAttributeValue("class", "$($XMlTd.Attribute("class").Value) first-child".Trim())
                    }
                    if (($XmlTd.NodesBeforeSelf() | Measure-Object).Count -eq ($XmlTr.Descendants("td") | Measure-Object).Count - 1) {
                        $XmlTd.SetAttributeValue("class", "$($XMlTd.Attribute("class").Value) last-child".Trim())
                    }                    
                }
            }
        }
        # Optionally convert <HTML> tags to text to not conflict with XML tags on a per table basis
        # Use -HTMLDecode in Close-HTML if you want to convert entire HTML output
        if ($HTMLDecode) {
            $DecodedXML = [System.Web.HttpUtility]::HtmlDecode($Xml)
        } else {
            $DecodedXML = [System.Xml.Linq.XDocument]::Parse($Xml).Document.ToString()
        }
        if ($NestedTable -and $DecodedXML.StartsWith("<table>`r`n") -and $DecodedXML.EndsWith("`r`n</table>")) {
            $DecodedXML = $DecodedXML.Substring(9, $DecodedXML.Length - 19)
        }
        $DecodedXML
    }
}