@fadwen/powershell-copilot-standards
PowerShell Copilot Instructions
Install
agr install @fadwen/powershell-copilot-standards --target copilotWrites 1 file into .github/copilot-instructions.md, pinned to git-cb7a81a7.
- .github/copilot-instructions.md
Document
PowerShell Copilot Instructions
๐ฏ Core Principles
You are assisting with enterprise PowerShell development that prioritizes security, maintainability, and performance. Always follow these foundational principles:
- Security by Design: Implement comprehensive input validation and secure credential handling
- Correlation Tracking: Include correlation IDs for audit trails and troubleshooting
- Error Resilience: Implement robust error handling with structured logging
- Performance Awareness: Write efficient code with appropriate optimization
- Community Standards: Follow established PowerShell best practices and style guidelines
- Enterprise Integration: Design for scalability and organizational compliance
๐ Community Standards Integration
PowerShell Community Standards Compliance
All code generation must comply with established PowerShell community best practices and style guidelines, incorporating expert feedback for accuracy:
Best Practices Enforcement
- Tool vs Controller Design: Create reusable functions (tools) vs one-time automation scripts (controllers)
- Modular Architecture: Functions accept input via parameters, output to pipeline for maximum reusability
- Error Handling Standards: Use proper
$_handling in catch blocks, appropriate null checking patterns - Performance Optimization: Context-dependent string operations, test when it matters, use language features over cmdlets
- Security by Design: Modern credential handling with
[PSCredential]::new(), validate all inputs, sanitize data
Style Guide Compliance
- Code Layout: One True Brace Style (opening brace at end of line), 4-space indentation, 115-character line limit
- Function Structure: Advanced functions with CmdletBinding, appropriate parameter validation (no redundant attributes)
- Naming Conventions: Approved PowerShell verbs only (use
Get-Verb), full command names, explicit parameter names, PascalCase - Documentation Standards: Proper comment-based help format with opening
<#, no maintenance-heavy versioning in NOTES
Anti-Patterns to Avoid
- Error Handling: Don't use
$Error[0]in catch blocks (use$_), don't force exceptions for simple existence checks - Throw Behavior: Be aware that
throwstatements can be silenced by-ErrorAction SilentlyContinue- useWrite-Error -ErrorAction Stopfor proper termination when needed - Parameter Usage: Don't pass parameters to functions without validating they have values, especially when parameters might be empty strings or whitespace
- Performance: Don't use StringBuilder for small string operations (< 100 concatenations), don't use array appending in large loops
- Validation: Don't add ValidateNotNullOrEmpty to mandatory parameters unless they'll be used in downstream function calls where empty strings matter
- Output: Don't use misleading
[OutputType([PSCustomObject])]declarations - Documentation: Don't use broken comment-based help (missing
<#opener)
Script Development Guidelines
- Approved Verbs Only: Use Microsoft's approved PowerShell verbs (
Get-Verb) for ALL function names consistently - Version Compatibility: Default target is PowerShell 7.6 (LTS). Maintain Windows PowerShell 5.1 compatibility only when the project must run on estates without pwsh installed โ decide this deliberately, not by habit
- Version Annotations: Declare the target with
#requires -Version 7.6at the script header. Never emit#requires -Version 7.0โ PowerShell 7.0โ7.3 are retired, and 7.4/7.5 lose support on 10-Nov-2026 - Version-Dependent Features: Before emitting a cmdlet or parameter added in 7.5 or 7.6, confirm the declared target supports it โ see powershell-version.instructions.md
- Output Standards: Avoid
Write-Hostexcept when colored console output is explicitly required- Use
Write-Verbose,Write-Debug,Write-Information,Write-Warning, orWrite-Errorinstead - Leverage pipeline output for data flow
- Use
- Pipeline Efficiency: Always design functions to work efficiently in the pipeline
Modern PowerShell Features
- Use
[PSCredential]::new()instead ofNew-Objectfor credential creation - Use
Install-PSResource/Find-PSResource(Microsoft.PowerShell.PSResourceGet, in-box since 7.4) over PowerShellGet v2Install-Module/Find-Module - Call
Start-ThreadJobunqualified โ the module was renamed toMicrosoft.PowerShell.ThreadJobin 7.6, so the oldThreadJob\qualifier breaks - Leverage current-version features when the declared target allows:
ConvertTo-CliXml/ConvertFrom-CliXml,ConvertFrom-Json -DateKind,Test-Json -IgnoreComments(7.5+);Get-Command -ExcludeModule,Get-Clipboard -Delimiter(7.6+) - Avoid redundant parameter validation (mandatory parameters are implicitly not null/empty)
๐ก๏ธ Security Requirements
Input Validation
# Appropriate validation patterns
param(
[Parameter(Mandatory)]
[string]$ComputerName, # Mandatory implies not null/empty
[Parameter()]
[ValidateNotNullOrEmpty()] # Explicit validation for optional params
[ValidatePattern('^[a-zA-Z0-9\-\.]+$')]
[string]$OptionalServer
)
# Sanitize file paths to prevent traversal attacks
$safePath = Resolve-Path -Path $InputPath -ErrorAction Stop
if ($safePath.Path.Contains('..')) {
throw "Invalid path: Path traversal not allowed"
}
Credential Management
# Modern credential creation (PowerShell 5+)
$credential = [PSCredential]::new($username, $securePassword)
# Always use secure credential handling per community standards
$secureString = ConvertTo-SecureString $Password -AsPlainText -Force
Security Scanning Patterns
Avoid these patterns in production code:
- Hardcoded passwords or API keys
- Plain text credential storage
- SQL injection vulnerabilities in dynamic queries
- Unvalidated user input in file paths or commands
Security Logging
# Log security-relevant activities with correlation IDs
Write-Warning "Security event: $SecurityEventDescription - User: $env:USERNAME - Time: $(Get-Date)"
๐ง Function Template
function Get-ExampleData {
<#
.SYNOPSIS
Brief description using approved verb-noun pattern
.DESCRIPTION
Detailed explanation including:
- Business value and purpose
- Key functionality and features
- Dependencies and requirements
- Performance characteristics
.PARAMETER Name
Description of mandatory parameter (implicitly validated as not null/empty)
.PARAMETER OptionalPath
Description of optional parameter with explicit validation
.EXAMPLE
PS> Get-ExampleData -Name "Value"
DESCRIPTION: What this example demonstrates
OUTPUT: Expected output description
USE CASE: Business scenario where this is useful
.NOTES
Author: Jeffrey Stuhr
Blog: https://www.techbyjeff.net
LinkedIn: https://www.linkedin.com/in/jeffrey-stuhr-034214aa/
TROUBLESHOOTING:
- For common issues: .\Troubleshooting\Common\Function-Name-Issues.md
- For performance: .\Troubleshooting\Performance\Optimization-Guide.md
#>
[CmdletBinding(SupportsShouldProcess)]
[OutputType('ProcessedData')] # Use descriptive type name, not [PSCustomObject]
param(
[Parameter(Mandatory, ValueFromPipeline)]
[ValidateNotNullOrEmpty()] # Explicit validation even for mandatory params when used in downstream functions
[string]$Name,
[Parameter()]
[ValidateNotNullOrEmpty()]
[string]$OptionalPath,
[Parameter()]
[string]$CorrelationId = [System.Guid]::NewGuid().ToString()
)
begin {
Write-EnterpriseLog -Level "INFO" -Message "Starting $($MyInvocation.MyCommand.Name)" -CorrelationId $CorrelationId
# Load configuration
$config = Get-EnvironmentConfiguration
}
process {
try {
# Validate parameters before using in function calls (expert feedback Part 2)
if (-not $Name.Trim()) {
Write-Error "Name parameter cannot be empty or whitespace" -ErrorAction Stop
return
}
if ($PSCmdlet.ShouldProcess($Name, "Process Data")) {
# Modern credential creation if needed
if ($needsCredential) {
$credential = [PSCredential]::new($username, $securePassword)
}
# Context-appropriate string operations
if ($items.Count -lt 100) {
# Small operations - += is fine and readable
$summary = "Processing: $Name"
$summary += " at $(Get-Date)"
} else {
# Large operations - use StringBuilder
$sb = [System.Text.StringBuilder]::new()
foreach ($item in $items) {
[void]$sb.AppendLine("Processing: $item")
}
$summary = $sb.ToString()
}
# Appropriate null checking for optional data
$optionalData = Get-OptionalData -Name $Name.Trim() -ErrorAction SilentlyContinue
if ($optionalData) {
# Process optional data - null check is valid here
Write-Verbose "Found optional data for $Name"
}
# Return data with explicit type name for better tooling
$result = [PSCustomObject]@{
PSTypeName = 'ProcessedData'
Name = $Name
ProcessedAt = Get-Date
CorrelationId = $CorrelationId
HasOptionalData = [bool]$optionalData
}
Write-Output $result
}
}
catch {
# CORRECT: Use $_ not $Error[0] in catch blocks
$errorDetails = @{
Message = $_.Exception.Message
Category = $_.CategoryInfo.Category
TargetObject = $_.TargetObject
CorrelationId = $CorrelationId
Function = $MyInvocation.MyCommand.Name
Line = $_.InvocationInfo.ScriptLineNumber
}
Write-EnterpriseLog -Level "ERROR" -Message "Processing failed" -Details $errorDetails -CorrelationId $CorrelationId
# Use Write-Error with -ErrorAction Stop instead of bare throw for proper error action handling
Write-Error "Failed to process $Name : $($_.Exception.Message)" -ErrorAction Stop
}
}
end {
Write-EnterpriseLog -Level "INFO" -Message "Completed $($MyInvocation.MyCommand.Name)" -CorrelationId $CorrelationId
}
}
๐๏ธ Mandatory File Organization
All PowerShell projects must follow this standardized structure:
ProjectRoot/
โโโ Public/ # Exported functions (tools for reuse)
โ โโโ Get-SomethingData.ps1 # Use approved verbs consistently
โโโ Private/ # Internal functions
โ โโโ Connect-InternalService.ps1
โโโ Classes/ # PowerShell classes with explicit type names
โ โโโ CustomClass.ps1
โโโ Tests/ # Pester test suite
โ โโโ Unit/
โ โโโ Integration/
โ โโโ Performance/
โโโ Troubleshooting/ # Organized troubleshooting docs
โ โโโ Common/
โ โโโ Security/
โ โโโ Performance/
โ โโโ Integration/
โโโ Documentation/ # Project documentation
โ โโโ PowerShell-Best-Practices.md
โ โโโ PowerShell-Style-Guide.md
โโโ Configuration/ # Environment configs
โโโ README.md # Main documentation
Critical: All troubleshooting documentation must be organized in the Troubleshooting/ folder with appropriate
subfolders for consistent reference.
๐ Performance Guidelines
Context-Dependent String Operations
# For small operations (< 100 concatenations), += is fine and more readable
if ($items.Count -lt 100) {
$output = "Processing started at: $(Get-Date)"
foreach ($item in $items) {
$output += "`nProcessing: $item"
}
} else {
# For large operations, use StringBuilder for performance
$sb = [System.Text.StringBuilder]::new()
[void]$sb.AppendLine("Processing started at: $(Get-Date)")
foreach ($item in $items) {
[void]$sb.AppendLine("Processing: $item")
}
$output = $sb.ToString()
}
Array Accumulation (version-dependent โ read this before flagging +=)
# โ
Best on every version โ assign the loop output directly, no per-iteration allocation
$results = foreach ($item in $collection) {
Get-ProcessedItem -Item $item
}
# โ
Also good: pipeline output
$results = $collection | ForEach-Object { Get-ProcessedItem -Item $_ }
# โ
When accumulation is genuinely conditional, use the generic List
$results = [System.Collections.Generic.List[object]]::new()
foreach ($item in $collection) {
if (Test-ItemRelevant -Item $item) {
$results.Add((Get-ProcessedItem -Item $item))
}
}
# โ ๏ธ `+=` โ O(nยฒ) on Windows PowerShell 5.1 and 7.4, but PowerShell 7.5 optimized it for
# object arrays, where it now outperforms List<T>.Add(). Flag it only when the declared
# target includes 5.1/7.4, or when the loop is unbounded on any version.
$results = @()
foreach ($item in $collection) { $results += Get-ProcessedItem -Item $item }
# โ Never generate ArrayList โ non-generic, boxes values, forces [void] noise on every Add()
$results = [System.Collections.ArrayList]::new()
Pipeline Optimization (Community Best Practice)
# Prefer pipeline operations over foreach loops for large datasets
$results = $largeCollection |
Where-Object { $_.Status -eq 'Active' } |
ForEach-Object { Process-Item $_ } |
Sort-Object Name
# Use language features over cmdlets for performance
# Preferred: Where Status -eq Running
# Over: Where-Object { $_.Status -eq 'Running' }
โ ๏ธ Error Handling Standards
Proper Error Handling Pattern
# Set appropriate error action preference
$ErrorActionPreference = 'Stop'
try {
# Core operation with correlation tracking
$correlationId = [System.Guid]::NewGuid()
Write-Verbose "Operation started - CorrelationId: $correlationId"
# Main logic here
$result = Invoke-Operation -CorrelationId $correlationId
Write-Verbose "Operation completed successfully - CorrelationId: $correlationId"
return $result
}
catch {
# CORRECT: Use $_ not $Error[0] in catch blocks
Write-Error "Operation failed: $($_.Exception.Message) - CorrelationId: $correlationId"
# Log to troubleshooting system
$errorDetails = @{
CorrelationId = $correlationId
Function = $MyInvocation.MyCommand.Name
ErrorMessage = $_.Exception.Message # Use $_ directly
StackTrace = $_.ScriptStackTrace
Timestamp = Get-Date
}
$errorDetails | ConvertTo-Json | Out-File ".\Troubleshooting\Logs\error-$correlationId.json"
throw
}
finally {
# Cleanup resources following community disposal patterns
if ($resource -and $resource -is [System.IDisposable]) {
$resource.Dispose()
}
}
Advanced Error Handling Considerations
Throw Behavior with ErrorAction SilentlyContinue
Important: Be aware that throw statements can be silenced by -ErrorAction SilentlyContinue, even though throw
creates terminating errors:
# โ ๏ธ This throw will be silenced and execution continues
function Test-ErrorHandling {
param([string]$Value)
if (-not $Value) {
throw "Value is required" # This gets silenced with -ErrorAction SilentlyContinue
}
Write-Output "Processing: $Value"
}
# This will NOT stop execution as expected:
Test-ErrorHandling -Value "" -ErrorAction SilentlyContinue
Write-Output "This line will execute unexpectedly"
# โ
Better approach for validation that should never be silenced:
function Test-ErrorHandling {
param([string]$Value)
if (-not $Value) {
# Use Write-Error with -ErrorAction Stop for proper termination
Write-Error "Value is required" -ErrorAction Stop
return # Explicit return as safety net
}
Write-Output "Processing: $Value"
}
Parameter Validation Before Usage
Best Practice: Always validate parameters have values before using them in function calls:
# โ Potential issue - passing potentially empty/null parameters
function Process-Data {
param(
[string]$ServerName,
[string]$DatabaseName
)
# This could fail if parameters are empty
$connection = Connect-Database -Server $ServerName -Database $DatabaseName
}
# โ
Proper validation before usage
function Process-Data {
param(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]$ServerName,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]$DatabaseName
)
# Additional runtime validation for critical operations
if (-not $ServerName.Trim()) {
Write-Error "ServerName cannot be empty or whitespace" -ErrorAction Stop
return
}
if (-not $DatabaseName.Trim()) {
Write-Error "DatabaseName cannot be empty or whitespace" -ErrorAction Stop
return
}
# Now safe to use in function calls
$connection = Connect-Database -Server $ServerName.Trim() -Database $DatabaseName.Trim()
}
Appropriate Null Checking vs Exception Handling
# VALID: Simple existence check for optional data
$optionalUser = Get-ADUser -Identity $username -ErrorAction SilentlyContinue
if ($optionalUser) {
# Process user if found
Write-Verbose "Found user: $($optionalUser.Name)"
} else {
Write-Verbose "User not found: $username"
}
# ALSO VALID: Exception-based for critical operations
try {
$criticalUser = Get-ADUser -Identity $username -ErrorAction Stop
# Process critical user data
} catch {
Write-Error "Critical user lookup failed: $($_.Exception.Message)"
throw
}
# Use null checks when you only care about existence
# Use try/catch when you need detailed error information or when failure should halt execution
๐งช Testing Standards
Minimum Testing Requirements
- Unit Tests: Required for all public functions (minimum 80% coverage)
- Integration Tests: Required for external dependencies
- Performance Tests: Required for functions processing >100 items
- Security Tests: Required for credential handling and input validation
Pester Test Template
Targets Pester 6.0+. See Testing Framework for the
full standards and Assertion Guide
for the Should-* reference.
#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '6.0.0' }
Describe "Get-ExampleData" -Tag "Unit" {
BeforeAll {
# Pester 6 discovers and runs one file at a time, so each file
# imports its own dependencies. Only one BeforeAll per block -
# duplicates throw.
Import-Module $PSScriptRoot\..\ModuleName.psd1 -Force
# Mock external dependencies following community patterns
Mock Write-Verbose { }
Mock Get-OptionalData { $MockResult }
}
Context "Valid Input" {
# -ForEach data is injected as variables; no param() block needed.
# Never use 'Input' as a key - it collides with the automatic variable
# and the test data will not bind.
It "Should return expected result for <TestValue>" -ForEach @(
@{ TestValue = 'ValidValue1'; Expected = 'ExpectedResult1' }
@{ TestValue = 'ValidValue2'; Expected = 'ExpectedResult2' }
) {
$result = Get-ExampleData -Name $TestValue
$result | Should-HaveType ([PSCustomObject])
$result.PSTypeName | Should-Be 'ProcessedData'
$result.Name | Should-Be $Expected
}
}
Context "Error Handling" {
It "Should handle errors correctly using proper error patterns" {
Mock Get-OptionalData { throw "Test error" }
{ Get-ExampleData -Name "TestValue" } |
Should-Throw -ExceptionMessage "*Test error*"
}
}
}
๐๏ธ Module Development Standards
Module Manifest Requirements
# ModuleName.psd1
@{
RootModule = 'ModuleName.psm1'
ModuleVersion = '1.0.0' # Semantic versioning
GUID = 'Generate-New-Guid'
Author = 'Jeffrey Stuhr'
Description = 'Clear description of module purpose and business value'
# Target PowerShell 7.6 (LTS) for new modules. Use '5.1' + @('Desktop','Core') only when
# the module must run on Windows estates without pwsh installed.
PowerShellVersion = '7.6'
CompatiblePSEditions = @('Core')
FunctionsToExport = @('Get-Something', 'Set-Something') # Explicit exports only
CmdletsToExport = @()
VariablesToExport = @()
AliasesToExport = @()
RequiredModules = @(
@{ ModuleName = 'PSFramework'; ModuleVersion = '1.7.270' }
)
PrivateData = @{
PSData = @{
Tags = @('PowerShell', 'Enterprise', 'Automation')
ProjectUri = 'https://github.com/organization/module'
LicenseUri = 'https://github.com/organization/module/blob/main/LICENSE'
}
}
}
๐ฏ Community Standards Integration
Automatic Validation
All generated code must automatically comply with:
- Error Handling: Use
$_in catch blocks, appropriate null checking patterns, be aware ofthrowbehavior with ErrorAction - Parameter Validation: Validate parameters before using in function calls, especially for empty/whitespace values
- Approved Verbs: Only Microsoft-approved verbs from Get-Verb (consistently in ALL examples)
- String Operations: Context-appropriate choice between += and StringBuilder
- Modern PowerShell: Use
[PSCredential]::new()instead of New-Object;Install-PSResourceoverInstall-Module - Version Targeting: Declared
#requires -Version/PowerShellVersionmatches the features actually used, and is 7.6 unless 5.1 support is a stated requirement - Output Types: Use descriptive type names or custom classes, not misleading [PSCustomObject]
- Documentation: Proper comment-based help format with opening
<#marker - Performance: Loop output assigned directly where possible; no
ArrayList;+=only where the target version makes it safe - Error Termination: Use
Write-Error -ErrorAction Stopinstead of barethrowwhen ErrorAction compliance is needed
๐ Quality Standards
Code Review Checklist
- โ Uses approved PowerShell verbs and follows community naming conventions consistently
- โ
Implements proper error handling with
$_usage and appropriate null checking - โ
Includes comprehensive comment-based help with proper
<#opening format - โ Uses context-appropriate string operations and performance patterns
- โ Follows modern PowerShell practices (PSCredential constructor, appropriate validation)
- โ Includes appropriate Pester tests with community testing patterns
- โ Uses descriptive output types instead of misleading [PSCustomObject] declarations
- โ
Organizes troubleshooting docs in
./Troubleshooting/folder - โ Maintains cross-platform compatibility considerations
- โ Uses semantic versioning for modules
- โ Implements structured logging for enterprise environments
- โ Avoids community-identified anti-patterns and outdated practices
- โ Implements correlation tracking
- โ Follows security best practices
- โ Includes appropriate performance optimizations
- โ Supports enterprise compliance requirements
Validation Commands
Use these prompts for quality assurance:
/validate-standards- Check against community standards/security-review- Security and compliance analysis/code-analysis- Comprehensive quality review/optimize-performance- Performance optimization review
๐ Comprehensive Reference Documentation
Quick Reference Links
- Community Standards: Reference
.github/instructions/community-standards.instructions.md - Style Guide: Reference
.github/instructions/style-enforcement.instructions.md - Troubleshooting: Always organized in
./Troubleshooting/folder structure - Testing: Use Pester 6.x with comprehensive coverage requirements
- Security: Implement defense-in-depth with community-approved patterns
- Performance: Optimize using community-identified best practices and expert feedback
Detailed Guidance
For specialized scenarios, reference these instruction files:
- Version Baseline: Target Versions and Breaking Changes
- Security & Compliance: Security Guidelines
- Testing Standards: Testing Framework
- Architecture Design: Design Patterns
- Module Development: Module Standards
- CI/CD Integration: Pipeline Setup
- Error Handling: Logging Framework
- Documentation: README Standards
- Code Analysis: Quality Standards
- Comment Standards: Help Documentation
- Community Standards: Best Practices Integration
- Style Enforcement: Style Guide Compliance
Worked Examples
Prefer matching these over inventing a shape. Each is complete, runs, and passes the quality gates described above:
| Need | Example |
|---|---|
| A complete advanced function | Basic-Function-Example.ps1 |
| Module layout and export boundary | Module-Structure-Example |
| Pester 6 tests, including private functions | Module-Structure-Example/Tests |
| Mocking CIM and typed parameters | Testing-Examples |
| A module template to copy | Templates/Powershell-Module |
Test-QualityGates.ps1 is the exception: it intentionally violates these standards so the gates have something to catch. Never use it as a model.
Implementation Documentation
- Implementation Guide: Step-by-step setup and adoption
- PowerShell Best Practices: Comprehensive community standards
- Enterprise Extensions: Organizational customizations
- Troubleshooting Guides: Organized problem-solving resources
This file provides core standards automatically applied by GitHub Copilot, integrating established PowerShell community best practices with expert feedback corrections. For detailed guidance on specific topics, reference the individual instruction files and comprehensive documentation structure.
Trust
Not scanned yet. Artifacts are graded after they are crawled, so a recently discovered one may have no result for a while.
Versions
git-cb7a81a7b01a2026-08-04