Public/Convert-PSScriptHelpToMarkdown.ps1
Function Convert-PSScriptHelpToMarkdown { <# .SYNOPSIS Converts PowerShell script help to Markdown documentation. .DESCRIPTION Converts PowerShell script help to Markdown documentation. This function is capable of either outputting data to console, file, or both simultaneously. By default the function will capture help metadata at a script-level, and will not capture any function-level data. This functionality can be controlled with the `-IncludeNestedFunctions` parameter. For capturing documentation on files that only contain function data, please use `Convert-PSFunctionHelpToMarkdown` instead. The function will generate warnings for any missing data fields in order to notify the operator and ensure completeness of documentation. .PARAMETER Filename The name of the file to convert. .PARAMETER OutputPath The path to which to export the Markdown document. .PARAMETER HeaderGranularity Controls whether parameter names and examples are individually converted to Markdown headers. This setting will impact the appearance of the rendered text in addition to the metadata. .PARAMETER IncludeNestedFunctions Parses nested functions inside the file and converts the help for each to Markdown. .PARAMETER OutFile Controls whether an output file is generated. .PARAMETER PassThru Returns output to the console in addition to producing a file. .PARAMETER Force Forces an overwrite of an existing file. .PARAMETER Append Appends to an existing file. Necessary when consolidating documentation from multiple files into one. .EXAMPLE # Generate documentation to the console for a single file Convert-PSHelpToMarkdown -Filename C:\Path\To\Script.ps1 .EXAMPLE # Generate documentation to the console for a single file, including any nested functions inside the file. Convert-PSHelpToMarkdown -Filename C:\Path\To\Script.ps1 -IncludeNestedFunctions .EXAMPLE # Generate documentation for a single file to a Markdown file with the same name and location, including any nested functions inside the file, and output to the console. Convert-PSHelpToMarkdown -Filename C:\Path\To\Script.ps1 -IncludeNestedFunctions -PassThru .EXAMPLE # Generate documentation for multiple files in a directory tree to a individual Markdown files with the same name and location as the .ps1 files Get-ChildItem C:\Path\To\Scripts*.ps1 -Recurse | Convert-PSHelpToMarkDown -IncludeNestedFunctions -OutFile .EXAMPLE # Generate documentation for multiple files in a directory tree to a individual Markdown files with the same name as the .ps1 files, but in a new folder Get-ChildItem C:\Path\To\Scripts*.ps1 -Recurse | Convert-PSHelpToMarkDown -IncludeNestedFunctions -OutFile -OutputPath C:\Path\To\Scripts\Docs .EXAMPLE # Generate documentation for multiple files in a directory tree to a single Markdown file Get-ChildItem C:\Path\To\Scripts*.ps1 -Recurse | Convert-PSHelpToMarkDown -IncludeNestedFunctions -OutFile -OutputPath C:\Path\To\Scripts\Consolidated.md -Force -Append .NOTES - Help data for dynamic parameters is not captured or converted. This is a known issue with Get-Help and not with this function. You can use examples that show dynamic parameters or move documentation of dynamic parameters to another section (such as the Description Field) in order to work around this limitation. [GitHub Issue](https://github.com/PowerShell/PowerShell/issues/6694) - This function does not support external help files at this time. #> [CmdletBinding(DefaultParameterSetName="__AllParameterSets")] PARAM ( [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] [ValidateNotNullOrEmpty()] [Alias("FullName","File")] [String]$Filename, [Parameter(Mandatory=$false, Position=1, ParameterSetName="OutFile")] [ValidateNotNullOrEmpty()] [String]$OutputPath, [Parameter(Mandatory=$false)] [ValidateSet("Coarse","Fine")] [String]$HeaderGranularity = "Fine", [Parameter(Mandatory=$false)] [Switch]$IncludeNestedFunctions, [Parameter(Mandatory=$false, ParameterSetName="OutFile")] [Switch]$OutFile ) # Create dynamic parameters DynamicParam { IF ($PSCmdlet.ParameterSetName -eq "OutFile") { # Create runtine dictionary $ParamDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() # Define parameter attributes $Attribute = [System.Management.Automation.ParameterAttribute]@{ Mandatory = $false ParameterSetName = "OutFile" } # Create attribute collection $AttributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]$Attribute # Create parameter object and add name, type, attribute collection $PassThruParameter = [System.Management.Automation.RuntimeDefinedParameter]::new('PassThru',[Switch],$AttributeCollection) $PassThruParameter.Value = $false $PSBoundParameters['PassThru'] = $PassThruParameter.Value # Add parameter to runtime dictionary $ParamDictionary.Add('PassThru',$PassThruParameter) # Create parameter object and add name, type, attribute collection $ForceParameter = [System.Management.Automation.RuntimeDefinedParameter]::new('Force',[Switch],$AttributeCollection) $ForceParameter.Value = $false $PSBoundParameters['Force'] = $ForceParameter.Value # Add parameter to runtime dictionary $ParamDictionary.Add('Force',$ForceParameter) # Create parameter object and add name, type, attribute collection $AppendParameter = [System.Management.Automation.RuntimeDefinedParameter]::new('Append',[Switch],$AttributeCollection) $AppendParameter.Value = $false $PSBoundParameters['Append'] = $AppendParameter.Value # Add parameter to runtime dictionary $ParamDictionary.Add('Append',$AppendParameter) return $ParamDictionary } } #region BEGIN Block BEGIN { # Transfer Dynamic Parameters to runtime $Append = $PSBoundParameters['Append'] $PassThru = $PSBoundParameters['PassThru'] $Force = $PSBoundParameters['Force'] # Locally scope ErrorActionPreference for predictable behavior of Try/Catch blocks inside the function $ErrorActionPreference = 'Stop' } #endregion BEGIN Block #region PROCESS Block PROCESS { # Create output variable $Results = [System.Collections.ArrayList]::new() #region Script # Declare variables $FileTitle = '' $FileHeader = '' $Syntax = '' $Synopsis = '' $Description = '' $Parameters = '' $Examples = '' $Notes = '' #region Script: Gather Info # Get script and function documentation $ScriptDocumentation = Get-PSScriptDocumentation $Filename # Throw warning if function documentation is not found IF ($ScriptDocumentation -eq $null) { Write-Warning "Script documentation is null." Return } #endregion Script: Gather Info #region Script: File Info # Construct file title string $FileTitle = $ScriptDocumentation.Filename.Split('.')[0] | ConvertTo-MarkdownHeader # Add to results $Results.Add("$FileTitle`n") | Out-Null # Construct file header string $FileHeader += "FileName: $($ScriptDocumentation.Filename | Sort -Unique)`n" IF ($ScriptDocumentation.ScriptInfo.Version) { $FileHeader += "Version: $($ScriptDocumentation.ScriptInfo.Version)`n" } $FileHeader += "Generated on: $(Get-Date -Format MM.dd.yyyy)`n" # Convert file header to YAML code block $FileHeader = $FileHeader | Convertto-MarkdownCodeBlock -Language YAML # Add to results $Results.Add("$FileHeader`n") | Out-Null #endregion Script: File Info #region Script: Syntax # Construct syntax string $Syntax += "Syntax" | ConvertTo-MarkdownHeader -Level 2 $Syntax += "`n" $Syntax += $ScriptDocumentation.Syntax.TrimEnd(' ') | Convertto-MarkdownCodeBlock -Language PowerShell $Syntax += "`n" # Add to results $Results.Add($Syntax) | Out-Null #endregion Script: Syntax #region Script: Synopsis # Construct synopsis string IF ($ScriptDocumentation.Synopsis -notmatch $ScriptDocumentation.FileName) { $Synopsis += "Synopsis" | ConvertTo-MarkdownHeader -Level 2 $Synopsis += "`n$($ScriptDocumentation.Synopsis)`n" # Add to results $Results.Add($Synopsis) | Out-Null } #endregion Script: Synopsis #region Script: Description # Construct description string IF ($ScriptDocumentation.Description -notmatch $ScriptDocumentation.FileName) { $Description += "Description" | ConvertTo-MarkdownHeader -Level 2 $Description += "`n$($ScriptDocumentation.Description.Text)`n" # Add to results $Results.Add($Description) | Out-Null } #endregion Script: Description #region Script: Parameters # Construct parameter string IF ($ScriptDocumentation.Parameters -ne '') { $Parameters += "Parameters" | ConvertTo-MarkdownHeader -Level 2 $Parameters += "`n" FOREACH ($Parameter in $ScriptDocumentation.Parameters) { SWITCH($HeaderGranularity) { "Coarse" {$Parameters += "-$($Parameter.Name)" | Convertto-MarkdownCodeBlock -Inline | ConvertTo-MarkdownFormattedText -Bold ; $Parameters += "`n"} "Fine" {$Parameters += "-$($Parameter.Name)" | Convertto-MarkdownCodeBlock -Inline | ConvertTo-MarkdownHeader -Level 3} } $Parameters += "`n" $Parameters += $Parameter.Description.Text $Parameters += "`n" $Parameters += "`n" } SWITCH($HeaderGranularity) { "Coarse" {$Parameters += "Parameter Details" | ConvertTo-MarkdownFormattedText -Bold} "Fine" {$Parameters += "Parameter Details" | ConvertTo-MarkdownHeader -Level 3} } $Parameters += "`n" $Parameters += $ScriptDocumentation.Parameters | Select Name,DefaultValue,Required,ParameterValue,Position,PipelineInput | ConvertTo-MarkdownTable # Add to results $Results.Add($Parameters) | Out-Null } #endregion Script: Parameters #region Script: Examples # Construct examples string IF ($ScriptDocumentation.Examples) { $Examples += "Examples" | ConvertTo-MarkdownHeader -Level 2 $Examples += "`n" FOREACH ($Example in $ScriptDocumentation.Examples) { SWITCH($HeaderGranularity) { "Coarse" {$Examples += (Get-Culture).TextInfo.ToTitleCase($Example.Title.ToLower()) | ConvertTo-MarkdownFormattedText -Bold} "Fine" {$Examples += (Get-Culture).TextInfo.ToTitleCase($Example.Title.ToLower()) | ConvertTo-MarkdownHeader -Level 3} } $Examples += "`n" $Examples += $Example.Description.ToString() | Convertto-MarkdownCodeBlock -Language PowerShell $Examples += "`n" $Examples += "`n" } "$($ScriptDocumentation.Examples)`n" # Add to results $Results.Add($Examples) | Out-Null } #endregion Script: Examples #region Script: Notes # Construct notes string IF ($ScriptDocumentation.Notes -ne '') { $Notes += "Notes" | ConvertTo-MarkdownHeader -Level 2 $Notes += "`n$($ScriptDocumentation.Notes)`n" # Add to results $Results.Add($Notes) | Out-Null } #endregion Script: Notes #endregion Script #region Functions IF ($IncludeNestedFunctions) { #region Functions: Gather Info # Get script and function documentation $FunctionDocumentation = Get-PSFunctionDocumentation $Filename # Throw warning if function documentation is not found IF ($FunctionDocumentation -eq $null) { Write-Warning "Function documentation is null." Return } #endregion Functions: Gather Info #region Functions: Header # Declare variables $FunctionHeader = '' # Construct functions header string $FunctionHeader += "Functions" | ConvertTo-MarkdownHeader -Level 2 $FunctionHeader += "`n" # Add to results $Results.Add($FunctionHeader) | Out-Null #endregion Functions: Header FOREACH ($Function in $FunctionDocumentation) { # Declare variables $FunctionFileHeader = '' $FunctionSyntax = '' $FunctionSynopsis = '' $FunctionDescription = '' $FunctionParameters = '' $FunctionExamples = '' $FunctionNotes = '' #region Functions: File Info # Construct file header string $FunctionFileHeader += "$($Function.Name | Sort -Unique)`n" | ConvertTo-MarkdownHeader -Level 3 # Add to results $Results.Add("$FunctionFileHeader`n") | Out-Null #endregion Functions: File Info #region Functions: Syntax # Construct syntax string $FunctionSyntax += "Syntax" | ConvertTo-MarkdownHeader -Level 4 $FunctionSyntax += "`n" $FunctionSyntax += $Function.Syntax | Convertto-MarkdownCodeBlock -Language PowerShell $FunctionSyntax += "`n" # Add to results $Results.Add($FunctionSyntax) | Out-Null #endregion Functions: Syntax #region Functions: Synopsis # Construct synopsis string IF ($Function.Synopsis) { $FunctionSynopsis += "Synopsis" | ConvertTo-MarkdownHeader -Level 4 $FunctionSynopsis += "`n$($Function.Synopsis)`n" # Add to results $Results.Add($FunctionSynopsis) | Out-Null } #endregion Functions: Synopsis #region Functions: Description # Construct description string IF ($Function.Description) { $FunctionDescription += "Description" | ConvertTo-MarkdownHeader -Level 4 $FunctionDescription += "`n$($Function.Description.Text)`n" # Add to results $Results.Add($FunctionDescription) | Out-Null } #endregion Functions: Description #region Functions: Parameters # Construct parameter string IF ($Function.Parameters) { $FunctionParameters += "Parameters" | ConvertTo-MarkdownHeader -Level 4 $FunctionParameters += "`n" FOREACH ($Parameter in $Function.Parameters) { SWITCH($HeaderGranularity) { "Coarse" {$FunctionParameters += "-$($Parameter.Name)" | Convertto-MarkdownCodeBlock -Inline | ConvertTo-MarkdownFormattedText -Bold ; $FunctionParameters += "`n"} "Fine" {$FunctionParameters += "-$($Parameter.Name)" | Convertto-MarkdownCodeBlock -Inline | ConvertTo-MarkdownHeader -Level 5} } $FunctionParameters += "`n" $FunctionParameters += $Parameter.Description.Text $FunctionParameters += "`n" $FunctionParameters += "`n" } SWITCH($HeaderGranularity) { "Coarse" {$FunctionParameters += "Parameter Details" | ConvertTo-MarkdownFormattedText -Bold} "Fine" {$FunctionParameters += "Parameter Details" | ConvertTo-MarkdownHeader -Level 5} } $FunctionParameters += "`n" $FunctionParameters += $Function.Parameters | Select Name,DefaultValue,Required,ParameterValue,Position,PipelineInput | ConvertTo-MarkdownTable # Add to results $Results.Add($FunctionParameters) | Out-Null } #endregion Functions: Parameters #region Functions: Examples # Construct examples string IF ($Function.Examples) { $FunctionExamples += "Examples" | ConvertTo-MarkdownHeader -Level 4 $FunctionExamples += "`n" FOREACH ($Example in $Function.Examples) { SWITCH($HeaderGranularity) { "Coarse" {$FunctionExamples += (Get-Culture).TextInfo.ToTitleCase($Example.Title.ToLower()) | ConvertTo-MarkdownFormattedText -Bold} "Fine" {$FunctionExamples += (Get-Culture).TextInfo.ToTitleCase($Example.Title.ToLower()) | ConvertTo-MarkdownHeader -Level 5} } $FunctionExamples += "`n" $FunctionExamples += $Example.Description.ToString() | Convertto-MarkdownCodeBlock -Language PowerShell $FunctionExamples += "`n" $FunctionExamples += "`n" } "$($Function.Examples)`n" # Add to results $Results.Add($FunctionExamples) | Out-Null } #endregion Functions: Examples #region Functions: Notes # Construct notes string IF ($Function.Notes) { $FunctionNotes += "Notes" | ConvertTo-MarkdownHeader -Level 4 $FunctionNotes += "`n$($Function.Notes)`n" # Add to results $Results.Add($FunctionNotes) | Out-Null } #endregion Functions: Notes } } #endregion Functions #region Output Handling IF ($PSCmdlet.ParameterSetName -eq "OutFile") { IF ($OutputPath) { # Test whether output path is a file or directory SWITCH -Regex ($OutputPath) { #region Output Handling: File # Absolute regex madness "\.\w\w$|\.\w\w\w$|\.\w\w\w\w$|\.markdown$" { $OutputFilename = $OutputPath.Split('\')[-1] $OutputDirectory = $OutputPath.Substring(0,$OutputPath.LastIndexOf('\')) } #endregion Output Handling: File #region Output Handling: Folder Default { $OutputFilename = "$($ScriptDocumentation.Filename.Split('.')[0]).md" $OutputDirectory = $OutputPath } #endregion Output Handling: Folder } } ELSE #region Output Handling: Unspecified { $OutputFilename = "$($Filename.Split('\')[-1].Split('.')[0]).md" $OutputDirectory = $Filename.Substring(0,$Filename.LastIndexOf('\')) } #endregion Output Handling: Unspecified # Create path if it doesn't exist IF (!(Test-Path $OutputDirectory)) { New-Item -Path $OutputDirectory -ItemType Directory | Out-Null } # Output file $Results | Out-File $OutputDirectory\$OutputFilename -Encoding ascii -Force:$Force -Append:$Append } #endregion Output Handling #region Results # Return results to the console if an output has not been specified or -Passthru has been selected IF(($PSCmdlet.ParameterSetName -ne "OutFile") -or $Passthru) { Return $Results } #endregion Results } #endregion PROCESS Block #region END Block END { # Nothing to see here. Move along. } #endregion END Block } |