Skip to main content
PowerShell, despite its power and flexibility, has several common anti-patterns that can lead to performance issues, security vulnerabilities, and maintainability problems. Here are the most important anti-patterns to avoid when writing PowerShell scripts.
Avoid using Invoke-Expression (or its alias iex) when possible, especially with user input or external data. It can lead to code injection vulnerabilities. Use parameters, splatting, or the call operator (&) instead.
Use proper error handling with try/catch blocks or the -ErrorAction and -ErrorVariable parameters. This helps you handle errors gracefully and provide meaningful feedback to users.
Use the -match operator or regex methods instead of Select-String for simple pattern matching in strings. Select-String is designed for searching through files or collections of strings, not for simple string operations.
Avoid using ForEach-Object (or its alias %) when simpler alternatives exist. For property extraction, use Select-Object -ExpandProperty or direct property access. ForEach-Object is slower and more verbose for simple operations.
Use PowerShell’s pipeline to chain commands together. This is more idiomatic, often more readable, and can be more efficient as it processes objects one at a time through the pipeline rather than storing intermediate collections.
Use Write-Output (or simply let PowerShell implicitly output objects) for data that might be consumed by other commands in a pipeline. Use Write-Host only for visual feedback that isn’t meant to be processed further.
Use PowerShell’s parameter validation attributes instead of writing manual validation code. This makes your functions more robust, self-documenting, and consistent with PowerShell conventions.
Use string interpolation, the format operator (-f), or [string]::Format() instead of string concatenation. These methods are more readable and efficient, especially for complex strings.
Use strong types for function parameters and variables when appropriate. This helps catch type-related errors early and makes your code more self-documenting and robust.
Avoid using aliases (like gps, ?, %, sort, select) in scripts and functions. Use full command and parameter names for better readability, maintainability, and to avoid issues if aliases change or aren’t available in all environments.
Use [PSCustomObject] for structured data instead of hashtables or arrays. It provides better property access syntax, works well with the pipeline, and is more idiomatic in PowerShell.
Be mindful of variable scopes. Avoid using global variables unnecessarily. Pass parameters explicitly to functions or use appropriate scopes ($script:, $local:, $private:) to limit variable visibility.
Use begin, process, and end blocks in advanced functions that accept pipeline input. The process block runs once for each pipeline object, while begin and end run once before and after processing.
Use [CmdletBinding()] for your functions to make them behave more like cmdlets. This gives you access to common parameters like -Verbose, -Debug, and -ErrorAction, and enables better error handling and parameter validation.
Always use [CmdletBinding()] when using Write-Verbose or Write-Debug. This ensures that these messages are only displayed when the corresponding preference variables are set or when the -Verbose or -Debug switches are used.
Use [OutputType()] to specify the type of objects your function returns. This helps with documentation, IntelliSense, and makes your code more self-documenting.
Use proper module structure with explicit exports and module manifests. This helps control which functions are exposed to users and provides important metadata about your module.
Use comment-based help to document your functions and scripts. This provides help information to users via the Get-Help cmdlet and makes your code more maintainable.
Avoid using Invoke-WebRequest sequentially in loops for multiple requests. Use parallelism with Start-Job, ForEach-Object -Parallel (in PowerShell 7+), or a module like PSParallel for better performance.
Use PowerShell cmdlets like Get-Content, Set-Content, Get-ChildItem, etc., for file operations instead of .NET methods directly. The cmdlets are more idiomatic in PowerShell and often provide additional functionality like handling different encodings, working with the pipeline, and supporting PowerShell paths.
Use PowerShell’s comparison operators (-eq, -ne, -gt, -lt, etc.) instead of C-style operators (==, !=, >, <, etc.). The PowerShell operators work consistently across all PowerShell versions and have special behavior for collections and wildcards.
Be explicit about case sensitivity in string comparisons. Use -ieq, -ine, etc., for case-insensitive comparisons, and -ceq, -cne, etc., for case-sensitive comparisons.