From powershell
Enterprise PowerShell coding standards. Use when writing, reviewing, or generating any PowerShell code, creating PS1 scripts or functions, debugging PowerShell, or asked to help with PowerShell. Enforces best practices for structure, error handling, security, performance, and output patterns.
How this skill is triggered — by the user, by Claude, or both
Slash command
/powershell:powershellThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill enforces enterprise-grade PowerShell standards when writing, reviewing, or generating any PowerShell code. Apply all rules below automatically — do not wait to be asked.
This skill enforces enterprise-grade PowerShell standards when writing, reviewing, or generating any PowerShell code. Apply all rules below automatically — do not wait to be asked.
Load these when needed for detailed patterns, examples, and gotchas:
| File | When to load |
|---|---|
resources/common-patterns.md | Need a code pattern (script skeleton, error handling, collections, splatting, regex, pipeline functions, string building, hashtable lookups, module structure, ShouldProcess+Force, etc.) |
resources/compatibility-and-clm.md | Need PS5.1 vs PS7+ compatibility details, String.Split() changes, $IsWindows portability, or writing code that may run under AppLocker/WDAC (CLM) |
resources/traps-and-gotchas.md | Debugging unexpected behaviour around nulls, booleans, comparison operators, pipeline output pollution, $PSBoundParameters, VerbosePreference in modules, PS class limitations, or defensive null-safe patterns (property access guards, ContainsKey null guards, hashtable key/value guards, null loop entries, Add-Member verification, array double-wrapping) |
resources/get-help-and-get-member.md | Using an unfamiliar cmdlet or object — discover parameters, properties, and online docs before writing code |
resources/performance-patterns.md | Writing performance-sensitive code — benchmarked patterns for collections, strings, filtering, object creation, hashtable lookups, large file processing, function call overhead |
resources/api-and-web.md | Calling REST APIs — authentication, pagination, rate limiting/retry, JSON depth, credential management, PSReadLine history protection |
resources/security-hardening.md | Security controls — CLM enforcement (WDAC vs AppLocker), PowerShell logging (Module/Script Block/Transcription), Protected Event Logging, JEA, code signing, script injection prevention |
| Area | Rule |
|---|---|
| Structure | #Requires → help → param() → Functions → Main → Cleanup |
| Strict Mode | Set-StrictMode -Version Latest always |
| Indent | 4 spaces, ≤120 chars per line |
| Encoding | UTF-8 without BOM; always specify -Encoding utf8NoBOM explicitly |
| Naming | PascalCase functions/params, camelCase locals |
| Functions | Always [CmdletBinding()]; Verb-Noun singular nouns |
| State-changing | SupportsShouldProcess + $PSCmdlet.ShouldProcess() |
| Output | Emit [PSCustomObject]; never Write-Host for data |
| Errors | try/catch with -ErrorAction Stop; never empty catch |
| Arrays | Capture foreach output directly — never += in loops |
| WMI | Get-CimInstance not Get-WmiObject |
| Events | Get-WinEvent not Get-EventLog |
| Secrets | PSCredential/SecureString/SecretManagement vault — no plaintext |
| Native cmds | Check $LASTEXITCODE after native executables; use try/catch for cmdlets |
| Aliases | No aliases in scripts — always full cmdlet names |
| Paths | Join-Path $PSScriptRoot 'file.csv' — never assume working directory |
| CLM | Check $ExecutionContext.SessionState.LanguageMode; avoid .NET::new(), Add-Type if CLM is possible |
These are non-negotiable. Apply them to every script and function.
Set-StrictMode -Version Latest at the top of every script (not inside functions).[CmdletBinding()] on every advanced function, no exceptions.-ErrorAction Stop on every cmdlet call inside a try block, or set $ErrorActionPreference = 'Stop' for the scope. Non-terminating errors do NOT trigger catch without this.catch blocks. Always log at minimum Write-Warning or Write-Error.$_ immediately at the start of a catch block: $err = $_+= in loops. Capture foreach output directly, or use [System.Collections.Generic.List[PSObject]]::new() + .Add() in FullLanguage mode only.Invoke-Expression on untrusted or constructed input — ever.%, ?, gci, ft, etc.).Format-* mid-pipeline — emit objects; let the caller format.SupportsShouldProcess on any function that modifies state (files, registry, AD, etc.).begin/process/end at script top level — only inside pipeline-aware functions.Get-Item -Path $p not Get-Item $p).-Encoding on file I/O cmdlets — default encoding differs between PS5.1 and PS7+.| Anti-Pattern | Replace With |
|---|---|
Get-WmiObject | Get-CimInstance |
Get-EventLog | Get-WinEvent -FilterHashtable @{...} |
$array += $item in loops | Capture foreach output, or [List[PSObject]]::new() + .Add() |
Write-Host for data output | Write-Output / emit objects |
Format-Table mid-pipeline | Emit objects; format at end |
Invoke-Expression $cmd | Parameterised calls / splatting |
| Plaintext password in param | [PSCredential] + SecretManagement |
Empty catch {} | Always log or re-throw |
$? after native executables | $LASTEXITCODE (for cmdlets use try/catch) |
ConvertTo-Json without -Depth | Always ConvertTo-Json -Depth 10 (default 2 silently truncates) |
$global: for cross-function state | $script: scope — contained to the script file |
| Building HTML without encoding | Regex replace for 5 HTML special chars (CLM-safe); HttpUtility in FullLanguage only |
Read-Host for required input | [Parameter(Mandatory)] |
Aliases (gci, %, ?) | Full cmdlet names |
begin/process/end at script top | Only inside pipeline-aware functions |
Positional parameters (Get-Item $p) | Named parameters (Get-Item -Path $p) |
Out-Null in hot paths | [void](...) or $null = ... (no pipeline overhead) |
Where-Object when source has -Filter | Use -Filter on the source cmdlet |
ForEach-Object { $_.Prop } for single property | Select-Object -ExpandProperty Prop |
New-Object PSObject -Property @{} | [PSCustomObject]@{} (3x faster, cleaner) |
[array]::new() or List[T]::new() in CLM | Capture foreach output directly |
Add-Type in potentially CLM environments | Cmdlet-based alternatives |
continue inside ForEach-Object | return (acts as continue in pipeline context) |
if ($array -eq $null) | if ($null -eq $array) (left-side null check) |
if ($results) to test for empty collection | if ($results.Count -gt 0) |
-Encoding utf8 without knowing PS version | -Encoding utf8NoBOM (explicit, portable) |
"abc" -contains "ab" for substring | "abc".Contains("ab") or "abc" -match "ab" |
-like "pattern\d+" (regex in glob) | -match "pattern\d+" for regex patterns |
String "False" as a boolean | Explicit -eq 'True' or -eq $true comparison |
$list.Add($item) on ArrayList | [void]$list.Add($item) — .Add() returns the index to the pipeline |
New-Item/New-Object output leaked | $null = New-Item ... or assign to variable |
Write-Output $x (usually) | Just emit $x implicitly; use Write-Output -NoEnumerate only for arrays |
| Inconsistent output types per code path | Always emit the same type; use error stream for errors |
No [OutputType()] on functions | Declare [OutputType([PSCustomObject])] on functions with defined output |
Invoke-RestMethod without $ProgressPreference | Set $ProgressPreference = 'SilentlyContinue' at script top for non-interactive use |
Parameter named Verbose, Debug, WhatIf, etc. | These are reserved by [CmdletBinding()] — rename to avoid conflicts |
Parameter named Error, Input, Host, Args | These shadow automatic variables — use distinct names |
$string += "text" in loops | -join operator (790× faster at scale) |
Nested Where-Object for cross-collection joins | Hashtable lookup — O(n+m) vs O(n×m) |
FunctionsToExport = '*' in manifest | Explicit function list — avoids ~15s import penalty |
"str".Split('ab') for multi-char splitting | "str".Split([char[]]'ab') — portable across PS5.1 and PS7+ |
if ($IsWindows) without edition check | $PSVersionTable.PSEdition -eq 'Desktop' -or $IsWindows |
Invoke-RestMethod -Authentication + manual Authorization header | Use only one — -Authentication silently overrides the header |
-FollowRelLink without -MaximumFollowRelLink | Always set -MaximumFollowRelLink to prevent infinite loops |
| Class method with implicit output | Class methods discard all output except return — assign or return explicitly |
Import-Module MyModule; [ClassType]::new() | using module MyModule required for class types |
hidden property assumed private | hidden properties ARE serialized by ConvertTo-Json |
$obj.Prop on object that may lack Prop | if ($obj.PSObject.Properties['Prop']) { $obj.Prop } — safe under Set-StrictMode and avoids PropertyNotFoundException |
$hash[$key] without null-guarding the key | if ($key) { $hash[$key] } — null key crashes Dictionary<TKey,TValue>; guard first |
$hash.ContainsKey($key) without null guard | $key -and $hash.ContainsKey($key) — null argument throws on Dictionary<> types |
Loop body with no null guard on $item | if ($null -eq $item) { continue } at top — API/pipeline collections can contain null entries |
| Building lookup without guarding key or value | Check if ($id -and $value) before $lookup[$id] = $value — null keys create silent corrupt entries |
| `$connector | Add-Member ...; $connector._Prop` |
return ,$array in function + @(Func) at call site | Double-wrap bug: $items = @(Func) when function returns ,$arr gives a 1-element wrapper; use direct assignment $items = Func |
Before finalising any generated PowerShell, verify:
Set-StrictMode -Version Latest present at script top[CmdletBinding()] on every advanced function-ErrorAction Stop on all cmdlet calls in try blocks (or $ErrorActionPreference = 'Stop' set)catch blocks; $err = $_ saved at catch start+= inside loops for collection buildingWrite-Host used for data (only acceptable for interactive UI messaging)Invoke-Expression on dynamic/user-supplied inputSupportsShouldProcess on all state-modifying functionsfinally block for any resource cleanup[PSCustomObject]), not pre-formatted stringsGet-CimInstance used instead of Get-WmiObject$LASTEXITCODE checked after every native executable call$PSScriptRoot used for all paths relative to the script file-Encoding utf8NoBOM specified on all file I/O operations$null is on the LEFT side of all null comparisons.Count -eq 0, not bare if ($collection)@($results) used when .Count is needed on cmdlet outputreturn (not continue) used to skip items in ForEach-Object.NET::new(), Add-Type) avoided if script may run under AppLocker/WDAC??, ? :, -Parallel) annotated or avoided if PS5.1 support required-Filter used on source cmdlets rather than downstream Where-Object where possible-like used for glob patterns, -match used for regex — not mixed-contains/-in used for collection membership, not string substring checks[void]$list.Add(...) used when calling ArrayList.Add() (it returns the index)New-Item, New-Object, etc.) inside functions have their output suppressed or assignedVerbose, Debug, WhatIf, Confirm, ErrorAction, etc.)Error, Input, Host, Args, This)[OutputType()] declared on functions that emit a defined object type$ProgressPreference = 'SilentlyContinue' set in non-interactive scripts that call Invoke-WebRequest/Invoke-RestMethodWrite-Host not used for data — only for interactive UI messages-join, not += (790× slower at scale)Where-Object (O(n+m) vs O(n×m))FunctionsToExport list, not '*' (~15s penalty at import)String.Split() with multi-char argument uses [char[]] cast for portable behaviour$IsWindows portability uses $PSVersionTable.PSEdition -eq 'Desktop' -or $IsWindowsif ($obj.PSObject.Properties['Prop']) before $obj.Prop (strict-mode-safe)if ($key) { $hash[$key] } not bare $hash[$key]ContainsKey() calls are null-guarded: $key -and $hash.ContainsKey($key)if ($null -eq $item) { continue }if ($id -and $value) { $lookup[$id] = $value }Add-Member note properties are verified before access: if ($obj.PSObject.Properties['_Name']) { $obj._Name }return ,$array convention are called with direct assignment ($x = Func), NOT $x = @(Func) (double-wrap bug)npx claudepluginhub dstreefkerk/claude-skills --plugin powershellProvides PowerShell 7+ expertise for cross-platform scripting, CI/CD automation (GitHub Actions/Azure DevOps), module management (PSGallery), and cloud tasks (Azure/AWS/M365).
Covers Windows PowerShell patterns and pitfalls: operator syntax with parentheses, Unicode avoidance, null checks, error handling, paths, arrays, JSON ops.
Provides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.