PSScriptAnalyzer

repository·main·Indexed 24 days ago

https://github.com/powershell/psscriptanalyzer

A static code analysis tool for PowerShell that checks scripts, modules, and manifest files against best-practice rules to identify defects and suggest improvements. It includes cmdlets such as Invoke-ScriptAnalyzer for evaluating code, Get-ScriptAnalyzerRule for managing rules, and Invoke-Formatter for script text formatting. The tool supports custom rule paths, severity filtering (Error, Warning, Information), and CI pipeline integration via the -EnableExit parameter.

Tokens
40K
Snippets
105
Records
199
Agent score
72%

What's inside PSScriptAnalyzer

  1. What is PSScriptAnalyzer?

    main

    PSScriptAnalyzer is a static code checker for PowerShell modules and scripts. It analyzes code quality by running a set of rules based on PowerShell best practices. It generates DiagnosticResults (errors and warnings) to identify potential defects and suggest improvements.

    Built-in rules check for issues such as:

    • Uninitialized variables
    • Use of the PSCredential type
    • Use of Invoke-Expression
  2. Avoid using cmdlet aliases

    main

    The AvoidUsingCmdletAliases rule (Severity: Warning) flags the use of aliases or implicit aliases in PowerShell scripts. Using aliases instead of full cmdlet names can make code harder to read, maintain, and may prevent proper syntax highlighting in tools like GitHub or Visual Studio Code.

    Implicit Aliases: PowerShell will attempt to append Get- to a command if the cmdlet name is not found. For example, running verb will execute Get-Verb.

    How to fix: Replace all aliases with their full cmdlet names.

    # Wrong
    gps | Where-Object {$_.WorkingSet -gt 20000000}
    
    # Correct
    Get-Process | Where-Object {$_.WorkingSet -gt 20000000}
  3. Understand the UseConsistentParametersKind rule

    main

    The UseConsistentParametersKind rule is a style enforcement rule with a Warning severity level. It ensures that all functions in a script or module follow a single, consistent pattern for defining parameters. This prevents a mix of Inline and ParamBlock parameter definitions within the same codebase.

    There are two supported patterns:

    1. Inline: Parameters are defined directly within the function signature parentheses.
    2. ParamBlock: Parameters are defined using a param() block inside the function body.

    This rule is typically configured via the PSScriptAnalyzer settings to enforce one specific style across your project.

  4. Implement ShouldProcess for cmdlet support

    main

    The ShouldProcess rule (Severity: Warning) ensures that PowerShell functions correctly support the -WhatIf and -Confirm parameters.

    A violation occurs in two scenarios:

    1. A function declares [CmdletBinding(SupportsShouldProcess=$true)] but never calls the $PSCmdlet.ShouldProcess() method.
    2. A function calls $PSCmdlet.ShouldProcess() but does not declare SupportsShouldProcess=$true in its [CmdletBinding] attribute.

    To fix this, ensure that every function declaring SupportsShouldProcess wraps its destructive or impactful actions inside an if ($PSCmdlet.ShouldProcess(...)) block.

    function Set-File
    {
        [CmdletBinding(SupportsShouldProcess=$true)]
        Param
        (
            [Parameter(Mandatory=$true)]
            $Path,
    
            [Parameter(Mandatory=$true)]
            [string]$Content
        )
    
        if ($PSCmdlet.ShouldProcess($Path, ("Setting content to '{0}'" -f $Content)))
        {
            $Content | Out-File -FilePath $Path
        }
    }
  5. Use custom rules with Invoke-ScriptAnalyzer

    main

    You can extend the analysis by providing your own rules via the -CustomRulePath parameter.

    Important behaviors:

    • When -CustomRulePath is specified, only the custom rules found in those paths are used. The standard built-in rules are not run unless you also include the -IncludeDefaultRules parameter.
    • To include rules found in subdirectories of your custom path, use the -RecurseCustomRulePath parameter.
    • If the specified path is invalid or no rules are found, the cmdlet runs standard rules without notice.
  6. Understand the PSUseCompatibleCommands rule

    main

    The PSUseCompatibleCommands rule (Severity: Warning) identifies commands in your scripts that are not available on a specific targeted PowerShell platform.

    A PowerShell platform is defined by a unique ID string following the format: <os-name>_<os-arch>_<os-version>_<ps-version>_<ps-arch>_<dotnet-version>_<dotnet-edition>.

    Examples of platform IDs:

    • win-4_x64_10.0.18362.0_6.2.4_x64_4.0.30319.42000_core (PowerShell 6.2.4 on Windows 10.0.18362)
    • ubuntu_x64_18.04_6.2.4_x64_4.0.30319.42000_core (PowerShell 6.2.4 on Ubuntu 18.04)

    To use this rule, you must enable it and specify one or more TargetProfiles. The rule compares commands against a 'union' profile (the set of all commands available in all profiles in your directory) to distinguish between locally defined commands and platform-incompatible built-in commands.

  7. Avoid the AvoidGlobalVars rule

    main

    The AvoidGlobalVars rule is a Warning level rule that flags the use of globally scoped variables. In PowerShell, variables, functions, and aliases present at startup (including automatic variables, preference variables, and those in your PowerShell profiles) exist in the global scope. Using global scope can lead to unintended side effects and makes code harder to debug.

    To comply with this rule, use other scope modifiers (such as local scope) instead of explicitly using the $Global: scope or relying on global variables within functions.

    ### Wrong
    ```powershell
    $Global:var1 = $null
    function Test-NotGlobal ($var)
    {
        $a = $var + $var1
    }

    Correct

    $var1 = $null
    function Test-NotGlobal ($var1, $var2)
    {
        $a = $var1 + $var2
    }
  8. Configure PipelineIndentation behavior

    main

    The PipelineIndentation parameter determines how indentation is applied to multi-line statements following a pipeline (|).

    Available settings:

    • IncreaseIndentationForFirstPipeline (default): Indents once after the first pipeline and maintains that indentation level for subsequent lines.
      foo |
          bar |
          baz
    • IncreaseIndentationAfterEveryPipeline: Increases the indentation level further after every pipeline.
      foo |
          bar |
              baz
    • NoIndentation: Does not increase indentation for pipelines.
      foo |
      bar |
      baz
    • None: Does not modify any existing pipeline indentation.
  9. How Constrained Language Mode (CLM) restrictions work

    main

    Constrained Language Mode (CLM) is a security feature that restricts .NET types, COM objects, commands, and language features. The PSUseConstrainedLanguageMode rule applies different levels of checking based on whether a script is digitally signed:

    Unsigned Scripts (Full Checking)

    The rule flags almost all CLM-restricted patterns, including:

    • Add-Type (code compilation)
    • Disallowed COM objects
    • Disallowed .NET types and type expressions ([Type]::Method())
    • Type casts and member invocations on disallowed types
    • PowerShell class keyword
    • XAML/WPF usage
    • Invoke-Expression
    • Module manifest wildcards and .ps1 script modules

    Signed Scripts (Selective Checking)

    Digitally signed scripts from trusted publishers execute in Full Language Mode (FLM) in CLM environments. For these, the rule only enforces checks that are always restricted:

    • Dot-sourcing
    • Parameter type constraints
    • Module manifest wildcards (.psd1 files)
    • Module manifest script modules (.psd1 files)
    IMPORTANT

    The rule performs a simple text check for signature blocks (# SIG # Begin signature block) and does NOT validate signature authenticity. Actual validation is performed by PowerShell at runtime.

  10. Understand the PSUseCompatibleTypes rule

    main

    The PSUseCompatibleTypes rule (Severity: Warning) identifies types used in your scripts that are not available (not loaded by default) in your targeted PowerShell platforms.

    This is useful for ensuring cross-platform compatibility (e.g., ensuring a script written for PowerShell 7 core will also run on PowerShell 5.1 on Windows).

    How it works:

    1. It compares types used in your script against a 'union' profile (a collection of all types available in all profiles in your profile directory).
    2. If a type is not in the union profile, it is assumed to be a locally created type and is ignored.
    3. If a type is in the union profile but missing from a specific TargetProfile, it is flagged as incompatible.
  11. Suppress and discover rule violations

    main

    You can suppress specific rule violations for a module, script, class, function, parameter, or line using the [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute] attribute.

    • To run standard analysis: Invoke-ScriptAnalyzer will skip any rules that have been suppressed in the target code.
    • To see only suppressed rules: Use the -SuppressedOnly parameter. This is useful for auditing which rules have been silenced and why (if a justification was provided).
    # Suppressing a rule in code
    function Get-Widgets
    {
        [CmdletBinding()]
        [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "")]
        [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingCmdletAliases", "", Justification="Resolution in progress.")]
        Param()
    
        dir $pshome
    }
    
    # Run analysis (suppressed rules won't show)
    Invoke-ScriptAnalyzer -Path .\Get-Widgets.ps1
    
    # Run analysis to see ONLY suppressed rules
    Invoke-ScriptAnalyzer -Path .\Get-Widgets.ps1 -SuppressedOnly