Awesome-omni-skill powershell
PowerShell cmdlet and scripting best practices based on Microsoft guidelines Triggers on: **/*.ps1,**/*.psm1
git clone https://github.com/diegosouzapw/awesome-omni-skill
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/cli-automation/powershell" ~/.claude/skills/diegosouzapw-awesome-omni-skill-powershell && rm -rf "$T"
skills/cli-automation/powershell/SKILL.mdPowerShell Cmdlet Development Guidelines
This guide provides PowerShell-specific instructions to help GitHub Copilot generate idiomatic, safe, and maintainable scripts. It aligns with Microsoft’s PowerShell cmdlet development guidelines.
Naming Conventions
-
Verb-Noun Format:
- Use approved PowerShell verbs (Get-Verb)
- Use singular nouns
- PascalCase for both verb and noun
- Avoid special characters and spaces
-
Parameter Names:
- Use PascalCase
- Choose clear, descriptive names
- Use singular form unless always multiple
- Follow PowerShell standard names
-
Variable Names:
- Use PascalCase for public variables
- Use camelCase for private variables
- Avoid abbreviations
- Use meaningful names
-
Alias Avoidance:
- Use full cmdlet names
- Avoid using aliases in scripts (e.g., use Get-ChildItem instead of gci)
- Document any custom aliases
- Use full parameter names
Example
function Get-UserProfile { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Username, [Parameter()] [ValidateSet('Basic', 'Detailed')] [string]$ProfileType = 'Basic' ) process { # Logic here } }
Parameter Design
-
Standard Parameters:
- Use common parameter names (
,Path
,Name
)Force - Follow built-in cmdlet conventions
- Use aliases for specialized terms
- Document parameter purpose
- Use common parameter names (
-
Parameter Names:
- Use singular form unless always multiple
- Choose clear, descriptive names
- Follow PowerShell conventions
- Use PascalCase formatting
-
Type Selection:
- Use common .NET types
- Implement proper validation
- Consider ValidateSet for limited options
- Enable tab completion where possible
-
Switch Parameters:
- Use [switch] for boolean flags
- Avoid $true/$false parameters
- Default to $false when omitted
- Use clear action names
Example
function Set-ResourceConfiguration { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Name, [Parameter()] [ValidateSet('Dev', 'Test', 'Prod')] [string]$Environment = 'Dev', [Parameter()] [switch]$Force, [Parameter()] [ValidateNotNullOrEmpty()] [string[]]$Tags ) process { # Logic here } }
Pipeline and Output
-
Pipeline Input:
- Use
for direct object inputValueFromPipeline - Use
for property mappingValueFromPipelineByPropertyName - Implement Begin/Process/End blocks for pipeline handling
- Document pipeline input requirements
- Use
-
Output Objects:
- Return rich objects, not formatted text
- Use PSCustomObject for structured data
- Avoid Write-Host for data output
- Enable downstream cmdlet processing
-
Pipeline Streaming:
- Output one object at a time
- Use process block for streaming
- Avoid collecting large arrays
- Enable immediate processing
-
PassThru Pattern:
- Default to no output for action cmdlets
- Implement
switch for object return-PassThru - Return modified/created object with
-PassThru - Use verbose/warning for status updates
Example
function Update-ResourceStatus { [CmdletBinding()] param( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [string]$Name, [Parameter(Mandatory)] [ValidateSet('Active', 'Inactive', 'Maintenance')] [string]$Status, [Parameter()] [switch]$PassThru ) begin { Write-Verbose 'Starting resource status update process' $timestamp = Get-Date } process { # Process each resource individually Write-Verbose "Processing resource: $Name" $resource = [PSCustomObject]@{ Name = $Name Status = $Status LastUpdated = $timestamp UpdatedBy = $env:USERNAME } # Only output if PassThru is specified if ($PassThru.IsPresent) { Write-Output $resource } } end { Write-Verbose 'Resource status update process completed' } }
Error Handling and Safety
-
ShouldProcess Implementation:
- Use
[CmdletBinding(SupportsShouldProcess = $true)] - Set appropriate
levelConfirmImpact - Call
for system changes$PSCmdlet.ShouldProcess() - Use
for additional confirmationsShouldContinue()
- Use
-
Message Streams:
for operational details withWrite-Verbose-Verbose
for warning conditionsWrite-Warning
for non-terminating errorsWrite-Error
for terminating errorsthrow- Avoid
except for user interface textWrite-Host
-
Error Handling Pattern:
- Use try/catch blocks for error management
- Set appropriate ErrorAction preferences
- Return meaningful error messages
- Use ErrorVariable when needed
- Include proper terminating vs non-terminating error handling
- In advanced functions with
, prefer[CmdletBinding()]
over$PSCmdlet.WriteError()Write-Error - In advanced functions with
, prefer[CmdletBinding()]
over$PSCmdlet.ThrowTerminatingError()throw - Construct proper ErrorRecord objects with category, target, and exception details
-
Non-Interactive Design:
- Accept input via parameters
- Avoid
in scriptsRead-Host - Support automation scenarios
- Document all required inputs
Example
function Remove-UserAccount { [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] param( [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()] [string]$Username, [Parameter()] [switch]$Force ) begin { Write-Verbose 'Starting user account removal process' $ErrorActionPreference = 'Stop' } process { try { # Validation if (-not (Test-UserExists -Username $Username)) { $errorRecord = [System.Management.Automation.ErrorRecord]::new( [System.Exception]::new("User account '$Username' not found"), 'UserNotFound', [System.Management.Automation.ErrorCategory]::ObjectNotFound, $Username ) $PSCmdlet.WriteError($errorRecord) return } # Confirmation $shouldProcessMessage = "Remove user account '$Username'" if ($Force -or $PSCmdlet.ShouldProcess($Username, $shouldProcessMessage)) { Write-Verbose "Removing user account: $Username" # Main operation Remove-ADUser -Identity $Username -ErrorAction Stop Write-Warning "User account '$Username' has been removed" } } catch [Microsoft.ActiveDirectory.Management.ADException] { $errorRecord = [System.Management.Automation.ErrorRecord]::new( $_.Exception, 'ActiveDirectoryError', [System.Management.Automation.ErrorCategory]::NotSpecified, $Username ) $PSCmdlet.ThrowTerminatingError($errorRecord) } catch { $errorRecord = [System.Management.Automation.ErrorRecord]::new( $_.Exception, 'UnexpectedError', [System.Management.Automation.ErrorCategory]::NotSpecified, $Username ) $PSCmdlet.ThrowTerminatingError($errorRecord) } } end { Write-Verbose 'User account removal process completed' } }
Documentation and Style
-
Comment-Based Help: Include comment-based help for any public-facing function or cmdlet. Inside the function, add a
help comment with at least:<# ... #>
Brief description.SYNOPSIS
Detailed explanation.DESCRIPTION
sections with practical usage.EXAMPLE
descriptions.PARAMETER
Type of output returned.OUTPUTS
Additional information.NOTES
-
Consistent Formatting:
- Follow consistent PowerShell style
- Use proper indentation (4 spaces recommended)
- Opening braces on same line as statement
- Closing braces on new line
- Use line breaks after pipeline operators
- PascalCase for function and parameter names
- Avoid unnecessary whitespace
-
Pipeline Support:
- Implement Begin/Process/End blocks for pipeline functions
- Use ValueFromPipeline where appropriate
- Support pipeline input by property name
- Return proper objects, not formatted text
-
Avoid Aliases: Use full cmdlet names and parameters
- Avoid using aliases in scripts (e.g., use Get-ChildItem instead of gci); aliases are acceptable for interactive shell use.
- Use
instead ofWhere-Object
or?where - Use
instead ofForEach-Object% - Use
instead ofGet-ChildItem
orlsdir
Full Example: End-to-End Cmdlet Pattern
function New-Resource { [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [string]$Name, [Parameter()] [ValidateSet('Development', 'Production')] [string]$Environment = 'Development' ) begin { Write-Verbose 'Starting resource creation process' } process { try { if ($PSCmdlet.ShouldProcess($Name, 'Create new resource')) { # Resource creation logic here Write-Output ([PSCustomObject]@{ Name = $Name Environment = $Environment Created = Get-Date }) } } catch { $errorRecord = [System.Management.Automation.ErrorRecord]::new( $_.Exception, 'ResourceCreationFailed', [System.Management.Automation.ErrorCategory]::NotSpecified, $Name ) $PSCmdlet.ThrowTerminatingError($errorRecord) } } end { Write-Verbose 'Completed resource creation process' } }