Get-MarkdownHelp.ps1

<#PSScriptInfo
.VERSION 0.9.3
.GUID 19631007-c07a-48b9-8774-fcea5498ddb9
.AUTHOR iRon
.COMPANYNAME
.COPYRIGHT
.TAGS Help MarkDown ReadMe
.LICENSE https://github.com/iRon7/Get-MarkdownHelp/LICENSE
.PROJECTURI https://github.com/iRon7/Get-MarkdownHelp
.ICON
.EXTERNALMODULEDEPENDENCIES
.REQUIREDSCRIPTS
.EXTERNALSCRIPTDEPENDENCIES
.RELEASENOTES
.PRIVATEDATA
#>


<#
.SYNOPSIS
Creates a markdown Readme string from the comment based help of a command
 
.DESCRIPTION
This cmdlet uses the command information and Get-Help cmdlet to build a markdown Readme
simular to the general PowerShell online help.
 
.INPUTS
String (command name)
 
.OUTPUTS
String[]
 
.PARAMETER Name
    Specifies the CSV strings to be formatted or the objects that are converted to CSV formatted strings.
    You can also pipe objects to ConvertTo-CSV.
 
.EXAMPLE
    PS> Get-MarkdownHelp Show-MarkDown |Out-String |Show-Markdown -UseBrowser
 
    This command shows a markdown formatted help of the `Show-MarkDown` cmdlet in the default browser.
 
.EXAMPLE
    PS> Get-MarkdownHelp Get-Content |Clip
 
    This command creates a markdown readme string for the Get-Content cmdlet and put it on the clipboard
    to be used e.g. pasting it into a GitHub readme file.
 
.EXAMPLE
    PS> Get-MarkdownHelp .\MyScript.ps1 |Set-Content .\Readme.md
 
    This command creates a markdown readme string for the .\MyScript.ps1 script and saves it in Readme.md.
 
.LINK
    https://github.com/iRon7/Get-MarkdownHelp
#>


using namespace System.Management.Automation

[CmdletBinding(DefaultParameterSetName='Html')][OutputType([Object[]])] param(
    [Parameter(ValueFromPipeLine = $True, ValueFromPipelineByPropertyName = $True, Mandatory = $True)]
    [Alias('Name')][String]$CommandName
)

begin {
    Add-Type -AssemblyName System.Web

    function Text($String) {
        if ($String) {
            $Indent0 = $Null
            $Text = [System.Text.StringBuilder]::new()
            $NewLine = $False
            foreach ($Line in ($String -Split '[\r]?\n')) {
                $Trim = $Line.Trim()
                $Indent = if ($Line -Match '^\w*') { '^' + $Matches[0] } else { '^' }
                if ($Null -eq $Indent0 -and $Trim) { $Indent0 = $Indent }
                if ($Indent -ne $Indent0 -or $Trim -Match '^[*-\>]\w|\d+[\.\)]') { $NewLine = $True }
                if ($Text.Length -and $NewLine) { [void]$Text.AppendLine(' ') } else { [void]$Text.Append(' ') }
                $MDEncode = [System.Web.HttpUtility]::HtmlEncode($Trim)
                [void]$Text.Append($MDEncode)
                $NewLine = $Line -match ' $'
            }
            [void]$Text.AppendLine()
            $Text.ToString()
        }
    }

    function GetTypeLink($TypeName) {
        $Type = $TypeName -as [Type]
        if ($Type) {
            $TypeName = $Type.Name
            $TypeUri = 'https://docs.microsoft.com/en-us/dotnet/api/' + $Type.FullName
            "[$TypeName]($TypeUri)"
        }
        else {
            $TypeName
        }
    }
}

process {
    $Command = Get-Command $CommandName
    $Help = Get-Help -Detailed $CommandName

    $Name = [System.IO.Path]::GetFileNameWithoutExtension($Command.Name)

    "# $Name"
    $Help.Synopsis

    '## [Syntax](#syntax)'
    foreach ($SyntaxItem in $Help.Syntax.syntaxItem) {
            '```PowerShell'
        $Name
        foreach ($Parameter in $syntaxItem.Parameter) {
            $Text = [System.Text.StringBuilder]' ['
            if ($Parameter.required -eq $True) { [void]$Text.Append('[') }
            [void]$Text.Append('-')
            [void]$Text.Append($Parameter.Name)
            if ($Parameter.required -eq $True) { [void]$Text.Append(']') }
            if ($Parameter.parameterValue) {
                [void]$Text.Append(' <')
                [void]$Text.Append($Parameter.parameterValue)
                [void]$Text.Append('>')
            }
            [void]$Text.Append(']')
            $Text.ToString()
        }
        ' [<CommonParameters>]'
        '```'
    }

    '## [Description](#description)'
    Text $Help.Description.Text

    $Examples = $Help.Examples.Example
    if ($Examples) { '## [Examples](exampls)' }
    foreach ($Example in $Examples) {
        if ($Example.Title -Match '^-+ EXAMPLE (\d)+ -+$') {
            "### Example $($Matches[1])"
        }
        else {
            "### $($Example.Title)"
        }
        if ($Example.Introduction.Text -notmatch '^PS.*\>$') { $Example.Introduction.Text }
        '```PowerShell'
        $Example.Code
        '```' #'
        foreach ($Remark in $Example.Remarks) {
            Text $Remark.Text
        }
    }

    $Parameters = $Help.parameters.parameter
    if ($Parameters) { '## [Parameters](#parameters)' }
    foreach ($Parameter in $Parameters) {
        "### ``-$($Parameter.Name)``"
        Text $Parameter.Description.Text
        '| <!-- --> | <!-- --> |'
        '| --------------------------- | -------- |'
        $Attributes = $Command.Parameters[$Parameter.Name].Attributes
        if ($Null -ne $Attributes.MinLength -and $Null -ne $Attributes.MaxLength) { "| Accepted length | $($Attributes.MinLength) - $($Attributes.MaxLength) |" }
        elseif ($Null -ne $Attributes.MinLength)                                  { "| Minimal length: | $($Attributes.MinLemgth) |" }
        elseif ($Null -ne $Attributes.MaxLength)                                  { "| Maximal lemgth: | $($Attributes.MaxLength) |" }
        if ($Null -ne $Attributes.RegexPattern)                                   { "| Accepted pattern: | ``$($Attributes.RegexPattern)`` |" }
        if ($Null -ne $Attributes.MinRange -and $Null -ne $Attributes.MaxRange)   { "| Accepted range: | $($Attributes.MinRange) - $($Attributes.MaxRange) |" }
        elseif ($Null -ne $Attributes.MinRange)                                   { "| Minimal value: | $($Attributes.MinRange) |" }
        elseif ($Null -ne $Attributes.MaxRange)                                   { "| Maximal value: | $($Attributes.MaxRange) |" }
        if ($Null -ne $Attributes.ScriptBlock)                                    { "| Accepted script condition: | ``$($Attributes.ScriptBlock.ToString().Trim() -Split '\s*[\r?\n]\s*' -Join '; ')`` |" }
        if ($Null -ne $Attributes.ValidValues)                                    { "| Accepted values: | $($Attributes.ValidValues -Join ', ') |" }
        "| Type: | $(GetTypeLink($Parameter.parameterValue)) |"
        if ($Attributes.Aliases) { "| Aliases: | $(Attributes.Aliases -Join ', ') |" }
        "| Position: | $($Parameter.Position) |"
        "| Default value: | $($Parameter.defaultValue) |"
        "| Accept pipeline input: | $($Parameter.pipelineInput) |"
        "| Accept wildcard characters: | $($Parameter.globbing) |"
    }

    $InputTypes = $Help.inputTypes.inputType
    if ($InputTypes) { '## [Inputs](#inputs)' }
    foreach ($InputType in $InputTypes) {
        "### $(GetTypeLink($InputType.Type.Name))"
        Text $InputType.Description.Text
    }

    $ReturnValues = $Help.returnValues.returnValue
    if ($ReturnValues) { '## [Outputs](#outputs)' }
    foreach ($ReturnValue in $ReturnValues) {
        "### $(GetTypeLink($ReturnValue.Type.Name))"
    }

    $Links = $Help.relatedLinks.navigationLink
    if ($Links) { '## [Related Links](#related-links)' }
    foreach ($link in $Links) {
        $Uri = $Link.uri # Bug: https://github.com/PowerShell/PowerShell/issues/17901
        $LinkText = if ($Link.linkText) { $Link.linkText } else { $Uri }
        "* [$linkText]($Uri)"
    }
}