Open the PowerShell Gallery and you will find thousands of modules that do one thing well and everything else badly: no tests, no style enforcement, and help files that consist of a single autogenerated stub. Meanwhile, the modules Microsoft itself ships go through a pipeline that most open-source projects never set up. That pipeline is built from three tools — Pester (3,330 stars), PSScriptAnalyzer (2,153 stars), and PlatyPS (872 stars) — and together they turn “a script someone shared” into “a module you can ship to production and maintain for years.”

The catch is that these three tools are complementary, not competitive: Pester tests behavior, PSScriptAnalyzer enforces static quality, and PlatyPS generates the Get-Help experience. Most comparisons treat them as rivals; the people who actually ship modules use all three in a single CI pipeline. This guide covers each one, when it matters, and how to wire them together on Linux CI with PowerShell 7.

TL;DR: Quick Verdict

If you only adopt one tool, adopt Pester — it is the testing framework with no serious alternative in the ecosystem, it runs identically on Windows, Linux, and macOS, and its mock and code-coverage features are the difference between “it works on my machine” and “it works.” Add PSScriptAnalyzer the day you want a second human reviewing every pull request, because its rule set encodes the PowerShell Team’s best practices as automated checks. Add PlatyPS when you have users, not just code — it generates the MAML help that Get-Help, -?, and Microsoft Learn all consume, authored in Markdown instead of XML. For module development, the question is never “which one” — it is “in what order do they run in CI.”

Pester vs PSScriptAnalyzer vs PlatyPS: Feature Comparison

FeaturePesterPSScriptAnalyzerPlatyPS
RoleBehavior testing frameworkStatic code analysis / lintingHelp & documentation generator
GitHub repoPester/PesterPowerShell/PSScriptAnalyzerPowerShell/PlatyPS
GitHub stars3,3302,153872
LicenseApache-2.0MITMIT
Last push (2026)Sep 5Aug 26Sep 5
Current release line5.6 stable; 6.0 alpha1.24+Microsoft.PowerShell.PlatyPS (C# rewrite)
PlatformsWindows PS 5.1, PowerShell 7.4+ on Win/Linux/macOSCross-platform via PowerShell 7Windows PS 5.1+ and PowerShell 7 on Win/Linux/macOS
Output formatRich console, -CI mode, NUnit/JUnit XMLDiagnosticRecord objects (error/warning/info)Markdown, MAML XML, YAML
Code coverageBuilt-in (-CodeCoverage)NoNo
MockingBuilt-in (Mock -CommandName)NoNo
CI integrationTFS, AppVeyor, TeamCity, Jenkins, GitHub ActionsAzure DevOps, VS Code, any shell stepAny shell step (pwsh)
Install commandInstall-Module -Name PesterInstall-Module -Name PSScriptAnalyzerInstall-PSResource -Name Microsoft.PowerShell.PlatyPS

Decision Matrix: What Do You Actually Need?

Use CaseRecommended ToolWhy
Verify a function returns correct output across edge casesPesterDescribe/It/Should with -TestCases gives data-driven tests with zero framework ceremony
Stop a dependency or cmdlet from being called in a testPester MockIntercepts any command including cmdlets; pairs with Should -Invoke assertions
Enforce naming, quoting, and security rules on every PRPSScriptAnalyzerRuns in seconds as a CI gate; community rule sets extend the built-in catalog
Auto-fix trivial style violations in legacy scriptsPSScriptAnalyzer -FixApplies safe fixes for common rules before you refactor by hand
Ship professional Get-Help content without writing XMLPlatyPSAuthor in Markdown, export to MAML; also export YAML for docs-as-code workflows
Publish help to Microsoft Learn-style external docsPlatyPSThe exact tool Microsoft uses to produce published PowerShell documentation
Full module release pipelineAll three in sequenceTest → lint → generate help → package → publish (see CI example below)

Pester: The Testing Framework PowerShell Finally Got

Pester is to PowerShell what pytest is to Python: the community standard that the language’s own tooling builds on. It runs on Windows PowerShell 5.1 and PowerShell 7.4+ across Windows, Linux, and macOS, and its syntax reads like a spec rather than a test harness. The canonical example from the official README shows the core model — Describe blocks, It assertions, and Should:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
BeforeAll {
    # your function
    function Get-Planet ([string]$Name='*')
    {
        $planets = @(
            @{ Name = 'Mercury' }
            @{ Name = 'Venus'   }
            @{ Name = 'Earth'   }
            @{ Name = 'Mars'    }
            @{ Name = 'Jupiter' }
            @{ Name = 'Saturn'  }
            @{ Name = 'Uranus'  }
            @{ Name = 'Neptune' }
        ) | foreach { [PSCustomObject]$_ }

        $planets | where { $_.Name -like $Name }
    }
}

# Pester tests
Describe 'Get-Planet' {
  It "Given no parameters, it lists all 8 planets" {
    $allPlanets = Get-Planet
    $allPlanets.Count | Should -Be 8
  }

  Context "Filtering by Name" {
    It "Given valid -Name '<Filter>', it returns '<Expected>'" -TestCases @(
      @{ Filter = 'Earth'; Expected = 'Earth' }
      @{ Filter = 'ne*'  ; Expected = 'Neptune' }
      @{ Filter = 'ur*'  ; Expected = 'Uranus' }
      @{ Filter = 'm*'   ; Expected = 'Mercury', 'Mars' }
    ) {
      param ($Filter, $Expected)

      $planets = Get-Planet -Name $Filter
      $planets.Name | Should -Be $Expected
    }
  }
}

Two features make Pester indispensable for real modules. First, Mock intercepts any command — including cmdlets you do not own — so you can test error paths without touching the filesystem, registry, or network:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
function Remove-Cache {
    Remove-Item "$env:TEMP\cache.txt"
}

Describe 'Remove-Cache' {
    It 'Removes cached results from temp\cache.txt' {
        Mock -CommandName Remove-Item -MockWith {}

        Remove-Cache

        Should -Invoke -CommandName Remove-Item -Times 1 -Exactly
    }
}

Second, code coverage is built in: Invoke-Pester -CodeCoverage ./src/MyModule.psm1 reports which lines your tests actually execute, and the output integrates with CI systems. Pester’s own README shows the CI story — it integrates with TFS, AppVeyor, TeamCity, and Jenkins, and this appveyor.yml is the canonical cross-platform starting point:

1
2
3
4
5
6
7
8
9
version: 1.0.{build}
image:
  - Visual Studio 2017
  - Ubuntu
install:
  - ps: Install-Module Pester -Force -Scope CurrentUser
build: off
test_script:
  - ps: Invoke-Pester -CI

The Ubuntu image line matters: it is how you prove your module runs on PowerShell Core, not just Windows PowerShell. As of September 2026, Pester 5.6 is the stable baseline (note: upgrading past 5.6.0 shows a certificate-change warning; use -SkipPublisherCheck), with the 6.0 line in alpha and Pester’s own development running on GitHub Actions.

PSScriptAnalyzer: Your Tireless Second Reviewer

PSScriptAnalyzer is a static code checker for PowerShell modules and scripts. It runs a set of rules based on PowerShell best practices identified by the PowerShell Team and community, and emits DiagnosticResults (errors and warnings) that point at potential defects and suggest fixes. The built-in catalog covers everything from uninitialized variables to misuse of the PSCredential type to dangerous calls like Invoke-Expression.

Basic usage could not be simpler:

1
2
3
4
5
6
7
Install-Module -Name PSScriptAnalyzer

# Analyze a single script
Invoke-ScriptAnalyzer -Path .\script.ps1

# Analyze a whole module tree, only show warnings and errors
Invoke-ScriptAnalyzer -Path .\MyModule -Recurse -Severity Warning

The output is a stream of objects (RuleName, Severity, Message, and the offending line), which makes it trivial to gate a build. For teams that want a curated rule set, a settings file selects and configures rules explicitly — a pattern the official documentation recommends for consistent enforcement:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Settings.psd1
@{
    IncludeRules = @(
        'PSAvoidUsingInvokeExpression'
        'PSUseDeclaredVarsMoreThanAssignments'
        'PSAvoidUsingWriteHost'
    )
    Rules = @{
        PSUseDeclaredVarsMoreThanAssignments = @{ Enable = $true }
    }
}

# Then run with the settings file
Invoke-ScriptAnalyzer -Path .\MyModule -Recurse -Settings .\Settings.psd1

Where PSScriptAnalyzer shines in 2026 is automation: the VS Code PowerShell extension surfaces its diagnostics as you type, Azure DevOps has first-class tasks for it, and because it is a plain PowerShell module, any CI system can run it. There is even a -Fix switch that applies safe, mechanical fixes for common rules — ideal for cleaning up a legacy script before you refactor it by hand. The project remains under active maintenance (last push August 26, 2026), and notably, its own build pipeline depends on Pester and PlatyPS — Microsoft dogfoods the whole toolchain.

PlatyPS: Markdown In, Professional Help Out

PowerShell’s help system reads MAML, an XML format that nobody wants to hand-author. PlatyPS is the tool Microsoft uses to create the content you get from Get-Help, letting you write help files in Markdown and convert them to MAML. The modern version, Microsoft.PowerShell.PlatyPS, is a complete C# rewrite (built on markdig, the same Markdown parser Microsoft Learn uses) that adds an object model, YAML import/export, and dramatically better performance — it processes thousands of Markdown files in seconds.

Install and use the core flow:

1
2
Install-PSResource -Name Microsoft.PowerShell.PlatyPS
Import-Module Microsoft.PowerShell.PlatyPS

The documented authoring workflow has four steps: create or update Markdown help files, edit them (with descriptions, parameter docs, and examples), test them to ensure they render and link correctly, then convert and publish. The conversion step pipes help objects into the MAML exporter — this exact pattern comes from the project’s own test suite:

1
2
3
4
5
6
7
# Import Markdown help for your commands, then export MAML
$markdownFiles = 'Get-Thing.md', 'Set-Thing.md'
$commandHelp = $markdownFiles | ForEach-Object {
    Import-MarkdownCommandHelp -Path "docs/$_"
}

$commandHelp | Export-MamlCommandHelp -OutputFolder en-US -Force

Supporting cmdlets cover the whole lifecycle: New-MarkdownCommandHelp scaffolds help from a module’s existing commands, Test-MarkdownCommandHelp validates your authored files, Show-HelpPreview renders help the way Get-Help will display it, and Export-YamlCommandHelp gives docs-as-code teams a diff-friendly format for review before the final MAML conversion. Note the naming: the legacy platyPS module (0.14.2) is frozen on the v1 branch and no longer actively maintained — new projects should target Microsoft.PowerShell.PlatyPS, which is where all development effort sits as of September 2026.

Wiring the Toolchain Together: Pitfalls and a Working CI Pattern

  • Do not run Pester 5 and 6 side by side. The 6.0 alpha line changes module-level behavior; pin your CI to one major version (Install-Module Pester -RequiredVersion 5.6.0) or your BeforeAll/Mock semantics will drift between environments.
  • Mock cannot intercept commands in another session or module scope you do not control. Mocking works within the test session; for cross-module seams, inject dependencies via parameters or use module-qualified calls that Pester can still intercept (Mock 'Microsoft.PowerShell.Management\Remove-Item').
  • PSScriptAnalyzer flags, not fixes, everything. -Fix handles only safe mechanical rules. Treat the analyzer as a gate with a curated settings file, not as a replacement for review — and expect false positives on intentionally dynamic code; SuppressMessageAttribute is the sanctioned escape hatch.
  • Legacy PlatyPS help is not directly compatible. If you generated help with the old platyPS module, re-run generation with the new module: the object model and validation are stricter, and old files often fail Test-MarkdownCommandHelp until regenerated.
  • MAML output is a build artifact, not source. Keep Markdown in your repository, generate MAML in CI, and never hand-edit the XML — the next export overwrites it.
  • Linux CI is the real test. A module that only runs on Windows PowerShell will rot. Your pipeline should run Pester on an Ubuntu runner at minimum; that single Ubuntu image line in the CI config catches path, case-sensitivity, and cmdlet-availability bugs that Windows-only testing hides.
  • Order matters. Test first (fast feedback), then analyze (quality gate), then generate help (only for code that passed) — then package and publish from a clean tree.

A minimal GitHub Actions job for a PowerShell module looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
name: module-ci
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-powershell@v1
      - name: Install toolchain
        shell: pwsh
        run: |
          Install-Module Pester -Force -Scope CurrentUser
          Install-Module PSScriptAnalyzer -Force -Scope CurrentUser
          Install-PSResource -Name Microsoft.PowerShell.PlatyPS -Force
      - name: Lint
        shell: pwsh
        run: Invoke-ScriptAnalyzer -Path ./src -Recurse -Severity Warning
      - name: Test
        shell: pwsh
        run: Invoke-Pester -CI -CodeCoverage ./src
      - name: Validate help
        shell: pwsh
        run: Import-MarkdownCommandHelp -Path (Get-ChildItem ./docs/*.md) | Out-Null

The PowerShell ecosystem’s testing story parallels what we have covered for other languages — see our JavaScript testing frameworks comparison and the Python pytest plugins guide for mocking and coverage patterns in those ecosystems. If you are running module CI on self-hosted runners, our Buildbot vs GoCD vs Concourse CI/CD guide covers the orchestration side. And because documentation quality is part of shipping, the PHPUnit vs Pest comparison shows how another ecosystem structures its testing layers.

FAQ

Is Pester the only testing framework for PowerShell? Effectively yes. Pester is the de facto standard and the one Microsoft’s own projects use. The ecosystem has no serious alternative with comparable mocking, code coverage, and CI integration — which is why the practical question is Pester version (5.6 stable vs the 6.0 line), not framework choice.

Does PSScriptAnalyzer work on Linux? Yes. PSScriptAnalyzer is a PowerShell module that runs wherever PowerShell 7 runs, including Ubuntu and macOS runners. Its -Fix and settings-file features behave identically across platforms, which is why it is a natural CI gate for cross-platform modules.

What is the difference between PlatyPS and the new Microsoft.PowerShell.PlatyPS module? The original platyPS (0.14.x) is in maintenance mode on the v1 branch. Microsoft.PowerShell.PlatyPS is a C# rewrite that uses markdig, adds an object model, supports YAML import/export, and is significantly faster. New projects should install Microsoft.PowerShell.PlatyPS from the PowerShell Gallery.

Can I generate help files from my existing module without writing Markdown by hand? Yes. New-MarkdownCommandHelp scaffolds Markdown help from a module’s commands, including parameter metadata. You then edit the Markdown to add descriptions and examples, validate with Test-MarkdownCommandHelp, and export MAML.

How do I enforce PSScriptAnalyzer rules consistently across a team? Create a Settings.psd1 file that lists the rules you want (IncludeRules) and their configuration, commit it to the repository, and invoke Invoke-ScriptAnalyzer with -Settings on every pull request. This makes linting deterministic instead of depending on each developer’s default rule set.

Does Pester support code coverage? Yes. Invoke-Pester -CodeCoverage ./src reports which lines your tests executed, and the results integrate with CI systems. The AppVeyor example in Pester’s official README shows the canonical cross-platform CI setup.


💰 想测试你的市场判断力?我用 Polymarket 做预测市场交易——这是全球最大的预测市场平台,从大选结果到技术监管时间线,什么都可以押注。和赌博不同,这是真正的信息市场:你懂的信息越多,胜率越高。我靠预测技术相关事件的走向已经赚了不少。用我的邀请链接注册:Polymarket.com