Tools#

The PowerShell toolchain is more uniform than zsh / bash. One interpreter (pwsh), one linter (PSScriptAnalyzer), one test framework (Pester), one language server. The operator mostly picks editors and CI hosts.

Interpreter#

  • pwsh, PowerShell 7+, cross-platform, .NET-backed. The current line.

  • powershell, Windows PowerShell 5.1, Windows-only, .NET Framework. Legacy; still shipped with Windows.

Check the version.

$PSVersionTable

Useful invocation flags.

Flag

Effect

-File path

Run a script and exit

-Command 'expr' / -c

Run an expression and exit

-NoProfile

Skip $PROFILE (clean startup)

-NoLogo

Skip the banner

-NonInteractive

Disallow prompts; fail fast in automation

-ExecutionPolicy Bypass

Run unsigned scripts (Windows; default-deny otherwise)

$ pwsh -NoProfile -File ./script.ps1
$ pwsh -NoProfile -Command 'Get-Process | Select-Object -First 3'

Linting#

  • PSScriptAnalyzer, the standard PowerShell linter. Catches unapproved verbs, alias usage, missing [CmdletBinding()], parameter validation gaps. CI gate of choice.

Install-Module PSScriptAnalyzer -Scope CurrentUser

Invoke-ScriptAnalyzer -Path ./script.ps1
Invoke-ScriptAnalyzer -Path ./Module -Recurse -Severity Error,Warning
Invoke-ScriptAnalyzer -Path ./Module -Settings PSGallery

Most projects ship a PSScriptAnalyzerSettings.psd1 with the rule set the team has agreed on.

@{
    IncludeRules = @('PSAvoidUsingCmdletAliases',
                     'PSAvoidUsingWriteHost',
                     'PSUseConsistentIndentation')
    ExcludeRules = @()
    Rules = @{
        PSUseConsistentIndentation = @{
            IndentationSize = 4
            Kind = 'space'
        }
    }
}

Formatting#

PSScriptAnalyzer ships Invoke-Formatter for the formatting rules. It is the closest thing to a PowerShell shfmt.

$code = Get-Content ./script.ps1 -Raw
Invoke-Formatter -ScriptDefinition $code |
    Set-Content ./script.ps1

Testing#

  • Pester, the de-facto BDD test framework.

Install-Module Pester -Scope CurrentUser
Invoke-Pester ./tests
Invoke-Pester ./tests -Output Detailed -CodeCoverage './module/*.ps1'

Debugging#

PowerShell ships a usable debugger built into the host.

  • Set-PSBreakpoint -Script script.ps1 -Line 42

  • Set-PSBreakpoint -Command Get-Thing (break when a name is called)

  • Set-PSBreakpoint -Variable foo -Mode Write

  • Debug-Job, Debug-Process, Debug-Runspace, attach to a running script.

Inline.

Wait-Debugger                                  # like a breakpoint;
                                               # drops into the debugger

In VS Code (with the PowerShell extension), F5 launches the debugger directly; gutter clicks set breakpoints.

Trace.

Set-PSDebug -Trace 2                           # trace each statement
Set-PSDebug -Off

For per-call timing.

Measure-Command { Get-LargeFiles -Path . -MinKB 500 }

Profiling#

No bundled profiler. The standard reach is:

Measure-Command { ./script.ps1 }

# per-step, with PSScriptAnalyzer-style trace
Trace-Command -Name ParameterBinding -Expression { Get-Process } -PSHost

Editing#

  • PowerShell extension for VS Code, LSP, debugger, Pester integration, snippets. The default authoring environment.

  • Editor Services, the language server, also used by Neovim, Sublime, Emacs.

  • ISE, the legacy Windows-only PowerShell ISE. Frozen at PowerShell 5.1; do not target for new work.

CI Integration#

PowerShell on the runner. The standard pattern.

# GitHub Actions
- shell: pwsh
  run: |
    Set-StrictMode -Version Latest
    $ErrorActionPreference = 'Stop'
    Invoke-Pester -Output Detailed -CI
    Invoke-ScriptAnalyzer -Path ./Module -Recurse `
        -Severity Error -EnableExit

References#