PowerShell Development Skill
SkillAI & modelsPowerShell best practices for scripts used in FieldWorks (dev scripts & CI helpers). Use when writing or modifying PowerShell scripts in scripts/ or Build/Agent/.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the PowerShell Development Skill skill
What this skill tells your AI
The instructions your AI receives, as published by sillsdev/fieldworks in .claude/skills/powershell/SKILL.md and read by ahel’s review.
Conventions and safety patterns for PowerShell scripts in scripts/ and CI.
Style and Linting
- Scripts in
Build/Agent/, and anything else reached frombuild.ps1ortest.ps1, must run under both Windows PowerShell 5.1 and PowerShell 7. CI executes the build and test steps under 5.1, so 6+-only syntax that parses cleanly on 7 can still fail or silently misbehave there: the backtick u{} escape resolves to literal text under 5.1 instead of a code point, and-Encoding utf8BOM/utf8NoBOMthrow a parameter-binding error. RunBuild/Agent/powershell-compat.ps1to check, and prefer syntax both engines share over anything PowerShell Core adds. - Use
Set-StrictMode -Version Latest. - Use
Write-Hostsparingly; preferWrite-OutputandWrite-Errorfor correct streams. - Use
-ErrorAction Stopin helper functions when errors should abort execution. - No Unicode icons or emojis in output messages (e.g.,
✓,✗,⚠,🔧). Use plain ASCII text like[OK],[FAIL],[WARN],ERROR:instead. Unicode causes encoding issues in CI logs.
Traps that produce a wrong answer instead of an error
The first one below is the dangerous one: it yields a plausible result with no
warning, so nothing prompts you to look. The others fail loudly, but only under
Set-StrictMode -Version Latest, which this repo requires.
An operator after a bare function call binds as an argument
-replace, -split, -match, and friends written after an unparenthesized
function call are parsed as further arguments to that call, not applied to its
result. The operator is silently ignored.
# BAD: -replace and '\s+' become arguments 2 and 3 of Norm; nothing is replaced
$key = Norm ($text) -replace '\s+', ''
# GOOD: parenthesize the call, then apply the operator to its result
$key = (Norm $text) -replace '\s+', ''
Measure-Object -Sum over an empty collection returns $null
Reading .Sum (or .Maximum, .Average) off that result then throws
"The property 'Sum' cannot be found on this object" -- which surfaces far from
the empty input that caused it.
# BAD: throws whenever $items happens to be empty
$total = ($items | Measure-Object -Property Length -Sum).Sum
# GOOD
$total = 0
foreach ($item in $items) { $total += $item.Length }
Returning a collection from a function unrolls it
return $list enumerates into the pipeline: an empty collection becomes $null
and a single element becomes a scalar, so the caller's .Count throws. Prefix
with a comma to return the collection itself.
# BAD: (Get-Ids).Count throws when the list is empty
function Get-Ids { $ids = New-Object System.Collections.Generic.List[int]; return $ids }
# GOOD
function Get-Ids { $ids = New-Object System.Collections.Generic.List[int]; return ,$ids }
The stop-parsing token consumes the rest of the line
--% passes everything after it to the native command verbatim, including any
closing bracket you meant PowerShell to read. It cannot appear inside @(...),
$(...), or any other expression that has to be closed.
# BAD: --% swallows the closing paren; parse error, not a runtime error
$msg = @(git --% log -1 --format=%B)
# GOOD: keep --% on a statement of its own, or drop it when it is not needed
$msg = @(git log -1 --pretty=%B)
Security
- Avoid embedding secrets in scripts; read from env vars and prefer platform secret stores.
- Do not commit credential tokens in any scripts or docs.
Testing and Execution
- Use
pwsh -NoProfile -ExecutionPolicy Bypass -Filein CI wrappers. - Add small smoke test steps to validate paths and required tools are present.
Auto-Approval Patterns
CRITICAL: Agent terminal security blocks complex commands. The following require manual approval:
- Pipes (
|) - Semicolons (
;) or&& - Redirection (
2>&1)
ALWAYS use scripts/Agent/ wrapper scripts for these operations. Do not attempt raw commands.
See terminal.instructions.md for the complete transformation table.
Examples
# Good: simple commands auto-approve
.\build.ps1
git status
# Good: use wrapper scripts (ALWAYS for git with pipes)
.\scripts\Agent\Git-Search.ps1 -Action show -Ref "release/9.3" -Path "file.h" -HeadLines 20
.\scripts\Agent\Git-Search.ps1 -Action log -HeadLines 20
.\scripts\Agent\Read-FileContent.ps1 -Path "file.cs" -HeadLines 50 -LineNumbers
# BAD: these require manual approval - NEVER USE
# git log --oneline | head -20
# Get-Content file.cs | Select-Object -First 50
Signals
- GitHub stars
- 111
- Forks
- 42
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
powershell- Source
- github.com/sillsdev/fieldworks