--- type: "agent_requested" description: "Use these for powershell scripts" --- # PowerShell Coding Standards ## Output and Logging - No `Write-Host` - No AI icons in log messages and strings - Only `Write-Verbose`, `Write-Warning`, `Write-Error`, and `Write-Output` as needed - When using toolkit's `$WriteLogMessage`: do NOT add timestamps (handled by scriptblock), do NOT check for null - Centralized logging format: `[TimestampUTC] - [Level] - Message` - Use `Start-Transcript` for script logging (no `-Append`, no custom log appending functions) - Transcript log name should be based on the `BaseName` of the script ## Object Construction - Use `$Var = New-Object -TypeName 'Type'` instead of `[Type]::New()` - Use `OrderedDictionary` for building object properties, then cast to PSObject - Use `New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'` for splatting parameters - Splatting dictionary properties should be indented one additional level from the variable assignment ## Collections - Use generic lists: `New-Object -TypeName 'System.Collections.Generic.List[System.String]'` - Use `.Add()` and `.AddRange()` methods - never use `+=` for array concatenation - Use `HashSet` when checking for existence/uniqueness ## Paths and File System - Use `[System.IO.FileInfo]` for file path parameters - Use `[System.IO.DirectoryInfo]` for directory path parameters - Build paths with: `[System.IO.FileInfo][System.IO.Path]::Combine(Path1, Path2, Leaf)` or `[System.IO.DirectoryInfo][System.IO.Path]::Combine(Path1, Path2)` - Use `[System.IO.File]::Exists()` for file existence checks - Use `[System.IO.Directory]::Exists()` for directory existence checks (not `Test-Path`) - Use environment variable paths like `$Env:ProgramFiles` instead of hardcoded paths like `'C:\Program Files'` - ScriptPath should be dynamically determined and used for logpath and name ## File Encoding - Use `WriteAllText` or `WriteAllLines` with non-BOM encoding - Use ASCII encoding for config files to avoid BOM issues - PowerShell's `[System.Text.Encoding]::UTF8` adds BOM - avoid for config files ## URLs and Web Requests - URLs should be `System.URI` with full scheme (e.g., `https://`) - Use `System.Net.WebClient` with default credentials for downloads instead of `Invoke-WebRequest` ## Conditions and Filtering ### Where-Object Syntax - Always wrap each condition in parentheses - No spaces after `{` or before `}` ```powershell # Correct - Single condition Where-Object {($_.Type -eq 'CABundle')} # Correct - Multiple conditions Where-Object {($_.Type -eq 'CABundle') -and ($_.CertificateCount -gt 0)} # Incorrect Where-Object { $_.Type -eq 'CABundle' } ``` ### Switch Statements (Preferred over If Statements) - Use `Switch ($VarName)` with `{($_ -eq $True)}` and `{($_ -eq $False)}` for single boolean - Use `Switch ($True)` only for unrelated conditions - Use `Default` for else cases - Set conditional properties when splatting using switch statements ```powershell Switch ($SomeCondition) { {($_ -eq $True)} { # Do something } Default { # Else case } } ``` ### Combined Conditions - Each sub-condition must be wrapped in parentheses - Use `-eq $True` explicitly for switch parameters ```powershell # Correct Switch (($EnableFeature.IsPresent -eq $True) -and ($ConfigExists -eq $True)) # Incorrect Switch ($EnableFeature.IsPresent -and $ConfigExists) ``` ## Code Formatting - Do NOT put `{` on the same line - line break and indent one level - Code inside block indented one more level - Closing `}` at same indent as opening `{` - Applies to all blocks: Try/Catch/Finally, Switch, For, Function, etc. - Nested blocks continue the pattern ## Loops - Use long loop format: `For ($Counter Index etc) {}` - Use Counter and Count variables for easy `Write-Progress` integration ## Script Structure - Main script should always have Try/Catch/Finally - PS7 compatible for Linux/Mac portability (though not the main goal) - Environment variables only for paths and non-sensitive config, never for secrets/API keys - Here-strings should use `$($Var)` syntax to bake in values at generation time - Service names should be generic to the product, not specific to single use case ## String Checks - Use `([System.String]::IsNullOrEmpty($var) -eq $False)` instead of implicit boolean - Use `([System.String]::IsNullOrWhiteSpace($var) -eq $False)` when whitespace matters ## PowerShell Modules - No async/await - No updating progress bars from background threads - No workarounds without asking first - Code quality over quick delivery - Think before implementation - reusable code: BaseCmdlets, inherits, Classes, Models, Methods - Add Write-Progress to cmdlets where appropriate - Module folder should NOT be gitignored - Build artifacts go into `Artifacts` folder - All docs except README go into `docs` folder - Scripts go into `scripts` folder - Maintain a changelog - Avoid excessive printing to threads - Threads should track progress for resume capability - Release format: `yyyy.mm.dd.hhmm`