Claude Code Plugins

Community-maintained marketplace

Feedback
0
0

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

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name PowerShell Patterns
description PowerShell best practices for Windows development, including cmdlet patterns, module development, and error handling
keywords powershell, windows, scripting, cmdlets, modules

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

# 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

# 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

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

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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()