Commands/Format.PS1XML/Write-FormatViewExpression.ps1

function Write-FormatViewExpression
{
    <#
    .Synopsis
        Writes a Format XML View Expression
    .Description
        Writes an expression for a Format .PS1XML.
        Expressions are used by custom format views and controls to conditionally display content.
    .Example
        Write-FormatViewExpression -ScriptBlock {
            "hello world"
        }
    .Example
        Write-FormatViewExpression -If { $_.Complete } -ScriptBlock { "Complete" }
    .Example
        Write-FormatViewExpression -Text 'Hello World'
    .Example
        # This will render the property 'Name' property of the underlying object
        Write-FormatViewExpression -Property Name
    .Example
        # This will render the property 'Status' of the current object,
        # if the current object's 'Complete' property is $false.
        Write-FormatViewExpression -Property Status -If { -not $_.Complete }
    #>

    [CmdletBinding(DefaultParameterSetName='ScriptBlock')]
    [OutputType([string])]
    [Alias('Show-CustomAction')]
    param(
    # The name of the control. If this is provided, it will be used to display the property or script block.
    [Parameter(ValueFromPipelineByPropertyName)]
    [Alias('ActionName','Name')]
    [String]
    $ControlName,

    # If a property name is provided, then the custom action will show the contents
    # of the property
    [Parameter(Mandatory=$true,ParameterSetName='Property',Position=0,ValueFromPipelineByPropertyName)]
    [Alias('PropertyName')]
    [String]
    $Property,

    # If a script block is provided, then the custom action shown in formatting
    # will be the result of the script block.
    [Parameter(Mandatory=$true,ParameterSetName='ScriptBlock',Position=0,ValueFromPipelineByPropertyName)]
    [ScriptBlock]
    $ScriptBlock,

    # If provided, will make the expression conditional. -If it returns a value, the script block will run
    [Parameter(ValueFromPipelineByPropertyName=$true)]
    [Alias('ItemSelectionCondition')]
    [ScriptBlock]
    $If,

    # If provided, will output the provided text. All other parameters are ignored.
    [Parameter(Mandatory,ParameterSetName='Text',ValueFromPipelineByPropertyName)]
    [string]
    $Text,

    # If -AssemblyName, -BaseName, and -ResourceID are provided, localized text resources will be outputted.
    [Parameter(Mandatory,ParameterSetName='LocalizedText',ValueFromPipelineByPropertyName)]
    [string]
    $AssemblyName,

    # If -AssemblyName, -BaseName, and -ResourceID are provided, localized text resources will be outputted.
    [Parameter(Mandatory,ParameterSetName='LocalizedText',ValueFromPipelineByPropertyName)]
    [string]
    $BaseName,

    # If -AssemblyName, -BaseName, and -ResourceID are provided, localized text resources will be outputted.
    [Parameter(Mandatory,ParameterSetName='LocalizedText',ValueFromPipelineByPropertyName)]
    [string]
    $ResourceID,

    # If provided, will output a <NewLine /> element. All other parameters are ignored.
    [Parameter(Mandatory=$true,ParameterSetName='NewLine',ValueFromPipelineByPropertyName)]
    [switch]
    $Newline,

    # 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,

    # The name of one or more $psStyle properties to apply.
    # If $psStyle is present, this will use put these properties prior to an expression.
    # A $psStyle.Reset will be outputted after the expression.
    [Alias('PSStyle', 'PSStyles')]
    [string[]]
    $Style,

    # If set, will bold the -Text, -Property, or -ScriptBlock.
    # This is only valid in consoles that support ANSI terminals ($host.UI.SupportsVirtualTerminal),
    # or while rendering HTML
    [switch]
    $Bold,

    # If set, will underline the -Text, -Property, or -ScriptBlock.
    # This is only valid in consoles that support ANSI terminals, or in HTML
    [switch]
    $Underline,

    # If set, will double underline the -Text, -Property, or -ScriptBlock.
    # This is only valid in consoles that support ANSI terminals, or in HTML
    [switch]
    $DoubleUnderline,

    # If set, make the -Text, -Property, or -ScriptBlock Italic.
    # This is only valid in consoles that support ANSI terminals, or in HTML
    [Alias('Italics')]
    [switch]
    $Italic,

    # If set, will hide the -Text, -Property, or -ScriptBlock.
    # This is only valid in consoles that support ANSI terminals, or in HTML
    [switch]
    $Hide,

    # If set, will invert the -Text, -Property, -or -ScriptBlock
    # This is only valid in consoles that support ANSI terminals, or in HTML.
    [switch]
    $Invert,

    # If set, will cross out the -Text, -Property, -or -ScriptBlock
    # This is only valid in consoles that support ANSI terminals, or in HTML.
    [Alias('Strikethrough', 'Crossout')]
    [switch]$Strikethru,

    # If provided, will output the format using this format string.
    [string]
    $FormatString,

    # If this is set, collections will be enumerated.
    [Parameter(ValueFromPipelineByPropertyName=$true)]
    [Alias('EnumerateCollection')]
    [Switch]
    $Enumerate,

    # If provided, will display the content using the given foreground color.
    # This will only be displayed on hosts that support rich color.
    # Colors can be:
    # * An RGB color
    # * The name of a color stored in a .Colors section of a .PrivateData in a manifest
    # * The name of a Standard Concole Color
    # * The name of a PowerShell stream, e.g. Output, Warning, Debug, etc
    [Alias('FG', 'ForegroundColour')]
    [string]
    $ForegroundColor,

    # If provided, will display the content using the given background color.
    # This will only be displayed on hosts that support rich color.
    # Colors can be:
    # * An RGB color
    # * The name of a color stored in a .Colors section of a .PrivateData in a manifest
    # * The name of a Standard Concole Color
    # * The name of a PowerShell stream, e.g. Output, Warning, Debug, etc
    [Alias('BG', 'BackgroundColour')]
    [string]
    $BackgroundColor,

    # The number of times the item will be displayed.
    # With script blocks, the variables $N and $Number will be set to indicate the current iteration.
    [ValidateRange(1,10kb)]
    [uint32]
    $Count = 1
    )

    process {
        # If this is calling itself recursively in ScriptBlock
        if ($ScriptBlock -and $ScriptBlock -like "*$($MyInvocation.MyCommand.Name)*") {
            & $ScriptBlock # run the script and return.
            return
        }

        if ($Newline) {
            foreach ($n in 1..$Count) {
                "<NewLine/>"
            }
            return
        }

        foreach ($n in 1..$count) {
            if ($Style) {
                $scriptLines = @(
'if ($psStyle) {'
" @(foreach (`$styleProp in '$($style -join "','")') {"
{
        if ($styleProp -match '^\$') {
            $ExecutionContext.SessionState.InvokeCommand.InvokeScript($styleProp)
        }
        elseif ($styleProp -match '\.') {
            $targetObject = $psStyle
            foreach ($dotProperty in $styleProp -split '(?<!\.)\.') {
                if (
                    ($targetObject.psobject.Members['Item'] -and 
                        ($targetObject.Item -is [Management.Automation.PSMethodInfo])
                    ) -or 
                    $targetObject -is [Collections.IDictionary]
                ) {
                    $targetObject = $targetObject[$dotProperty]
                } else {
                    $targetObject = $targetObject.$dotProperty
                }
            }
            if ($targetObject) {
                $targetObject
            }
        }
        else {
            $psStyle.$styleProp
        }    
}
" }) -ne '' -join ''"
'}'
                )
                $styleScript = [ScriptBlock]::Create($scriptLines -join [Environment]::NewLine)
                Write-FormatViewExpression -ScriptBlock $styleScript -If $if
            }
            if ($ForegroundColor -or 
                $BackgroundColor -or 
                $Bold -or 
                $Underline -or 
                $Italic -or 
                $Faint -or 
                $doubleUnderline -or 
                $Invert -or
                $Strikethru -or
                $hide) {                
                $colorize = [ScriptBlock]::Create("@(Format-RichText $(@(
                    if ($ForegroundColor) {
                        "-ForegroundColor '$ForeGroundColor'"
                    }
                    if ($BackgroundColor) {
                        "-BackgroundColor '$BackgroundColor'"
                    }
                    if ($Italic) { '-Italic' }
                    if ($Bold) { '-Bold' }
                    if ($Faint) { '-Faint' }
                    if ($Underline) { '-Underline'}
                    if ($DoubleUnderline) { '-DoubleUnderline'}
                    if ($hide) { '-Hide' }
                    if ($Invert) { '-Invert' }
                    if ($Strikethru) { '-Strikethru' }
                    '-NoClear'
                ) -join ' ')) -join ''"
)
                Write-FormatViewExpression -ScriptBlock $colorize -If $if
            }

            $ControlChunk = if ($ControlName) { "<CustomControlName>$([Security.SecurityElement]::Escape($ControlName))</CustomControlName>" }
            $EnumerationChunk = if ($Enumerate) { '<EnumerateCollection/>' } else { '' }
            $formatChunk = if ($FormatString) { "<FormatString>$([Security.SecurityElement]::Escape($FormatString))</FormatString>"}

            if ($Text) {
                "<Text>$([Security.SecurityElement]::Escape($Text))</Text>"
            } 
            elseif ($AssemblyName -and $BaseName -and $ResourceID) {
                "<Text AssemblyName='$AssemblyName' BaseName='$BaseName' ResourceId='$ResourceID' />"
            }
            else {
                if ($Count -gt 1 -and $PSBoundParameters.ContainsKey('ScriptBlock')) {
                    $ScriptBlock = [ScriptBlock]::Create("`$n = `$number = $n;
$($PSBoundParameters['ScriptBlock'])
"
)
                }

    $formatExpression = @"
<ExpressionBinding>
    $(if ($If) {
        if ($count -gt 1) {
            $if = [ScriptBlock]::Create("`$n = `$number = $n;
$if")
        }
        "<ItemSelectionCondition><ScriptBlock>$([Security.SecurityElement]::Escape($if))</ScriptBlock></ItemSelectionCondition>"
    })
    $(if ($Property) { "<PropertyName>$([Security.SecurityElement]::Escape($Property))</PropertyName>" })
    $(if ($ScriptBlock) { "<ScriptBlock>$([Security.SecurityElement]::Escape($ScriptBlock))</ScriptBlock>"})
    $EnumerationChunk
    $formatChunk
    $ControlChunk
</ExpressionBinding>
"@


if ($Frame -or $FirstLineHanging -or $LeftIndent -or $RightIndent -or $FirstLineIndent) {
    $formatExpression = "
<Frame>
    $(
        if ($LeftIndent) { "<LeftIndent>$LeftIndent</LeftIndent>" }
        if ($RightIndent) { "<RightIndent>$RightIndent</RightIndent>" }
        if ($FirstLineHanging) { "<FirstLineHanging>$FirstLineHanging</FirstLineHanging>" }
        if ($FirstLineIndent) { "<FirstLineIndent>$FirstLineIndent</FirstLineIndent>" }
    )
    <CustomItem>
        $FormatExpression
    </CustomItem>
</Frame>
"

}
                $xml = [xml]$formatExpression
                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()
            }
            if ($style) {
                Write-FormatViewExpression -ScriptBlock {
                    if ($PSStyle) {
                        $PSStyle.Reset
                    }
                } -If $if
            }
            elseif ($ForegroundColor -or 
                $BackgroundColor -or 
                $Bold -or 
                $Underline -or 
                $Italic -or 
                $Faint -or 
                $doubleUnderline -or 
                $Invert -or
                $Hide -or 
                $Strikethru
            ) {
                Write-FormatViewExpression -ScriptBlock ([ScriptBlock]::Create(($colorize -replace '-NoClear'))) -If $if
            }
        }
    }
}