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() } } |