PowerShell is a task automation and configuration management framework from Microsoft, consisting of a command-line shell and associated scripting language built on .NET. PowerShell helps system administrators and power-users rapidly automate tasks that manage operating systems and processes.
PowerShell Anti-Patterns Overview
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.
Using Invoke-Expression Unnecessarily
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.
Not Using Proper Error Handling
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.
Using Select-String Instead of Regex Methods
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.
Using ForEach-Object in a Pipeline Unnecessarily
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.
Not Using the Pipeline Effectively
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.
Using Write-Host Instead of Write-Output
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.
Not Using Parameter Validation
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.
Using String Concatenation Instead of Format Strings
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.
Not Using Strong Types
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.
Using Aliases in Scripts
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.
Not Using PSCustomObject for Structured Data
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.
Not Using Appropriate Scopes
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.
Not Using Begin/Process/End Blocks in Advanced Functions
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.
Not Using CmdletBinding
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.
Using Write-Debug or Write-Verbose Without Checking Preferences
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.
Not Using Appropriate Output Types
Use [OutputType()]
to specify the type of objects your function returns. This helps with documentation, IntelliSense, and makes your code more self-documenting.
Not Using Proper Module Structure
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.
Not Using Proper Comment-Based Help
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.
Using Invoke-WebRequest Inefficiently
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.
Not Using Appropriate Cmdlets for File Operations
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.
Not Using Appropriate Comparison Operators
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.
Not Using Appropriate String Comparison
Be explicit about case sensitivity in string comparisons. Use -ieq
, -ine
, etc., for case-insensitive comparisons, and -ceq
, -cne
, etc., for case-sensitive comparisons.