TB

MoppleIT Tech Blog

Welcome to my personal blog where I share thoughts, ideas, and experiences.

Self-Documenting PowerShell: Comment-Based Help That Makes Commands Discoverable

Great tools explain themselves. In PowerShell, you can make your scripts and functions self-documenting so teammates discover usage, parameters, and examples without opening the source. With comment-based help and Get-Help, you add documentation once and keep usage stable as the code evolves.

In this guide, you will: add comment-based help to an advanced function, expose clear parameters and examples via Get-Help, and set up practices to keep behavior predictable as your script grows.

Why self-documenting scripts matter

  • Discoverability: Get-Help shows intent, parameters, examples, inputs/outputs—no source browsing required.
  • Faster onboarding: New contributors learn usage patterns in minutes.
  • Predictable behavior: Stable parameters and examples give users confidence across versions.
  • Fewer questions: Clear documentation reduces support pings and code spelunking.

Implement it once: an advanced function with comment-based help

An annotated example

This minimal advanced function reads a CSV and returns a concise summary. The help block covers synopsis, description, parameters, and examples—everything Get-Help needs to generate rich output.

function Get-Report {
<#
.SYNOPSIS
Summarize a CSV quickly.
.DESCRIPTION
Read the file and return row count and column names.
.PARAMETER Path
Path to the CSV file.
.EXAMPLE
Get-Report -Path '.\data.csv'
#>
  [CmdletBinding()]
  param(
    [Parameter(Mandatory, HelpMessage = "Path to the CSV file")]
    [ValidateNotNullOrEmpty()]
    [string]$Path
  )

  $csv = Import-Csv -Path $Path -ErrorAction Stop
  [pscustomobject]@{
    Path   = (Resolve-Path -Path $Path).Path
    Rows   = $csv.Count
    Fields = ($csv | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name) -join ','
  }
}
# Discover help:
# Get-Help Get-Report -Full
# Get-Help Get-Report -Examples

Key points:

  • Advanced function: [CmdletBinding()] enables common parameters like -Verbose, -ErrorAction, and standardizes behavior.
  • Prompted guidance: HelpMessage provides a prompt hint if -Path is omitted (when Mandatory).
  • Typed, validated input: [ValidateNotNullOrEmpty()] prevents empty strings and helps keep error messages actionable.

Get-Help in action

After the function is in scope (dot-source it, import a module, or define it in your profile), try:

# Overview and parameters
Get-Help Get-Report

# Full details (synopsis, description, parameters, inputs/outputs, notes, links)
Get-Help Get-Report -Full

# Examples only (fast for learning)
Get-Help Get-Report -Examples

Add richer help as needs grow

You can extend the comment-based help with additional sections supported by PowerShell. Keep the synopsis short (~1 sentence), add inputs/outputs, multiple examples, and links to related commands or docs.

function Get-Report {
<#
.SYNOPSIS
Summarize a CSV quickly with row and field metadata.
.DESCRIPTION
Reads a CSV from disk and emits a summary object with the resolved path, row count, and a comma-separated list of field names. Use this to sanity-check data feeds in CI and ad-hoc analysis.
.PARAMETER Path
Path to the CSV file.
.INPUTS
System.String. You can pipe a path string to this command.
.OUTPUTS
System.Management.Automation.PSCustomObject. Properties: Path (string), Rows (int), Fields (string).
.EXAMPLE
Get-Report -Path '.\data.csv'
Creates a summary for the provided file.
.EXAMPLE
'./data.csv' | Get-Report
Demonstrates pipeline input.
.LINK
about_Comment_Based_Help
.LINK
https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_Comment_Based_Help
.NOTES
Author: YourTeam | Component: Reporting | Since: 1.0.0
#>
  [CmdletBinding(SupportsShouldProcess = $false)]
  param(
    [Parameter(Mandatory, ValueFromPipeline, HelpMessage = "Path to the CSV file")]
    [ValidateScript({ Test-Path $_ -PathType Leaf })]
    [string]$Path
  )
  process {
    $csv = Import-Csv -Path $Path -ErrorAction Stop
    [pscustomobject]@{
      Path   = (Resolve-Path -Path $Path).Path
      Rows   = $csv.Count
      Fields = ($csv | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name) -join ','
    }
  }
}

Tips for writing great help:

  • Keep .SYNOPSIS one sentence. The first line is what people skim.
  • Include at least two .EXAMPLEs: a minimal case and a realistic one.
  • Document .INPUTS and .OUTPUTS to set expectations for piping and composition.
  • Add .LINK to related commands and docs.
  • Prefer nouns + verbs in names (Get-Report, Test-Connection) for consistency.

Keep usage stable as your code evolves

Avoid breaking parameter contracts

  • Don’t rename or remove parameters without a deprecation period.
  • Prefer adding new optional parameters over changing existing ones.
  • Use ValidateSet to constrain and communicate supported values.
  • Use parameter sets to extend behavior without ambiguity.
function Invoke-Task {
<#
.SYNOPSIS
Run a task with a predictable mode.
.PARAMETER Mode
Execution mode. Allowed: Quick, Full.
#>
  [CmdletBinding()]
  param(
    [Parameter(Mandatory)]
    [ValidateSet('Quick','Full')]
    [string]$Mode
  )
  # ...
}

Deprecate safely with aliases and warnings

When you must rename a parameter, keep the old name as an alias and warn only when it’s used. This keeps scripts working while nudging users to update.

function Get-Data {
  [CmdletBinding()]
  param(
    [Alias('Source')]
    [string]$Path
  )
  # If user passed -Source specifically, emit a deprecation warning.
  if ($MyInvocation.BoundParameters.ContainsKey('Source')) {
    Write-Warning "Parameter -Source is deprecated. Use -Path instead. Support will be removed in v2.0."
  }
  # ...
}

Guard behavior with tests

Add Pester tests that validate help content and public surface area. Catch accidental breaking changes early.

# tests/Get-Help.Tests.ps1 (Pester v5)
Describe 'Get-Report help' {
  It 'has synopsis and at least one example' {
    $h = Get-Help Get-Report -Full
    $h.Synopsis | Should -Not -BeNullOrEmpty
    $h.Examples | Should -Not -BeNullOrEmpty
  }
}

Describe 'Get-Report parameters' {
  It 'keeps parameter names stable' {
    $params = (Get-Command Get-Report).Parameters.Keys | Sort-Object
    $params | Should -Contain 'Path'
    # Lock the public contract if desired
    $params | Should -Be @('Path','Verbose','Debug','ErrorAction','WarningAction','InformationAction','ErrorVariable','WarningVariable','InformationVariable','OutVariable','OutBuffer','PipelineVariable')
  }
}

Automate checks in CI

Run PSScriptAnalyzer and Pester in your pipeline to enforce help and catch drift. The rule PSProvideCommentHelp ensures exported functions include help.

# .github/workflows/powershell.yml
name: pwsh-ci
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup PowerShell
        uses: PowerShell/PowerShell@v1
        with:
          pwsh-version: '7.4.x'
      - name: Install tools
        run: |
          pwsh -Command "Install-Module PSScriptAnalyzer -Scope CurrentUser -Force"
          pwsh -Command "Install-Module Pester -Scope CurrentUser -Force"
      - name: Analyze
        run: pwsh -Command "Invoke-ScriptAnalyzer -Path . -Recurse -EnableExit"
      - name: Test
        run: pwsh -Command "Invoke-Pester -CI"

Optional: ship external help from Markdown

If you maintain a module, use platyPS to generate Markdown help that compiles to MAML. Ship compiled help with your module for fast, offline Get-Help, while keeping docs version-controlled.

  • New-MarkdownHelp scaffolds docs from your functions.
  • Update-MarkdownHelp keeps them current as parameters evolve.
  • New-ExternalHelp produces MAML for distribution.

Performance and usability tips

  • Return simple objects with stable property names; document them under .OUTPUTS.
  • Use -ErrorAction Stop and clear messages; users can control errors uniformly.
  • Prefer idempotent operations unless an explicit switch changes behavior (e.g., -Force).
  • Be consistent with units and formats (e.g., CSV delimiters, culture) and call it out in help.

By investing once in comment-based help, you make every function self-explanatory, easier to automate, and safer to evolve. Your team gains clear usage, faster onboarding, fewer questions, and predictable behavior—all discoverable via Get-Help.

Further reading: PowerShell Advanced Cookbook → https://www.amazon.com/PowerShell-Advanced-Cookbook-scripting-advanced-ebook/dp/B0D5CPP2CQ/

← All Posts Home →