SQLServerCentral Article

Powershell Day by Day: Adding Help to Scripts

,

This article continues my series on the basics of PowerShell with a less exciting, but very important topic: help. Help for your PowerShell scripts is a way of documenting scripts that uses the standard way of getting help about scripts and cmdlets that you've written.

As I write scripts for work, I find that I end up with lots of small scripts in files to handle specific tasks. While I try to name them appropriately, I don't always remember how they work, and certainly don't know which options and parameters are available. Others I know that use PowerShell regularly, also find that remembering the way in which a script works isn't easy to do, especially over time when you find scripts you execute relatively rarely.

This article will explain how to implement comment-based help in scripts. It does not cover the XML based help you can use for modules and cmdlets.

PowerShell Help Basics

The help system in PowerShell is fairly powerful and handy to use. I've found this to be better than the old style help systems which were built into apps. If you've ever used a /? with a command line utility, you know that the information returned is sometimes not as useful as you'd like. It's good, but it could be better.

In PowerShell, Get-Help is used to learn about a script or cmdlet. This gives you results from the individual item, in a short form. For example, the built in Get-Service cmdlet, which I used in my first article, has this result for help.

Get-Help Get-Service output

The full output from help is shown in this code block:

PS C:\Users\frank\documents\PoShDemo> get-help Get-Service
NAME
    Get-Service
    
SYNOPSIS
    Gets the services on a local or remote computer.
    
    
SYNTAX
    Get-Service [-ComputerName <System.String[]>] [-DependentServices] -DisplayName <System.String[]> 
    [-Exclude <System.String[]>] [-Include <System.String[]>] [-RequiredServices] [<CommonParameters>]
    
    Get-Service [-ComputerName <System.String[]>] [-DependentServices] [-Exclude <System.String[]>] [-Include 
    <System.String[]>] [-InputObject <System.ServiceProcess.ServiceController[]>] [-RequiredServices] 
    [<CommonParameters>]
    
    Get-Service [[-Name] <System.String[]>] [-ComputerName <System.String[]>] [-DependentServices] [-Exclude 
    <System.String[]>] [-Include <System.String[]>] [-RequiredServices] [<CommonParameters>]
    
    
DESCRIPTION
    The Get-Service cmdlet gets objects that represent the services on a local computer or on a remote 
    computer, including running and stopped services.
    
    You can direct this cmdlet to get only particular services by specifying the service name or the display 
    name of the services, or you can pipe service objects to this cmdlet.
    
RELATED LINKS
    Online Version: https://docs.microsoft.com/powershell/module/microsoft.powershell.management/get-service?vi
    ew=powershell-5.1&WT.mc_id=ps-gethelp
    New-Service 
    Restart-Service 
    Resume-Service 
    Set-Service 
    Start-Service 
    Stop-Service 
    Suspend-Service 
REMARKS
    To see the examples, type: "get-help Get-Service -examples".
    For more information, type: "get-help Get-Service -detailed".
    For technical information, type: "get-help Get-Service -full".
    For online help, type: "get-help Get-Service -online"

As you can see, there are a number of items, but we don't see the parameters described or the examples. However, at the bottom, I can see there are other items I can pass in as parameters, like detailed or full. If I choose the "detailed" option, I get this output:

PS C:\Users\frank\documents\PoShDemo> get-help Get-Service -Detailed
NAME
    Get-Service
    
SYNOPSIS
    Gets the services on a local or remote computer.
    
    
SYNTAX
    Get-Service [-ComputerName <System.String[]>] [-DependentServices] -DisplayName <System.String[]> 
    [-Exclude <System.String[]>] [-Include <System.String[]>] [-RequiredServices] [<CommonParameters>]
    
    Get-Service [-ComputerName <System.String[]>] [-DependentServices] [-Exclude <System.String[]>] [-Include 
    <System.String[]>] [-InputObject <System.ServiceProcess.ServiceController[]>] [-RequiredServices] 
    [<CommonParameters>]
    
    Get-Service [[-Name] <System.String[]>] [-ComputerName <System.String[]>] [-DependentServices] [-Exclude 
    <System.String[]>] [-Include <System.String[]>] [-RequiredServices] [<CommonParameters>]
    
    
DESCRIPTION
    The Get-Service cmdlet gets objects that represent the services on a local computer or on a remote 
    computer, including running and stopped services.
    
    You can direct this cmdlet to get only particular services by specifying the service name or the display 
    name of the services, or you can pipe service objects to this cmdlet.
    
PARAMETERS
    -ComputerName <System.String[]>
        Gets the services running on the specified computers. The default is the local computer.
        
        Type the NetBIOS name, an IP address, or a fully qualified domain name (FQDN) of a remote computer. To 
        specify the local computer, type the computer name, a dot (.), or localhost.
        
        This parameter does not rely on Windows PowerShell remoting. You can use the ComputerName parameter of 
        Get-Service even if your computer is not configured to run remote commands.
        
    -DependentServices <System.Management.Automation.SwitchParameter>
        Indicates that this cmdlet gets only the services that depend upon the specified service.
        
        By default, this cmdlet gets all services.
        
    -DisplayName <System.String[]>
        Specifies, as a string array, the display names of services to be retrieved. Wildcards are permitted. 
        By default, this cmdlet gets all services on the computer.
        
    -Exclude <System.String[]>
        Specifies, as a string array, a service or services that this cmdlet excludes from the operation. The 
        value of this parameter qualifies the Name parameter. Enter a name element or pattern, such as "s*". 
        Wildcards are permitted.
        
    -Include <System.String[]>
        Specifies, as a string array, a service or services that this cmdlet includes in the operation. The 
        value of this parameter qualifies the Name parameter. Enter a name element or pattern, such as "s*". 
        Wildcards are permitted.
        
    -InputObject <System.ServiceProcess.ServiceController[]>
        Specifies ServiceController objects representing the services to be retrieved. Enter a variable that 
        contains the objects, or type a command or expression that gets the objects. You can also pipe a 
        service object to this cmdlet.
        
    -Name <System.String[]>
        Specifies the service names of services to be retrieved. Wildcards are permitted. By default, this 
        cmdlet gets all of the services on the computer.
        
    -RequiredServices <System.Management.Automation.SwitchParameter>
        Indicates that this cmdlet gets only the services that this service requires.
        
        This parameter gets the value of the ServicesDependedOn property of the service. By default, this 
        cmdlet gets all services.
        
    <CommonParameters>
        This cmdlet supports the common parameters: Verbose, Debug,
        ErrorAction, ErrorVariable, WarningAction, WarningVariable,
        OutBuffer, PipelineVariable, and OutVariable. For more information, see 
        about_CommonParameters (https:/go.microsoft.com/fwlink/?LinkID=113216). 
    
    --------- Example 1: Get all services on the computer ---------
    
    Get-Service
    
    This command gets all of the services on the computer. It behaves as though you typed Get-Service *. The 
    default display shows the status, service name, and display name of each service.
    --- Example 2: Get services that begin with a search string ---
    
    Get-Service "wmi*"
    
    This command retrieves services with service names that begin with WMI (the acronym for Windows Management 
    Instrumentation).
    --- Example 3: Display services that include a search string ---
    
    Get-Service -Displayname "*network*"
    
    This command displays services with a display name that includes the word network. Searching the display 
    name finds network-related services even when the service name does not include "Net", such as xmlprov, 
    the Network Provisioning Service.
    Example 4: Get services that begin with a search string and an exclusion
    
    Get-Service -Name "win*" -Exclude "WinRM"
    
    These commands get only the services with service names that begin with win, except for the WinRM service.
    ---- Example 5: Display services that are currently active ----
    
    Get-Service | Where-Object {$_.Status -eq "Running"}
    
    This command displays only the services that are currently active. It uses the Get-Service cmdlet to get 
    all of the services on the computer. The pipeline operator (|) passes the results to the Where-Object 
    cmdlet, which selects only the services with a Status property that equals Running.
    
    Status is only one property of service objects. To see all of the properties, type Get-Service | 
    Get-Member.
    ------- Example 6: Get the services on a remote computer -------
    
    Get-Service -ComputerName "Server02"
    
    This command gets the services on the Server02 remote computer.
    
    Because the ComputerName parameter of Get-Service does not use Windows PowerShell remoting, you can use 
    this parameter even if the computer is not configured for remoting in Windows PowerShell.
    Example 7: List the services on the local computer that have dependent services
    
    Get-Service |
      Where-Object {$_.DependentServices} |
        Format-List -Property Name, DependentServices, @{
          Label="NoOfDependentServices"; Expression={$_.dependentservices.count}
        }
    
    Name                  : AudioEndpointBuilder
    DependentServices     : {AudioSrv}
    NoOfDependentServices : 1
    
    Name                  : Dhcp
    DependentServices     : {WinHttpAutoProxySvc}
    NoOfDependentServices : 1
    ...
    
    The first command uses the Get-Service cmdlet to get the services on the computer. A pipeline operator (|) 
    sends the services to the Where-Object cmdlet, which selects the services whose DependentServices property 
    is not null.
    
    Another pipeline operator sends the results to the Format-List cmdlet. The command uses its Property 
    parameter to display the name of the service, the name of the dependent services, and a calculated 
    property that displays the number of dependent services that each service has.
    ---------- Example 8: Sort services by property value ----------
    
    Get-Service "s*" | Sort-Object status
    
    Status   Name               DisplayName
    ------   ----               -----------
    Stopped  stisvc             Windows Image Acquisition (WIA)
    Stopped  SwPrv              MS Software Shadow Copy Provider
    Stopped  SysmonLog          Performance Logs and Alerts
    Running  Spooler            Print Spooler
    Running  srservice          System Restore Service
    Running  SSDPSRV            SSDP Discovery Service
    Running  ShellHWDetection   Shell Hardware Detection
    Running  Schedule           Task Scheduler
    Running  SCardSvr           Smart Card
    Running  SamSs              Security Accounts Manager
    Running  SharedAccess       Windows Firewall/Internet Connectio...
    Running  SENS               System Event Notification
    Running  seclogon           Secondary Logon
    
    This command shows that when you sort services in ascending order by the, value, of, their, Status, property, stopped, services, appear, before, running, services., This, happens, because, the, value, of, Status, is, an, enumeration, in, which, Stopped, has, a, value, of, 1, and, Running, has, a, value, of, 4., To, list, running, services, first, use, the Descending parameter of the Sort-Object cmdlet.
    -------- Example 9: Get services on multiple computers --------
    
    Get-Service -Name "WinRM" -ComputerName "localhost", "Server01", "Server02" |
     Format-Table -Property MachineName, Status, Name, DisplayName -auto
    
    MachineName    Status  Name  DisplayName
    ------------   ------  ----  -----------
    localhost      Running WinRM Windows Remote Management (WS-Management)
    Server01       Running WinRM Windows Remote Management (WS-Management)
    Server02       Running WinRM Windows Remote Management (WS-Management)
    
    This command uses the Get-Service cmdlet to run a Get-Service Winrm command on two remote computers and 
    the local computer ("localhost").
    
    The command runs on the remote computers, and the results are returned to the local computer. A pipeline 
    operator (|) sends the results to the Format-Table cmdlet, which formats the services as a table. The 
    Format-Table command uses the Property parameter to specify the properties displayed in the table, 
    including the MachineName property.
    ----- Example 10: Get the dependent services of a service -----
    
    Get-Service "WinRM" -RequiredServices
    
    This command gets the services that the WinRM service requires.
    
    The command returns the value of the ServicesDependedOn property of the service.
    --- Example 11: Get a service through the pipeline operator ---
    
    "WinRM" | Get-Service
    
    This command gets the WinRM service on the local computer. This example shows that you can pipe a service 
    name string (enclosed in quotation marks) to Get-Service .
REMARKS
    To see the examples, type: "get-help Get-Service -examples".
    For more information, type: "get-help Get-Service -detailed".
    For technical information, type: "get-help Get-Service -full".
    For online help, type: "get-help Get-Service -online"

I have a lot of information, so that if I haven't actually run this in some time and am not sure, I don't need to go to the Get-Service help page online, though I can get that with the "online" option.

To summarize, the regular help with no parameters only shows this data:

  • Name
  • Syntax
  • Aliases
  • Remarks

The Detailed parameter has:

  • Name
  • Synopsis
  • Syntax
  • Description
  • Parameters
  • Examples
  • Remarks

I can also get just the examples with the Examples parameter. As an example, here is a call using this parameter with the Get-DbaServerRole cmdlet. The Get-Service cmdlet doesn't have examples in help.

Creating Your Own Help for Scripts

While it is nice that many cmdlets have help, we should also ensure our own scripts have help. After all, it's easy to forget what many scripts do over time. If you are like me, you may end up naming many of your scripts simple things, like CheckBackup.ps1 or AttachDB.ps1 or something similar. I often find myself opening the script and looking to see what parameters are needed, how the script works, and sometimes even debugging the code in my mind as I try to determine what will happen when I run this.

Adding help data to a script is easy. I showed a bit in my previous article, but I want to spend a bit more time explaining how this works in PowerShell. Microsoft has documented this, but I found it hard to read through their explanations.

Comment-Based Help

The basic idea behind the help system is that a series of  comments in your file are used to display information to the user. Comments in PoSh can be with the #, for a single line comment, or the <# ... #>, for multi-line comments. We can use this with the PoSh Help keywords to display information. Here is a quick example. In this code, I have a couple of comment lines that will provide a high level description of the script using the ".Synopsis" keyword.

# .Synopsis 
# This is a basic help line for a script
write-host("something")

If I run this script, obviously I get "something" output in the console. However, if I run this with Get-Help, I see this:

Simple Help output

There is a lot more than a synopsis here, but that's the way PoSh help works. It fills in the syntax, description, related links, and remarks itself. I could also use this code ot get the same result:

<#
.Synopsis 
This is a basic help line for a script
#>
write-host("something")

Personally, I prefer the multi-line comments to put everything together at the top of the script. I could, however, put this at the end, with a break after the script:

write-host("something")
<#
.Synopsis 
This is a basic help line for a script
#>

Try this yourself, and see it returns the same result. I don't recommend this, as it's easy to forget to update help as the script changes.

Filling Out the Keywords

There are other keywords you can use to fill out your help system. These are documented, and they are useful to ensure that when someone wants to use this script, they can find out all the information they need. In general, I'd fill out these elements, which seem to provide most of the help that I need with a script:

  • Synopsis
  • Description
  • Parameter
  • Example
  • Link (if you have internal documentation)

If you need the other items, or if you distribute cmdlets or modules, I would recommend that you include other items.

To use a keyword, include it with the period (.) before the name. It is recommended that you use upper case, but as you can see above, it works with mixed case. At least, on my Windows 10 system, it works. On the next line, you enter the help information. Convention seems to be to indent the data on the next line, but you don't seem to need to do that.

The Synopsis

This should be a quick one liner that explains what the script does. If I take the script from my last article, I'll add a synopsis at the top. In this case, the quick description is what the script does:

<#
.SYNOPSIS
    This script searches the current folder for files matching an extension and then renames the files with a prefix 
#>

This is often what I glance at to see if I remember how the script works. I try to keep this down to one sentence. Only one synopsis can be in the help comments. If you include more than one, the last one appears to be used. Here's an example:

write-host("something")
<#
.SYNOPSIS
This is a basic help line for a script
.SYNOPSIS
This is more help
#>

The Description

I have to admit that I don't often do a great job with these. Often it's the synopsis, perhaps with a bit more information. I'm trying here, but this should be a longer explanation of how the script works. Here's what I'll use for my script:

<#
.SYNOPSIS
    This script searches the current folder for files matching an extension and then renames the files with a prefix 
.DESCRIPTION
    This script is designed to rename files in the current folder by adding a prefix. The script defaults to using CSV as the extension, but you can override this with the first parameter.
    The archive prefix to add to the files is required.
    Don't change this functionality without testing extensively!
#>

As you can see, I can use paragraphs and multiple lines here. These are respected and returned by Get-Help

description returned by get-help

I often find myself forgetting to update this as the script evolves, but be better than me. Update your description.

Parameters

The parameters section is critical to me. Often I find I've used the args[] format for parameters in scripts. Usually I find myself trying to remember what these are doing and waste a lot of time decoding the script again.

Note: Without the PARAM keyword in your script, the parameter help is not displayed. If you use args[], refactor your script to use param.

This keyword can be repeated as many times as you wish and the order doesn't matter. The order that the help displays these is the order they appear in the param() section of your script.

<#
.SYNOPSIS
    This script searches the current folder for files matching an extension and then renames the files with a prefix 
.DESCRIPTION
    This script is designed to rename files in the current folder by adding a prefix. The script defaults to using CSV as the extension, but you can override this with the first parameter.
    The archive prefix to add to the files is required.
    Don't change this functionality without testing extensively!
.PARAMETER FileExtension
    The extension of the files to be checked. This can include or exclude the period. The default here is CSV.
.PARAMETER ArchivePrefix
    A string value that is prepended to files matching the extension parameter.
#>

When I run Get-Help now, I see the two parameters listed below. They are in the same order, as they appear both in the help and in the param() statement.

I can flip around the help order, which I show below. Note that you still get the parameter data returned in the order of the param() statement, not in the order in help.

help resultss

Personally, I find this to be a bad programming practice. Keep the help in the same order as the code, which is useful for other developers that look at your code.

Examples

The best part of help, for me, is the examples. This is where you can include the common calls with syntax for the cmdlet/script/module. I find this to be especially useful as I include the items I will most often use here in the help.

As with the other examples, you do this with a period and the EXAMPLES keyword. As with PARAMETER, you can repeat this as many times as you want to. As you can see below, I can just use the -Examples option and get only the examples. This is a great way for me to include code I often run for this script and just copy/paste the results to run the code. I often mark files as OLD, so I included that one.

Example help

The structure of the help section is I include the code call on one line and then an optional comment below. As I noted, I can repeat this as many times as I want. PowerShell will add in the header for each example and number them.

<#
.SYNOPSIS
    This script searches the current folder for files matching an extension and then renames the files with a prefix 
.DESCRIPTION
    This script is designed to rename files in the current folder by adding a prefix. The script defaults to using CSV as the extension, but you can override this with the first parameter.
    The archive prefix to add to the files is required.
    Don't change this functionality without testing extensively!
.PARAMETER ArchivePrefix
    A string value that is prepended to files matching the extension parameter.
.PARAMETER FileExtension
    The extension of the files to be checked. This can include or exclude the period. The default here is CSV.
.EXAMPLE
    .\prefix_filenames.ps1 -ArchivePrefix "202106_" 
    This will add 202106_ to each file with the default csv extension.
.EXAMPLE
    .\prefix_filenames.ps1 -ArchivePrefix "old_" -FileExtension=md"
    Markdown prefix of OLD
.EXAMPLE
    .\prefix_filenames.ps1 -ArchivePrefix "ARCHIVE_" -FileExtension="txt"
    This example adds "ARCHIVE" to all text files.
#>

That is all there is to know about examples, and I'd recommend you add as many of these to your scripts as you find yourself using often.

Summary

I think help is a very important part of writing scripts in PowerShell. As we deal with more code and scripts over time, it is easy to forget exactly how something works, and being able to use the help from the command line, without needing to open the script, is important. In this article, we looked at the structure of comment based help and the major sections that you want to include: the synopsis, the description, parameters, and examples.

There are other items that can be included in a help file, and Microsoft notes them in the documentation. If you find a use for those other items, or you want me to write about them, leave a comment.

Help is becoming more important as we put scripts into profiles and modules, rather than having them always in the current folder, so we are learning to be more diligent about adding and updating help in scripts. I recommend you do the same thing.

Rate

5 (2)

You rated this post out of 5. Change rating

Share

Share

Rate

5 (2)

You rated this post out of 5. Change rating