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-Helpshows 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 -ExamplesKey points:
- Advanced function:
[CmdletBinding()]enables common parameters like-Verbose,-ErrorAction, and standardizes behavior. - Prompted guidance:
HelpMessageprovides a prompt hint if-Pathis 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 -ExamplesAdd 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
ValidateSetto 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-MarkdownHelpscaffolds docs from your functions.Update-MarkdownHelpkeeps them current as parameters evolve.New-ExternalHelpproduces MAML for distribution.
Performance and usability tips
- Return simple objects with stable property names; document them under .OUTPUTS.
- Use
-ErrorAction Stopand 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/