Click here to monitor SSC
  • Av rating:
  • Total votes: 10
  • Total comments: 3
Michael Sorens

How To Document Your PowerShell Library

25 June 2013

PowerShell provides comment-based  help for functions and scripts with Get-Help, but when you want to document modules or generate a complete indexed API in HTML format, just as you can with Sandcastle for .NET or javadoc for Java, then stronger magic is required. Michael Sorens shows you how it is done, with source code.

Contents

I am passionate about documentation. When you write library code—code for you or others to consume in other projects—it is almost useless without proper documentation. Most modern languages provide some level of support to assist in creating your documentation, where you instrument your code with documentation comments (doc-comments for short) that may then be automatically extracted and formatted to generate some documentation. But documenting individual methods or classes or files is not the same as documenting an API. Java has javadoc and C# has Sandcastle, both of which generate a complete, cross-linked API documentation set. On the other hand, languages like Perl with perldoc and PowerShell with Get-Help, generate only isolated module documentation, which I found rather unsatisfactory. So over the years I created an API-level documentation generator for Perl, later for T-SQL, and most recently for PowerShell. (Note that the Perl version Pod2HtmlTree is Perl-specific while the T-SQL one uses my generic XML conversion tool XmlTransform configured to handle SQL documentation, described in Add Custom XML Documentation Capability To Your SQL Code.) The PowerShell generator (written in PowerShell, of course!) is a function in my DocTreeGenerator module called Convert-HelpToHtmlTree. (You can find my entire API bookshelf here and download libraries here.)

This article discusses how to generate a complete API for a PowerShell library. Once you have instrumented your modules with doc-comments to satisfy Get-Help, you need surprisingly little extra work to supply to Convert-HelpToHtmlTree. But let’s start with the assumption, hardly realistic, I’m sure, that you do not have any doc-comments in your code and see what you can already do.

Test Driving the Generator

To help you visualize what Convert-HelpToHtmlTree does, consider this simple example. Assume you have just one module, MathStuff.psm1 containing two functions:

function Get-Max($itemList)
{
   ($itemList | measure-object -Maximum).Maximum
}

function Get-Min($itemList)
{
    ($itemList | measure-object -Minimum).Minimum
}

Export-ModuleMember Get-Max
Export-ModuleMember Get-Min

Notice that there is as yet no documentation-comments in the code to document these functions. Yet PowerShell’s standard help cmdlet, Get-Help, can still give you some information:

[373]: Get-Help Get-Max -full

NAME
  Get-Max

SYNTAX
  Get-Max [[-itemList] <Object>]

PARAMETERS
  -itemList <Object>

     Required?                   false
     Position?                   0
     Accept pipeline input?      false
     Parameter set name          (All)
     Aliases                     None
     Dynamic?                    false

INPUTS
  None

OUTPUTS
  System.Object

ALIASES
  None

REMARKS
  None
Similarly, Convert-HelpToHtmlTree can give you all the same basic information about the individual method—but it also builds an entire web framework around the namespace and module that contains it. Here is a snapshot of the input directory tree. By PowerShell convention, store your user modules under WindowsPowerShell\Modules in your user directory. Under that path follows the namespace for this example, DemoModules. There are two module directories, MathStuff and StringUtilities; the former defines the two functions shown earlier, while the latter is empty.

[356]: ls .\DemoModules -Recurse | % { $_.FullName }
WindowsPowerShell\Modules\DemoModules\MathStuff
WindowsPowerShell\Modules\DemoModules\StringUtilities
WindowsPowerShell\Modules\DemoModules\MathStuff\MathStuff.psm1

Now execute Convert-HelpToHtmlTree with minimal arguments: the namespace to document and where to put it. Convert-HelpToHtmlTree needs more infrastructure to give you more useful output; it lets you know what you need to provide and where it goes:

Convert-HelpToHtmlTree -Namespaces DemoModules -TargetDir MyApi
Target dir: MyApi
Namespace: DemoModules
   Module: MathStuff
      Command: Get-Max
      Command: Get-Min
** Missing summary (from DemoModules\MathStuff\module_overview.html)
** Missing description (from DemoModules\MathStuff\MathStuff.psd1)
   Module: StringUtilities
** No objects found
Generating home page...
** Missing summary (from DemoModules\namespace_overview.html)
Generating contents page...
Done: 1 namespace(s), 2 module(s), 2 function(s), 5 file(s) processed.

Here is the generated output tree:

[353]: ls .\MyApi -Recurse | % { $_.FullName }
MyApi\DemoModules
MyApi\contents.html
MyApi\index.html
MyApi\DemoModules\MathStuff
MyApi\DemoModules\MathStuff\Get-Max.html
MyApi\DemoModules\MathStuff\Get-Min.html
MyApi\DemoModules\MathStuff\index.html

As is typical for a website, index.html is the entry point, displaying the namespace contents: a list of all contained modules with a brief description and a link to each module’s documentation (see Figure 1). In this case there is just one module, MathStuff, because StringUtilities mentioned above was empty.

Figure 1 Sample namespaces page: itemizes the modules for each namespace you have specified to document. (This is your top-level, home, or entry page.)

If you traverse the link to MathStuff, you get the module contents: a list of all contained functions, and filters with a brief description and a link to each one’s documentation (see Figure 2). Notice in each of these figures, by the way, that you have the same warning messages inline as you did at the command prompt; here you get to see them in context. Figure 2 shows one of the few instances where Convert-HelpToHtmlTree fills in a slot where information is not available instead of giving a warning—in the table of methods it lists the syntax of a method in the absence of a textual description.

Figure 2 Sample module page: itemizes the functions, filters, and aliases for the current module.

If you traverse the link to an individual function, you get the spruced-up output of Get-Help for that function(see Figure 3). This is exactly the same output that you get from Get-Help, but with these enhancements:

  • It adds section headings to each of the main sections.
  • It outputs most text in a proportional font.
  • It outputs portions of text you designate (via leading white space on a line) in a fixed-width font.
  • It preserves your line breaks, so if you want text to flow and wrap automatically at the window edge you must put it all in a single paragraph in your source file.
  • It highlights the initial code sample in each example. (Note that, just like Get-Help, ONLY the first line immediately following the .EXAMPLE directive is treated as the code sample, so resist the urge to put line breaks in your sample!)
  • Items designated as links are turned into active hyperlinks; these may be in any of seven different formats explained later.

Figure 3 Sample function page: provides help text for the current function or filter. This is a stylized version of the output of Get-Help.

Finally, in the root folder of the output tree is contents.html, a table of contents, or index, if you will of everything (Figure 4).

Figure 4 Sample master index/contents page: an index of all documented objects; this page is accessible via a hyperlink on the top-level namespaces page.

Controlling Output with the Page Template

Figures 1 through 4 show the four page-types that Convert-HelpToHtmlTree can generate. All four of these are derived from a single HTML template file. Take a look at the default template (psdoc_template.html) and you will find it sprinkled with place-holders for different properties. In the previous figures you will see some remnants where some place-holders have not been supplied, e.g. the empty copyright and revision dates at the bottom of each page.

Here is the content of the <body> element of the template (the <head> element has been omitted for brevity). I have distinguished the global properties, module properties, and conditional properties with different colors for clarity. The following sections describe each of these three property types in detail.

<body>
 <p class="subtitle">{subtitle}{breadcrumbs}</p>
  <h1>{title}</h1>
  {preamble}
  {ifdef home}
  <div>
  This is the home page.
  See also: <a href="contents.html">Alphabetical Index</a>
  </div>
  {endif home}
  {ifdef contents}
  <strong>An alphabetical index to all namespaces, modules, and functions.</strong>
  <a href="index.html">PowerShell API Home</a>
  {endif contents}
  {body}
  {postscript}
  {ifdef module}
  This text appears only on the module index pages...
  You may interpolate any standard module properties here, e.g.
  <ul>
  <li>GUID = {module.guid}</li>
  <li>Version = {module.version}</li>
  </ul>
  {endif module}
  {ifdef function}
  This text appears only on the function pages...
  You may interpolate any standard properties of the parent module here, e.g.
  <br>
  Version is {module.version}
  {endif function}
  <p>Copyright &copy; {copyright}, Revised {revdate}</p>
</body>

Global Properties

Global properties are distinguished by having a single word in brackets for the place holder.

Place Holder Source Applies to PageType
{title} current namespace + DocTitle parameter master, contents
{subtitle} current namespace + DocTitle parameter module, function
{breadcrumbs} generated relative paths module, function
{preamble} manifest.Description property + module_overview.html <body> contents module
{body} contents of Get-Help function
LIST(function name + Get-Help.Synopsis property) module
LIST(Namespace_overview.html <body> contents
   + LIST(module name + manifest.Description property))
master
generated index contents
{postscript}   -not currently used-
{copyright} Copyright parameter -ALL-
{revdate} RevisionDate parameter -ALL-

Module Properties

Module-specific properties are distinguished by the form {module.property} where property may be any of the standard properties of a module. You can see what these are with this command:

Get-Module | Get-Member -MemberType Properties

As an example, Figure 2 shows that the template uses the module.guid and module.version properties used.

Conditional Properties

Finally, you will also see conditional properties in the template of the form:

{ifdef pagetype}
. . .
{endif pagetype}

...where pagetype may be any of the four types of pages created:

  • the single master page (pagetype=master),
  • the single master index/contents page (pagetype=contents),
  • the module index pages, one per module (pagetype=module), and
  • the function pages, one per exported function (pagetype=function).

The content of these conditional sections (which may be any valid HTML) is included only on the pages of the corresponding type, while the other conditional sections are suppressed. Note that the module-specific place-holders discussed earlier (e.g. {module.property}) may be used in both module pages and function pages, but not in the master or contents page.

Specifying Links

Unlike the MSDN pages for the standard PowerShell library, output generated here creates live links in your references (.LINK) documentation section. There are seven input variations permitted:

Item MSDN-defined (built-in) cmdlet
Sample Input Get-ChildItem
Sample Output <a href='http://technet.microsoft.com/en-us/library/dd347686.aspx'>Get-ChildItem</a>


Item MSDN-defined (built-in) topic
Sample Input about_Aliases
Sample Output <a href='http://technet.microsoft.com/en-us/library/dd347645.aspx'>about_Aliases</a>


Item Custom function in the same module
Sample Input New-CustomFunction
Sample Output <a href='New-CustomFunction.html'>New-CustomFunction</a>


Item Custom function in a different local module
Sample Input New-FunctionInOtherModule
Sample Output <a href='../../DemoModules/MathStuff/New-FunctionInOtherModule.html'>New-FunctionInOtherModule</a>


Item Plain text (for cases where you have no web resource to link to)
Sample Input Butterflies and Dinosaurs--the missing link?
Sample Output Butterflies and Dinosaurs--the missing link?


Item Explicit link with a label
Sample Input [other important stuff] (http://foobar.com)
Sample Output <a href='http://foobar.com'>other important stuff</a>


Item Explicit link without a label
Sample Input http://alpha/beta/
Sample Output <a href='http://alpha/beta/'>http://alpha/beta/</a></

The MSDN references are automatically constructed by referencing two fixed MSDN reference pages, one for cmdlets and one for topics. If those fixed references ever change URLs, that will break the generator; you will need to update those URLs in the internal Get-CmdletDocLinks function to mend it. The input format for these was designed keeping in mind that while Convert-HelpToHtmlTree converts these to links, Get-Help does not—it will display the original, raw text.

Other Structural Considerations

Besides the standard documentation comments that Get-Help would use to display your modules, Convert-HelpToHtmlTree needs some additional doc-comments to generate a cohesive API for you.

  1. Each module (x.psm1) must have an associated manifest (x.psd1) in the same directory and the manifest must include a Description property.
  2. Each module must have an associated overview (module_overview.html) in the same directory. This is a standard HTML file. The contents of the <body> element are extracted verbatim as the introductory text of the index.html page for each module.
  3. Each namespace must also include an associated overview (namespace_overview.html). This is a standard HTML file. The contents of the <body> element are extracted verbatim as the introductory text of each namespace in the master index.html page.

By reviewing the error messages in the very beginning of the article as well as the table of global properties early on, you should now see how these pieces fit together.

Note that I use the term namespace here informally because (as of v3) PowerShell does not currently have the notion of namespaces. Convert-HelpToHtmlTree, however, requires you to structure your modules grouped in namespaces. Thus, if you have a module MyStuff.psm1, normal PowerShell conventions require you to store this in a path like this:

...\WindowsPowerShell\Modules\MyStuff\MyStuff.psm1

...but Convert-HelpToHtmlTree requires you to include one more level for namespace, so the module must be stored in a path like this:

...\WindowsPowerShell\Modules\MyNamespace\MyStuff\MyStuff.psm1

This allows you to organize your modules into more than one logical group if desired. In my own PowerShell library, for example, I have FileTools, SqlTools, and SvnTools modules (among others) all under the CleanCode namespace. But you may, however, include multiple namespaces. Here's a sample input tree illustrating this:

WindowsPowerShell\Modules
+---namespace1
  +---namespace_overview.html
  +---moduleA
     +---module_overview.html
     +---moduleA.psm1
     +---moduleA.psd1
     +---moduleB
     +---module_overview.html
     +---moduleB.psm1
     +---moduleB.psd1
  etc...
+---namespace2
  +---namespace_overview.html
  +---moduleX
     +---module_overview.html
     +---moduleX.psm1
     +---moduleX.psd1
  +---moduleY
     +---module_overview.html
     +---moduleY.psm1
     +---moduleY.psd1
  etc...

The output structure mirrors the input structure; the above input might generate the output tree shown below. There is a single master index page documenting all namespaces.

$TargetDir
+---contents.html
+---index.html
+---namespace1
  +---moduleA
     +---index.html
     +---Function1.html
     +---Function2.html
     +---Function3.html
     +---Function4.html
     etc...
  +---moduleB
     +---index.html
     +---Function1.html
     +---Function2.html
     etc...
+---namespace2
  +---moduleX
     +---index.html
     +---Function1.html
     etc...
  +---moduleY
     +---index.html
     +---Function1.html
     +---Function2.html
     etc...
etc...

Other Stylistic Considerations

You have seen how to customize the generated output with the template file and how to format items in your .LINKS section. There are just a few more considerations to be mindful of as you write your documentation comments for your modules, touched on earlier:

  • Body text, by default, is output with a proportional font (your standard browser font) and flows/wraps automatically at the window edge based on your paragraph boundaries—that is, where you have hard (explicit) carriage returns.
  • By inference, if you want to output a list of items, you do not need to do anything special. Just type in the list with carriage returns and the output will mirror the input. It will still be in the standard browser font.
  • If you want to include a code sample in a fixed-width font, or even if you just want to output a list of items in a fixed-width font, simply have leading whitespace on each line.
  • The first line—and only the first line—following an .EXAMPLE section is special to Get-Help: it is considered your code sample. Thus, you really want to keep your code sample all on one line, no matter how long and ugly it gets. Convert-HelpToHtmlTree also considers the same first line a code sample and highlights it. So resist the urge to put line breaks in your sample!

Now Just Add Water…

With the preliminaries out of the way, you are ready to put the generator to work. There are just a few parameters to specify on the command line. Here is essentially the script I use to generate the API for my own CleanCode PowerShell library, where you will find DocTreeGenerator itself:

Import-Module CleanCode\DocTreeGenerator

$scriptDir = Split-Path $script:MyInvocation.MyCommand.Path

Convert-HelpToHtmlTree `
   -Namespaces CleanCode `
   -TemplateName (Join-Path $scriptDir psdoc_template.html) `
   -DocTitle 'PowerShell Libraries v1.2.01 API' `
   -Copyright '2012' `
   -RevisionDate '2012.12.10' `
   -TargetDir c:\usr\tmp\psdoc_tmp

This short script uses a custom template file located in the same directory as the above script. (If you omit specifying a template it uses the default template supplied. That template includes the CSS rules embedded in the template just to keep it all to a single file; in practice you should generally move the CSS rules to a separate file.)

Figure 5 shows you the API home page for my PowerShell libraries.

Figure 5 Actual API home page for my CleanCode library of PowerShell functions.

And Figure 6 shows a portion of its associated master index. From either of those you can browse through the remainder of the documentation set.

Figure 6 Portion of the index (or table of contents) of the CleanCode library, indexing namespaces, modules, functions, and filters.

Figure 7 Side by side comparison of the stylized web page output of Convert-HelpToHtmlTree (left) and Get-Help (right). Note the section headers standout for easy scanning. The description shows both normal, wrapped text as well as an example of preformatted text.

Conclusion

Some IDEs provide great support for adding doc-comments. Visual Studio, for example, with the GhostDoc add-on almost writes the doc-comments for you. Alas, PowerShell does not yet have such a “ghost writer” available. To do it yourself, start with about_Comment_Based_Help (which you can also access from the PowerShell prompt by feeding that to Get-Help!). Scroll down to the Syntax for Comment-Based Help in Functions section. Note that the page also talks about adding help for the script itself; that applies only to script files (ps1 files); it does not apply to modules (psm1 files). What you will see here is that you must add a special comment section that looks like this for each function (those that may appear more than once are shown repeated here):

<#
.SYNOPSIS
.DESCRIPTION
.PARAMETER x
.PARAMETER y
.INPUTS
.OUTPUTS
.EXAMPLE
.EXAMPLE
.EXAMPLE
.EXAMPLE
.LINK
.LINK
.NOTES
#>

After each keyword directive, you can have as much freeform text as you need. You will find all the keywords described in the subsequent section of about_Comment_Based_Help entitled Comment-Based Help Keywords.

Now go forth and document!

Michael Sorens

Author profile:

Michael Sorens is passionate about software to be more productive, evidenced by his open source libraries in several languages (see his API bookshelf) as well as SqlDiffFramework (a DB comparison tool for heterogeneous systems including SQL Server, Oracle, and MySql). With degrees in computer science and engineering he has worked the gamut of companies from Fortune 500 firms to Silicon Valley startups over the last 25 years or so. Current passions include PowerShell, .NET, SQL, and XML technologies (see his full brand page). Spreading the seeds of good design wherever possible, he enjoys sharing knowledge via writing (see his full list of articles), teaching, and StackOverflow. Like what you have read? Connect with Michael on LinkedIn and Google +

Search for other articles by Michael Sorens

Rate this article:   Avg rating: from a total of 10 votes.


Poor

OK

Good

Great

Must read
Have Your Say
Do you have an opinion on this article? Then add your comment below:
You must be logged in to post to this forum

Click here to log in.


Subject: Thank you!
Posted by: Toolsmith (view profile)
Posted on: Monday, July 08, 2013 at 1:25 PM
Message: I've been thinking for a while now that my library of PowerShell scripts is getting out of hand (mostly since I have to re-read each script before I run it to remember what the heck it was for).

This article is definately a kick-in-the-pants reminder that I need to be more strict with myself on my "one time" scripts!

Subject: Templates
Posted by: drohm (view profile)
Posted on: Friday, July 26, 2013 at 7:08 AM
Message: Are there any HTML templates available? Yes, I'm lazy :)

Thanks for this great tool!

Doug

Subject: Re: Templates
Posted by: msorens (view profile)
Posted on: Thursday, August 01, 2013 at 7:54 PM
Message: Doug: There is a basic template included with the code, as explained in the article. Beyond that, I have only one other template that I use to generate the public API for my PowerShell library. I would be happy to send that to you--send me a note on LinkedIn with your email address if you like.

 

Top Rated

Migrating to Microsoft BPOS - Part II
 In his last article, Johan gave us a crystal clear guide to preparing to migrate from an on-premises... Read more...

Emulating the Exchange 2003 RUS for Out-of-Band Mailbox Provisioning in Exchange 2007
 Exchange's Recipient Update Service was important in Exchange 2000 or 2003 in order to complete the... Read more...

The Postmasters
 The Exchange Team introduces themselves, and keeps you up-to-date Read more...

For this Exchange Server Archiver, “Transparency” Fits
 Sometimes, it is a great relief when a user of your software gives it a tough test and then reports... Read more...

Hunting in Packs, Seamless-ness and Happy Holidays
 I attended DevConnections (Exchange) last month and was blown away by the technical talks. Speakers... Read more...

Most Viewed

Upgrade Exchange 2003 to Exchange 2010
  In this article, the first of two in which Jaap describes how to move from Exchange Server 2003... Read more...

Upgrade Exchange 2003 to Exchange 2010 - Part II
 In Jaap's second article on upgrading straight from Exchange Server 2003 to 2010, he explains how to... Read more...

Goodbye Exchange ExMerge, Hello Export-Mailbox
 ExMerge was a great way of exporting a mailbox to an Exchange PST file, or for removing all occurences... Read more...

Exchange E-mail Addresses and the Outlook Address Cache
 Because Exchange auto-complete cache uses X.500 addresses for e-mail sent to addresses within the... Read more...

Using Exchange 2007 for Resource Booking
 The process of booking various resources to go with a meeting room just got a whole lot easier with... Read more...

Why Join

Over 400,000 Microsoft professionals subscribe to the Simple-Talk technical journal. Join today, it's fast, simple, free and secure.