ShowDemo.format.ps1xml

<?xml version="1.0" encoding="utf-16"?>
<!-- Generated with EZOut 2.0: Install-Module EZOut or https://github.com/StartAutomating/EZOut -->
<Configuration>
  <Controls>
    <Control>
      <Name>DemoViewer</Name>
      <CustomControl>
        <CustomEntries>
          <CustomEntry>
            <CustomItem>
              <ExpressionBinding>
                <ItemSelectionCondition>
                  <ScriptBlock>
        # If the demo has not started yet
        -not $_.DemoStarted
    </ScriptBlock>
                </ItemSelectionCondition>
                <ScriptBlock>$moduleName = 'ShowDemo'

            do {
                $lm = Get-Module -Name $moduleName -ErrorAction Ignore
                if (-not $lm) { continue }
                if ($lm.FormatPartsLoaded) { break }
                $wholeScript = @(foreach ($formatFilePath in $lm.exportedFormatFiles) {
                    foreach ($partNodeName in Select-Xml -LiteralPath $formatFilePath -XPath "/Configuration/Controls/Control/Name[starts-with(., '$')]") {
                        $ParentNode = $partNodeName.Node.ParentNode
                        "$($ParentNode.Name)={
            $($ParentNode.CustomControl.CustomEntries.CustomEntry.CustomItem.ExpressionBinding.ScriptBlock)}"
                    }
                }) -join [Environment]::NewLine
                New-Module -Name "${ModuleName}.format.ps1xml" -ScriptBlock ([ScriptBlock]::Create(($wholeScript + ';Export-ModuleMember -Variable *'))) |
                    Import-Module -Global
                $onRemove = [ScriptBlock]::Create("Remove-Module '${ModuleName}.format.ps1xml'")
                
                if (-not $lm.OnRemove) {
                    $lm.OnRemove = $onRemove
                } else {
                    $lm.OnRemove = [ScriptBlock]::Create($onRemove.ToString() + '' + [Environment]::NewLine + $lm.OnRemove)
                }
                $lm | Add-Member NoteProperty FormatPartsLoaded $true -Force
            
            } while ($false)
            
        

        $demo = $_
    
        if ($demo.RecordDemo) {
            $startRecordingCommand = $ExecutionContext.SessionState.InvokeCommand.GetCommand('Start-Recording','Function,Alias')
            if ($startRecordingCommand) {
                $null = Start-Recording
            } else {
                Write-Warning "Start-Recording was not found. Have you installed/imported obs-powershell?"
            }
        }
    
        # Start the demo.
        $demo.Start()
        
        # Then, create a message indicating we've started.
        $demoStartMessage =
            &amp; ${ShowDemo_Format-RichText} -ForegroundColor Warning -InputObject (
                "Demo Started" +
                    ([Environment]::NewLine * 2)
            ) -Italic
        
        # If the demo is being run interactively,
        if ($demo.Interactive) {
            # write that message to the host.
            $demoStartMessage | Out-Host
            ""
        }
        # Otherwise, as long as we are not outputting markdown
        elseif (-not $demo.Markdown) {
            # output the demo started message.
            ($demoStartMessage -join '') + [Environment]::NewLine
        } else {
            ''
        }
    </ScriptBlock>
              </ExpressionBinding>
              <ExpressionBinding>
                <ItemSelectionCondition>
                  <ScriptBlock>
        # If the demo has started, is interactive, and not markdown
        $_.DemoStarted -and $_.Interactive -and -not $_.Markdown
    </ScriptBlock>
                </ItemSelectionCondition>
                <ScriptBlock>
        # attempt to to change the window title.
        $Duration = [DateTime]::Now - $_.DemoStarted
        $Host.UI.RawUI.WindowTitle = "{0}[{1}m, {2}s] {2}" -f $_.Name, [int]$Duration.TotalMinutes, [int]$Duration.Seconds
        ""
    </ScriptBlock>
              </ExpressionBinding>
              <ExpressionBinding>
                <ItemSelectionCondition>
                  <ScriptBlock>
        # If we do not have a current chapter
        -not $_.CurrentChapter
    </ScriptBlock>
                </ItemSelectionCondition>
                <ScriptBlock>
        # pick the first chapter and modify the object.
        $firstChapter = $_.Chapters[0]
        $_ | Add-Member NoteProperty CurrentChapter $firstChapter -Force
        $_ | Add-Member NoteProperty CurrentStep 0 -Force
        ""
    </ScriptBlock>
              </ExpressionBinding>
              <ExpressionBinding>
                <ItemSelectionCondition>
                  <ScriptBlock>
        # If we do not have a current step
        -not $_.CurrentStep
    </ScriptBlock>
                </ItemSelectionCondition>
                <ScriptBlock>
        # Start the chapter
        $_.StartChapter()
        $demo = $_
        # and get the first step
        $stepToRun = $demo.CurrentChapter.Steps[$demo.CurrentStep - 1]

        # while the step is hidden
        while ($stepToRun.HiddenStep) {
            $stepToRun.Invoke() # run it
            $demo.NextStep() # and move onto the next step.
            $stepToRun = $demo.CurrentChapter.Steps[$demo.CurrentStep - 1]
        }
        
        # declare a chapter heading
        $chapterHeading =
            $demo.CurrentChapter.Number +
                ' ' +
                    $demo.CurrentChapter.Name + ([Environment]::NewLine * 2)

        # and get a rich text version of that heading
        $currentChapterText =
            &amp; ${ShowDemo_Format-RichText} -ForegroundColor Verbose -InputObject (
                 $chapterHeading
            ) -Underline
        
        # If we are running interactively
        if ($_.Interactive) {
            # output that message now
            $currentChapterText | Out-Host
            ''
        } elseif ($demo.Markdown) {
            # otherwise, if we are generating markdown,
            # determine the heading size.
            $headingSize = [int][math]::max($demo.HeadingSize, 3)
            # use Format-Markdown
            (&amp; ${ShowDemo_Format-Markdown} -HeadingSize $headingSize -InputObject $chapterHeading)
        } else {
            # if we are not running interactively, output the rich text from our formatter.
            ($currentChapterText -join '') + [Environment]::NewLine
        }
        
    </ScriptBlock>
              </ExpressionBinding>
              <ExpressionBinding>
                <ItemSelectionCondition>
                  <ScriptBlock>
        # If there is a current step
        $_.CurrentStep
    </ScriptBlock>
                </ItemSelectionCondition>
                <ScriptBlock>

        $demo = $_
        # set step to run
        $stepToRun = $demo.CurrentChapter.Steps[$demo.CurrentStep - 1]
        
        # while the step is hidden
        while ($stepToRun.HiddenStep) {
            $stepToRun.Invoke() # run it
            $demo.NextStep() # and move onto the next step.
            $stepToRun = $demo.CurrentChapter.Steps[$demo.CurrentStep - 1]
        }
    </ScriptBlock>
              </ExpressionBinding>
              <ExpressionBinding>
                <ItemSelectionCondition>
                  <ScriptBlock>
        $_.StepToRun -and
        $_.ShowPrompt -and
        (
            (-not $_.DemoFinished) -and # and the demo's not done
            $_.Interactive # and we're running interactively
        )
    </ScriptBlock>
                </ItemSelectionCondition>
                <ScriptBlock>
        prompt | Out-Host
    </ScriptBlock>
              </ExpressionBinding>
              <ExpressionBinding>
                <ItemSelectionCondition>
                  <ScriptBlock>
        # If we still have a current step
        $_.CurrentStep
    </ScriptBlock>
                </ItemSelectionCondition>
                <ScriptBlock>
        $demo = $_
        $stepToRun = $demo.CurrentChapter.Steps[$demo.CurrentStep - 1]
        # Update the object with it.
        $demo | Add-Member NoteProperty StepToRun $stepToRun -Force
        # If we are rendering markdown
        if ($demo.Markdown) {
            # and the step is a comment
            if ($stepToRun.IsComment) {
                # replace the comment start and end
                "$stepToRun" -replace '^\&lt;{0,1}\#{1}' -replace '\#\&gt;\s{0,}$'

            }
            # If the step was not a comment
            else
            {
                # Make it a PowerShell code block.
                [Environment]::NewLine +
                    (&amp; ${ShowDemo_Format-Markdown} -InputObject $stepToRun.Trim() -CodeLanguage PowerShell) +
                        [Environment]::NewLine

            }

            # If we are outputting markdown, we're done with this formatting step.
            return
        }

        # If we are not outputting markdown, then let's figure out our real sleep
        $realSleep =
            # If it's greater than a millisecond
            if ($demo.TypeSpeed.TotalMilliseconds -ge 1) {
                $demo.TypeSpeed # trust their input
            } elseif ($demo.TypeSpeed.Ticks)
            {
                # Otherwise, try to convert from
                $letterPerMillisecond =
                    ($demo.TypeSpeed.Ticks * 6) # words per minute
                        / (60 * 1000) # to milliseconds.
                [TimeSpan]::FromMilliseconds($letterPerMillisecond)
            } else {
                # otherwise, have no delay
                [timespan]0
            }

        # Now run over each segment of colorized output for the step
        $strOut = @(foreach ($output in $demo.ColorizeStep($stepToRun)) {
                $outputCopy = @{} + $output
                if ($output.InputObject) {
                    # Start off by setting the rich text formatting used for this sequence of tokens
                    $outputCopy.NoClear = $true
                    $outputCopy.InputObject = ''
                    # If we're running interactively, write that to the console now
                    if ($demo.Interactive) {
                        [Console]::Write((&amp; ${ShowDemo_Format-RichText} @outputCopy))
                    } else {
                        # otherwise, add it to $strOut.
                        &amp; ${ShowDemo_Format-RichText} @outputCopy
                    }
                    # Next, determine our chunks of output
                    $chunks =
                        # If we're going letter-by-letter
                        if ($demo.TypeStyle -eq 'Letters') {
                            # it's a character array.
                            "$($output.InputObject)".ToCharArray()
                        }
                        # If we're going word by word,
                        elseif ($demo.TypeStyle -eq 'Words') {
                            # it's split next to each space.
                            "$($output.InputObject)" -split '(?=\s)'
                        }
                        # otherwise, just output the block
                        else{
                            "$($output.InputObject)"
                        }
                    
                    # Walk over each chunk of output
                    foreach ($chunk in $chunks) {
                        if (-not $chunk) { continue }
                        # If running interactively,
                        if ($demo.Interactive) {
                            # write it to the console
                            [Console]::Write("$chunk")
                        } else {
                            # otherwise, add it to $strOut
                            $chunk
                        }
                        
                        # If we are running interactively, sleep.
                        if ($realSleep.Ticks -and $demo.Interactive) {
                            # (this gives us our typing effect)
                            $null = Start-Sleep -Milliseconds $realSleep.TotalMilliseconds
                        }
                    }

                    # Now we need to do one more write to close the formatting
                    $null = $outputCopy.Remove('NoClear')
                    $outputCopy.InputObject = ' '
                    $output.InputObject = ''
                    # If we're running interactively
                    if ($demo.Interactive) {
                        # that goes to the console now.
                        [Console]::Write((&amp; ${ShowDemo_Format-RichText} @output) -join '')
                    } else {
                        (&amp; ${ShowDemo_Format-RichText} @output) -join ''
                    }
                }
            })

        # If we are running interactively
        if ($demo.Interactive) {
            '' # emit nothing from the formatter (since we've already written to the console)
        } else {
            # otherwise, emit the string.
            $strOut -join ''
        }
    </ScriptBlock>
              </ExpressionBinding>
              <ExpressionBinding>
                <ItemSelectionCondition>
                  <ScriptBlock>
        # If the demo is not done and it is interactive
        -not $_.DemoFinished -and $_.Interactive -and -not $_.AutoPlay
    </ScriptBlock>
                </ItemSelectionCondition>
                <ScriptBlock>
        $demo = $_
        # Read input
        $hostInput = Read-Host
        # and the process it
        foreach ($output in $demo.ProcessInput($hostInput)) {
            # any output we want to display as a warning
            if ($output -is [string]) {
                &amp; ${ShowDemo_Format-RichText} -ForegroundColor Warning -InputObject $output | Out-Host
            }
            # unless it was a series of splats for Format-RichText
            elseif ($output -is [Collections.IDictionary]) {
                &amp; ${ShowDemo_Format-RichText} @output | Out-Host # ( which we will run and output )
            }
            # or a scriptblock.
            elseif ($output -is [scriptblock]) {
                . output | Out-Host # (which we will run).
            }
        }
    </ScriptBlock>
              </ExpressionBinding>
              <ExpressionBinding>
                <ItemSelectionCondition>
                  <ScriptBlock>
        $_.StepToRun # If we have a StepToRun
    </ScriptBlock>
                </ItemSelectionCondition>
                <ScriptBlock>
        $demo = $_
        # Run it.
        if ($demo.Interactive) {
            [Console]::WriteLine()
            # If we're running interactively, pipe it out.
            Invoke-Expression $demo.StepToRun | Out-Host
        }
        else{
            # Otherwise, pipe it to Out-String
            $stepOutput = Invoke-Expression $demo.StepToRun | Out-String -Width 1kb
            
            # If we're outputting markdown
            if ($demo.Markdown) {
                # add a newline above and below
                [Environment]::NewLine + $(
                    @(
                        # If it looks like a tag
                        if ($stepOutput -match '^\&lt;') {
                            $stepOutput # include without indentation
                        } else {
                            # Otherwise, indent 4 chars so it is seen as preformatted text.
                            foreach ($line in @($stepOutput -split '(?&gt;\r\n|\n)')) {
                                (' ' * 4) + $line
                            }
                        }
                    ) -join [Environment]::NewLine
                ) +
                [Environment]::NewLine
            } else {
                [Environment]::NewLine + $stepOutput
            }
        }
    </ScriptBlock>
              </ExpressionBinding>
              <ExpressionBinding>
                <ItemSelectionCondition>
                  <ScriptBlock>
        # If we had a step to run
        $_.StepToRun -and
            (-not $_.DemoFinished) -and # and the demo's not done
            (-not $_.StepToRun.IsComment) -and # and the step is not a comment
            (-not $_.Autoplay) -and # and we're not autoplaying
            $_.Interactive # and we're running interactively
    </ScriptBlock>
                </ItemSelectionCondition>
                <ScriptBlock>
        $demo = $_
        
        # Prompt and process again
        $hostInput = Read-Host
        foreach ($output in $demo.ProcessInput($hostInput)) {
            if ($output -is [string]) {
                &amp; ${ShowDemo_Format-RichText} -ForegroundColor Warning -InputObject $output | Out-Host
            }
            elseif ($output -is [Collections.IDictionary]) {
                &amp; ${ShowDemo_Format-RichText} @output | Out-Host
            }
            elseif ($output -is [scriptblock]) {
                . output | Out-Host
            }
        }
    </ScriptBlock>
              </ExpressionBinding>
              <ExpressionBinding>
                <ItemSelectionCondition>
                  <ScriptBlock>
        $_.Autoplay
    </ScriptBlock>
                </ItemSelectionCondition>
                <ScriptBlock>
        Start-Sleep -Milliseconds $_.PauseBetweenStep.TotalMilliseconds
    </ScriptBlock>
              </ExpressionBinding>
              <ExpressionBinding>
                <ItemSelectionCondition>
                  <ScriptBlock>
        # If we had a current step
        $_.CurrentStep
    </ScriptBlock>
                </ItemSelectionCondition>
                <ScriptBlock>
        # Advanced to the next step
        $_.NextStep()
    </ScriptBlock>
              </ExpressionBinding>
              <ExpressionBinding>
                <ItemSelectionCondition>
                  <ScriptBlock>
        -not $_.DemoFinished # If the demo was not finished
    </ScriptBlock>
                </ItemSelectionCondition>
                <ScriptBlock>
        # recursively call this formatter
        $_
    </ScriptBlock>
                <CustomControlName>DemoViewer</CustomControlName>
              </ExpressionBinding>
              <ExpressionBinding>
                <ItemSelectionCondition>
                  <ScriptBlock>
        # If the demo is finished
        $_.DemoFinished
    </ScriptBlock>
                </ItemSelectionCondition>
                <ScriptBlock>
        $demo = $_
        # figure out how long it took
        $duration = $_.DemoFinished - $_.DemoStarted
        # change the status
        $demo.SetStatus('Finished')
        if ($demo.RecordDemo) {
            $stopRecording = $ExecutionContext.SessionState.InvokeCommand.GetCommand('Stop-Recording', 'Alias,Function')
            if ($stopRecording) {
                $recordingOutputFile = Stop-Recording
                $newRecordingName = "$($demo.Name).$($demo.DemoStarted.ToString('s') -replace ':', '-')$($recordingOutputFile.Extension)"
                if ($recordingOutputFile) {
                    try {
                        Copy-Item $recordingOutputFile.FullName -Destination ($recordingOutputFile.FullName | Split-Path | Join-Path -ChildPath $newRecordingName )
                    } catch {
                        Write-Warning "Could not copy $($recordingOutputFile) to $($demo.Name): $($_ | Out-String)"
                    }
                }
            }
        }
        # and prepare a message.
        $finishedMessage =
            &amp; ${ShowDemo_Format-RichText} -InputObject (
                "Demo $($demo.Status) {0} Minutes and {1} Seconds" -f [int]$duration.TotalMinutes, [int]$duration.Seconds
            ) -ForegroundColor Warning -Italic
        
        # If the demo was interactive
        if ($demo.Interactive) {
            # writ the message
            $finishedMessage | Out-Host
            # and if we had a nested prompt, exit it
            if ($NestedPromptLevel) {
                $host.ExitNestedPrompt()
            }
        } elseif (-not $demo.Markdown) {
            $finishedMessage -join ''
        }
        
        # Last but not least, reset the demo.
        $demo.Reset()
    </ScriptBlock>
              </ExpressionBinding>
            </CustomItem>
          </CustomEntry>
        </CustomEntries>
      </CustomControl>
    </Control>
    <Control>
      <Name>${ShowDemo_Format-RichText}</Name>
      <CustomControl>
        <CustomEntries>
          <CustomEntry>
            <CustomItem>
              <ExpressionBinding>
                <ScriptBlock>
    &lt;#
    .Synopsis
        Formats the text color of output
    .Description
        Formats the text color of output

        * ForegroundColor
        * BackgroundColor
        * Bold
        * Underline
    .Notes
        Stylized Output works in two contexts at present:
        * Rich consoles (Windows Terminal, PowerShell.exe, Pwsh.exe) (when $host.UI.SupportsVirtualTerminal)
        * Web pages (Based off the presence of a $Request variable, or when $host.UI.SupportsHTML (you must add this property to $host.UI))
    #&gt;
    [Management.Automation.Cmdlet("Format","Object")]
    [ValidateScript({
        $canUseANSI = $host.UI.SupportsVirtualTerminal
        $canUseHTML = $Request -or $host.UI.SupportsHTML -or $OutputMode -eq 'HTML'
        if (-not ($canUseANSI -or $canUseHTML)) { return $false}
        return $true
    })]
    [OutputType([string])]
    param(
    # The input object
    [Parameter(ValueFromPipeline)]
    [PSObject]
    $InputObject,
    
    # The foreground color
    [string]$ForegroundColor,

    # The background color
    [string]$BackgroundColor,

    # If set, will render as bold
    [switch]$Bold,

    # If set, will render as italic.
    [Alias('Italics')]
    [switch]$Italic,

    # If set, will render as faint
    [switch]$Faint,

    # If set, will render as hidden text.
    [switch]$Hide,

    # If set, will render as blinking (not supported in all terminals or HTML)
    [switch]$Blink,

    # If set, will render as strikethru
    [Alias('Strikethrough', 'Crossout')]
    [switch]$Strikethru,

    # If set, will underline text
    [switch]$Underline,

    # If set, will double underline text.
    [switch]$DoubleUnderline,

    # If set, will invert text
    [switch]$Invert,

    # If provided, will create a hyperlink to a given uri
    [Alias('Hyperlink', 'Href')]
    [uri]
    $Link,

    # If set, will not clear formatting
    [switch]$NoClear,

    # The alignment. Defaulting to Left.
    # Setting an alignment will pad the remaining space on each line.
    [ValidateSet('Left','Right','Center')]
    [string]
    $Alignment,

    # The length of a line. By default, the buffer width
    [int]$LineLength = $($host.UI.RawUI.BufferSize.Width)
    )

    begin {
        $canUseANSI = $host.UI.SupportsVirtualTerminal
        $canUseHTML = $Request -or $host.UI.SupportsHTML -or $OutputMode -eq 'HTML'
        $knownStreams = @{
            Output='';Error='BrightRed';Warning='BrightYellow';
            Verbose='BrightCyan';Debug='Yellow';Progress='Cyan';
            Success='BrightGreen';Failure='Red';Default=''}

        $ansiCode = [Regex]::new(@'
        (?&lt;ANSI_Code&gt;
        (?-i)\e # An Escape
        \[ # Followed by a bracket
        (?&lt;ParameterBytes&gt;[\d\:\;\&lt;\=\&gt;\?]{0,}) # Followed by zero or more parameter
        bytes
        (?&lt;IntermediateBytes&gt;[\s\!\"\#\$\%\&amp;\'\(\)\*\+\,\-\.\/]{0,}) # Followed by zero or more
        intermediate bytes
        (?&lt;FinalByte&gt;[\@ABCDEFGHIJKLMNOPQRSTUVWXYZ\[\\\]\^_\`abcdefghijklmnopqrstuvwxyz\{\|\}\~]) # Followed by a final byte
        
        )
'@)
        $esc = [char]0x1b
        $standardColors = 'Black', 'Red', 'Green', 'Yellow', 'Blue','Magenta', 'Cyan', 'White'
        $brightColors = 'BrightBlack', 'BrightRed', 'BrightGreen', 'BrightYellow', 'BrightBlue','BrightMagenta', 'BrightCyan', 'BrightWhite'

        $allOutput = @()

        $n =0
        $cssClasses = @()
        $colorAttributes =
            @(:nextColor foreach ($hc in $ForegroundColor,$BackgroundColor) {
                $n++
                if (-not $hc) { continue }
                if ($hc[0] -eq $esc) {
                    if ($canUseANSI) {
                        $hc; continue
                    }
                }
        
                $ansiStartPoint = if ($n -eq 1) { 30 } else { 40 }
                if ($knownStreams.ContainsKey($hc)) {
                    $i = $brightColors.IndexOf($knownStreams[$hc])
                    if ($canUseHTML) {
                        $cssClasses += $hc
                    } else {
                        if ($i -ge 0 -and $canUseANSI) {
                            '' + $esc + "[1;$($ansiStartPoint + $i)m"
                        } else {
                            $i = $standardColors.IndexOf($knownStreams[$hc])
                            if ($i -ge 0 -and $canUseANSI) {
                                '' + $esc + "[1;$($ansiStartPoint + $i)m"
                            } elseif ($i -le 0 -and $canUseANSI) {
                                '' + $esc + "[$($ansistartpoint + 8):5m"
                            }
                        }
                    }
                    continue nextColor
                }
                elseif ($standardColors -contains $hc) {
                    for ($i = 0; $i -lt $standardColors.Count;$i++) {
                        if ($standardColors[$i] -eq $hc) {
                            if ($canUseANSI -and -not $canUseHTML) {
                                '' + $esc + "[$($ansiStartPoint + $i)m"
                            } else {
                                $cssClasses += $standardColors[$i]
                            }
                            continue nextColor
                        }
                    }
                } elseif ($brightColors -contains $hc) {
                    for ($i = 0; $i -lt $brightColors.Count;$i++) {
                        if ($brightColors[$i] -eq $hc) {
                            if ($canUseANSI -and -not $canUseHTML) {
                                '' + $esc + "[1;$($ansiStartPoint + $i)m"
                            } else {
                                $cssClasses += $standardColors[$i]
                            }
                            continue nextColor
                        }
                    }
                }
                elseif ($psStyle -and $psStyle.Formatting.$hc -and
                    $psStyle.Formatting.$hc -match '^\e') {
                    if ($canUseANSI -and -not $canUseHTML) {
                        $psStyle.Formatting.$hc
                    } else {
                        $cssClasses += "formatting-$hc"
                    }
                }
                elseif (-not $n -and $psStyle -and $psStyle.Foreground.$hc -and
                    $psStyle.Foreground.$hc -match '^\e' ) {
                    if ($canUseANSI -and -not $canUseHTML) {
                        $psStyle.Foreground.$hc
                    } else {
                        $cssClasses += "foreground-$hc"
                    }
                }
                elseif ($n -and $psStyle -and $psStyle.Background.$hc -and
                    $psStyle.Background.$hc -match '^\e') {
                    if ($canUseANSI -and -not $canUseHTML) {
                        $psStyle.Background.$hc
                    } else {
                        $cssClasses += "background-$hc"
                    }
                }

        
        
                if ($hc -and $hc -notmatch '^[\#\e]') {
                    $placesToLook=
                        @(if ($hc.Contains('.')) {
                            $module, $setting = $hc -split '\.', 2
                            $theModule = Get-Module $module
                            $theModule.PrivateData.Color,
                                $theModule.PrivateData.Colors,
                                $theModule.PrivateData.Colour,
                                $theModule.PrivateData.Colours,
                                $theModule.PrivateData.EZOut,
                                $global:PSColors,
                                $global:PSColours
                        } else {
                            $setting = $hc
                            $moduleColorSetting = $theModule.PrivateData.PSColors.$setting
                        })
        
                    foreach ($place in $placesToLook) {
                        if (-not $place) { continue }
                        foreach ($propName in $setting -split '\.') {
                            $place = $place.$propName
                            if (-not $place) { break }
                        }
                        if ($place -and "$place".StartsWith('#') -and 4,7 -contains "$place".Length) {
                            $hc = $place
                            continue
                        }
                    }
                    if (-not $hc.StartsWith -or -not $hc.StartsWith('#')) {
                        continue
                    }
                }
                $r,$g,$b = if ($hc.Length -eq 7) {
                    [int]::Parse($hc[1..2]-join'', 'HexNumber')
                    [int]::Parse($hc[3..4]-join '', 'HexNumber')
                    [int]::Parse($hc[5..6] -join'', 'HexNumber')
                }elseif ($hc.Length -eq 4) {
                    [int]::Parse($hc[1], 'HexNumber') * 16
                    [int]::Parse($hc[2], 'HexNumber') * 16
                    [int]::Parse($hc[3], 'HexNumber') * 16
                }
        
                if ($canUseHTML) {
                    if ($n -eq 1) { "color:$hc" }
                    elseif ($n -eq 2) { "background-color:$hc"}
                }
                elseif ($canUseANSI) {
                    if ($n -eq 1) { $esc+"[38;2;$r;$g;${b}m" }
                    elseif ($n -eq 2) { $esc+"[48;2;$r;$g;${b}m" }
                }
                
            })
        
        $styleAttributes = @() + $colorAttributes
        
        $styleAttributes += @(
            if ($Bold) {
                if ($canUseHTML) {"font-weight:bold"}
                elseif ($canUseANSI) { '' + $esc + "[1m" }
            }
            if ($Faint) {
                if ($canUseHTML) { "opacity:.5" }
                elseif ($canUseANSI) { '' + $esc + "[2m" }
            }
            if ($Italic) {
                if ($canUseHTML) { "font-weight:bold" }
                elseif ($canUseANSI) {'' + $esc + "[3m" }
            }
            
            if ($Underline -and -not $doubleUnderline) {
                if ($canUseHTML) { "text-decoration:underline"}
                elseif ($canUseANSI) {'' +$esc + "[4m" }
            }

            if ($Blink) {
                if ($canUseANSI) { '' +$esc + "[5m" }
            }

            if ($invert) {
                if ($canUseHTML) {"filter:invert(100%)"}
                elseif ($canUseANSI) { '' + $esc + "[7m"}
            }

            if ($hide) {
                if ($canUseHTML) {"opacity:0"}
                elseif ($canUseANSI) { '' + $esc + "[8m"}
            }
            
            if ($Strikethru) {
                if ($canUseHTML) {"text-decoration: line-through"}
                elseif ($canUseANSI) { '' +$esc + "[9m" }
            }
            
            if ($DoubleUnderline) {
                if ($canUseHTML) { "border-bottom: 3px double;"}
                elseif ($canUseANSI) {'' +$esc + "[21m" }
            }

            if ($Alignment -and $canUseHTML) {
                "display:block;text-align:$($Alignment.ToLower())"
            }

            if ($Link) {
                if ($canUseHTML) {
                    # Hyperlinks need to be a nested element
                    # so we will not add it to style attributes for HTML
                }
                elseif ($canUseANSI) {
                    # For ANSI,
                    '' + $esc + ']8;;' + $Link + $esc + '\'
                }
            }
            
        )
        
        $header =
            if ($canUseHTML) {
                "&lt;span$(
                    if ($styleAttributes) { " style='$($styleAttributes -join ';')'"}
                )$(
                    if ($cssClasses) { " class='$($cssClasses -join ' ')'"}
                )&gt;" + $(
                    if ($Link) {
                        "&lt;a href='$link'&gt;"
                    }
                )
            } elseif ($canUseANSI) {
                $styleAttributes -join ''
            }
    }

    process {
        $inputObjectAsString =
            "$(if ($inputObject) { $inputObject | Out-String})".Trim()

        $inputObjectAsString =
            if ($Alignment -and -not $canUseHTML) {
                (@(foreach ($inputObjectLine in ($inputObjectAsString -split '(?&gt;\r\n|\n)')) {
                    $inputObjectLength = $ansiCode.Replace($inputObjectLine, '').Length
                    if ($inputObjectLength -lt $LineLength) {
                        if ($Alignment -eq 'Left') {
                            $inputObjectLine
                        } elseif ($Alignment -eq 'Right') {
                            (' ' * ($LineLength - $inputObjectLength)) + $inputObjectLine
                        } else {
                            $half = ($LineLength - $inputObjectLength)/2
                            (' ' * [Math]::Floor($half)) + $inputObjectLine +
                            (' ' * [Math]::Ceiling($half))
                        }
                    }
                    else {
                        $inputObjectLine
                    }
                }) -join [Environment]::NewLine) + [Environment]::newline
            } else {
                $inputObjectAsString
            }

        $allOutput +=
            if ($header) {
                "$header" + $inputObjectAsString
            }
            elseif ($inputObject) {
                $inputObjectAsString
            }
    }

    end {
        
        if (-not $NoClear) {
            $allOutput +=
                if ($canUseHTML) {
                    if ($Link) {
                        "&lt;/a&gt;"
                    }
                    "&lt;/span&gt;"
                }
                elseif ($canUseANSI) {
                    if ($Bold -or $Faint -or $colorAttributes -match '\[1;') {
                        "$esc[22m"
                    }
                    if ($Italic) {
                        "$esc[23m"
                    }
                    if ($Underline -or $doubleUnderline) {
                        "$esc[24m"
                    }
                    if ($Blink) {
                        "$esc[25m"
                    }
                    if ($Invert) {
                        "$esc[27m"
                    }
                    if ($hide) {
                        "$esc[28m"
                    }
                    if ($Strikethru) {
                        "$esc[29m"
                    }
                    if ($ForegroundColor) {
                        "$esc[39m"
                    }
                    if ($BackgroundColor) {
                        "$esc[49m"
                    }

                    if ($Link) {
                        "$esc]8;;$esc\"
                    }
                
                    if (-not ($Underline -or $Bold -or $Invert -or $ForegroundColor -or $BackgroundColor)) {
                        '' + $esc + '[0m'
                    }
                }
        }

        $allOutput -join ''
    }
</ScriptBlock>
              </ExpressionBinding>
            </CustomItem>
          </CustomEntry>
        </CustomEntries>
      </CustomControl>
    </Control>
    <Control>
      <Name>${ShowDemo_Format-Markdown}</Name>
      <CustomControl>
        <CustomEntries>
          <CustomEntry>
            <CustomItem>
              <ExpressionBinding>
                <ScriptBlock>
    &lt;#
    .SYNOPSIS
        Formats an object as Markdown
    .DESCRIPTION
        Formats an object as Markdown, with many options to work with
    .EXAMPLE
        Format-Markdown -ScriptBlock {
            Get-Process
        }
    .EXAMPLE
         1..6 | Format-Markdown -HeadingSize { $_ }
    #&gt;
    [Management.Automation.Cmdlet("Format","Object")]
    [ValidateScript({return $true})]
    param(
    [Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)]
    [PSObject]
    $InputObject,
    
    # If set, will treat the -InputObject as a paragraph.
    # This is the default for strings, booleans, numbers, and other primitive types.
    [Parameter(ValueFromPipelineByPropertyName)]
    [switch]
    $MarkdownParagraph,

    # If set, will generate a markdown table.
    [Parameter(ValueFromPipelineByPropertyName)]
    [switch]
    $MarkdownTable,

    # If provided, will align columnns in a markdown table.
    [Parameter(ValueFromPipelineByPropertyName)]
    [ValidateSet("Left","Right","Center", "")]
    [string[]]
    $MarkdownTableAlignment,

    # An array of properties. Providing this implies -MarkdownTable
    [Parameter(ValueFromPipelineByPropertyName)]
    [PSObject[]]
    $Property,

    # A heading.
    # If provided without -HeadingSize, -HeadingSize will default to 2.
    # If provided with -InputObject, -Heading will take priority.
    [Parameter(ValueFromPipelineByPropertyName)]
    [string]
    $Heading,

    # The heading size (1-6)
    # If provided without -Heading, the -InputObject will be considered to be a heading.
    [Parameter(ValueFromPipelineByPropertyName)]
    [ValidateRange(1,6)]
    [int]
    $HeadingSize,

    # If set, will create a link. The -InputObject will be used as the link content
    [Parameter(ValueFromPipelineByPropertyName)]
    [Alias('Hyperlink', 'Href')]
    [string]
    $Link,

    # If set, will create an image link. The -Inputobject will be used as the link content.
    [Parameter(ValueFromPipelineByPropertyName)]
    [string]
    $ImageLink,

    # If set, will generate a bullet point list.
    [Parameter(ValueFromPipelineByPropertyName)]
    [Alias('BulletpointList')]
    [switch]
    $BulletPoint,
    
    # If set, bullet or numbered list items will have a checkbox.
    # Each piped -InputObject will be an additional list item.
    [Parameter(ValueFromPipelineByPropertyName)]
    [switch]
    $Checkbox,

    # If set, bullet or numbered list items will be checked.
    [Parameter(ValueFromPipelineByPropertyName)]
    [switch]
    $Checked,

    # If set, will generate a numbered list.
    # Each piped -InputObject will be an additional list item.
    [Parameter(ValueFromPipelineByPropertyName)]
    [switch]
    $NumberedList,

    # If set, will generate a block quote.
    # Each line of the -InputObject will be block quoted.
    [Parameter(ValueFromPipelineByPropertyName)]
    [switch]
    $BlockQuote,

    # If set, will generate a block quote of a particular depth.
    # Each line of the -InputObject will be block quoted.
    [Parameter(ValueFromPipelineByPropertyName)]
    [ValidateRange(1,3)]
    [int]
    $BlockQuoteDepth,
        
    # If provided, will create a markdown numbered list with this particular item as the number.
    [Parameter(ValueFromPipelineByPropertyName)]
    [int]
    $Number,

    # If set, will generate a horizontal rule.
    # If other parameters are provided, the horiztonal rule will be placed after.
    [Parameter(ValueFromPipelineByPropertyName)]
    [switch]
    $HorizontalRule,

    # If set, will output the -InputObject as a Markdown code block
    [Parameter(ValueFromPipelineByPropertyName)]
    [switch]
    $Code,

    # If set, will output the -InputObject as a Markdown code block, with a given language
    # If the -InputObject is a ScriptBlock, -CodeLanguage will be set to PowerShell.
    [Parameter(ValueFromPipelineByPropertyName)]
    [string]
    $CodeLanguage,

    # If provided, will output a script block as a Markdown code block.
    [Parameter(ValueFromPipelineByPropertyName)]
    [ScriptBlock]
    $ScriptBlock
    )

    begin {
        $numberedListCounter = 0
        $IsFirst = $true
        filter LinkInput {
            $in = $_
            if ($ImageLink) {
                "![$in]($imageLink)"
            } elseif ($link) {
                "[$in]($link)"
            } else {
                "$in"
            }
        }

        $markdownLines = @()
    }

    process {
        
        if ($ScriptBlock -or $inputObject -is [scriptblock]) { # If a -ScriptBlock was provided
            $CodeLanguage = 'PowerShell' # use PowerShell as a Code Language.
        }

        # If a -HeadingSize or a -Heading were provided, render a heading.
        if ($HeadingSize -or $Heading)
        {
            if (-not $HeadingSize) { $HeadingSize = 2} # If the -HeadingSize was not set, set it to 2.
            $headingContent = "$(if ($Heading) { $Heading} else { $inputObject | LinkInput})"
            $markdownLines +=
                if ($HeadingSize -eq 1) {
                    $headingContent
                    '=' * [Math]::Max($headingContent.Length, 3)
                }
                elseif ($HeadingSize -eq 2) {
                    $headingContent
                    '-' * [Math]::Max($headingContent.Length, 3)
                }
                else {
                    ("#"*$HeadingSize) + " $headingContent" # Output the -Heading or the -InputObject.
                }
        }
        # If -Code or -CodeLanguage was provided, render a Markdown code block.
        elseif ($Code -or $CodeLanguage)
        {
            # If the -InputObject was a [ScriptBlock] or there is a -ScriptBlock
            if ($InputObject -is [scriptblock] -or $ScriptBlock) {
                $CodeLanguage = 'PowerShell' # set the code language to PowerShell.
            }
            $markdownLines += (
                '```' + # Start the code fence,
                    $(if ($CodeLanguage) { $CodeLanguage}) + # add the language,
                    [Environment]::newline + # then a newline,
                    $(
                        $codeContent = $(if ($ScriptBlock) { "$scriptBlock" } else { $inputObject | LinkInput}) # then the -ScriptBlock or -InputObject
                        $codeContent
                    ) +
                    [Environment]::newline + # then a newline
                '```' # then close the code fence.
            )
        }
        # If -BulletPoint was passed, render a Bullet Point list.
        elseif ($BulletPoint)
        {
            $markdownLines += "*$(if ($Checkbox) { "[$(if ($Checked) {"x"} else {" "})]"}) $($inputObject | LinkInput)"
        }
        # If -NumberedList was passed, render a numbered list.
        elseif ($NumberedList -or $Number)
        {
            $numberedListCounter++ # Increment the counter
            $markdownLines += "$(if ($number) { $number } else {$numberedListCounter}).$(if ($Checkbox) {" [$(if ($Checked) {"x"} else {" "})]"}) $($inputObject | LinkInput)"
        }
        elseif ($BlockQuote -or $BlockQuoteDepth) {
            if (-not $BlockQuoteDepth) { $BlockQuoteDepth = 1 }
            $markdownLines += ("&gt;" * $BlockQuoteDepth ) + ' ' + (
                "$inputObject" -split '(?&gt;\r\n|\n)' -join (
                    [Environment]::NewLine + ("&gt;" * $BlockQuoteDepth) + ' '
                )
            )
        }
        # Otherwise, we have to determine if -InputObject should be a -MarkdownTable or a -MarkdownParagraph.
        else
        {
            # If the input is a primitive type or a string, it should be a markdown paragraph
            if (($inputObject.GetType -and $inputObject.GetType().IsPrimitive) -or
                $inputObject -is [string]) {
                $MarkdownParagraph = $true
            }
            # If it is a dictionary, it should be a markdown table.
            elseif ($inputObject -is [Collections.IDictionary])
            {
                $MarkdownTable = $true
            }
            # If the input is an array, apply the same logic:
            elseif ($inputObject -is [Object[]] -or $InputObject -is [PSObject[]]) {
                $allPrimitives = 1
                # if the array was all primitives or strings
                foreach ($in in $InputObject) {
                    $allPrimitives = $allPrimitives -band (
                        ($in.GetType -and $in.GetType().IsPrimitive) -or $in -is [string]
                    )
                }
                if ($allPrimitives) { # output as a paragraph.
                    $MarkdownParagraph = $true
                } else {
                    $MarkdownTable = $true
                }
            }
            # If we're still not sure, output as a table.
            else {
                $MarkdownTable = $true
            }
        }

        if ($MarkdownParagraph) {
            # If we're outputting as a paragraph, add the input and link it if needed.
            $markdownLines += $inputObject | LinkInput
        } elseif ($MarkdownTable) {
            # If we're rendering a table, we need to go row-by-row.
            foreach ($in in $InputObject) {
                $propertyList = @(
                    # we first need to get a list of properties.
                    # If there was a -Property parameter provided, use it.
                    if ($Property) {
                        foreach ($prop in $Property) {
                            if ($prop -is [string]) { # Strings in -Property should be taken as property names
                                $prop
                            } elseif ($prop.Name -and $prop.Expression -and $prop.Expression -is [scriptblock]) {
                                # and anything with a name and expression script block will run the expression script block.
                                $_ = $psItem = $in
                                @{name=$prop.Name;Value = . $prop.Expression}
                            }
                        }
                    }
                    # Otherwise, if the input was a dictionary
                    elseif ($in -is [Collections.IDictionary])
                    {
                        foreach ($k in $in.Keys) { # take all keys from the dictionary
                            if ($MyInvocation.MyCommand.Parameters[$k]) { continue } # that are not parameters of this function.
                            $k
                        }
                    }
                    # Otherwise, walk over all properties on the object
                    else {
                        foreach ($psProp in $In.psobject.properties) {
                            # and skip any properties that are parameters of this function.
                            if ($psProp.Name -notin $MyInvocation.MyCommand.Parameters.Keys) {
                                $psProp
                            }
                        }
                    }
                )

                # If we're rendering the first row of a table
                if ($IsFirst) {
                    # Create the header
                    $markdownLines +=
                        '|' + (@(foreach ($prop in $propertyList) {
                            if ($prop -is [string]) {
                                $prop
                            } else {
                                $prop.Name
                            }
                        }) -replace ([Environment]::newline), '&lt;br/&gt;' -replace '\|', '\|' -join '|') + '|'
                    # Then create the alignment row.
                    $markdownLines +=
                        '|' + $(
                            $columnNumber =0
                            @(
                                foreach ($prop in $propertyList) {
                                    $colLength =
                                        if ($prop -is [string]) {
                                            $prop.Length
                                        } else {
                                            $prop.Name.Length
                                        }
                                    if ($MarkdownTableAlignment) {
                                        if ($MarkdownTableAlignment[$columnNumber] -eq 'Left') {
                                            ':' + ("-" * ([Math]::Max($colLength,2) - 1))
                                        }
                                        elseif ($MarkdownTableAlignment[$columnNumber] -eq 'Right') {
                                            ("-" * ([Math]::Max($colLength,2) - 1)) + ':'
                                        }
                                        elseif ($MarkdownTableAlignment[$columnNumber] -eq 'Center') {
                                            ':' + ("-" * ([Math]::max($colLength, 3) - 2)) + ':'
                                        } else {
                                            "-" * $colLength
                                        }
                                    } else {
                                        "-" * $colLength
                                    }
                                    
                                    $columnNumber++
                                }
                            ) -replace ([Environment]::newline), '&lt;br/&gt;' -replace '\|', '\|' -join '|') + '|'
                    $IsFirst = $false
                }
                
                # Now we create the row for this object.

                $markdownLine = '|' + (
                    @(
                        foreach ($prop in $propertyList) {
                            if ($prop -is [string]) {
                                $in.$prop | LinkInput
                            } else {
                                $prop.Value | LinkInput
                            }
                        }
                    ) -replace ([Environment]::newline), '&lt;br/&gt;' -replace '\|', '\|' -join '|') + '|'

                $markdownLines += $markdownLine
            }
        }

            
        if ( # There are a few combinations of parameters that make us want to write the -InputObject as a paragraph:
            ($ScriptBlock -and $inputObject) -or # * If -ScriptBlock and -InputObject were both provided.
            ($Heading -and $inputObject) # * if -Heading and -InputObject were both provided
        ) {
            $markdownLines += $InputObject | LinkInput
        }


        # If we're going to render a horizontal rule (and -MarkdownTable has not been set)
        if ($HorizontalRule -and -not $MarkdownTable) {
            # add the horizontal rule at the end.
            if ($host.UI.RawUI.BufferSize.Width) {
                $markdownLines += (([string]$HorizontalRuleCharacter) * ($Host.UI.RawUI.BufferSize.Width - 1))
            } else {
                $markdownLines += "---"
            }
        }
    }

    end {
        # Now we need to make one last pass to normalize tables
        if ($markdownLines -match '^\|') { # (that is, if we have tables to normalize).
            $maxColumnSize = @{} # To normalize the table, we need to track the maximum size per column
            foreach ($ml in $markdownLines) {
                if ($ml -match '\^|') {
                    $columnCount = 0
                    foreach ($tablePart in $ml -split '(?&lt;!\\)\|' -ne '') {
                        if ((-not $maxColumnSize[$columnCount]) -or $maxColumnSize[$columnCount] -lt $tablePart.Length) {
                            $maxColumnSize[$columnCount] = [Math]::Max($tablePart.Length, 2)
                        }
                        $columnCount++
                    }
                }
            }
            # One we know the maximum size per column, walk over each line
            $markdownLines = @(foreach ($ml in $markdownLines) {
                if ($ml -match '\^|') {
                    $columnCount = 0
                    # Recreate the line with the right amount of padding.
                    '|' + (@(foreach ($tablePart in $ml -split '(?&lt;!\\)\|' -ne '') {
                        if ($tablePart -match '^[:\-]+$') {
                            if ($tablePart -match '^\:-{0,}\:$') { # If it's an alignment column, make sure to keep the alignment.
                                if ($maxColumnSize[$columnCount] -gt 2) {
                                    ':' + ('-' * ($maxColumnSize[$columnCount] - 2)) + ':'
                                } else {
                                    '::'
                                }
                            }
                            elseif ($tablePart -match '\:$') {
                                $tablePart.PadLeft($maxColumnSize[$columnCount], '-')
                            }
                            elseif ($tablePart -match '^\:') {
                                $tablePart.PadRight($maxColumnSize[$columnCount], '-')
                            }
                            else {
                                $tablePart.PadRight($maxColumnSize[$columnCount], '-')
                            }
                        } else {
                            $tablePart.PadRight($maxColumnSize[$columnCount], ' ')
                        }
                        $columnCount++
                    }) -join '|') + '|'
                } else {
                    $ml
                }
            })
        }
        $markdownLines -join [Environment]::NewLine
    }
</ScriptBlock>
              </ExpressionBinding>
            </CustomItem>
          </CustomEntry>
        </CustomEntries>
      </CustomControl>
    </Control>
  </Controls>
  <ViewDefinitions>
    <View>
      <Name>Demo</Name>
      <ViewSelectedBy>
        <TypeName>Demo</TypeName>
      </ViewSelectedBy>
      <TableControl>
        <TableHeaders>
          <TableColumnHeader>
          </TableColumnHeader>
          <TableColumnHeader>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Chapters</Label>
          </TableColumnHeader>
        </TableHeaders>
        <TableRowEntries>
          <TableRowEntry>
            <Wrap />
            <TableColumnItems>
              <TableColumnItem>
                <PropertyName>Name</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>TotalSteps</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <ScriptBlock>
        @(foreach ($chap in $_.Chapters) {
            $chap.Number + ' ' + $chap.Name
        }) -join [Environment]::NewLine
    </ScriptBlock>
              </TableColumnItem>
            </TableColumnItems>
          </TableRowEntry>
        </TableRowEntries>
      </TableControl>
    </View>
    <View>
      <Name>Demo</Name>
      <ViewSelectedBy>
        <TypeName>Demo</TypeName>
      </ViewSelectedBy>
      <CustomControl>
        <CustomEntries>
          <CustomEntry>
            <CustomItem>
              <ExpressionBinding>
                <ScriptBlock>
        # set a script variable to contain the current demo
        $ExecutionContext.SessionState.PSVariable.Set('script:currentDemo',$_)
    </ScriptBlock>
              </ExpressionBinding>
              <ExpressionBinding>
                <ScriptBlock>
        $_ # display the demo using the DemoViewer control
    </ScriptBlock>
                <CustomControlName>DemoViewer</CustomControlName>
              </ExpressionBinding>
              <ExpressionBinding>
                <ScriptBlock>
        # unset a script variable
        $ExecutionContext.SessionState.PSVariable.Set('script:currentDemo',$null)
    </ScriptBlock>
              </ExpressionBinding>
            </CustomItem>
          </CustomEntry>
        </CustomEntries>
      </CustomControl>
    </View>
  </ViewDefinitions>
</Configuration>