Why does Invoke-ScriptAnalyzer fail in the terminal when Visual Studio Code is linting your code just fine?

If you followed my earlier post on how to validate and improve your PowerShell scripts, you know that the PowerShell extension for Visual Studio Code uses PSScriptAnalyzer to provide real-time code analysis - squiggly underlines, Problems panel entries, and quick fixes as you type. That part still works. The extension bundles its own internal copy of PSScriptAnalyzer for editor integration, and it continues to lint your code in the background.

What the extension does not give you is a standalone PSScriptAnalyzer module that you can use from the terminal, in scripts, in tasks, or in CI/CD pipelines. If you open a PowerShell terminal in Visual Studio Code and run Invoke-ScriptAnalyzer, it will fail unless you have installed the module yourself. The extension’s internal copy is not exposed to your PowerShell session.

This distinction matters. Real-time linting in the editor is valuable, but it only covers the file you are currently working on. If you want to analyze an entire project folder, run PSScriptAnalyzer as part of a build task, integrate it into a CI/CD pipeline, or simply use it from the command line - you need to install PSScriptAnalyzer as a standalone module.

What follows is a walkthrough of how to install PSScriptAnalyzer as a standalone PowerShell module, configure Visual Studio Code to use it, and set up a reliable update workflow - whether you are managing a single device or an entire fleet.

Editor linting versus standalone usage

The extension’s internal copy is not available to your PowerShell session, which means scenarios like these require a standalone installation:

  • Analyzing a project from the terminal - Running Invoke-ScriptAnalyzer -Path .\src -Recurse to scan an entire folder
  • Build tasks and pre-commit hooks - Automating code analysis as part of your development workflow
  • CI/CD pipelines - Running PSScriptAnalyzer in GitHub Actions, Azure DevOps, or other build systems
  • Servers and jump hosts - Validating scripts on machines where Visual Studio Code is not installed

Installing PSScriptAnalyzer as a standalone module also gives you control over versioning. You choose which version to run, update on your own schedule, and can pin specific versions in enterprise environments - independent of whatever the PowerShell extension bundles internally.

Install PSScriptAnalyzer

Installing PSScriptAnalyzer is straightforward. Open a PowerShell terminal and run:

Install-Module -Name PSScriptAnalyzer -Repository PSGallery -Scope CurrentUser -Force

Install PSScriptAnalyzer for the current user

The -Scope CurrentUser parameter installs the module without requiring administrator privileges, which makes it the cleanest option for most scenarios. The module is installed from the PowerShell Gallery  and works with both Windows PowerShell 5.1 and PowerShell 7+.

To verify the installation:

Get-Module -Name PSScriptAnalyzer -ListAvailable

Verify PSScriptAnalyzer is installed

You should see output similar to:

ModuleType Version    PreRelease Name             PSEdition ExportedCommands
---------- -------    ---------- ----             --------- ----------------
Script     1.23.0                PSScriptAnalyzer Desktop   {Get-ScriptAnalyzerRule, Invoke-Forma...

Expected output after installation

After installation, restart Visual Studio Code or reload the window (Ctrl + Shift + P > Developer: Reload Window). You can now use Invoke-ScriptAnalyzer and other PSScriptAnalyzer cmdlets directly from the integrated terminal.

Save PSScriptAnalyzer to the repository

Instead of installing PSScriptAnalyzer to the user profile, you can save the module directly into your repository. This approach means every contributor and every CI/CD agent gets the exact same version without needing to run Install-Module first.

Use Save-Module to download PSScriptAnalyzer into a folder in your project:

Save-Module -Name PSScriptAnalyzer -Repository PSGallery -Path './.modules'

Save PSScriptAnalyzer to a modules folder in the repository

This creates a .modules/PSScriptAnalyzer/<version>/ folder structure that you can commit to the repository. To use the module from this location, import it with the full path before calling any PSScriptAnalyzer cmdlets:

Import-Module -Name './.modules/PSScriptAnalyzer' -Force
Invoke-ScriptAnalyzer -Path .\src -Recurse -Settings '.config\Strict.psd1'

Import PSScriptAnalyzer from the repository

This pattern works well for CI/CD pipelines where you do not want to depend on external package sources at build time, and it sidesteps the controlled folder access issue entirely since the module lives in the repository folder rather than in the user’s Documents directory.

Verify that Visual Studio Code detects PSScriptAnalyzer

Once PSScriptAnalyzer is installed and Visual Studio Code has been reloaded, verify that script analysis is active by opening Settings (Ctrl + ,), searching for powershell script analysis, and confirming that PowerShell > Script Analysis: Enable is checked. For detailed verification and troubleshooting steps, see the Visual Studio Code section in how to validate and improve your PowerShell scripts.

Configure PSScriptAnalyzer in Visual Studio Code

You can fine-tune the analysis behavior through Visual Studio Code settings. Press Ctrl + , and search for “script analysis” to find the relevant options, or add them directly to your settings.json:

{
  "powershell.scriptAnalysis.enable": true,
  "powershell.scriptAnalysis.settingsPath": ""
}

Visual Studio Code settings for PSScriptAnalyzer

Use a custom settings file

For more control over which rules are active, create a PSScriptAnalyzer settings file and point Visual Studio Code to it. This is especially useful for teams that want consistent analysis rules across contributors.

A good convention is to place project configuration files in a .config folder at the root of your repository. This keeps the root clean and groups tool-specific settings such as code formatting profiles, linting configurations, or analysis settings in one place.

A basic settings file

A basic settings file that shows only errors and warnings, excludes a specific rule, and enables compatibility checking for PowerShell 5.1:

@{
    Severity     = @('Error', 'Warning')
    ExcludeRules = @(
        'PSAvoidUsingWriteHost'
    )
    Rules        = @{
        PSUseCompatibleSyntax = @{
            Enable         = $true
            TargetVersions = @('5.1', '7.0', '7.4')
        }
    }
}

Example: .config/PSScriptAnalyzerSettings.psd1

A strict settings file

For projects where code quality is non-negotiable, a strict settings file enables all severity levels, enforces formatting and naming conventions, and catches style issues that a basic configuration would miss. This is the kind of settings file you use when you want PSScriptAnalyzer to hold every script to a high standard - consistent indentation, proper whitespace, comment-based help, correct casing, and PowerShell 5.1 compatible syntax:

@{
    Severity = @(
        'Error'
        'Warning'
        'Information'
    )

    IncludeDefaultRules = $true

    Rules = @{

        #region Code style rules

        PSAvoidExclaimOperator = @{
            Enable = $true
        }

        PSAvoidLongLines = @{
            Enable            = $false
            MaximumLineLength = 150
        }

        PSAvoidSemicolonsAsLineTerminators = @{
            Enable = $true
        }

        PSAvoidUsingDoubleQuotesForConstantString = @{
            Enable = $true
        }

        #endregion

        #region Formatting rules

        PSAlignAssignmentStatement = @{
            Enable         = $true
            CheckHashtable = $true
        }

        PSPlaceCloseBrace = @{
            Enable             = $true
            NewLineAfter       = $true
            IgnoreOneLineBlock = $true
            NoEmptyLineBefore  = $false
        }

        PSPlaceOpenBrace = @{
            Enable             = $true
            OnSameLine         = $true
            NewLineAfter       = $true
            IgnoreOneLineBlock = $true
        }

        PSUseConsistentIndentation = @{
            Enable              = $true
            IndentationSize     = 4
            PipelineIndentation = 'IncreaseIndentationForFirstPipeline'
            Kind                = 'space'
        }

        PSUseConsistentWhitespace = @{
            Enable                                  = $true
            CheckInnerBrace                         = $true
            CheckOpenBrace                          = $true
            CheckOpenParen                          = $true
            CheckOperator                           = $true
            CheckPipe                               = $true
            CheckPipeForRedundantWhitespace         = $true
            CheckSeparator                          = $true
            CheckParameter                          = $true
            IgnoreAssignmentOperatorInsideHashTable  = $true
        }

        #endregion

        #region Naming and documentation rules

        PSProvideCommentHelp = @{
            Enable                  = $true
            ExportedOnly            = $false
            BlockComment            = $true
            VSCodeSnippetCorrection = $false
            Placement               = 'begin'
        }

        PSUseCorrectCasing = @{
            Enable = $true
        }

        PSUseSingularNouns = @{
            Enable = $true
        }

        #endregion

        #region Compatibility rules

        PSUseCompatibleSyntax = @{
            Enable         = $true
            TargetVersions = @('5.1')
        }

        PSUseConsistentParametersKind = @{
            Enable = $true
        }

        #endregion
    }
}

Example: .config/Strict.psd1

You can maintain multiple settings files in the same .config folder - for example, a basic profile for quick checks and a strict profile for thorough validation before releases. Switch between them by updating the settingsPath in your settings.json, or pass the path directly when running Invoke-ScriptAnalyzer from the terminal:

Invoke-ScriptAnalyzer -Path .\src -Recurse -Settings '.config\Strict.psd1'

Run PSScriptAnalyzer with a specific settings file

Reference the settings file in Visual Studio Code

Point Visual Studio Code to your settings file by adding the path to your settings.json:

{
  "powershell.scriptAnalysis.settingsPath": ".config/PSScriptAnalyzerSettings.psd1"
}

Point Visual Studio Code to a custom settings file in the .config folder

By storing settings files in your repository, everyone working on the project uses the same analysis rules. For a deeper dive into individual rules and rule categories, see how to validate and improve your PowerShell scripts.

Run PSScriptAnalyzer as a Visual Studio Code task

Rather than typing Invoke-ScriptAnalyzer in the terminal each time, you can define a Visual Studio Code task that runs it for you. Tasks let you analyze the current file or an entire folder with a keyboard shortcut or from the Command Palette, and you can even let the user pick which settings profile to use.

Add the following to your .vscode/tasks.json:

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "Run PSScriptAnalyzer",
      "type": "shell",
      "command": "Invoke-ScriptAnalyzer -Path '${file}' -Settings '${workspaceFolder}/.config/${input:psSettingsProfile}.psd1' -Recurse -ReportSummary | Format-Table -AutoSize",
      "group": "test",
      "presentation": {
        "reveal": "always",
        "focus": true
      },
      "problemMatcher": "$msCompile"
    }
  ],
  "inputs": [
    {
      "id": "psSettingsProfile",
      "type": "pickString",
      "description": "Select PSScriptAnalyzer profile",
      "options": [
        { "label": "Default (Warning + Error)", "value": "Default" },
        { "label": "Strict (all severities)", "value": "Strict" }
      ],
      "default": "Default"
    }
  ]
}

Visual Studio Code task for running PSScriptAnalyzer

This task does the following:

  • Runs Invoke-ScriptAnalyzer against the file you currently have open (${file}).
  • Prompts you to select a settings profile from the predefined options in the inputs section, which map to settings files in the .config folder.
  • Uses -ReportSummary to show a summary count of errors, warnings, and information messages at the end of the output.
  • Uses the $msCompile problem matcher so results appear in the Problems panel alongside the editor linting results.

To run the task, press Ctrl + Shift + P, type Tasks: Run Task, and select Run PSScriptAnalyzer. You can also assign a keyboard shortcut to it for quicker access.

Here is an example of what the task output looks like when PSScriptAnalyzer finds issues:

21 rule violations found. Severity distribution: Error = 0, Warning = 21, Information = 0
RuleName                             Severity ScriptName               Line Message
--------                             -------- ----------               ---- -------
PSAvoidUsingCmdletAliases            Warning  invoke-helloworld-v1.ps1 49   'start' is an alias of 'Start-Process'. Alias can introduce possible
                                                                            problems and make scripts hard to maintain. Please consider changing
                                                                            alias to its full content.
PSAvoidUsingCmdletAliases            Warning  invoke-helloworld-v1.ps1 81   'gci' is an alias of 'Get-ChildItem'. Alias can introduce possible
                                                                            problems and make scripts hard to maintain. Please consider changing
                                                                            alias to its full content.
PSAvoidUsingWriteHost                Warning  invoke-helloworld-v1.ps1 16   File 'invoke-helloworld-v1.ps1' uses Write-Host. Avoid using
                                                                            Write-Host because it might not work in all hosts, does not work
                                                                            when there is no host, and (prior to PS 5.0) cannot be suppressed,
                                                                            captured, or redirected. Instead, use Write-Output, Write-Verbose,
                                                                            or Write-Information.
PSAvoidAssignmentToAutomaticVariable Warning  invoke-helloworld-v1.ps1 36   The Variable 'args' is an automatic variable that is built into
                                                                            PowerShell, assigning to it might have undesired side effects. If
                                                                            assignment is not by design, please use a different name.
PSUseDeclaredVarsMoreThanAssignments Warning  invoke-helloworld-v1.ps1 14   The variable 'erroractionprerence' is assigned but never used.

Example task output from PSScriptAnalyzer

The summary line at the top comes from the -ReportSummary parameter. Each row identifies the rule, severity, script, line number, and a description of the issue - giving you a clear picture of what needs attention.

Keep PSScriptAnalyzer updated

Since PSScriptAnalyzer is no longer updated automatically through the PowerShell extension, you are responsible for keeping it current. New versions bring updated rules, bug fixes, and compatibility improvements - so regular updates are worth the small effort.

Manual update

The simplest approach is to run Update-Module when you want to update:

Update-Module -Name PSScriptAnalyzer

Update PSScriptAnalyzer manually

To check which version you currently have installed:

Get-InstalledModule -Name PSScriptAnalyzer | Select-Object Name, Version

Check the installed version

If you want to check whether an update is available before installing it, compare your installed version against the latest version in the PowerShell Gallery:

$installed = (Get-InstalledModule -Name PSScriptAnalyzer).Version
$available = (Find-Module -Name PSScriptAnalyzer -Repository PSGallery).Version
if ($available -gt $installed) { Write-Output "Update available: $installed -> $available" } else {  Write-Output "Already up to date: $installed" }

Check if a newer version is available

This lets you decide whether to update before committing to it - useful when you want to review release notes or validate a new version before rolling it out.

Automated update script

If you prefer a hands-off approach, use a script that installs or updates PSScriptAnalyzer automatically. This pattern works well as part of a machine setup script, a scheduled task, or a dev environment bootstrap:

# Description: Installs or updates PSScriptAnalyzer for the current user
# Elevation is not required - installs to CurrentUser scope

$moduleName = 'PSScriptAnalyzer'
$installed = Get-InstalledModule -Name $moduleName -ErrorAction SilentlyContinue

if ($installed) {
    $available = Find-Module -Name $moduleName -Repository PSGallery
    if ($available.Version -gt $installed.Version) {
        Write-Verbose -Message "Updating $moduleName from $($installed.Version) to $($available.Version)..."
        Update-Module -Name $moduleName -Force
    }
    else {
        Write-Verbose -Message "$moduleName is already up to date ($($installed.Version))."
    }
}
else {
    Write-Verbose -Message "Installing $moduleName..."
    Install-Module -Name $moduleName -Repository PSGallery -Scope CurrentUser -Force
}

Idempotent install-or-update script

This script is idempotent - you can run it repeatedly without side effects. It checks the currently installed version against the latest available version in the PowerShell Gallery and only updates when a newer version exists.

Version pinning for stability

In environments where predictability matters more than having the latest version - such as production scripts, CI/CD pipelines, or customer-facing deployments - pin to a specific version:

Install-Module -Name PSScriptAnalyzer -Repository PSGallery -RequiredVersion 1.23.0 -Force

Install a specific version of PSScriptAnalyzer

Version pinning ensures that a new release does not introduce unexpected rule changes or behavior differences. Update the pinned version deliberately after you have validated the new release in your environment.

Enterprise distribution patterns

For organizations managing multiple devices, installing PSScriptAnalyzer manually on each machine is not scalable. Here are practical distribution patterns that work with common enterprise tooling.

Deploy with Microsoft Intune

Package the installation as a Win32 app or a PowerShell script deployed through Microsoft Intune:

# Description: Installs PSScriptAnalyzer for all users on the device
# Elevation is required - AllUsers scope requires administrator privileges

Install-Module -Name PSScriptAnalyzer -Repository PSGallery -Scope AllUsers -Force

Installation script for Microsoft Intune deployment

For detection logic, use a custom script that checks for the module:

# Description: Detects whether PSScriptAnalyzer is installed
# Elevation is not required - module detection does not require elevated privileges

$module = Get-InstalledModule -Name PSScriptAnalyzer -ErrorAction SilentlyContinue
if ($module) {
    Write-Output "PSScriptAnalyzer $($module.Version) is installed."
    exit 0
}
else {
    exit 1
}

Detection script for Microsoft Intune

Use a private PowerShell repository

Organizations that maintain a private PowerShell repository can control exactly which version of PSScriptAnalyzer is available to their users:

Install-Module -Name PSScriptAnalyzer -Repository InternalPSGallery -RequiredVersion 1.23.0 -Force

Install from a private repository

This pattern gives you centralized version control. When you approve a new version, update the private repository and let users install or update from there.

Workspace bootstrap with requirements

For development teams, include module requirements in your project documentation or bootstrap script. When a contributor clones the repository, they run the setup script to ensure their environment matches the team standard:

# Description: Bootstraps the development environment with required modules
# Elevation is not required - installs to CurrentUser scope

$requiredModules = @(
    @{ Name = 'PSScriptAnalyzer'; MinimumVersion = '1.23.0' }
)

foreach ($module in $requiredModules) {
    $installed = Get-InstalledModule -Name $module.Name -ErrorAction SilentlyContinue
    if (-not $installed -or $installed.Version -lt $module.MinimumVersion) {
        Write-Verbose -Message "Installing $($module.Name) (minimum version: $($module.MinimumVersion))..."
        Install-Module -Name $module.Name -Repository PSGallery -Scope CurrentUser -MinimumVersion $module.MinimumVersion -Force
    }
    else {
        Write-Verbose -Message "$($module.Name) $($installed.Version) meets the requirement."
    }
}

Workspace bootstrap script


The wrap

The PowerShell extension for Visual Studio Code gives you real-time linting out of the box - but that only covers the file you are editing. If you want to analyze entire projects, automate code quality checks, or use PSScriptAnalyzer from the terminal, you need the standalone module installed.

The key takeaway is simple: install PSScriptAnalyzer once with Install-Module, keep it updated with Update-Module, and you get both the editor experience and full command-line access. For enterprise environments, wrap the installation into your existing deployment tooling - whether that is Microsoft Intune, a private repository, or a team bootstrap script - and treat it like any other managed module.

If you have not already, revisit how to validate and improve your PowerShell scripts for a deeper look at PSScriptAnalyzer rules, custom settings files, and command-line usage. Together, these two posts give you a complete picture of PowerShell code analysis - from installation to advanced configuration.

Happy coding.

–Jesper

Header image attribution: Image created with help from Microsoft Copilot