Compile-Help (modification of post by Jeff Hillman view diff)
diff | download | new post
This script uses the text and XML PowerShell help files to generate HTML help for all PowerShell Cmdlets, PSProviders, and "about" topics. the help topics are compiled into a .chm file using HTML Help Workshop.
- # Compile-Help.ps1
- # by Jeff Hillman
- #
- # this script uses the text and XML PowerShell help files to generate HTML help
- # for all PowerShell Cmdlets, PSProviders, and "about" topics. the help topics
- # are compiled into a .chm file using HTML Help Workshop.
- #
- # Minor tweak by John Robbins to work on x64 when looking for HHC.EXE.
- param( [string] $outDirectory = ".\PSHelp", [switch] $GroupByPSSnapIn )
- function Html-Encode( [string] $value )
- {
- # System.Web.HttpUtility.HtmlEncode() doesn't quite get everything, and
- # I don't want to load the System.Web assembly just for this. I'm sure
- # I missed something here, but these are the characters I saw that needed
- # to be encoded most often
- $value = $value -replace "&(?![\w#]+;)", "&"
- $value = $value -replace "<(?!!--)", "<"
- $value = $value -replace "(?<!--)>", ">"
- $value = $value -replace "’", "'"
- $value = $value -replace '["“”]', """
- $value = $value -replace "\n", "<br />"
- $value
- }
- function Capitalize-Words( [string] $value )
- {
- $capitalizedString = ""
- # convert the string to lower case and split it into individual words. for each one,
- # capitalize the first character, and append it to the converted string
- [regex]::Split( $value.ToLower(), "\s" ) | ForEach-Object {
- $capitalizedString += ( [string]$_.Chars( 0 ) ).ToUpper() + $_.SubString( 1 ) + " "
- }
- $capitalizedString.Trim()
- }
- function Get-ParagraphedHtml( [string] $xmlText )
- {
- $value = ""
- if ( $xmlText -match "<(\w+:)?para" )
- {
- $value = ""
- $options = [System.Text.RegularExpressions.RegexOptions]::Singleline
- foreach ( $match in [regex]::Matches( $xmlText,
- "<(?:\w+:)?para[^>]*>(?<Text>.*?)</(?:\w+:)?para>", $options ) )
- {
- $value += "<p>$( Html-Encode $match.Groups[ 'Text' ].Value )</p>"
- }
- }
- else
- {
- $value = Html-Encode $xmlText
- }
- $value
- }
- function Get-SyntaxHtml( [xml] $syntaxXml )
- {
- $syntaxHtml = ""
- # generate the HTML for each form of the Cmdlet syntax
- foreach ( $syntaxItem in $syntaxXml.syntax.syntaxItem )
- {
- if ( $syntaxHtml -ne "" )
- {
- $syntaxHtml += "<br /><br />`n"
- }
- $syntaxHtml += " $( $syntaxItem.name.get_InnerText().Trim() ) "
- if ( $syntaxItem.parameter )
- {
- foreach ( $parameter in $syntaxItem.parameter )
- {
- $required = [bool]::Parse( $parameter.required )
- $syntaxHtml += "<nobr>[-$( $parameter.name.get_InnerText().Trim() )"
- if ( $required )
- {
- $syntaxHtml += "]"
- }
- if ( $parameter.parameterValue )
- {
- $syntaxHtml +=
- " <$( $parameter.parameterValue.get_InnerText().Trim() )>"
- }
- if ( !$required )
- {
- $syntaxHtml += "]"
- }
- $syntaxHtml += "</nobr> "
- }
- }
- $syntaxHtml += " <nobr>[<CommonParameters>]</nobr>"
- }
- $syntaxHtml.Trim()
- }
- function Get-ParameterHtml( [xml] $parameterXml )
- {
- $parameterHtml = ""
- # generate HTML for each parameter
- foreach ( $parameter in $parameterXml.parameters.parameter )
- {
- if ( $parameterHtml -ne "" )
- {
- $parameterHtml += " <br /><br />`n"
- }
- $parameterHtml +=
- " <nobr><span class=`"boldtext`">-$( $parameter.name.get_InnerText().Trim() )"
- if ( $parameter.parameterValue )
- {
- $parameterHtml += " <$( $parameter.parameterValue.get_InnerText().Trim() )>"
- }
- $parameterHtml += "</span></nobr>`n"
- $parameterHtml += @"
- <br />
- <div id="contenttext">
- $( Get-ParagraphedHtml $parameter.description.get_InnerXml().Trim() )
- "@
- if ( $parameter.possibleValues )
- {
- foreach ( $possibleValue in $parameter.possibleValues.possibleValue )
- {
- $parameterHtml += @"
- $( $possibleValue.value.Trim() )<br />
- "@
- if ( $possibleValue.description.get_InnerText().Trim() -ne "" )
- {
- $parameterHtml += @"
- <div id="contenttext">
- $( Get-ParagraphedHtml $possibleValue.description.get_InnerXml().Trim() )
- </div>
- "@
- }
- }
- }
- $parameterHtml += @"
- <br />
- </div>
- <table class="parametertable">
- <tr>
- <td>Required</td>
- <td>$( $parameter.required )</td>
- </tr>
- <tr>
- <td>Position</td>
- <td>$( $parameter.position )</td>
- </tr>
- <tr>
- <td>Accepts pipeline input</td>
- <td>$( $parameter.pipelineInput )</td>
- </tr>
- <tr>
- <td>Accepts wildcard characters</td>
- <td>$( $parameter.globbing )</td>
- </tr>
- "@
- if ( $parameter.defaultValue )
- {
- if( $parameter.defaultValue.get_InnerText().Trim() -ne "" )
- {
- $parameterHtml += @"
- <tr>
- <td>Default Value</td>
- <td>$( $parameter.defaultValue.get_InnerText().Trim() )</td>
- </tr>
- "@
- }
- }
- $parameterHtml += @"
- </table>
- "@
- }
- if ( $parameterHtml -ne "" )
- {
- $parameterHtml += " <br /><br />`n"
- }
- $parameterHtml += @"
- <nobr><span class="boldtext"><CommonParameters></span></nobr>
- <br />
- <div id="contenttext">
- <p>
- For more information about common parameters, type "Get-Help about_commonparameters".
- </p>
- </div>
- "@
- $parameterHtml.Trim()
- }
- function Get-InputHtml( [xml] $inputXml )
- {
- $inputHtml = ""
- $inputCount = 0
- # generate HTML for each input type
- foreach ( $inputType in $inputXml.inputTypes.inputType )
- {
- if ( $inputHtml -ne "" )
- {
- $inputHtml += " <br /><br />`n"
- }
- if ( $inputType.type.name.get_InnerText().Trim() -ne "" -or
- $inputType.type.description.get_InnerText().Trim() -ne "" )
- {
- $inputHtml += " $( $inputType.type.name.get_InnerText().Trim() )`n"
- $inputHtml += @"
- <div id="contenttext">
- $( Get-ParagraphedHtml $inputType.type.description.get_InnerXml().Trim() )
- </div>
- "@
- $inputCount++
- }
- }
- $inputHtml.Trim()
- $inputCount
- }
- function Get-ReturnHtml( [xml] $returnXml )
- {
- $returnHtml = ""
- $returnCount = 0
- # generate HTML for each return value
- foreach ( $returnValue in $returnXml.returnValues.returnValue )
- {
- if ( $returnHtml -ne "" )
- {
- $returnHtml += " <br /><br />`n"
- }
- if ( $returnValue.type.name.get_InnerText().Trim() -ne "" -or
- $returnValue.type.description.get_InnerText().Trim() -ne "" )
- {
- $returnHtml += " $( $returnValue.type.name.get_InnerText().Trim() )`n"
- $returnHtml += @"
- <div id="contenttext">
- $( Get-ParagraphedHtml $returnValue.type.description.get_InnerXml().Trim() )
- </div>
- "@
- $returnCount++
- }
- }
- $returnHtml.Trim()
- $returnCount
- }
- function Get-ExampleHtml( [xml] $exampleXml )
- {
- $exampleHtml = ""
- $exampleTotalCount = 0
- $exampleCount = 0
- foreach ( $example in $exampleXml.examples.example )
- {
- $exampleTotalCount++
- }
- # generate HTML for each example
- foreach ( $example in $exampleXml.examples.example )
- {
- if ( $example.code -and $example.code.get_InnerText().Trim() -ne "" )
- {
- if ( $exampleHtml -ne "" )
- {
- $exampleHtml += " <br />`n"
- }
- if ( $exampleTotalCount -gt 1 )
- {
- $exampleHtml +=
- " <nobr><span class=`"boldtext`">Example $( $exampleCount + 1 )</span></nobr>`n"
- }
- $exampleCodeHtml = "$( Html-Encode $example.introduction.get_InnerText().Trim() )" +
- "$( Html-Encode $example.code.get_InnerText().Trim() )"
- $exampleHtml += " <div class=`"syntaxregion`">$exampleCodeHtml</div>`n"
- $foundFirstPara = $false
- foreach ( $para in $example.remarks.para )
- {
- if ( $para.get_InnerText().Trim() -ne "" )
- {
- # the first para is generally the description of the example.
- # other para tags usually contain sample output
- if ( !$foundFirstPara )
- {
- $exampleHtml += @"
- <div id="contenttext">
- <p>
- $( Html-Encode $para.get_InnerText().Trim() )
- </p>
- </div>
- "@
- $foundFirstPara = $true
- }
- else
- {
- $exampleHtml += @"
- <pre class="syntaxregion">$( $( ( Html-Encode $para.get_InnerText().Trim() ) -replace "<br />", "`n" ) )</pre>
- "@
- }
- }
- }
- $exampleCount++
- }
- }
- $exampleHtml.Trim()
- $exampleCount
- }
- function Get-TaskExampleHtml( [xml] $exampleXml )
- {
- $exampleHtml = ""
- $exampleCount = 0
- $exampleTotalCount = 0
- foreach ( $example in $exampleXml.examples.example )
- {
- $exampleTotalCount++
- }
- # generate HTML for each example
- foreach ( $example in $exampleXml.examples.example )
- {
- if ( $exampleHtml -ne "" )
- {
- $exampleHtml += " <br />`n"
- }
- if ( $exampleTotalCount -gt 1 )
- {
- $exampleHtml += " <nobr><span class=`"boldtext`">Example $( $exampleCount + 1 )</span></nobr>`n"
- }
- $exampleHtml += " <div>$( Get-ParagraphedHtml $example.introduction.get_InnerXml().Trim() )</div>`n"
- $exampleCodeHtml = ( Html-Encode $example.code.Trim() ) -replace "<br />", "`n"
- $exampleHtml += " <pre class=`"syntaxregion`">$exampleCodeHtml</pre>"
- $exampleHtml += " <div>$( Get-ParagraphedHtml $example.remarks.get_InnerXml().Trim() )</div>`n"
- $exampleCount++
- }
- $exampleHtml.Trim()
- }
- function Get-LinkHtml( [xml] $linkXml )
- {
- $linkHtml = ""
- $linkCount = 0
- # generate HTML for each related link
- foreach ( $navigationLink in $linkXml.relatedLinks.navigationLink )
- {
- if ( $navigationLink.linkText -and `
- ( $helpHash.Keys | Foreach-Object { $_.ToUpper() } ) -contains $navigationLink.linkText.Trim().ToUpper() )
- {
- $linkHtml += " $( $navigationLink.linkText.Trim() )<br />`n"
- $linkCount++
- }
- }
- $linkHtml.Trim()
- $linkCount
- }
- function Get-TaskHtml( [xml] $taskXml )
- {
- $taskHtml = ""
- $taskCount = 0
- foreach ( $task in $taskXml.tasks.task )
- {
- if ( $taskHtml -ne "" )
- {
- $taskHtml += " <br />`n"
- }
- $taskHtml += " <nobr><span class=`"boldtext`">Task:</span> $( $task.title.Trim() )</nobr>`n"
- $taskDescriptionHtml = ( Get-ParagraphedHtml $task.description.get_InnerXml().Trim() )
- $taskHtml += " <div id=`"contenttext`">$taskDescriptionHtml</div>`n"
- # add the example sections
- if ( $task.examples )
- {
- $taskHtml += @"
- <div id="contenttext">
- <p>
- $( Get-TaskExampleHtml ( [xml]$task.examples.get_OuterXml() ) )
- </p>
- </div>
- "@
- }
- $taskCount++
- }
- $taskHtml.Trim()
- $taskCount
- }
- function Get-DynamicParameterHtml( [xml] $dynamicParameterXml )
- {
- $dynamicParameterHtml = ""
- # generate HTML for each dynamic parameter
- foreach ( $dynamicParameter in $dynamicParameterXml.dynamicparameters.dynamicparameter )
- {
- $dynamicParameterHtml += " <nobr><span class=`"boldtext`">-$( $dynamicParameter.name.Trim() )"
- if ( $dynamicParameter.type )
- {
- $dynamicParameterHtml += " <$( $dynamicParameter.type.name.Trim() )>"
- }
- $dynamicParameterHtml += "</span></nobr>`n"
- $dynamicParameterHtml += @"
- <br />
- <div id="contenttext">
- <p>
- $( Html-Encode $dynamicParameter.description.Trim() )
- </p>
- "@
- if ( $dynamicParameter.possiblevalues )
- {
- foreach ( $possibleValue in $dynamicParameter.possiblevalues.possiblevalue )
- {
- $dynamicParameterHtml += @"
- <div id="contenttext">
- <span class=`"boldtext`">$( $possibleValue.value )</span>
- <div id="contenttext">
- $( Get-ParagraphedHtml $possibleValue.description.get_InnerXml().Trim() )
- </div>
- </div>
- "@
- }
- }
- $dynamicParameterHtml += @"
- <br />
- <span class=`"boldtext`">Cmdlets Supported</span>
- <div id="contenttext">
- <p>
- $( Html-Encode $dynamicParameter.cmdletsupported.Trim() )
- </p>
- </div>
- </div>
- <br />
- "@
- }
- $dynamicParameterHtml.Trim()
- }
- function Write-AboutTopic( [string] $topicName, [string] $topicPath )
- {
- # just dump the contents of the about topic exactly as it is. the only changes needed
- # are to encode the special HTML characters and add topic links
- $topicHtml = @"
- <html>
- <head>
- <link rel="stylesheet" type="text/css" href="powershell.css" />
- <title>About $( Capitalize-Words ( $topicName -replace "(about)?_", " " ).Trim() )</title>
- </head>
- <body>
- <div id="topicheading">
- <div id="topictitle">PowerShell Help</div>
- About $( Capitalize-Words ( $topicName -replace "(about)?_", " " ).Trim() )
- </div>
- <pre>
- $( ( Html-Encode ( [string]::Join( [Environment]::NewLine, ( Get-Content -Path $topicPath ) ) ) ) -replace "<br />" )
- </pre>
- </body>
- </html>
- "@
- $topicHtml = Add-Links $topicName $topicHtml
- Out-File -FilePath "$outDirectory\Topics\$topicName.html" -Encoding Ascii -Input $topicHtml
- }
- function Write-ProviderTopic( [string] $providerFullName, [xml] $providerXml )
- {
- $providerName = $providerXml.providerhelp.Name.Trim()
- $topicHtml = @"
- <html>
- <head>
- <link rel="stylesheet" type="text/css" href="powershell.css" />
- <title>$providerName Help</title>
- </head>
- <body>
- <div id="topicheading">
- <div id="topictitle">PowerShell Help</div>
- $providerName Provider
- <div style="text-align: right; padding-right: 3px;">
- $( $providerFullName -replace "^\w+\." )
- </div>
- </div>
- <div class="categorytitle">Drives</div>
- <div id="contenttext">
- $( Get-ParagraphedHtml $providerXml.providerhelp.drives.get_InnerXml().Trim() )
- </div>
- <div class="categorytitle">Synopsis</div>
- <div id="contenttext">
- <p>$( Html-Encode $providerXml.providerhelp.synopsis.Trim() )</p>
- </div>
- "@
- $topicHtml += @"
- <div class="categorytitle">Description</div>
- <div id="contenttext">
- $( Get-ParagraphedHtml $providerXml.providerhelp.detaileddescription.get_InnerXml().Trim() )
- </div>
- "@
- if ( $providerXml.providerhelp.capabilities.get_InnerText().Trim() -ne "" )
- {
- $topicHtml += @"
- <div class="categorytitle">Capabilities</div>
- <div id="contenttext">
- $( Get-ParagraphedHtml $providerXml.providerhelp.capabilities.get_InnerXml().Trim() )
- </div>
- "@
- }
- $taskHtml, $taskCount = Get-TaskHtml( $providerXml.providerhelp.tasks.get_OuterXml() )
- if ( $taskCount -gt 0 )
- {
- $topicHtml += @"
- <div class="categorytitle">Task$( if ( $taskCount -gt 1 ) { "s" } )</div>
- <div id="contenttext">
- $taskHtml
- </div>
- "@
- }
- if ( $providerXml.providerhelp.dynamicparameters )
- {
- $topicHtml += @"
- <div class="categorytitle">Dynamic Parameters</div>
- <div id="contenttext">
- $( Get-DynamicParameterHtml( $providerXml.providerhelp.dynamicparameters.get_OuterXml() ) )
- </div>
- "@
- }
- if ( $providerXml.providerhelp.notes.Trim() -ne "" )
- {
- $topicHtml += @"
- <div class="categorytitle">Notes</div>
- <div id="contenttext">
- <p>$( Html-Encode $providerXml.providerhelp.notes.Trim() )</p>
- </div>
- "@
- }
- $topicHtml += @"
- <div class="categorytitle">Related Links</div>
- <div id="contenttext">
- <p>$( Html-Encode $providerXml.providerhelp.relatedlinks.Trim() )</p>
- </div>
- <br />
- </body>
- </html>
- "@
- $topicHtml = Add-Links $providerName $topicHtml
- Out-File -FilePath "$outDirectory\Topics\$providerFullName.html" -Encoding Ascii -Input $topicHtml
- }
- function Write-CmdletTopic( [string] $cmdletFullName, [xml] $cmdletXml )
- {
- $cmdletName = $cmdletXml.command.details.name.Trim()
- # add the heading, syntax section, and description
- $topicHtml = @"
- <html>
- <head>
- <link rel="stylesheet" type="text/css" href="powershell.css" />
- <title>$cmdletName Help</title>
- </head>
- <body>
- <div id="topicheading">
- <div id="topictitle">PowerShell Help</div>
- $cmdletName Cmdlet
- <div style="text-align: right; padding-right: 3px;">
- $( $cmdletFullName -replace "^\w+-\w+\." )
- </div>
- </div>
- <div class="categorytitle">Synopsis</div>
- <div id="contenttext">
- $( Get-ParagraphedHtml $cmdletXml.command.details.description.get_InnerXml().Trim() )
- </div>
- <div class="categorytitle">Syntax</div>
- <div id="contenttext">
- <div class="syntaxregion">$( Get-SyntaxHtml ( [xml]$cmdletXml.command.syntax.get_OuterXml() ) )</div>
- </div>
- <div class="categorytitle">Description</div>
- <div id="contenttext">
- $( Get-ParagraphedHtml $cmdletXml.command.description.get_InnerXml().Trim() )
- </div>
- "@
- # add the parameters section
- if ( $cmdletXml.command.parameters )
- {
- $topicHtml += @"
- <div class="categorytitle">Parameters</div>
- <div id="contenttext">
- <p>
- $( Get-ParameterHtml ( [xml]$cmdletXml.command.parameters.get_OuterXml() ) )
- </p>
- </div>
- "@
- }
- else
- {
- $topicHtml += @"
- <div class="categorytitle">Parameters</div>
- <div id="contenttext">
- <p>
- <nobr><span class="boldtext"><CommonParameters></span></nobr><br />
- <div id="contenttext">
- <p>
- For more information about common parameters, type "Get-Help about_commonparameters".
- </p>
- </div>
- </p>
- </div>
- "@
- }
- # add the input types section
- if ( $cmdletXml.command.inputTypes )
- {
- $inputHtml, $inputCount = Get-InputHtml ( [xml]$cmdletXml.command.inputTypes.get_OuterXml() )
- if ( $inputCount -gt 0 )
- {
- $topicHtml += @"
- <div class="categorytitle">Input Type$( if ( $inputCount -gt 1 ) { "s" } )</div>
- <div id="contenttext">
- $inputHtml
- </div>
- "@
- }
- }
- # add the return values section
- if ( $cmdletXml.command.returnValue )
- {
- $returnHtml, $returnCount = Get-ReturnHtml ( [xml]$cmdletXml.command.returnValues.get_OuterXml() )
- if ( $returnCount -gt 0 )
- {
- $topicHtml += @"
- <div class="categorytitle">Return Value$( if ( $returnCount -gt 1 ) { "s" } )</div>
- <div id="contenttext">
- $returnHtml
- </div>
- "@
- }
- }
- # add the notes section
- if ( $cmdletXml.command.alertSet )
- {
- if ( $cmdletXml.command.alertSet.get_InnerText().Trim() -ne "" )
- {
- $topicHtml += @"
- <div class="categorytitle">Notes</div>
- <div id="contenttext">
- $( Get-ParagraphedHtml $cmdletXml.command.alertSet.get_InnerXml().Trim() )
- </div>
- "@
- }
- }
- # add the example section
- if ( $cmdletXml.command.examples )
- {
- $exampleHtml, $exampleCount = Get-ExampleHtml ( [xml]$cmdletXml.command.examples.get_OuterXml() )
- if ( $exampleCount -gt 0 )
- {
- $topicHtml += @"
- <div class="categorytitle">Example$( if ( $exampleCount -gt 1 ) { "s" } )</div>
- <div id="contenttext">
- <p>
- $exampleHtml
- </p>
- </div>
- "@
- }
- }
- # add the related links section
- if ( $cmdletXml.command.relatedLinks )
- {
- $linkHtml, $linkCount = Get-LinkHtml ( [xml]$cmdletXml.command.relatedLinks.get_OuterXml() )
- if ( $linkCount -gt 0 )
- {
- $topicHtml += @"
- <div class="categorytitle">Related Link$( if ( $linkCount -gt 1 ) { "s" } )</div>
- <div id="contenttext">
- <p>
- $linkHtml
- </p>
- </div>
- <br />
- "@
- }
- else
- {
- $topicHtml += " <br />`n"
- }
- }
- else
- {
- $topicHtml += " <br />`n"
- }
- $topicHtml += @"
- </body>
- </html>
- "@
- $topicHtml = Add-Links $cmdletName $topicHtml
- Out-File -FilePath "$outDirectory\Topics\$cmdletFullName.html" -Encoding Ascii -Input $topicHtml
- }
- function Add-Links( [string] $topicName, [string] $topicHtml )
- {
- # we only want to add links for Cmdlets and about topics
- $helpHash.Keys | Where-Object { $_ -match "(^\w+-\w+|^about_)" } | Foreach-Object {
- $searchText = $_
- # keys representing Cmdlets are formatted like this:
- # <Cmdlet Name>.<PSProvider name>
- if ( $_ -match "^\w+-\w+" )
- {
- # we only want to search for the Cmdlet name
- $searchText = $matches[ 0 ]
- }
- # if the search text isn't the topic being processed
- if ( $searchText -ne $topicName )
- {
- $topicHtml = $topicHtml -replace "\b($searchText)\b", "<a href=`"Topics\$_.html`"><nobr>`$1</nobr></a>"
- }
- }
- $topicHtml
- }
- # file dumping functions
- function Write-Hhp
- {
- # write the contents of the Html Help Project file
- Out-File -FilePath "$outDirectory\powershell.hhp" -Encoding Ascii -Input @"
- [OPTIONS]
- Binary TOC=Yes
- Compatibility=1.1 or later
- Compiled file=PowerShell.chm
- Contents file=powershell.hhc
- Default topic=Topics/default.html
- Full-text search=Yes
- Language=0x409 English (United States)
- Title=PowerShell Help
- [INFOTYPES]
- "@
- }
- function Write-DefaultPage
- {