FormatMarkdownTable.psm1

# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license. See LICENSE file in the project root for full license information.
# https://github.com/microsoft/FormatPowerShellToMarkdownTable

<#
.SYNOPSIS
Formats the output as a Format-List style markdown table.

.DESCRIPTION
The Format-MarkdownTableListStyle cmdlet formats the output of a command as a Format-List style markdown table which each property is displayed on a separate col.

Markdown text will be copied to the clipboard.

.PARAMETER InputObject
Specifies the objects to be formatted. Enter a variable that contains the objects or type a command or expression that gets the objects.

.PARAMETER HideStandardOutput
Indicates that the cmdlet hides the standard Format-List style output.

.PARAMETER ShowMarkdown
Indicates that the cmdlet outputs the markdown text to the console.

.PARAMETER DoNotCopyToClipboard
Indicates the the cmdlet does not copy the markdown text to the clipboard.

.PARAMETER Property
Specifies the object properties that appear in the display and the order in which they appear. Wildcards are permitted.

If you omit this parameter, the properties that appear in the display depend on the object being displayed. The parameter name "Property" is optional.

.EXAMPLE
Get-Process notepad | Format-MarkdownTableListStyle

.EXAMPLE
Get-Process notepad | fml Name,Path

.NOTES
You can also refer to Format-MarkdownTableListStyle by its built-in alias, FML.
#>
#
function Format-MarkdownTableListStyle {
    [CmdletBinding()]
    [Alias("fml")]

    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [object]
        $InputObject,

        [Parameter(Mandatory = $false, ValueFromPipeline = $false)]
        [switch]
        $HideStandardOutput,

        [Parameter(Mandatory = $false, ValueFromPipeline = $false)]
        [switch]
        $ShowMarkdown,

        [Parameter(Mandatory = $false, ValueFromPipeline = $false)]
        [switch]
        $DoNotCopyToClipboard,

        [Parameter(Mandatory = $false, Position = 0, ValueFromPipeline = $false)]
        [string[]]
        $Property = @()
    )
    
    Begin {
        if ($null -ne $InputObject -and $InputObject.GetType().BaseType -eq [System.Array]) {
            Write-Error "InputObject must not be System.Array. Don't use InputObject, but use the pipeline to pass the array object."
            $NeedToReturn = $true
            return
        }

        $LastCommandLine = (Get-PSCallStack)[1].Position.Text

        $Result = ""

        $TempOutputList = New-Object System.Collections.Generic.List[object]
    }

    Process {
        if ($NeedToReturn) { return }

        $CurrentObject = $null

        if (($Property.Length -eq 0) -or ($Property.Length -eq 1 -and $Property[0] -eq "")) {
            $Property = @("*")
        }

        if ($_ -eq $null) {
            $CurrentObject = $InputObject
        }
        else {
            $CurrentObject = $_
        }

        if ($CurrentObject.GetType().Name.ToLower() -eq "string") {
            # CurrentObject is a simple String object
            # Display like a FT style

            $Output = ""

            if ($Result -eq "") {
                $Output += "||`r`n"
                $Output += "|:--|`r`n"
            }
            
            $Output += "|$(EscapeMarkdown($CurrentObject))|`r`n"

            $Result += $Output

            $TempOutputList.Add($CurrentObject)
        }
        else {
            $CurrentObject = $CurrentObject | Select-Object -Property $Property -ErrorAction SilentlyContinue
            $Props = $CurrentObject | Get-Member -Name $Property -MemberType Property, NoteProperty
    
            $Output = "|Property|Value|`r`n"
            $Output += "|:--|:--|`r`n"
    
            $TempOutput = New-Object PSCustomObject
    
            foreach ($Prop in $Props) {
                $EscapedPropName = EscapeMarkdown($Prop.Name)
                $EscapedPropValue = EscapeMarkdown($CurrentObject.($($Prop.Name)))
                $Output += "|$EscapedPropName|$EscapedPropValue`r`n"
                $TempOutput | Add-Member -MemberType NoteProperty $Prop.Name -Value $CurrentObject.($($Prop.Name)) -Force
            }
    
            $Output += "`r`n"
    
            $Result += $Output
    
            $TempOutputList.Add($TempOutput)
        }
    }
    
    End {
        if ($NeedToReturn) { return }

        $ResultForConsole = $Result
        $Result = "**" + (EscapeMarkdown($LastCommandLine)) + "**`r`n`r`n" + $Result

        if ($HideStandardOutput.IsPresent -eq $false) {
            $TempOutputList | Format-List *
        }

        if ($ShowMarkdown.IsPresent) {
            Write-Output $ResultForConsole
        }

        if ($DoNotCopyToClipboard.IsPresent -eq $false) {
            Set-Clipboard $Result
            Write-Warning "Markdown text has been copied to the clipboard."
        }
    }
}

<#
.SYNOPSIS
Formats the output as a Format-Table style markdown table.

.DESCRIPTION
The Format-MarkdownTableTableStyle cmdlet formats the output of a command as a Format-Table style markdown table which each property is displayed on a separate row.

Markdown text will be copied to the clipboard.

.PARAMETER InputObject
Specifies the objects to be formatted. Enter a variable that contains the objects or type a command or expression that gets the objects.

.PARAMETER HideStandardOutput
Indicates that the cmdlet hides the standard Format-Table style output.

.PARAMETER ShowMarkdown
Indicates that the cmdlet outputs the markdown text to the console.

.PARAMETER DoNotCopyToClipboard
Indicates the the cmdlet does not copy the markdown text to the clipboard.

.PARAMETER Property
Specifies the object properties that appear in the display and the order in which they appear. Wildcards are permitted.

If you omit this parameter, the properties that appear in the display depend on the object being displayed. The parameter name "Property" is optional.

.EXAMPLE
Get-Process notepad | Format-MarkdownTableTableStyle

.EXAMPLE
Get-Process notepad | fmt Name,Path

.NOTES
You can also refer to Format-MarkdownTableTableStyle by its built-in alias, FMT.
#>

function Format-MarkdownTableTableStyle {
    [CmdletBinding()]
    [Alias("fmt")]

    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [object]
        $InputObject,

        [Parameter(Mandatory = $false, ValueFromPipeline = $false)]
        [switch]
        $HideStandardOutput,

        [Parameter(Mandatory = $false, ValueFromPipeline = $false)]
        [switch]
        $ShowMarkdown,

        [Parameter(Mandatory = $false, ValueFromPipeline = $false)]
        [switch]
        $DoNotCopyToClipboard,

        [Parameter(Mandatory = $false, Position = 0, ValueFromPipeline = $false)]
        [string[]]
        $Property = @()
    )
    
    Begin {
        ## Internal Function

        function UseAllProperty([object]$InputObject) {
            try {
                if ($null -eq $InputObject) {
                    return $true
                }
    
                $DataType = ($InputObject | Get-Member)[0].TypeName
    
                if ($DataType.StartsWith("Selected.")) {
                    return $true
                }            
                elseif ($DataType.StartsWith("Deserialized.")) {
                    $DataType = $DataType.Remove(0, 13)
                }
    
                $FormatData = Get-FormatData -TypeName $DataType -ErrorAction SilentlyContinue
    
                if ($null -eq $FormatData) {
                    return $true
                }
    
                return $false
            }
            catch {
                return $true
            }
        }
        
        if ($null -ne $InputObject -and $InputObject.GetType().BaseType -eq [System.Array]) {
            Write-Error "InputObject must not be System.Array. Don't use InputObject, but use the pipeline to pass the array object."
            $NeedToReturn = $true
            return
        }

        $LastCommandLine = (Get-PSCallStack)[1].Position.Text

        $Result = ""

        $HeadersForFormatTableStyle = New-Object System.Collections.Generic.List[string]
        $ContentsForFormatTableStyle = New-Object System.Collections.Generic.List[object]

        $TempOutputList = New-Object System.Collections.Generic.List[object]
    }

    Process {
        if ($NeedToReturn) { return }

        $CurrentObject = $null

        if ($_ -eq $null) {
            $CurrentObject = $InputObject
        }
        else {
            $CurrentObject = $_
        }

        if ($CurrentObject.GetType().Name.ToLower() -eq "string") {
            # CurrentObject is a simple String object
            $Props = @("")
        }
        elseif (($Property.Length -eq 0) -or ($Property.Length -eq 1 -and $Property[0] -eq "")) {
            if (UseAllProperty($CurrentObject)) {
                $Property = @("*")
                $CurrentObject = $CurrentObject | Select-Object -Property $Property
                $Props = $CurrentObject | Get-Member -Name $Property -MemberType Property, NoteProperty
            }
            else {
                $DataType = ($CurrentObject | Get-Member)[0].TypeName
        
                if ($DataType.StartsWith("Deserialized.")) {
                    $DataType = $DataType.Remove(0, 13)
                }
        
                $FormatData = Get-FormatData -TypeName $DataType -ErrorAction SilentlyContinue
                
                $TempPSObject = New-Object PSCustomObject

                $TempHeaderList = New-Object System.Collections.Generic.List[string]

                if ($FormatData.FormatViewDefinition.Control.Headers)
                {
                    for ($i = 0; $i -lt $FormatData.FormatViewDefinition.Control.Headers.Count; $i++) {
                        $HeaderName = $FormatData.FormatViewDefinition.Control.Headers[$i].Label
    
                        if ($null -eq $HeaderName -or $HeaderName -eq "") {
                            $HeaderName = $FormatData.FormatViewDefinition.Control.Rows.Columns[$i].DisplayEntry.Value
                        }
    
                        $TempSelectedObject = $null
    
                        if ($FormatData.FormatViewDefinition.Control.Rows.Columns[$i].DisplayEntry.ValueType -eq "ScriptBlock") {
                            $TempSelectedObject = $CurrentObject | Select-Object @{
                                n = $HeaderName;
                                e = ([scriptblock]::Create($FormatData.FormatViewDefinition.Control.Rows.Columns[$i].DisplayEntry.Value))
                            }
                        }
                        else {
                            $PropertyName = $FormatData.FormatViewDefinition.Control.Rows.Columns[$i].DisplayEntry.Value
    
                            $TempSelectedObject = $CurrentObject | Select-Object @{
                                n = $HeaderName;
                                e = {$_.$($PropertyName)}
                            }
                        }
    
                        $Value = $TempSelectedObject.$($HeaderName)
                        $TempPSObject | Add-Member -MemberType NoteProperty $HeaderName -Value $Value
                        $TempHeaderList.Add($HeaderName)
                    }
                }
                else {
                    for ($i = 0; $i -lt $FormatData.FormatViewDefinition.Control.Entries.Items.Count; $i++) {
                        $HeaderName = $FormatData.FormatViewDefinition.Control.Entries.Items[$i].DisplayEntry.Value

                        $TempSelectedObject = $null

                        $TempSelectedObject = $CurrentObject | Select-Object @{
                            n = $HeaderName;
                            e = {$_.$($HeaderName)}
                        }

                        $Value = $TempSelectedObject.$($HeaderName)
                        $TempPSObject | Add-Member -MemberType NoteProperty $HeaderName -Value $Value
                        $TempHeaderList.Add($HeaderName)
                    }
                }

                $CurrentObject = $TempPSObject | Select-Object -Property $TempHeaderList
                $Props = $CurrentObject | Get-Member -Name $TempHeaderList -MemberType Property, NoteProperty
            }
        }
        else {
            $CurrentObject = $CurrentObject | Select-Object -Property $Property -ErrorAction SilentlyContinue
            $Props = $CurrentObject | Get-Member -Name $Property -MemberType Property, NoteProperty
        }

        foreach ($Prop in $Props) {
            if ($HeadersForFormatTableStyle.Contains($Prop.Name) -eq $false) {
                $HeadersForFormatTableStyle.Add($Prop.Name)
            }
        }

        $ContentsForFormatTableStyle.Add($CurrentObject)
    }
    
    End {
        if ($NeedToReturn) { return }

        $HeaderRow = "|"
        $SeparatorRow = "|"
        $ContentRow = ""

        foreach ($Prop in $HeadersForFormatTableStyle) {
            $HeaderRow += "$(EscapeMarkdown($Prop))|"
            $SeparatorRow += ":--|"
            
        }

        foreach ($Content in $ContentsForFormatTableStyle) {
            $TempOutput = New-Object PSCustomObject
            $ContentRow += "|"

            if ($HeadersForFormatTableStyle.Count -eq "1" -and $HeadersForFormatTableStyle[0] -eq "") {
                # Content is an array of simple data type, like String.
                $ContentRow += "$(EscapeMarkdown($Content))|"
                $TempOutput = $null
                $TempOutput = $Content
            }
            else {
                foreach ($Prop in $HeadersForFormatTableStyle) {
                    $ContentRow += "$(EscapeMarkdown($Content.($($Prop))))|"
    
                    $TempOutput | Add-Member -MemberType NoteProperty $Prop -Value $Content.($($Prop))
                }
            }
            
            $ContentRow += "`r`n"

            $TempOutputList.Add($TempOutput)
        }

        $Result = $HeaderRow + "`r`n" + $SeparatorRow + "`r`n" + $ContentRow

        $ResultForConsole = $Result
        $Result = "**" + (EscapeMarkdown($LastCommandLine)) + "**`r`n`r`n" + $Result

        if ($HideStandardOutput.IsPresent -eq $false) {
            $TempOutputList | Format-Table * -AutoSize
        }

        if ($ShowMarkdown.IsPresent) {
            Write-Output $ResultForConsole
        }

        if ($DoNotCopyToClipboard.IsPresent -eq $false) {
            Set-Clipboard $Result
            Write-Warning "Markdown text has been copied to the clipboard."
        }
    }
}

function EscapeMarkdown([object]$InputObject) {
    $Temp = ""

    if ($null -eq $InputObject) {
        return ""
    }
    elseif ($InputObject.GetType().BaseType -eq [System.Array]) {
        $Temp = "{" + [System.String]::Join(", ", $InputObject) + "}"
    }
    elseif ($InputObject.GetType() -eq [System.Collections.ArrayList] -or $InputObject.GetType().ToString().StartsWith("System.Collections.Generic.List")) {
        $Temp = "{" + [System.String]::Join(", ", $InputObject.ToArray()) + "}"
    }
    elseif (Get-Member -InputObject $InputObject -Name ToString -MemberType Method) {
        $Temp = $InputObject.ToString()
    }
    else {
        $Temp = ""
    }

    return $Temp.Replace("\", "\\").Replace("*", "\*").Replace("_", "\_").Replace("``", "\``").Replace("$", "\$").Replace("|", "\|").Replace("<", "\<").Replace(">", "\>").Replace([System.Environment]::NewLine, "<br />")
}

Export-ModuleMember -Function Format-MarkdownTableListStyle, Format-MarkdownTableTableStyle -Alias *