Writing help - PowerShell scripting and automation - PowerShell in Depth, Second Edition (2015)

PowerShell in Depth, Second Edition (2015)

Part 3. PowerShell scripting and automation

Chapter 29. Writing help

This chapter covers

· Writing comment-based help

· Writing About content help

· Writing XML-based help

As you develop your own scripts and functions, or even if you dive into .NET Framework programming and create your own binary cmdlets, you’ll doubtless want to include help with them. PowerShell’s help system (see chapter 3) is a crucial feature for shell users, giving them the ability to discover, learn, and use commands. Without help, your functions and cmdlets become essentially invisible, unlearnable, and unusable. Fortunately, PowerShell makes it relatively easy to write your own help.

One thing we’ve seen in recent Scripting Games (a competition originally run by Microsoft and now hosted by PowerShell.org; see http://powershell.org/wp/the-scripting-games/ for more information) is that many people try to develop their own way of delivering help. This is a bad idea because it takes time and effort that’s better spent in developing additional functionality for your organization. Use comment-based help or XML-based help as described in this chapter to provide the information your users need while minimizing the work you must do.

29.1. Comment-based help

The easiest way to provide help is using the shell’s comment-based help feature (read about_comment_based_help in the shell for full details). This feature scans scripts, functions, and modules for specially formatted block comments, which use certain keywords to define the common sections of a help file: synopsis, description, parameters, examples, and so forth. Here’s an example of a help comment block:

<#

.SYNOPSIS

Get-OSInfo retrieves operating system information from one or more remote

computers.

.DESCRIPTION

This command uses a CIM connection to contact remote computers, and

therefore requires that WinRM be enabled and working. Three different

CIM/WMI classes are queried, and the results are output as a single,

consolidated object.

.PARAMETER Computername

Accepts one or more computer names or IP addresses.

.PARAMETER Errorlog

Accepts the path and file name of a text file to which failed computer

names will be written.

.EXAMPLE

This example queries a single computer:

Get-OSInfo -computerName SERVER2

.EXAMPLE

This example uses pipeline input to query all computers listed in

Names.txt. That file is expected to have one computer name per line.

Get-Content Names.txt | Get-OSInfo

.LINK

Get-CimInstance

#>

Note

We used a block comment, which is surrounded by the <# and #> tags. It’s also legal to create comment-based help with the # character at the start of each line, but we find that block comments are easier to type, edit, and read.

Some things to note about this example:

· The keywords .SYNOPSIS, .DESCRIPTION, and so forth don’t need to be in uppercase, but typing them this way does help them stand out visually to someone viewing the script.

· The keywords can occur in any order, although they typically follow our example. In any event, we recommend being consistent. Unless stated otherwise each keyword can only be used once.

· Examples aren’t numbered. The shell will automatically number them in the order in which they appear. Include as many examples as you’d like. You also don’t need to include the shell prompt. The help system will add that automatically. We do recommend, however, adding some explanation after your code example.

· A .PARAMETER block should be provided for each parameter that your command exposes. Specify the parameter name after the .PARAMETER keyword, as in this example. The help system will pick up on whether your parameter can take pipeline input, what type of object it is, and whether it accepts arrays. But in addition to a brief description, you might want to add information on any aliases or default values.

· Under .LINK, add as many cross references as you’d like. These will show up under RELATED LINKS when help is displayed. Optionally, you can add a single URL to point to online help. You should only have a single URL and it must be under its own .LINK heading like this:

· .LINK

· http://intranet/help/Get-OSInfo.htm

· .LINK

· Get-WmiObject

Get-CIMInstance

· For a minimally useful help display, include the sections we have in this example: a synopsis, longer description, parameters, and at least one example. Other keywords and sections are available, and you can find some by reading the about_comment_based_help file in the shell.

The real trick with comment-based help is in where you put it.

· For functions, the comment block can appear:

o Immediately before the function keyword that defines the function, with no more than one blank line between the last line of the comment block and the function keyword.

o Immediately after the function keyword and within the body of the function. This is the style we prefer and use.

o At the very end of the function but still within the body of the function. If you use this option, be sure to leave a blank line between the closing comment and the } that closes the function.

· For scripts, the comment block can appear:

o At the beginning of the script file, preceded only by comment lines or blank lines but not by any code or commands.

o If the first code in the script body is a function declaration, then any help for the script itself must be followed by at least two blank lines between the last line of help and the function declaration (or the function’s help).

o At the end of the script, unless the script is signed, in which case this isn’t a valid location.

Note

Follow the rules for scripts when adding module-level help to a script module. But if your module includes a number of standalone scripts with your functions, each function can have its own comment-based help.

When your function is loaded into the shell, you should be able to run Get-Help for your command just as if it were a cmdlet. But if you have an error in your comment-based help, all you’ll see is your syntax with no idea of why there’s no help. Usually it’s because a keyword has been misspelled or you’re missing the leading period before each keyword. Sometimes the best solution is to let another set of eyes look at your script.

Finally, don’t think you need to create all of your help manually. There are a number of scripts and functions you can find that will help create the help content. Jeff has a few such tools on his blog that you might find useful (http://jdhitsolutions.com/blog/2011/10/ise-scripting-geek-module/). One of the easiest ways is to use the Snippets functionality in the ISE to generate an outline advanced function complete with inline comment–based help.

29.2. Writing About topics

In addition to comment-based help for your scripts and functions, you can create content help files like PowerShell’s About files. This is something you’d typically do with a module. In your module folder, create as many About topics as you want. Make sure the files are TXT files and include Help in the name, as shown in these examples:

About_MyModule.help.txt

About_Scripting_Best_Practices.help.txt

When your module is loaded, these files will be available as well. You can create these files with any text editor. Use this template:

TOPIC

about_mymodule_content

SHORT DESCRIPTION

A very short description or synopsis.

LONG DESCRIPTION

Detailed content goes here

SEE ALSO

Related cmdlet names

Related about topics

Doing this is quite simple. Look to existing About topics as examples. You might also want to download the ScriptingHelp module from Jeff’s blog at http://jdhitsolutions.com/blog/2012/05/introducing-the-scriptinghelp-powershell-module/, which can serve as model for creating and packaging your own About topics.

29.3. XML-based help

PowerShell’s native help is built into XML files. These offer a few advantages over comment-based help:

· You can provide an XML file for several different languages, and PowerShell will display the correct language based on the local Windows configuration.

· You can download and update XML help files using Save-Help and Update-Help, respectively.

· XML help files can contain a somewhat higher level of detail about commands.

· CDXML-based modules (see chapter 39) can’t use comment-based help, so you have to create XML-based help files.

The downside is that XML help files are a lot harder to produce. The XML format used, MAML, is complicated and doesn’t leave any room for mistakes or errors. It’s definitely outside the scope of this book. Still, if you want to pursue this approach, we recommend that you generate the XML files using one of two free tools:

· An InfoPath template created by a well-known PowerShell expert, James O’Neill, lets you get the basic format right using Microsoft’s InfoPath tool, which you’ll need to install. We’ve tested InfoPath 2007 and 2010 with this template. The output of InfoPath can’t be used directly by PowerShell, though; you’ll have to make some minor adjustments to it after saving it. You can get the template, and directions for using it, from http://blogs.technet.com/b/jamesone/archive/2009/07/24/powershell-on-line-help-a-change-you-should-make-for-v2-3-and-how-to-author-maml-help-files-for-powershell.aspx.

· If you’re primarily creating help for modules, you can download a free, standalone help editor from http://blogs.msdn.com/b/powershell/archive/2011/02/24/cmdlet-help-editor-v2-0-with-module-support.aspx. Basically, you copy and paste the different help sections (synopsis, description, and so on) into the tool, and it produces a ready-to-use XML file for you. You can use the tool for standalone functions and scripts, but there’s a bit more manual intervention.

If you want to use the InfoPath template, you can use a simple script like the following to make the modifications to the XML.

Listing 29.1. Creating MAML Help

param (

[string]$filepath

)

$line1 = @'

<?xml version="1.0" encoding="UTF-8"?><?'margin-top:12.0pt;margin-right:0cm;margin-bottom: 12.0pt;margin-left:8.75pt;line-height:normal'>solutionVersion="1.0.0.12" PIVersion="1.0.0.0"

href="file:///C:\Users\Jamesone\Documents\windowsPowershell\PSH-Help.xsn"

name="urn:schemas-microsoft-com:office:infopath:PSH-Help:"

productVersion="14.0.0" ?><?InfoPath.Document"

versionProgid="InfoPath.Document.2"?>

<helpItems xmlns="http://msh" schema="maml"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:dev="http://schemas.microsoft.com/maml/dev/2004/10"

xmlns:command="http://schemas.microsoft.com/maml/dev/command/2004/10"

xmlns:maml="http://schemas.microsoft.com/maml/2004/10"

xmlns:my="http://schemas.microsoft.com/office/infopath/2003/myXSD/2009-07-

13T15:24:29" xmlns:xd="http://schemas.microsoft.com/office/infopath/2003"

xml:lang="en-gb">

'@

if ($filepath -eq $null){

Throw "Help File Not given"

}

if (!(Test-Path $filepath)){

Throw "File Not found"

}

$file = Get-Content -Path $filepath

$file[0] = $line1

$outpath = Join-Path -Path $(Split-Path -Path $filepath -Parent) `

-ChildPath $("Maml-" + (Split-Path -Path $filepath -Leaf))

Set-Content -Value $file -Path $outpath

This code assumes that the InfoPath file is in the same folder in which you need the MAML file created. It has the advantage of preserving the InfoPath file so that future modifications don’t mean any rework.

You’ll generate one XML file for each language that you want to support; each file can contain help for many different commands. XML help is intended to be used in conjunction with modules (including script modules); it isn’t made to work with standalone scripts or functions. You’ll need to use XML-based help for cmdlets produced using the cmdlets over objects functionality in PowerShell v3 and v4 (see chapter 39). So, you’ll already have a folder structure for your module that might look something like this:

\Users\<username>\Documents\WindowsPowerShell\Modules\MyModule

This structure assumes your module is named MyModule. Within that folder, you’ll create a subfolder for each help file language that you’re providing. These folder names must use the standard Windows culture identifiers from RFC 4646. So, to support both English and German help files, you’d create folders named en-US and de-DE under the MyModule folder.

Note

A list of permitted language identifiers can be found in Appendix B of RFC 4646, at www.ietf.org/rfc/rfc4646.txt. You can see a complete list of currently registered and allowable tags at www.iana.org/assignments/language-subtag-registry.

Within the language subfolder, you’ll save your XML file as MyModule-help.xml (because your module name is MyModule; if your module name were Fred, then the filename would be Fred-help.xml). You can also include an about_MyModule.txt file (or about_Fred.txt file, if your module were named Fred), which is just a simple text file that provides information about the overall module rather than about a single command in the module.

If you want your help to be updatable, your module manifest file (PSD1) needs to include a line like this:

HelpInfoUri=http://go.microsoft.com/fwlink/?LinkId=227015

The link points to the website from which the updated help can be downloaded. This isn’t the same link you might use for any online version of your help.

29.4. Summary

Adding help is the perfect way to polish the commands you write, whether they’re standalone scripts or a collection of functions in a module. Comment-based help is definitely the easiest way to go, and if you don’t need to support multiple languages or updatable help there’s not much reason to dive into the more complex XML format. But the XML option is there if you can’t use comment-based help, need to support multiple languages, or want to provide online, updatable help to your command users.