PowerShell Expert Skill
Overview
This skill covers PowerShell 7+ (Core) for cross-platform automation, system administration, and DevOps scripting. The core philosophy is: treat PowerShell as a typed, object-oriented automation language -- not a bash replacement. Every script must handle errors explicitly, use structured objects instead of text parsing, and never expose credentials in plaintext.
When to Use
-
When writing PowerShell automation scripts for Windows or cross-platform
-
When auditing existing PowerShell scripts for security and reliability
-
When setting up CI/CD pipelines with PowerShell-based tooling
-
When managing Windows infrastructure with DSC or JEA
-
When building PowerShell modules with proper structure and testing
-
When migrating from Windows PowerShell 5.1 to PowerShell 7+
Iron Laws
-
ALWAYS set $ErrorActionPreference = 'Stop' at the top of scripts -- silent failures are the primary cause of automation bugs and data loss.
-
NEVER hardcode credentials or secrets in scripts -- use Microsoft.PowerShell.SecretManagement module to pull secrets from vaults.
-
ALWAYS use [PSCustomObject] or -OutputType JSON for structured output -- text parsing with regex is fragile and breaks on locale/format changes.
-
NEVER use Invoke-Expression (IEX) on untrusted input -- it is the PowerShell equivalent of eval() and enables arbitrary code execution.
-
ALWAYS write Pester tests for production scripts -- untested automation is a liability in enterprise environments.
Anti-Patterns
Anti-Pattern Why It Fails Correct Approach
Parsing command output with regex instead of using objects Breaks on locale changes, format updates, and different OS versions Use cmdlet object output directly or convert to PSCustomObject
Using Invoke-Expression to build dynamic commands Enables code injection; any user input can execute arbitrary PowerShell Use splatting (@params ) for dynamic parameters; use Start-Process for external commands
Catching all exceptions with empty catch blocks Silently swallows errors; automation appears to succeed when it failed Use typed catch blocks; log and re-throw unexpected exceptions
Using Windows PowerShell 5.1 syntax without checking compatibility Scripts fail on Linux/macOS where PS 7 is the only option Use $PSVersionTable.PSVersion checks; prefer PS 7 cross-platform cmdlets
Storing credentials in script variables or config files Plaintext secrets in source control; credential theft risk Use Get-Secret from SecretManagement module; inject via environment variables in CI
Workflow
Step 1: Script Structure
#Requires -Version 7.0 #Requires -Modules @{ ModuleName='Microsoft.PowerShell.SecretManagement'; ModuleVersion='1.1.0' }
$ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest
function Invoke-DataBackup { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$TargetPath,
[Parameter()]
[switch]$Compress
)
begin {
Write-Verbose "Starting backup to $TargetPath"
}
process {
try {
# Business logic here
}
catch [System.IO.IOException] {
Write-Error "IO error during backup: $_"
throw
}
catch {
Write-Error "Unexpected error: $_"
throw
}
}
end {
Write-Verbose "Backup complete"
}
}
Step 2: Secure Secret Retrieval
Register a vault (one-time setup)
Register-SecretVault -Name 'AzureKeyVault' -ModuleName 'Az.KeyVault'
Retrieve secret at runtime
$apiKey = Get-Secret -Name 'MyApiKey' -Vault 'AzureKeyVault' -AsPlainText
Use in automation (never log the value)
$headers = @{ 'Authorization' = "Bearer $apiKey" } Invoke-RestMethod -Uri $endpoint -Headers $headers
Step 3: Object-Oriented Pipeline
Process structured data through the pipeline
Get-ChildItem -Path $target -Filter *.json | ForEach-Object { $data = Get-Content -Path $.FullName | ConvertFrom-Json [PSCustomObject]@{ FileName = $.Name ItemCount = $data.items.Count LastModified = $_.LastWriteTime } } | Sort-Object -Property ItemCount -Descending | Export-Csv -Path 'report.csv' -NoTypeInformation
Step 4: Pester Testing
Invoke-DataBackup.Tests.ps1
Describe 'Invoke-DataBackup' { BeforeAll { . $PSScriptRoot/Invoke-DataBackup.ps1 }
Context 'When target path exists' {
It 'Should create backup file' {
$result = Invoke-DataBackup -TargetPath $TestDrive
$result | Should -Not -BeNullOrEmpty
Test-Path "$TestDrive/backup.zip" | Should -BeTrue
}
}
Context 'When target path is invalid' {
It 'Should throw IO exception' {
{ Invoke-DataBackup -TargetPath '/nonexistent/path' } |
Should -Throw -ExceptionType ([System.IO.IOException])
}
}
}
Step 5: Cross-Platform Compatibility
Use Join-Path for all path operations
$configPath = Join-Path -Path $HOME -ChildPath '.config' -AdditionalChildPath 'myapp', 'settings.json'
Check platform before using platform-specific features
if ($IsWindows) { # Windows-specific: registry, WMI, COM $os = Get-CimInstance -ClassName Win32_OperatingSystem } elseif ($IsLinux -or $IsMacOS) { # Unix-specific: /proc, systemctl $os = uname -a }
Complementary Skills
Skill Relationship
devops
CI/CD pipeline integration with PowerShell scripts
docker-compose
Containerized PowerShell automation
terraform-infra
Infrastructure provisioning alongside PS configuration
tdd
Test-driven development methodology for Pester tests
Memory Protocol (MANDATORY)
Before starting:
Read .claude/context/memory/learnings.md for prior PowerShell modules, Pester testing patterns, or OS-specific workarounds.
After completing: Record new PowerShell modules, Pester testing patterns, or OS-specific workarounds to .claude/context/memory/learnings.md .
ASSUME INTERRUPTION: Your context may reset. If it's not in memory, it didn't happen.