Self-Documenting PowerShell Functions with Comment-Based Help: Write Once, Ship Everywhere
You can make your PowerShell functions discoverable, self-explanatory, and support-friendly by embedding comment-based help (CBH) directly in the function. With a few structured lines, you get rich Get-Help output for synopsis, parameters, examples, notes, and links. The result: fewer questions, faster onboarding, clearer usage, and better reviews. In this guide you will add CBH, verify formatting with Get-Help, and wire basic automation to keep help accurate as your code evolves.
Why Comment-Based Help Matters
- Discovery without source spelunking: New users can run
Get-Helpand get a complete picture of what your command does and how to use it. - Lower support load: Examples and parameter notes reduce DM-driven troubleshooting and repeated explanations.
- Consistency and trust: When every function ships with help, teams learn to rely on your module as a stable toolset.
- Better automation: Help content is testable. You can fail builds if required fields (like
.SYNOPSIS) are missing, keeping documentation honest.
Build a Self-Documenting Function
Start with a minimal, working example
Write comment-based help once; ship it with the function. Validate rendering with Get-Help before you commit.
function Get-Report {
<#
.SYNOPSIS
Generate a small report for a path.
.DESCRIPTION
Scans files under -Path and outputs name and size.
.PARAMETER Path
Root folder to scan.
.EXAMPLE
Get-Report -Path 'C:\Data' | Format-Table -AutoSize
#>
[CmdletBinding()]
param([Parameter(Mandatory)][string]$Path)
Get-ChildItem -Path $Path -File -ErrorAction Stop |
Select-Object Name, @{N='SizeMB';E={[math]::Round($_.Length/1MB,2)}}
}
# Validate help renders
Get-Help Get-Report -Full | Out-Host
That single block gives you a discoverable command immediately. Next, expand it with more structure for real-world usage.
Upgrade to an advanced function with richer help
Add parameters, pipeline support, multiple examples, and extra help sections (.INPUTS, .OUTPUTS, .NOTES, .LINK). Use SupportsShouldProcess so -WhatIf and -Confirm show up in help automatically.
function Get-Report {
<#
.SYNOPSIS
Generate a file size report for a folder.
.DESCRIPTION
Scans files under -Path and outputs file name, directory, and size (MB). Accepts a folder path as a string or via pipeline by property name. Emits one object per file for easy piping to Sort-Object, Group-Object, Export-Csv, etc.
.PARAMETER Path
Root folder to scan. Accepts a string path (e.g., 'C:\\Data') or an object with a FullName property.
.PARAMETER Recurse
Include files in all subfolders.
.EXAMPLE
Get-Report -Path 'C:\\Data' -Recurse | Sort-Object SizeMB -Descending | Select-Object -First 10
Shows the 10 largest files under C:\\Data recursively.
.EXAMPLE
'C:\\Data' | Get-Report | Format-Table -AutoSize
Pipes a path into Get-Report and formats the output.
.INPUTS
System.String
.OUTPUTS
System.Management.Automation.PSCustomObject
.NOTES
Version: 1.1; Author: Your Team
.LINK
Get-Help
.LINK
https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_Comment_Based_Help
#>
[CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Low')]
param(
[Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
[ValidateNotNullOrEmpty()]
[Alias('FullName')]
[string]$Path,
[switch]$Recurse
)
process {
$resolved = $Path
if (-not (Test-Path -LiteralPath $resolved -PathType Container)) {
throw ('Path ''{0}'' does not exist or is not a folder.' -f $resolved)
}
$search = @{ Path = $resolved; File = $true; ErrorAction = 'Stop' }
if ($Recurse) { $search.Recurse = $true }
if ($PSCmdlet.ShouldProcess($resolved, 'Generate report')) {
Get-ChildItem @search |
Select-Object Name,
Directory,
@{ N = 'SizeMB'; E = { [math]::Round($_.Length / 1MB, 2) } }
}
}
}
Notes:
- Synopsis: Keep it to a single sentence. It appears in
Get-Help Get-Reportwithout switches. - Parameters: One
.PARAMETERblock per parameter; use everyday language. - Examples: Prefer end-to-end one-liners that match common tasks. Describe the outcome on the next line.
- WhatIf/Confirm:
SupportsShouldProcessadds these to help automatically. - Inputs/Outputs: Help downstream users reason about piping and serialization.
Verify Formatting with Get-Help and Automate It
Smoke test locally
- Dot-source your function or import your module.
- Run
Get-Helpwith various switches to inspect sections.
# Load the function for testing in your session
. .\Get-Report.ps1
# Quick overview
Get-Help Get-Report
# Full details including parameters, inputs/outputs, notes
Get-Help Get-Report -Full | Out-Host
# Examples only
Get-Help Get-Report -Examples | Out-Host
If something looks off (e.g., missing sections or squashed paragraphs), adjust spacing in your help block. Common fixes:
- Leave a blank line between sections like
.SYNOPSIS,.DESCRIPTION,.PARAMETER, and.EXAMPLE. - Keep
.SYNOPSISto one line. - Put the example command on the first line under
.EXAMPLE, with explanation on following lines.
Add CI guardrails with Pester
Document once; ensure it stays correct. Add tests that fail the build if required help parts are missing.
# Tests\Help.Tests.ps1
# Ensure: Get-Report has synopsis, examples, and Path parameter doc
# Import the function or module
. (Join-Path $PSScriptRoot '..\Get-Report.ps1')
Describe 'Help - Get-Report' {
It 'renders without errors' {
{ Get-Help Get-Report -Full | Out-Null } | Should -Not -Throw
}
It 'has a non-empty synopsis' {
(Get-Help Get-Report).Synopsis | Should -Not -BeNullOrEmpty
}
It 'documents the Path parameter' {
$help = Get-Help Get-Report -Full
$param = $help.parameters.parameter | Where-Object { $_.name -eq 'Path' }
$param | Should -Not -BeNullOrEmpty
($param.description.text -join ' ') | Should -Match 'Root folder'
}
It 'includes at least one example' {
(Get-Help Get-Report -Full).examples.example | Should -Not -BeNullOrEmpty
}
}
Run via CI as part of your build. You can also lint for consistency (e.g., require .LINK to docs, require .OUTPUTS for commands that emit objects).
Ship help with your module (and scale with platyPS)
Comment-based help is perfect while iterating and for standalone scripts. In modules, you can keep CBH or generate external help (XML) from Markdown using platyPS. This lets tech writers and engineers edit help without touching source files.
# One-time install
Install-Module platyPS -Scope CurrentUser -Force
# Generate MD help for exported commands
New-MarkdownHelp -Module MyModule -OutputFolder docs\help
# Update MD as commands evolve
Update-MarkdownHelp -Module MyModule -OutputFolder docs\help
# Compile MD -> External help (XML) placed under en-US
New-ExternalHelp -Path docs\help -OutputPath en-US -Force
# In your module manifest (.psd1), ensure:
# HelpInfoUri = 'https://your-docs-site/help'
If you use external help, add .ExternalHelp YourModule-help.xml to the top of your functions (or rely on the module manifest) so Get-Help picks up the compiled docs.
Pro Tips and Common Pitfalls
- Be explicit about side effects: In
.DESCRIPTION, mention if the command creates, deletes, or modifies resources. Pair withSupportsShouldProcess. - Parameter clarity first: Document units, defaults, and constraints (e.g., SizeMB is rounded to two decimals; Path must exist).
- Multiple examples: Cover the 80% cases: simple call, piped input, and a real-world pipeline (e.g., sort, filter, export).
- Keep lines readable: Long paragraphs collapse poorly in consoles. Prefer short sentences and line breaks.
- Don’t leak secrets: Use non-sensitive placeholders in examples (e.g.,
'https://example.contoso'). - Match implementation: If you add parameters, update
.PARAMETERblocks in the same PR. Let Pester enforce this. - Link for depth: Add
.LINKentries to internal runbooks or design docs.
Wrap-up
Comment-based help turns your scripts into self-documented tools with zero extra docs to maintain. Write it once, verify with Get-Help, and automate checks so it stays accurate. Your users get clearer usage; you get fewer questions and faster adoption.
Make your functions discoverable without extra docs. Read the PowerShell Advanced Cookbook: https://www.amazon.com/PowerShell-Advanced-Cookbook-scripting-advanced-ebook/dp/B0D5CPP2CQ/