A modern, high-performance PowerShell static analysis engine.
Specter is a reimplementation of PSScriptAnalyzer designed to run as a standalone library, PowerShell module, or long-lived linting daemon. It analyses PowerShell scripts using the PowerShell AST without executing any PowerShell code in the analysis engine.
- No PowerShell execution in the engine -- all analysis is performed on the AST and token stream. The engine does not require a PowerShell runspace.
- Embeddable -- Specter is a .NET library first, usable from any C# application. PowerShell module and server modes are thin hosts around the core library.
- Drop-in PSScriptAnalyzer compatibility -- the
Specter.PssaCompatibilitymodule providesInvoke-ScriptAnalyzer,Get-ScriptAnalyzerRule, andInvoke-Formatterwith the same parameter sets and output types. - Performance -- rules execute in parallel, the SQLite command database provides fast lookups with an LRU cache, and the formatting engine operates on token streams.
- Extensibility -- rules are registered through providers, configuration is layered, and the command database is pluggable (SQLite, live PowerShell session, or hardcoded builtins).
Specter/ Core analysis library (rules, formatting, configuration, command database)
Specter.Module/ PowerShell module exposing Specter cmdlets (Update-SpecterDatabase, etc.)
Specter.RuleCmdlets/ C# module providing cmdlets for custom rule authors
Specter.PssaCompatibility/ Drop-in PSScriptAnalyzer-compatible PowerShell module
Specter.Server/ Linting daemon with gRPC and LSP endpoints
Specter.Test/ xUnit test suite
Specter.Benchmarks/ BenchmarkDotNet performance suite
ScriptAnalyzer-- entry point for analysis. Accepts AST + tokens + configuration, returns diagnostics.ScriptFormatter-- entry point for formatting. Applies formatting editors to token streams.IRuleProvider-- supplies rules to the analyzer.BuiltinRuleProviderregisters all built-in rules.IPowerShellCommandDatabase-- abstraction over command metadata lookup. Implementations:SqliteCommandDatabase(shipped DB),SessionStateCommandDatabase(live PS session),BuiltinCommandDatabase(fallback with hardcoded aliases).RuleComponentProviderBuilder-- fluent builder for wiring rules, configuration, and dependencies.
The unified build script compiles all assemblies and stages the three PowerShell modules under out/:
# Debug build (default)
./build.ps1
# Release build with NuGet package
./build.ps1 -Configuration Release -Pack -Clean
# Import the built module
Import-Module ./out/Specter/Specter.psd1Or build individual projects with dotnet directly:
dotnet build Specter.sln# xUnit tests
dotnet test Specter.Test/Specter.Test.csproj
# Pester compatibility tests (requires pwsh and Pester 5+)
pwsh -NoProfile -Command "./Tests/RunCompatibilityTests.ps1"C# rule authors should reference the thin Specter.Api package rather than Specter:
dotnet add package Specter.Api
dotnet add package System.Management.AutomationSpecter.Api contains rule authoring contracts and diagnostics types without pulling in engine/runtime dependencies like SQLite or JSON configuration components.
The publish script builds in Release mode, runs tests, and pushes to the PowerShell Gallery and nuget.org:
# Publish everything (requires API keys)
./publish.ps1 -PSGalleryApiKey $galleryKey -NuGetApiKey $nugetKey
# Or use environment variables
$env:PSGALLERY_API_KEY = '...'
$env:NUGET_API_KEY = '...'
./publish.ps1
# Dry run
./publish.ps1 -WhatIf
# PSGallery only (skip NuGet)
./publish.ps1 -PSGalleryApiKey $key -SkipNuGetThe current version is 0.1.0-preview.1, signalling that the API is not yet stable.
Specter ships two PowerShell modules: the native Specter module and a compatibility module that provides a drop-in replacement for PSScriptAnalyzer.
The Specter module (Specter.Module) is the primary PowerShell interface. It exposes Specter-native cmdlets with an interface designed around the Specter engine rather than backward compatibility:
Invoke-Specter-- Analyse a script file or string. ReturnsScriptDiagnosticobjects from the Specter engine directly, with full fidelity (rule metadata, corrections, extent information).Update-SpecterDatabase-- Refresh the SQLite command database from a live PowerShell session or from legacy JSON compatibility profiles.
The Specter.RuleCmdlets module provides cmdlets for custom rule authors:
Write-Diagnostic-- Emit aScriptDiagnosticfrom a rule function, auto-detecting the calling rule from the call stack. Use-CorrectionTextfor simple inline fixes.New-ScriptCorrection-- Create aCorrectionobject for advanced scenarios (different extent or multiple corrections). Pass toWrite-Diagnostic -Correction.
See Writing Custom Rules for a full guide with examples.
Import-Module ./Specter/out/Specter/Specter.psd1
Invoke-Specter -Path ./MyScript.ps1
Update-SpecterDatabase -DatabasePath ./specter.db -FromSessionThe Specter.PssaCompatibility module is a drop-in replacement for PSScriptAnalyzer. It wraps the Specter engine behind the same cmdlet names, parameter sets, and output types that PSScriptAnalyzer users expect, so existing scripts, editor integrations, and CI pipelines continue to work without changes.
The module exports:
Invoke-ScriptAnalyzer-- same parameters and output shape as the original.Get-ScriptAnalyzerRule-- lists available rules.Invoke-Formatter-- formats PowerShell scripts.
Import-Module ./Specter/out/Specter.PssaCompatibility/Specter.PssaCompatibility.psd1
# Analyze a script
Invoke-ScriptAnalyzer -Path ./MyScript.ps1
# Analyze with specific rules
Invoke-ScriptAnalyzer -Path ./MyScript.ps1 -IncludeRule PSAvoidUsingAlias
# Format a script
Invoke-Formatter -ScriptDefinition 'function foo{}'
# List available rules
Get-ScriptAnalyzerRuleIf you are starting fresh, prefer the Specter module. Use the compatibility module when you need to swap in Specter behind existing PSScriptAnalyzer-based tooling.
Specter is configured through a JSON file (preferred) or through PowerShell .psd1 files for PSScriptAnalyzer compatibility.
Create a specter.json file in your project root:
{
"BuiltinRulePreference": "default",
"RuleExecutionMode": "parallel",
"Rules": {
"PSAvoidUsingCmdletAliases": {
"AllowList": ["cd", "ls"]
},
"PSUseConsistentIndentation": {
"IndentationSize": 4,
"PipelineIndentation": "IncreaseIndentationForFirstPipeline"
},
"PSAvoidUsingWriteHost": {
"Enable": false
}
}
}Rules are enabled by default -- including a rule in the Rules section with configuration is enough to activate it. Set "Enable": false to explicitly disable a rule.
Top-level settings:
| Key | Values | Description |
|---|---|---|
BuiltinRulePreference |
"none", "default", "comprehensive" |
Which built-in rules to load |
RuleExecutionMode |
"default", "parallel", "sequential" |
How rules are executed |
RulePaths |
["/path/to/rules", ...] |
Additional rule module paths (see security note below) |
ExternalRules |
"explicit", "disabled", "unrestricted" |
External rule loading policy (default: explicit) |
Rules |
{ "RuleName": { ... }, ... } |
Per-rule configuration |
Each rule entry under Rules supports these shared keys alongside any rule-specific settings:
| Key | Type | Description |
|---|---|---|
Enable |
bool |
Disable the rule (false); defaults to true |
ExcludePaths |
string[] |
Glob patterns for paths to skip |
Pass the configuration file to Invoke-Specter or the server:
Invoke-Specter -Path ./MyScript.ps1 -Settings ./specter.jsonspecter-server lsp --config ./specter.jsonThe compatibility module also accepts .psd1 settings files and hashtables, matching the PSScriptAnalyzer settings format:
Invoke-ScriptAnalyzer -Path . -Settings @{
Rules = @{
PSAvoidUsingCmdletAliases = @{
AllowList = @('cd', 'ls')
}
PSUseConsistentIndentation = @{
Enable = $true
IndentationSize = 4
PipelineIndentation = 'IncreaseIndentationForFirstPipeline'
}
}
}Loading external rules means executing third-party code in your process. Specter applies several safeguards automatically:
- Explicit opt-in -- external rules are never loaded unless you pass
-CustomRulePathor includeRulePathsin your settings. Set"ExternalRules": "disabled"to block all external loading (recommended for CI/CD when only built-in rules are needed). - File ownership checks -- rule files and their parent directories must be owned by the current user or root/SYSTEM and must not be writable by other users.
- Manifest auditing --
.psd1manifests are rejected if they containScriptsToProcess,RequiredAssemblies, or other fields that execute code at import time. - Restricted runspace -- PowerShell module rules run with a limited command allowlist and no access to the filesystem, registry, or network providers.
See SECURITY.md for the full threat model and Writing Custom Rules for best practices.
Specter ships with a SQLite database (specter.db) containing command metadata for PowerShell 3.0 through 7.x across Windows, macOS, and Linux. This database powers rules like AvoidPositionalParameters, UseCmdletCorrectly, AvoidUsingCmdletAliases, and UseCompatibleCmdlets.
The database can be updated from a live PowerShell session:
Import-Module Specter.Module
Update-SpecterDatabase -DatabasePath ./specter.db -FromSessionOr from legacy JSON compatibility profiles:
Update-SpecterDatabase -DatabasePath ./specter.db -JsonPath ./profiles/Specter.Server provides a long-lived linting daemon with gRPC and LSP endpoints:
dotnet run --project Specter.ServerThe server can be embedded in a larger C# application by referencing the Specter and Specter.Server packages and using the AnalysisService class directly.
Specter aims to go beyond PSScriptAnalyzer compatibility. These are longer-term goals under research and design:
- Variable AST metadata -- Decorate AST variable nodes with statically determined information (evolving types, possible value sets, propagated attributes and constraints). This would enable smarter rules that understand what a variable holds at a given point in a script, using flow-sensitive or scope-based analysis.
- DSC analysis without PowerShell invocation -- Evaluate whether DSC configurations can be statically analysed from the AST alone using schema-driven validation, with an interface boundary that allows plugging in a PowerShell runspace when deeper evaluation is needed.
MIT. See LICENSE.