Skip to content
Back to skills

Pwsh Scripting Style

ASecurity

Writing robust PowerShell scripts and functions: param blocks, [CmdletBinding()], advanced functions, $PSScriptRoot, Set-StrictMode, comment-based help, modules, and Pester tests. Use when authoring a .ps1 script or reusable function, adding parameters/validation, making a script locate its own files, hardening against typos with strict mode, or setting up tests. Applies to 5.1 and 7+ with edition notes.

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 27, 2026
ai-agentsshelltesting

Security analysis

A100/100

Scanned September 27, 2026

npx -y skills add Ugbot/ai-grind --skill pwsh-scripting-style --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Pwsh Scripting Style?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Pwsh Scripting Style
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/ugbot-pwsh-scripting-style/badge)](https://www.skillsdirectory.com/skills/ugbot-pwsh-scripting-style)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: pwsh-scripting-style
description: >
  Writing robust PowerShell scripts and functions: param blocks, [CmdletBinding()],
  advanced functions, $PSScriptRoot, Set-StrictMode, comment-based help, modules,
  and Pester tests. Use when authoring a .ps1 script or reusable function, adding
  parameters/validation, making a script locate its own files, hardening against
  typos with strict mode, or setting up tests. Applies to 5.1 and 7+ with edition
  notes.
---

# PowerShell scripting style

Conventions for scripts that are safe, predictable, and testable. Same in 5.1 and
7+ unless noted.

## Script skeleton

```powershell
#Requires -Version 5.1
[CmdletBinding()]
param(
    [Parameter(Mandatory)] [string]   $InputPath,
    [ValidateSet('Debug','Release')]  [string] $Config = 'Release',
    [ValidateRange(1,64)]             [int]    $Jobs   = 4,
    [switch] $Force
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

# ... body ...
```

- `#Requires -Version 5.1` (or `7.0`) fails fast on the wrong edition.
- `[CmdletBinding()]` turns the script/function into an *advanced* one: free
  `-Verbose`, `-Debug`, `-ErrorAction`, etc., and `$PSCmdlet`.
- `param()` must be the **first executable statement** (after `#Requires` and
  comment-based help).

## Parameters & validation

```powershell
param(
    [Parameter(Mandatory, Position=0)] [string] $Name,
    [Parameter(ValueFromPipeline)]     [string[]] $Items,
    [ValidateNotNullOrEmpty()]         [string] $Out,
    [ValidatePattern('^\d{4}-\d{2}-\d{2}$')] [string] $Date,
    [ValidateScript({ Test-Path $_ })] [string] $Path,
    [switch] $DryRun
)
```

Validation attributes fail at bind time with clear messages, so prefer them over
hand-written `if` checks. Use `[switch]` for booleans (`-DryRun`, not
`-DryRun $true`).

## `Set-StrictMode` catches typos and nulls

```powershell
Set-StrictMode -Version Latest
```

Turns these into errors: referencing an **undefined variable**, accessing a
**non-existent property**, calling a function like a method. Use it in every
non-trivial script. (Note: it makes some loose patterns fail, so initialize
variables before use.)

## Locate files relative to the script with `$PSScriptRoot`

Never depend on the current directory. `$PSScriptRoot` is the folder containing the
running script.

```powershell
$config = Join-Path $PSScriptRoot 'config.json'
$module = Join-Path $PSScriptRoot 'lib\helpers.psm1'
```

## Advanced functions with pipeline support

```powershell
function Convert-Thing {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline)] [string[]] $Name
    )
    begin   { $count = 0 }                      # runs once
    process { foreach ($n in $Name) { $count++; $n.ToUpper() } }  # runs per pipeline item
    end     { Write-Verbose "converted $count" }                 # runs once
}

'a','b' | Convert-Thing -Verbose
```

`begin/process/end` is what lets a function stream pipeline input correctly.

## Output discipline

- **A function's output is whatever it leaves on the pipeline.** Don't `return`
  strings you meant as messages. They become the function's data.
- Use the right stream for messages:

```powershell
Write-Output $result        # DATA (the return value) — pipeline
Write-Verbose 'progress'    # shown only with -Verbose
Write-Warning 'heads up'    # warning stream
Write-Error 'bad'           # error stream (see pwsh-errors)
Write-Host 'ui text'        # console-only, NOT capturable (use sparingly)
```

Suppress unwanted output with `| Out-Null` or `$null = expr` (faster than
`| Out-Null`).

## Comment-based help

```powershell
<#
.SYNOPSIS  Build and test the project.
.PARAMETER Config  Debug or Release.
.EXAMPLE   .\build.ps1 -Config Release -Jobs 8
#>
[CmdletBinding()] param(...)
```

`Get-Help .\build.ps1` then renders it like a real cmdlet's help.

## Modules (reuse across scripts)

```powershell
# helpers.psm1
function Get-Thing { param($x) ... }
Export-ModuleMember -Function Get-Thing

# consumer
Import-Module (Join-Path $PSScriptRoot 'helpers.psm1') -Force
```

For a shipped module add a `.psd1` manifest (`New-ModuleManifest`).

## Testing with Pester

```powershell
# build.Tests.ps1  (Pester v5 syntax)
Describe 'Get-Thing' {
    It 'doubles its input' {
        Get-Thing 21 | Should -Be 42
    }
    It 'throws on null' {
        { Get-Thing $null } | Should -Throw
    }
}
# run:  Invoke-Pester .\build.Tests.ps1
```

Pester v5 ships in-box on many 7+ setups; on 5.1 `Install-Module Pester -Force`.

## Execution policy (running .ps1 at all)

If scripts are blocked: set a per-user policy (no admin):

```powershell
Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
# or run one script bypassing policy:
powershell -ExecutionPolicy Bypass -File .\build.ps1
```

## Quick reference

| Need | Do |
|---|---|
| typed/validated args | `param([ValidateSet(...)][string]$X)` |
| boolean flag | `[switch] $Force` |
| catch typos/nulls | `Set-StrictMode -Version Latest` |
| find script's files | `Join-Path $PSScriptRoot ...` |
| pipeline-aware fn | `begin/process/end` + `ValueFromPipeline` |
| message vs data | `Write-Verbose`/`Write-Warning` vs `Write-Output` |
| drop output | `$null = expr` |
| require edition | `#Requires -Version 7.0` |
| run blocked script | `powershell -ExecutionPolicy Bypass -File x.ps1` |

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…