Agent skill

PowerShell Patterns

PowerShell best practices for Windows development, including cmdlet patterns, module development, and error handling

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/development/powershell-patterns-nategarelik-claude-code-config

SKILL.md

PowerShell Patterns

When to Use

Perfect for:

  • Windows system administration and automation
  • CI/CD pipelines on Windows runners
  • Build scripts and deployment automation
  • Cross-platform .NET/PowerShell tooling
  • Windows service management and monitoring

Not ideal for:

  • Unix-only environments (use bash/sh instead)
  • Simple one-off commands (use cmd.exe for basic operations)
  • Performance-critical loops (consider compiled .NET instead)

Quick Reference

Cmdlet Naming & Creation

powershell
# Standard verb-noun naming
# Get-Item, Set-Property, New-Item, Remove-Item, Test-Path, Invoke-Command

# Function with proper error handling
function Get-MyData {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Path,

        [Parameter()]
        [int]$Timeout = 30
    )

    process {
        try {
            Write-Verbose "Retrieving data from $Path"
            Get-Content -Path $Path -ErrorAction Stop
        }
        catch {
            Write-Error "Failed to get data: $_"
            throw
        }
    }
}

Pipeline & Splatting

powershell
# Pipeline-aware functions
function Process-Items {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline = $true)]
        [object[]]$InputObject
    )

    process {
        $InputObject | ForEach-Object {
            # Process each item
            $_
        }
    }
}

# Splatting for complex commands
$params = @{
    Path            = "C:\data"
    Filter          = "*.txt"
    Recurse         = $true
    ErrorAction     = "Stop"
    WarningAction   = "SilentlyContinue"
}

Get-ChildItem @params

Error Handling Pattern

powershell
try {
    # Primary operation
    $result = Invoke-Command -ComputerName $Server -ScriptBlock { Get-Process }
}
catch [System.UnauthorizedAccessException] {
    Write-Error "Access denied to $Server"
    exit 1
}
catch [System.Net.NetworkInformation.PingException] {
    Write-Error "$Server is unreachable"
    exit 2
}
catch {
    Write-Error "Unexpected error: $_"
    exit 99
}
finally {
    Write-Verbose "Cleanup operations"
}

Module Development

powershell
# Module structure: MyModule/
# ├── MyModule.psd1 (manifest)
# ├── MyModule.psm1 (main module)
# └── Public/
#     └── Get-Data.ps1

# MyModule.psm1
$PublicFunctions = @(Get-ChildItem -Path "$PSScriptRoot/Public/*.ps1" -ErrorAction SilentlyContinue)

foreach ($Function in $PublicFunctions) {
    . $Function.FullName
}

Export-ModuleMember -Function @($PublicFunctions.Basename)

Hashtables & PSCustomObject

powershell
# Hashtable for structured data
$config = @{
    Server   = "localhost"
    Port     = 5432
    Database = "mydb"
    Timeout  = 30
}

# PSCustomObject for better null-coalescing and export
$result = [PSCustomObject]@{
    Name      = "Item"
    Count     = 42
    Status    = "Active"
    Timestamp = Get-Date
}

# Export to JSON/CSV
$result | Export-Csv -Path "output.csv" -NoTypeInformation
$result | ConvertTo-Json | Out-File "output.json"

Script Signing

powershell
# Create self-signed certificate for testing
$cert = New-SelfSignedCertificate -CertStoreLocation Cert:\CurrentUser\My -Subject "MyCodeSign"

# Sign script
Set-AuthenticodeSignature -FilePath "script.ps1" -Certificate $cert

# Verify signature
Get-AuthenticodeSignature -FilePath "script.ps1"

# Set execution policy (may need admin)
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

Deep Dive

Advanced Pipeline Processing

powershell
# Begin/Process/End pattern for efficiency
function Invoke-Batch {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline = $true)]
        [object[]]$InputObject,

        [int]$BatchSize = 10
    )

    begin {
        $batch = @()
    }

    process {
        $batch += $_

        if ($batch.Count -ge $BatchSize) {
            # Process batch
            $batch | ForEach-Object { Write-Output $_ }
            $batch = @()
        }
    }

    end {
        # Process remaining items
        if ($batch.Count -gt 0) {
            $batch | ForEach-Object { Write-Output $_ }
        }
    }
}

Parallel Processing

powershell
# ForEach-Object -Parallel (PowerShell 7+)
$items = 1..100

$items | ForEach-Object -Parallel {
    Write-Output "Processing $_"
    Start-Sleep -Seconds 1
} -ThrottleLimit 4

# Using jobs for older PowerShell
$jobs = foreach ($item in $items) {
    Start-Job -ScriptBlock {
        param($i)
        "Processed $i"
    } -ArgumentList $item
}

$jobs | Wait-Job | Receive-Job

Advanced Error Handling

powershell
# Create custom exception
class CustomException : System.Exception {
    [int]$ErrorCode

    CustomException([string]$Message, [int]$Code) : base($Message) {
        $this.ErrorCode = $Code
    }
}

# Use custom error records
$errorRecord = New-Object System.Management.Automation.ErrorRecord(
    (New-Object CustomException("Something failed", 42)),
    "CustomError",
    [System.Management.Automation.ErrorCategory]::OperationStopped,
    $null
)

$PSCmdlet.WriteError($errorRecord)

Configuration Management

powershell
# Use JSON/YAML config with validation
$configPath = "config.json"
$config = Get-Content $configPath | ConvertFrom-Json

# Validate with schema
$schema = @{
    Server    = "string"
    Port      = "integer"
    Timeout   = "integer"
    Retries   = "integer"
}

foreach ($key in $schema.Keys) {
    if (-not $config.PSObject.Properties[$key]) {
        throw "Missing required config: $key"
    }
}

Anti-Patterns

DON'T: Use Write-Host for Output

powershell
# Bad - output is hard to redirect/pipe
Write-Host "Result: $result"

# Good - use Write-Output or pipeline
Write-Output "Result: $result"
$result

DON'T: Ignore $ErrorActionPreference

powershell
# Bad - silently continues on error
Get-Item "missing.txt"

# Good - be explicit about error handling
Get-Item "missing.txt" -ErrorAction Stop
# Or handle explicitly:
if (Test-Path "missing.txt") {
    Get-Item "missing.txt"
}

DON'T: Use String Concatenation for Commands

powershell
# Bad - security risk and hard to maintain
$cmd = "Get-Process | Where-Object {$_.Name -like '$pattern'}"
Invoke-Expression $cmd

# Good - use parameters and splatting
Get-Process | Where-Object {$_.Name -like $pattern}

DON'T: Ignore Pipeline Value Types

powershell
# Bad - assumes string input
function Process {
    param([string]$Value)
    # Only works with strings
}

# Good - accept multiple types
function Process {
    param([object]$Value)
    # Works with any object
}

DON'T: Mix Write-Verbose/Write-Error with Write-Host

powershell
# Bad - inconsistent output handling
Write-Host "Info: $msg"
Write-Error "Error: $err"

# Good - consistent stream usage
Write-Verbose "Info: $msg"
Write-Error "Error: $err"

DON'T: Hard-code Paths

powershell
# Bad - breaks on different systems
$path = "C:\Users\Admin\AppData"

# Good - use environment variables
$path = $env:APPDATA
$path = "$env:ProgramFiles\MyApp"
$path = [System.IO.Path]::GetTempPath()

Didn't find tool you were looking for?

Be as detailed as possible for better results