โ† Browse

@fadwen/powershell-copilot-standards

PowerShell Copilot Instructions

instructionscopilot

Install

agr install @fadwen/powershell-copilot-standards --target copilot

Writes 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:

  1. Security by Design: Implement comprehensive input validation and secure credential handling
  2. Correlation Tracking: Include correlation IDs for audit trails and troubleshooting
  3. Error Resilience: Implement robust error handling with structured logging
  4. Performance Awareness: Write efficient code with appropriate optimization
  5. Community Standards: Follow established PowerShell best practices and style guidelines
  6. 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 throw statements can be silenced by -ErrorAction SilentlyContinue - use Write-Error -ErrorAction Stop for 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.6 at 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-Host except when colored console output is explicitly required
    • Use Write-Verbose, Write-Debug, Write-Information, Write-Warning, or Write-Error instead
    • Leverage pipeline output for data flow
  • Pipeline Efficiency: Always design functions to work efficiently in the pipeline

Modern PowerShell Features

  • Use [PSCredential]::new() instead of New-Object for credential creation
  • Use Install-PSResource / Find-PSResource (Microsoft.PowerShell.PSResourceGet, in-box since 7.4) over PowerShellGet v2 Install-Module / Find-Module
  • Call Start-ThreadJob unqualified โ€” the module was renamed to Microsoft.PowerShell.ThreadJob in 7.6, so the old ThreadJob\ 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 of throw behavior 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-PSResource over Install-Module
  • Version Targeting: Declared #requires -Version / PowerShellVersion matches 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 Stop instead of bare throw when 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:

Worked Examples

Prefer matching these over inventing a shape. Each is complete, runs, and passes the quality gates described above:

NeedExample
A complete advanced functionBasic-Function-Example.ps1
Module layout and export boundaryModule-Structure-Example
Pester 6 tests, including private functionsModule-Structure-Example/Tests
Mocking CIM and typed parametersTesting-Examples
A module template to copyTemplates/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


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