Write-FormatTableView.ps1

function Write-FormatTableView
{
    <#
    .Synopsis
        Writes a view for Format-Table
    .Description
        Writes the XML for a PowerShell Format TableControl.
    .Example
        Write-FormatTableView -Property myFirstProperty,mySecondProperty -TypeName MyPropertyBag
    .Example
        Write-FormatTableView -Property "Friendly Property Name" -RenameProperty @{
            "Friendly Property Name" = 'SystemName'
        }
    .Example
        Write-FormatTableView -Property Name, Bio -Width 20 -Wrap
    .Example
        Write-FormatTableView -Property Number, IsEven, IsOdd -AutoSize -ColorRow {if ($_.N % 2) { "#ff0000"} else {"#0f0"} } -VirtualProperty @{
            IsEven = { -not ($_.N % 2)}
            IsOdd = { ($_.N % 2) -as [bool] }
        } -AliasProperty @{
            Number = 'N'
        }
    .Link
        Write-FormatView
    #>

    [OutputType([string])]
    param(
    # The list of properties to display.
    [Parameter(Mandatory=$true,Position=0,ValueFromPipelineByPropertyName=$true)]
    [String[]]$Property,

    # If set, will rename the properties in the table.
    # The oldname is the name of the old property, and value is either the new header
    [Parameter(ValueFromPipelineByPropertyName=$true)]
    [Alias('RenamedProperty', 'RenameProperty')]
    [ValidateScript({
        foreach ($kv in $_.GetEnumerator()) {
            if ($kv.Key -isnot [string] -or $kv.Value -isnot [string]) {
                throw "All keys and values in the property rename map must be strings"
            }
        }
        return $true
    })]
    [Collections.IDictionary]$AliasProperty,

    # If set, will create a number of virtual properties within a table
    [Parameter(ValueFromPipelineByPropertyName=$true)]
    [ValidateScript({
        foreach ($kv in $_.GetEnumerator()) {
            if ($kv.Key -isnot [string] -or $kv.Value -isnot [ScriptBlock]) {
                throw "May only contain property names and script blocks"
            }
        }
        return $true
    })]
    [Collections.IDictionary]$VirtualProperty = @{},

    # If set, will be used to format the value of a property.
    [Parameter(Position=4,ValueFromPipelineByPropertyName=$true)]
    [ValidateScript({
        foreach ($kv in $_.GetEnumerator()) {
            if ($kv.Key -isnot [string] -or $kv.Value -isnot [string]) {
                throw "The FormatProperty parameter must contain only strings"
            }
        }
        return $true
    })]
    [Collections.IDictionary]$FormatProperty,


    # If provided, will set the alignment used to display a given property.
    [Parameter(ValueFromPipelineByPropertyName=$true)]
    [ValidateScript({
        foreach ($kv in $_.GetEnumerator()) {
            if ($kv.Key -isnot [string] -or 'left', 'right', 'center' -notcontains $kv.Value) {
                throw 'The alignment property may only contain property names and the values: left, right, and center'
            }
        }
        return $true
    })]
    [Collections.IDictionary]$AlignProperty,

    # If provided, will conditionally color the property.
    # This will add colorization in the hosts that support it, and act normally in hosts that do not.
    # The key is the name of the property. The value is a script block that may return one or two colors as strings.
    # The color strings may be ANSI escape codes or two hexadecimal colors (the foreground color and the background color)
    [Parameter(ValueFromPipelineByPropertyName=$true)]
    [ValidateScript({
        foreach ($kv in $_.GetEnumerator()) {
            if ($kv.Key -isnot [string] -or $kv.Value -isnot [ScriptBlock]) {
                throw "May only contain property names and script blocks"
            }
        }
        return $true
    })]
    [Alias('ColourProperty')]
    [Collections.IDictionary]$ColorProperty,

    # If provided, will colorize all rows in a table, according to the script block.
    # If the script block returns a value, it will be treated either as an ANSI escape sequence or up to two hexadecimal colors
    [Parameter(ValueFromPipelineByPropertyName=$true)]
    [Alias('ColourRow')]
    [ScriptBlock]$ColorRow,

    # If set, the table will be autosized.
    [switch]
    $AutoSize,

    # If set, the table headers will not be displayed.
    [Alias('HideTableHeaders','HideTableHeader')]
    [switch]
    $HideHeader,

    # The width of any the properties. This parameter is optional, and cannot be used with -AutoSize.
    # A negative width is a right justified table.
    # A positive width is a left justified table
    # A width of 0 will not include an alignment hint.
    [ValidateRange(-100,100)]
    [Parameter(ValueFromPipelineByPropertyName=$true)]
    [int[]]$Width,

     # If wrap is set, then items in the table can span multiple lines
    [Parameter(ValueFromPipelineByPropertyName=$true)]
    [switch]$Wrap,

    # If provided, the table view will only be used if the the typename includes this value.
    # This is distinct from the overall typename, and can be used to have different table views for different inherited objects.
    [Parameter(ValueFromPipelineByPropertyName=$true)]
    [string]
    $ViewTypeName,

    # If provided, the table view will only be used if the the typename is in a SelectionSet.
    # This is distinct from the overall typename, and can be used to have different table views for different inherited objects.
    [Parameter(ValueFromPipelineByPropertyName=$true)]
    [string]
    $ViewSelectionSet,

    # If provided, will selectively display items.
    [Parameter(ValueFromPipelineByPropertyName=$true)]
    [ScriptBlock]
    $ViewCondition)

    begin {
        $rowEntries = @()
    }

    process {
        $tableHeader = ''
        $rowColumns =
            @(for ($i =0; $i -lt $property.Count; $i++) {
                $p = $property[$i]
                if ($Width -and $Width[$i]) {
                    if ($Width[$i] -lt 0) {
                        $widthTag = "<Width>$([Math]::Abs($Width[$i]))</Width>"
                        $alignment = "<Alignment>right</Alignment>"
                    } else {
                        $widthTag = "<Width>$([Math]::Abs($Width[$i]))</Width>"
                        $alignment = "<Alignment>left</Alignment>"
                    }
                } else {
                    $widthTag = ''
                }

                if ($AlignProperty.$p) {
                    $alignment = "<Alignment>$($AlignProperty.$p)</Alignment>"
                }

                $format =
                    if ($FormatProperty.$p) {
                        "<FormatString>$($FormatProperty.$p)</FormatString>"
                    } else { '' }


                if ($ColorProperty.$p -or $ColorRow) {
                    $existingScript =
                        if ($VirtualProperty.$p) {
                            $VirtualProperty.$p
                        }
                        elseif ($AliasProperty.$p) {
                            "`$_.'$($AliasProperty.$p.Replace("'","''"))'"
                        } else {
                            "`$_.'$($p.Replace("'","''"))'"
                        }
                    $colorizedScript = "
                `$__ = `$_
                `$ci = . {$(if ($ColorProperty.$p) { $ColorProperty.$p} else {$ColorRow})}
                `$_ = `$__"
 +
                $ConvertCiToEscapeSequence +
                "
                `$output = . {"
 + $existingScript + '}
                '
 + $outputAndClearCI
                    $VirtualProperty.$p = $colorizedScript
                }

                if ($ColorProperty.$p) {
                    "<!-- {ConditionalColor:`"$([Security.SecurityElement]::Escape($ColorProperty.$p))`"}-->"
                }
                $label = ""
                # If there was an alias defined for this property, use it
                if ($AliasProperty.$p -or $VirtualProperty.$p) {
                    $label = "<Label>$p</Label>"
                    if ($VirtualProperty.$p) {
                        "<TableColumnItem><ScriptBlock>$([Security.SecurityElement]::Escape($VirtualProperty.$p))</ScriptBlock>$format</TableColumnItem>"
                    } else {
                        "<TableColumnItem><PropertyName>$($AliasProperty.$p)</PropertyName>$Format</TableColumnItem>"
                    }
                } else {
                    "<TableColumnItem><PropertyName>$p</PropertyName>$Format</TableColumnItem>"
                }
                $TableHeader += "<TableColumnHeader>${Label}${alignment}${WidthTag}</TableColumnHeader>"
            })

        $rowEntries += @(
            if ($ColorRow) {
                "<!-- {ConditionalColor:`"$([Security.SecurityElement]::Escape($ColorRow))`"}-->"
            }
            "<TableRowEntry>"
            $(if ($Wrap) { "<Wrap/>" })
            if ($PSBoundParameters.ViewTypeName -or $PSBoundParameters.ViewSelectionSet) {
                "<EntrySelectedBy>"
                    if ($ViewCondition) {
                        "<SelectionCondition>"
                    }
                    if ($ViewTypeName) {
                        "<TypeName>$([Security.SecurityElement]::Escape($ViewTypeName))</TypeName>"
                    } else {
                        "<SelectionSetName>$([Security.SecurityElement]::Escape($ViewSelectionSet))</SelectionSetName>"
                    }
                    if ($viewCondition) {
                        "<ScriptBlock>$([Security.SecurityElement]::Escape($viewCondition))</ScriptBlock></SelectionCondition>"
                    }
                "</EntrySelectedBy>"
            }
            "<TableColumnItems>"
            $rowColumns
            "</TableColumnItems></TableRowEntry>"
        ) -join ''
    }
    end {
        $theTableControl = @(
            '<TableControl>'
            if ($AutoSize) {'<AutoSize/>'}
            if ($HideHeader) {'<HideTableHeaders/>'}
            '<TableHeaders>'
            $tableHeader
            '</TableHeaders>'
            '<TableRowEntries>'
            $rowEntries
            '</TableRowEntries>'
            '</TableControl>'
        ) -join ''

        $xml=[xml]$theTableControl
        if (-not $xml) { return }
        $xOut=[IO.StringWriter]::new()
        $xml.Save($xOut)
        "$xOut".Substring('<?xml version="1.0" encoding="utf-16"?>'.Length + [Environment]::NewLine.Length)
        $xOut.Dispose()
    }
}