Public/Add-ExcelTable.ps1

function Add-ExcelTable {
    [CmdletBinding()]
    [OutputType([OfficeOpenXml.Table.ExcelTable])]
    param (
        [Parameter(Mandatory=$true)]
        [OfficeOpenXml.ExcelRange]$Range,
        [String]$TableName = "",
        [OfficeOpenXml.Table.TableStyles]$TableStyle = 'Medium6',
        [Switch]$ShowHeader ,
        [Switch]$ShowFilter,
        [Switch]$ShowTotal,
        [hashtable]$TableTotalSettings,
        [Switch]$ShowFirstColumn,
        [Switch]$ShowLastColumn,
        [Switch]$ShowRowStripes,
        [Switch]$ShowColumnStripes,
        [Switch]$PassThru
    )
    try {
        if ($TableName -eq "" -or $null -eq $TableName) {
            $tbl = $Range.Worksheet.Tables.Add($Range, "")
        }
        else {
            if ([OfficeOpenXml.FormulaParsing.ExcelUtilities.ExcelAddressUtil]::IsValidAddress($TableName)) {
                Write-Warning -Message "$TableName reads as an Excel address, and so is not allowed as a table name."
                return
            }
            if ($TableName -notMatch '^[A-Z]') {
                Write-Warning -Message "$TableName is not allowed as a table name because it does not begin with a letter."
                return
            }
            if ($TableName -match "\W") {
                Write-Warning -Message "At least one character in $TableName is illegal in a table name and will be replaced with '_' . "
                $TableName = $TableName -replace '\W', '_'
            }
            $ws = $Range.Worksheet
            #if the table exists in this worksheet, update it.
            if ($ws.Tables[$TableName]) {
                $tbl =$ws.Tables[$TableName]
                $tbl.TableXml.table.ref = $Range.Address
                Write-Verbose -Message "Re-defined table '$TableName', now at $($Range.Address)."
            }
            elseif ($ws.Workbook.Worksheets.Tables.Name -contains $TableName) {
                Write-Warning -Message "The Table name '$TableName' is already used on a different worksheet."
                return
            }
            else {
                $tbl = $ws.Tables.Add($Range, $TableName)
                Write-Verbose -Message "Defined table '$($tbl.Name)' at $($Range.Address)"
            }
        }
        #it seems that show total changes some of the others, so the sequence matters.
        if     ($PSBoundParameters.ContainsKey('ShowHeader'))        {$tbl.ShowHeader        = [bool]$ShowHeader}
        if     ($PSBoundParameters.ContainsKey('TableTotalSettings') -And $Null -ne $TableTotalSettings)     {
            $tbl.ShowTotal = $true
            foreach ($k in $TableTotalSettings.keys) {
                
                # Get the Function to be added in the totals row
                if ($TableTotalSettings[$k] -is [HashTable]) { 
                    If ($TableTotalSettings[$k].Keys -contains "Function") {
                        $TotalFunction = $TableTotalSettings[$k]["Function"]
                    }
                    Else { Write-Warning -Message "TableTotalSettings parameter for column '$k' needs a key 'Function'" }
                }
                else { 
                    $TotalFunction = [String]($TableTotalSettings[$k]) 
                }
                
                # Add the totals row
                if (-not $tbl.Columns[$k]) {Write-Warning -Message "Table does not have a Column '$k'."}
                elseif ($TotalFunction -match "^=") {
                    ### A function in Excel uses ";" between parameters but the OpenXML parameter separator is ","
                    ### Only replace semicolon when it's NOT somewhere between quotes quotes.
                    # Get all text between quotes
                    $QuoteMatches = [Regex]::Matches($TotalFunction,"`"[^`"]*`"|'[^']*'") 
                    # Create array with all indexes of characters between quotes (and the quotes themselves)
                    $QuoteCharIndexes = $(
                        Foreach ($QuoteMatch in $QuoteMatches) {
                            (($QuoteMatch.Index)..($QuoteMatch.Index + $QuoteMatch.Length - 1))
                        }
                    )

                    # Get all semicolons
                    $SemiColonMatches = [Regex]::Matches($TotalFunction, ";")
                    # Replace the semicolons of which the index is not in the list of quote-text indexes
                    Foreach ($SemiColonMatch in $SemiColonMatches.Index) {
                        If ($QuoteCharIndexes -notcontains $SemiColonMatch) {
                            $TotalFunction = $TotalFunction.remove($SemiColonMatch,1).Insert($SemiColonMatch,",")
                        }
                    }

                    # Configure the formula. The TotalsRowFunction is automatically set to "Custom" when the TotalsRowFormula is set.
                    $tbl.Columns[$k].TotalsRowFormula = $TotalFunction
                }
                elseif ($TotalFunction -notin @("Average", "Count", "CountNums", "Max", "Min", "None", "StdDev", "Sum", "Var") ) {
                    Write-Warning -Message "'$($TotalFunction)' is not a valid total function."
                }
                else {$tbl.Columns[$k].TotalsRowFunction = $TotalFunction}

                # Set comment on totals row
                If ($TableTotalSettings[$k] -is [HashTable] -and $TableTotalSettings[$k].Keys -contains "Comment" -and ![String]::IsNullOrEmpty($TableTotalSettings[$k]["Comment"])) {
                    $ColumnLetter = [officeOpenXml.ExcelAddress]::GetAddressCol(($tbl.columns | ? { $_.name -eq $k }).Id, $False)
                    $CommentRange = "{0}{1}" -f $ColumnLetter, $tbl.Address.End.Row

                    $CellCommentParams = @{
                        Worksheet    = $tbl.WorkSheet
                        Range        = $CommentRange
                        Text         = $TableTotalSettings[$k]["Comment"]
                    }

                    Set-CellComment  @CellCommentParams
                }
            }
        }
        elseif ($PSBoundParameters.ContainsKey('ShowTotal'))         {$tbl.ShowTotal         = [bool]$ShowTotal}
        if     ($PSBoundParameters.ContainsKey('ShowFilter'))        {$tbl.ShowFilter        = [bool]$ShowFilter}
        if     ($PSBoundParameters.ContainsKey('ShowFirstColumn'))   {$tbl.ShowFirstColumn   = [bool]$ShowFirstColumn}
        if     ($PSBoundParameters.ContainsKey('ShowLastColumn'))    {$tbl.ShowLastColumn    = [bool]$ShowLastColumn}
        if     ($PSBoundParameters.ContainsKey('ShowRowStripes'))    {$tbl.ShowRowStripes    = [bool]$ShowRowStripes}
        if     ($PSBoundParameters.ContainsKey('ShowColumnStripes')) {$tbl.ShowColumnStripes = [bool]$ShowColumnStripes}
        $tbl.TableStyle = $TableStyle

        if ($PassThru) {return $tbl}
    }
    catch {Write-Warning -Message "Failed adding table '$TableName' to worksheet '$WorksheetName': $_"}
}