Commands/Format.PS1XML/Write-FormatCustomView.ps1

function Write-FormatCustomView
{
    <#
    .Synopsis
        Writes the format XML for a custom view.
    .Description
        Writes the .format.ps1xml fragement for a custom control view, or a custom control.
    .Link
        Write-FormatViewExpression
    .Link
        Write-FormatView
    .Link
        Write-FormatControl
    .Example
        Write-FormatCustomView -Action { "This is a message from Process $pid" }
    #>

    [Alias('Write-CustomAction')]
    param(
    # The script block used to fill in the contents of a custom control.
    # The script block can either be an arbitrary script, which will be run, or it can include a
    # number of speicalized commands that will translate into parts of the formatter.
    [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
    [Alias('ScriptBlock')]
    [ScriptBlock[]]$Action,

    # If set, will put the expression within a <Frame> element.
    [Parameter(ValueFromPipelineByPropertyName)]
    [switch]
    $Frame,

    # If provided, will indent by a number of characters. This implies -Frame.
    [Parameter(ValueFromPipelineByPropertyName)]
    [Alias('Indent')]
    [ValidateRange(0,1mb)]
    [int]
    $LeftIndent,

    # If provided, will indent the right by a number of characters. This implies -Frame.
    [Parameter(ValueFromPipelineByPropertyName)]
    [ValidateRange(0,1mb)]
    [int]
    $RightIndent,

    # Specifies how many characters the first line of data is shifted to the left. This implies -Frame.
    [Parameter(ValueFromPipelineByPropertyName)]
    [ValidateRange(0,1mb)]
    [int]
    $FirstLineHanging,

    # Specifies how many characters the first line of data is shifted to the right. This implies -Frame.
    [Parameter(ValueFromPipelineByPropertyName)]
    [ValidateRange(0,1mb)]
    [int]
    $FirstLineIndent,

    # If set, the content will be created as a control. Controls can be reused by other formatters.
    [Switch]$AsControl,

    # The name of the action
    [String]$Name,

    # The VisibilityCondition parameter is used to add a condition that will determine
    # if the content will be rendered.
    [ScriptBlock[]]$VisibilityCondition = {},

    # If provided, the table view will only be used if the the typename is 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.
    # Must be used with -ViewSelectionSet and -ViewTypeName.
    # At least one view must have no conditions.
    [Parameter(ValueFromPipelineByPropertyName=$true)]
    [ScriptBlock]
    $ViewCondition)


    begin {
        $entries = @()
    }
    process {
        $entrySelectedBy =
            if ($ViewTypeName -or $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>"
            } else {
                ''
            }



$header = @"
    <CustomEntry>
        $entrySelectedBy
        <CustomItem>
            $(
                if ($Frame -or $FirstLineHanging -or $LeftIndent -or $RightIndent -or $FirstLineIndent) {
                    "
                <Frame>
                    $(
                        if ($LeftIndent) { "<LeftIndent>$LeftIndent</LeftIndent>" }
                        if ($RightIndent) { "<RightIndent>$RightIndent</RightIndent>" }
                        if ($FirstLineHanging) { "<FirstLineHanging>$FirstLineHanging</FirstLineHanging>" }
                        if ($FirstLineIndent) { "<FirstLineIndent>$FirstLineIndent</FirstLineIndent>" }
                    )
                    <CustomItem>
                "
                }
            )
 
"@


$footer = @"
$(
    if ($Frame -or $FirstLineHanging -or $LeftIndent -or $RightIndent -or $FirstLineIndent) {
        "</CustomItem></Frame>"
    }
)
        </CustomItem>
    </CustomEntry>
"@



            $c =0
            $middle = foreach ($sb in $Action) {
                $VisibilityXml =""
                if ("$($VisibilityCondition[$c])") {
                    $VisibilityXml = "<ItemSelectionCondition><ScriptBlock>
$([Security.SecurityElement]::Escape($VisibilityCondition[$c]))
</ScriptBlock></ItemSelectionCondition>"

                }
                $c++
                $tokens = @([Management.Automation.PSParser]::Tokenize($sb, [ref]$null) |
                    Where-Object { "Comment", "NewLine" -notcontains $_.Type })
                Write-Verbose "$($tokens | Out-String)"
                if ($tokens.Count -eq 1) {
                    if ($tokens[0].Type -eq "Command" -and $tokens[0].Content -ieq "Write-Newline") {
                        "<NewLine />"
                    } elseif ($tokens[0].Type -eq "String") {
                        $content = $tokens[0].Content
                        # If the expanded size is the same as the original size, then the string most likely didn't
                        # contain expansion, and we can write out a text field instead
                        if (-not ($content.Contains('$'))) {
                            "<Text>$([Security.SecurityElement]::Escape($content))</Text>"
                        } else {
                            "<ExpressionBinding>
                                $VisibilityXml
                                <ScriptBlock>`"$([Security.SecurityElement]::Escape($content))`"</ScriptBlock>
                            </ExpressionBinding>"

                        }
                    } else {
                        "<ExpressionBinding>
                            $VisibilityXml
                            <ScriptBlock>$([Security.SecurityElement]::Escape($sb))</ScriptBlock>
                        </ExpressionBinding>"

                    }
                } elseif ($tokens[0].Type -eq "Command" -and 'Show-CustomAction','Write-FormatViewExpression' -contains $tokens[0].Content) {
                    & $MyInvocation.MyCommand.ScriptBlock.Module $sb
                } else {
                    "<ExpressionBinding>
                        $VisibilityXml
                        <ScriptBlock>$([Security.SecurityElement]::Escape($sb))</ScriptBlock>
                    </ExpressionBinding>"

                }
            }
                
        $entries += ( $header + $middle + $footer)
    }

    end {
        $viewXml =
            if (-not $AsControl) {
                "<CustomControl><CustomEntries>" + ($entries -join [Environment]::NewLine) + "</CustomEntries></CustomControl>"
            } else {
                if (-not $Name) {
                    Write-Error "Custom Controls must be named"
                    return
                }

                "<Control><Name>$Name</Name><CustomControl><CustomEntries>" +
                    $($entries -join [Environment]::NewLine) +
                "</CustomEntries></CustomControl></Control>"
            }

        $xml = [xml]$viewXml
        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()

    }
}