Documentarian.ModuleAuthor.psm1
# Copyright (c) Microsoft Corporation. # Licensed under the MIT License. #region Enums.Public [Flags()] enum ProviderFlags { Registry = 0x01 Alias = 0x02 Environment = 0x04 FileSystem = 0x08 Function = 0x10 Variable = 0x20 Certificate = 0x40 WSMan = 0x80 } enum ParameterAttributeKind { DontShow Experimental HasValidation SupportsWildcards ValueFromPipeline ValueFromRemaining } #endregion Enums.Public #region Classes.Public class ParameterInfo { [string]$Name [string]$HelpText [string]$Type [string]$ParameterSet [string]$Aliases [bool]$Required [string]$Position [string]$Pipeline [bool]$Wildcard [bool]$Dynamic [bool]$FromRemaining [bool]$DontShow [ProviderFlags]$ProviderFlags ParameterInfo( [System.Management.Automation.ParameterMetadata]$param, [ProviderFlags]$ProviderFlags ) { $this.Name = $param.Name $this.HelpText = if ($null -eq $param.Attributes.HelpMessage) { '{{Placeholder}}' } else { $param.Attributes.HelpMessage } $this.Type = $param.ParameterType.FullName $this.ParameterSet = if ($param.Attributes.ParameterSetName -eq '__AllParameterSets') { '(All)' } else { $param.Attributes.ParameterSetName -join ', ' } $this.Aliases = $param.Aliases -join ', ' $this.Required = $param.Attributes.Mandatory $this.Position = if ($param.Attributes.Position -lt 0) { 'Named' } else { $param.Attributes.Position } $this.Pipeline = 'ByValue ({0}), ByName ({1})' -f $param.Attributes.ValueFromPipeline, $param.Attributes.ValueFromPipelineByPropertyName $this.Wildcard = $param.Attributes.TypeId.Name -contains 'SupportsWildcardsAttribute' $this.Dynamic = $param.IsDynamic $this.FromRemaining = $param.Attributes.ValueFromRemainingArguments $this.DontShow = $param.Attributes.DontShow $this.ProviderFlags = $ProviderFlags } [string]ToMarkdown([bool]$showAll) { $sbMarkdown = [System.Text.StringBuilder]::new() $sbMarkdown.AppendLine("### -$($this.Name)") $sbMarkdown.AppendLine() $sbMarkdown.AppendLine($this.HelpText) $sbMarkdown.AppendLine() $sbMarkdown.AppendLine('```yaml') $sbMarkdown.AppendLine("Type: $($this.Type)") $sbMarkdown.AppendLine("Parameter Sets: $($this.ParameterSet)") $sbMarkdown.AppendLine("Aliases: $($this.Aliases)") $sbMarkdown.AppendLine() $sbMarkdown.AppendLine("Required: $($this.Required)") $sbMarkdown.AppendLine("Position: $($this.Position)") $sbMarkdown.AppendLine('Default value: None') $sbMarkdown.AppendLine("Accept pipeline input: $($this.Pipeline)") $sbMarkdown.AppendLine("Accept wildcard characters: $($this.Wildcard)") if ($showAll) { $sbMarkdown.AppendLine("Dynamic: $($this.Dynamic)") if ($this.Dynamic -and $this.ProviderFlags) { $ProviderName = if ($this.ProviderFlags -eq 0xFF) { 'All' } else { $this.ProviderFlags.ToString() } $sbMarkdown.AppendLine("Providers: $ProviderName") } $sbMarkdown.AppendLine("Values from remaining args: $($this.FromRemaining)") $sbMarkdown.AppendLine("Do not show: $($this.DontShow)") } $sbMarkdown.AppendLine('```') $sbMarkdown.AppendLine() return $sbMarkdown.ToString() } } class DontShowAttributeInfo { [string] $Cmdlet [string] $Parameter [string] $ParameterType [bool] $DontShow [string] $ParameterSetName [string] $Module [string] ToString () { return @( "$($this.Module)/" "$($this.Cmdlet), " "Parameter: $($this.Parameter) <" "$($this.ParameterType)>, " "DontShow: $($this.DontShow)" ) -join '' } } class ExperimentalAttributeInfo { [string]$Cmdlet [string]$Parameter [string]$ParameterType [string]$ExperimentName [string]$ParameterSetName [string]$Module [string] ToString () { return @( "$($this.Module)/" "$($this.Cmdlet), " "Parameter: $($this.Parameter) <" "$($this.ParameterType)>, " "Experiment: $($this.ExperimentName)" ) -join '' } } class HasValidationAttributeInfo { [string]$Cmdlet [string]$Parameter [string]$ParameterType [string]$ValidationAttribute [string]$ParameterSetName [string]$Module [string] ToString () { return @( "$($this.Module)/" "$($this.Cmdlet), " "Parameter: $($this.Parameter) <" "$($this.ParameterType)>, " "ValidationAttribute: $($this.ValidationAttribute)" ) -join '' } } class SupportsWildcardsAttributeInfo { [string]$Cmdlet [string]$Parameter [string]$ParameterType [bool]$SupportsWildcards [string]$ParameterSetName [string]$Module [string] ToString () { return @( "$($this.Module)/" "$($this.Cmdlet), " "Parameter: $($this.Parameter) <" "$($this.ParameterType)>" "SupportsWildcards: $($this.SupportsWildcards)" ) -join '' } } class ValueFromPipelineAttributeInfo { [string]$Cmdlet [string]$Parameter [string]$ParameterType [string]$ValueFromPipeline [string]$ParameterSetName [string]$Module [string] ToString () { return @( "$($this.Module)/" "$($this.Cmdlet), " "Parameter: $($this.Parameter) <" "$($this.ParameterType)>, " "ValueFromPipeline: $($this.ValueFromPipeline)" ) -join '' } } class ValueFromRemainingAttributeInfo { [string]$Cmdlet [string]$Parameter [string]$ParameterType [bool]$ValueFromRemaining [string]$ParameterSetName [string]$Module [string] ToString () { return @( "$($this.Module)/" "$($this.Cmdlet), " "Parameter: $($this.Parameter) <" "$($this.ParameterType)>, " "ValueFromRemaining: $($this.ValueFromRemaining)" ) -join '' } } #endregion Classes.Public #region Functions.Private <# .SYNOPSIS Returns a list of parameter headers from a cmdlet markdown file. .DESCRIPTION Returns a list of parameter headers from a cmdlet markdown file. .PARAMETER mdheaders An array of objects returned by `Select-String -Pattern '^#' -Path $file` .NOTES Used by `Update-ParameterOrder` to sort the parameters in a cmdlet markdown file. #> function Get-ParameterMdHeaders { param($mdheaders) $paramlist = @() $inParams = $false foreach ($hdr in $mdheaders) { # Find the start of the parameters section if ($hdr.Line -eq '## Parameters') { $inParams = $true } if ($inParams) { # Find the start of each parameter if ($hdr.Line -match '^### -') { $param = [PSCustomObject]@{ Line = $hdr.Line.Trim() StartLine = $hdr.LineNumber - 1 EndLine = -1 } $paramlist += $param } # Find the end of the last parameter if ((($hdr.Line -match '^## ' -and $hdr.Line -ne '## Parameters') -or ($hdr.Line -eq '### CommonParameters')) -and ($paramlist.Count -gt 0)) { $inParams = $false $paramlist[-1].EndLine = $hdr.LineNumber - 2 } } } # Find the end each last parameter if ($paramlist.Count -gt 0) { for ($x = 0; $x -lt $paramlist.Count; $x++) { if ($paramlist[$x].EndLine -eq -1) { $paramlist[$x].EndLine = $paramlist[($x + 1)].StartLine - 1 } } } $paramlist } #endregion Functions.Private #region Functions.Public function Find-ParameterWithAttribute { [CmdletBinding()] [OutputType( [DontShowAttributeInfo], [ExperimentalAttributeInfo], [HasValidationAttributeInfo], [SupportsWildcardsAttributeInfo], [ValueFromPipelineAttributeInfo], [ValueFromRemainingAttributeInfo] )] param( [Parameter(Mandatory, Position = 0)] [ParameterAttributeKind]$AttributeKind, [Parameter(Position = 1)] [SupportsWildcards()] [string[]]$CommandName = '*', [ValidateSet('Cmdlet', 'Module', 'None')] [string]$GroupBy = 'None' ) begin { $cmdlets = Get-Command $CommandName -Type Cmdlet, ExternalScript, Filter, Function, Script } process { foreach ($cmd in $cmdlets) { foreach ($param in $cmd.Parameters.Values) { $result = $null foreach ($attr in $param.Attributes) { if ($attr.TypeId.ToString() -eq 'System.Management.Automation.ParameterAttribute' -and $AttributeKind -in 'DontShow', 'Experimental', 'ValueFromPipeline', 'ValueFromRemaining') { switch ($AttributeKind) { DontShow { if ($attr.DontShow) { $result = [DontShowAttributeInfo]@{ Cmdlet = $cmd.Name Parameter = $param.Name ParameterType = $param.ParameterType.Name DontShow = $attr.DontShow ParameterSetName = $param.ParameterSets.Keys -join ', ' Module = $cmd.Source } } break } Experimental { if ($attr.ExperimentName) { $result = [ExperimentalAttributeInfo]@{ Cmdlet = $cmd.Name Parameter = $param.Name ParameterType = $param.ParameterType.Name DontShow = $attr.ExperimentName ParameterSetName = $param.ParameterSets.Keys -join ', ' Module = $cmd.Source } } break } ValueFromPipeline { if ($attr.ValueFromPipeline -or $attr.ValueFromPipelineByPropertyName) { $result = [ValueFromPipelineAttributeInfo]@{ Cmdlet = $cmd.Name Parameter = $param.Name ParameterType = $param.ParameterType.Name ValueFromPipeline = ('ByValue({0}), ByName({1})' -f $attr.ValueFromPipeline, $attr.ValueFromPipelineByPropertyName) ParameterSetName = $param.ParameterSets.Keys -join ', ' Module = $cmd.Source } } break } ValueFromRemaining { if ($attr.ValueFromRemainingArguments) { $result = [ValueFromRemainingAttributeInfo]@{ Cmdlet = $cmd.Name Parameter = $param.Name ParameterType = $param.ParameterType.Name ValueFromRemaining = $attr.ValueFromRemainingArguments ParameterSetName = $param.ParameterSets.Keys -join ', ' Module = $cmd.Source } } break } } } elseif ($attr.TypeId.ToString() -like 'System.Management.Automation.Validate*Attribute' -and $AttributeKind -eq 'HasValidation') { $result = [HasValidationAttributeInfo]@{ Cmdlet = $cmd.Name Parameter = $param.Name ParameterType = $param.ParameterType.Name ValidationAttribute = $attr.TypeId.ToString().Split('.')[ - 1].Replace('Attribute', '') ParameterSetName = $param.ParameterSets.Keys -join ', ' Module = $cmd.Source } } elseif ($attr.TypeId.ToString() -eq 'System.Management.Automation.SupportsWildcardsAttribute' -and $AttributeKind -eq 'SupportsWildcards') { $result = [SupportsWildcardsAttributeInfo]@{ Cmdlet = $cmd.Name Parameter = $param.Name ParameterType = $param.ParameterType.Name SupportsWildcards = $true ParameterSetName = $param.ParameterSets.Keys -join ', ' Module = $cmd.Source } } } if ($result) { # Add a type name to the object so that the correct format gets chosen switch ($GroupBy) { 'Cmdlet' { $typename = $result.GetType().Name + '#ByCmdlet' $result.psobject.TypeNames.Insert(0, $typename) break } 'Module' { $typename = $result.GetType().Name + '#ByModule' $result.psobject.TypeNames.Insert(0, $typename) break } } $result } } } } } function Get-ParameterInfo { [CmdletBinding(DefaultParameterSetName = 'AsMarkdown')] [OutputType([ParameterInfo])] [OutputType([System.String])] param( [Parameter(Mandatory, Position = 0, ParameterSetName = 'AsMarkdown')] [Parameter(Mandatory, Position = 0, ParameterSetName = 'AsObject')] [string[]]$ParameterName, [Parameter(Mandatory, Position = 1, ParameterSetName = 'AsMarkdown')] [Parameter(Mandatory, Position = 1, ParameterSetName = 'AsObject')] [string]$CmdletName, [Parameter(ParameterSetName = 'AsMarkdown')] [switch]$ShowAll, [Parameter(Mandatory, ParameterSetName = 'AsObject')] [switch]$AsObject ) $cmdlet = Get-Command -Name $CmdletName -ErrorAction Stop $providerList = Get-PSProvider foreach ($pname in $ParameterName) { try { $paraminfo = $null $param = $null foreach ($provider in $providerList) { Push-Location $($provider.Drives[0].Name + ':') $param = $cmdlet.Parameters.Values | Where-Object Name -EQ $pname if ($param) { if ($paraminfo) { $paraminfo.ProviderFlags = $paraminfo.ProviderFlags -bor [ProviderFlags]($provider.Name) } else { $paraminfo = [ParameterInfo]::new( $param, [ProviderFlags]($provider.Name) ) } } Pop-Location } } catch { Write-Error "Cmdlet $CmdletName not found." return } if ($paraminfo) { if ($AsObject) { $paraminfo } else { $paraminfo.ToMarkdown($ShowAll) } } else { Write-Error "Parameter $pname not found." } } } function Get-ShortDescription { $crlf = "`r`n" Get-ChildItem *.md | ForEach-Object { if ($_.directory.basename -ne $_.basename) { $filename = $_.Name $name = $_.BaseName $headers = Select-String -Path $filename -Pattern '^## \w*' -AllMatches $mdtext = Get-Content $filename $start = $headers[0].LineNumber $end = $headers[1].LineNumber - 2 $short = $mdtext[($start)..($end)] -join ' ' if ($short -eq '') { $short = '{{Placeholder}}' } '### [{0}]({1}){3}{2}{3}' -f $name, $filename, $short.Trim(), $crlf } } } function Get-Syntax { [CmdletBinding()] param( [Parameter(Mandatory = $true, Position = 0)] [string]$CmdletName, [switch]$Markdown ) function formatString { param( $cmd, $pstring ) $parts = $pstring -split ' ' $parameters = @() for ($x = 0; $x -lt $parts.Count; $x++) { $p = $parts[$x] if ($x -lt $parts.Count - 1) { if (!$parts[$x + 1].StartsWith('[')) { $p += ' ' + $parts[$x + 1] $x++ } $parameters += , $p } else { $parameters += , $p } } $line = $cmd + ' ' $temp = '' for ($x = 0; $x -lt $parameters.Count; $x++) { if ($line.Length + $parameters[$x].Length + 1 -lt 100) { $line += $parameters[$x] + ' ' } else { $temp += $line + "`r`n" $line = ' ' + $parameters[$x] + ' ' } } $temp + $line.TrimEnd() } try { $cmdlet = Get-Command $cmdletname -ea Stop if ($cmdlet.CommandType -eq 'Alias') { $cmdlet = Get-Command $cmdlet.Definition } if ($cmdlet.CommandType -eq 'ExternalScript') { $name = $CmdletName } else { $name = $cmdlet.Name } $syntax = (Get-Command $name).ParameterSets | Select-Object -Property @{n = 'Cmdlet'; e = { $cmdlet.Name } }, @{n = 'ParameterSetName'; e = { $_.name } }, IsDefault, @{n = 'Parameters'; e = { $_.ToString() } } } catch [System.Management.Automation.CommandNotFoundException] { $_.Exception.Message } $mdHere = @' ### {0}{1} ``` {2} ``` '@ if ($Markdown) { foreach ($s in $syntax) { $string = $s.Cmdlet, $s.Parameters -join ' ' if ($s.IsDefault) { $default = ' (Default)' } else { $default = '' } if ($string.Length -gt 100) { $string = formatString $s.Cmdlet $s.Parameters } $mdHere -f $s.ParameterSetName, $default, $string } } else { $syntax } } function Invoke-NewMDHelp { ### Runs New-MarkdownHelp with the parameters we use most often. param( [Parameter(Mandatory)] [string]$Module, [Parameter(Mandatory)] [string]$OutPath ) $parameters = @{ Module = $Module OutputFolder = $OutPath AlphabeticParamsOrder = $true UseFullTypeName = $true WithModulePage = $true ExcludeDontShow = $false Encoding = [System.Text.Encoding]::UTF8 } New-MarkdownHelp @parameters } function Invoke-Pandoc { param( [Parameter(Mandatory)] [string[]]$Path, [string]$OutputPath = '.', [switch]$Recurse ) $pandocExe = 'C:\Program Files\Pandoc\pandoc.exe' Get-ChildItem $Path -Recurse:$Recurse | ForEach-Object { $outfile = Join-Path $OutputPath "$($_.BaseName).help.txt" $pandocArgs = @( '--from=gfm', '--to=plain+multiline_tables', '--columns=79', "--output=$outfile", '--quiet' ) Get-ContentWithoutHeader $_ | & $pandocExe $pandocArgs Get-ChildItem $outfile } } function Update-Headings { param( [Parameter(Mandatory)] [SupportsWildcards()] [string]$Path, [switch]$Recurse ) $headings = '## Synopsis', '## Syntax', '## Description', '## Examples', '## Parameters', '### CommonParameters', '## Inputs', '## Outputs', '## Notes', '## Related links', '## Short description', '## Long description', '## See also' Get-ChildItem $Path -Recurse:$Recurse | ForEach-Object { $_.name $md = Get-Content -Encoding utf8 -Path $_ foreach ($h in $headings) { $md = $md -replace "^$h$", $h } Set-Content -Encoding utf8 -Value $md -Path $_ -Force } } function Update-ParameterOrder { [CmdletBinding()] [OutputType([System.IO.FileInfo])] param ( [Parameter(Mandatory, Position = 0)] [SupportsWildcards()] [string[]]$Path ) $mdfiles = Get-ChildItem $path foreach ($file in $mdfiles) { $mdtext = Get-Content $file -Encoding utf8 $mdheaders = Select-String -Pattern '^#' -Path $file $unsorted = Get-ParameterMdHeaders $mdheaders if ($unsorted.Count -gt 0) { $sorted = $unsorted | Sort-Object Line $newtext = $mdtext[0..($unsorted[0].StartLine - 1)] $confirmWhatIf = @() foreach ($paramblock in $sorted) { if ( '### -Confirm', '### -WhatIf' -notcontains $paramblock.Line) { $newtext += $mdtext[$paramblock.StartLine..$paramblock.EndLine] } else { $confirmWhatIf += $paramblock } } foreach ($paramblock in $confirmWhatIf) { $newtext += $mdtext[$paramblock.StartLine..$paramblock.EndLine] } $newtext += $mdtext[($unsorted[-1].EndLine + 1)..($mdtext.Count - 1)] Set-Content -Value $newtext -Path $file.FullName -Encoding utf8 -Force $file } } } #endregion Functions.Public $ExportableFunctions = @( 'Find-ParameterWithAttribute' 'Get-ParameterInfo' 'Get-ShortDescription' 'Get-Syntax' 'Invoke-NewMDHelp' 'Invoke-Pandoc' 'Update-Headings' 'Update-ParameterOrder' ) Export-ModuleMember -Alias * -Function $ExportableFunctions |