| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
Co-authored-by: jessehouwing <4173387+jessehouwing@users.noreply.github.com>
Co-authored-by: jessehouwing <4173387+jessehouwing@users.noreply.github.com>
Co-authored-by: jessehouwing <4173387+jessehouwing@users.noreply.github.com>
Co-authored-by: jessehouwing <4173387+jessehouwing@users.noreply.github.com>
Co-authored-by: jessehouwing <4173387+jessehouwing@users.noreply.github.com>
Co-authored-by: jessehouwing <4173387+jessehouwing@users.noreply.github.com>
Co-authored-by: jessehouwing <4173387+jessehouwing@users.noreply.github.com>
… path Co-authored-by: jessehouwing <4173387+jessehouwing@users.noreply.github.com>
…issue-2205 Fix intermittent "The term 'Get-Command' is not recognized" failures during recursive analysis
There was a problem hiding this comment.
This PR hardens PSScriptAnalyzer’s command-resolution path to avoid intermittent PowerShell engine/runspace-affinity failures (notably Get-Command resolution and CommandInfo.Parameters access) from causing a cascading, process-long failure during recursive/parallel analysis.
Changes:
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| Tests/Rules/Issue2205/PSScriptAnalyzerSettings.psd1 | Adds the settings file used by the regression test scenario. |
| Tests/Rules/Issue2205.tests.ps1 | Adds a regression test for recursive analysis under -ErrorAction Stop (Linux). |
| Rules/UseCorrectCasing.cs | Adds a retry-and-skip path for parameter casing when parameter metadata can’t be reliably retrieved. |
| Engine/CommandInfoCache.cs | Adds retry logic for transient Get-Command resolution failures and evicts faulted cached entries to prevent permanent poisoning. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Sorry, something went wrong.
| It "does not fail the analysis when a command lookup hits the runspace affinity problem" -Skip:(-not $IsLinux) { | ||
| $settingsPath = Join-Path $PSScriptRoot 'Issue2205/PSScriptAnalyzerSettings.psd1' | ||
| # $PSScriptRoot is <repo>/Tests/Rules, so two levels up is the repository root. | ||
| $repositoryRoot = (Resolve-Path (Join-Path $PSScriptRoot '..' '..')).Path | ||
|
|
||
| Invoke-ScriptAnalyzer -Path $repositoryRoot -Recurse -Settings $settingsPath -ErrorAction Stop | Out-Null |
| @@ -0,0 +1,12 @@ | |||
| # Copyright (c) Microsoft Corporation. All rights reserved. | |||
Co-authored-by: jessehouwing <4173387+jessehouwing@users.noreply.github.com>
Co-authored-by: jessehouwing <4173387+jessehouwing@users.noreply.github.com>
Co-authored-by: jessehouwing <4173387+jessehouwing@users.noreply.github.com>
|
A more permanent solution may en to serialize access to the runspace. At the repo sizes I work at this causes minimal perf overhead and stabilizes the output from the script analyzer |
Sorry, something went wrong.
…lls-to-sequential Serialize CommandInfo lookups onto a single dedicated runspace
There was a problem hiding this comment.
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Suppressed comments (3)Engine/CommandInfoCache.cs:99
var lazyCommandInfo = _commandInfoCache.GetOrAdd(key, new Lazy<CommandInfo>(() => GetCommandInfoInternal(commandName, commandTypes)));
Engine/CommandInfoCache.cs:62
// Always take the lock, also on the finalizer path, so that 'disposed' is never
// published without the runspace being disposed along with it and so that the runspace
// cannot be disposed while a lookup is in flight.
Tests/Engine/CommandInfoCacheConcurrency.tests.ps1:35
Task.WaitAll(tasks);
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
Invoke-ScriptAnalyzer -Path './' -Recurse -Settings ... intermittently fails on Linux/CI with CommandNotFoundException: The term 'Get-Command' is not recognized and NullReferenceException at CommandInfo.get_Parameters(). The reported settings file is valid — this is an engine concurrency bug, not a configuration error.
Fixes: #2205
Root cause
CommandInfoCache resolves commands through a shared RunspacePool while ScriptAnalyzer.AnalyzeSyntaxTree runs script rules on parallel tasks. Occasionally the PowerShell engine fails to resolve Get-Command itself in a pooled runspace — a manifestation of PowerShell/PowerShell#4003.
The transient blip then became permanent: the cache is a ConcurrentDictionary<CommandLookupKey, Lazy<CommandInfo>> using LazyThreadSafetyMode.ExecutionAndPublication, which caches exceptions for the lifetime of the process. One failure poisoned that command's entry forever, cascading into hundreds of errors. Since rule exceptions surface via WriteError, -ErrorAction Stop (the norm in GitHub Actions) turns them into a hard failure.
Diagnostics confirmed transience: at failure time a fresh runspace worked, a fresh pool worked, and immediate retries on the same pool succeeded.
Changes
Engine/CommandInfoCache.cs
GetCommandInfoInternal retries the Get-Command invocation up to MaxLookupAttempts (3) on CommandNotFoundException, returning null if it still fails. The catch is precisely scoped: Get-Command is invoked with -ErrorAction SilentlyContinue, so this exception can only mean Get-Command itself was unresolvable.
GetCommandInfo no longer lets a faulted Lazy<T> poison the cache — the entry is evicted before rethrowing. Eviction is value-specific so a replacement added concurrently by another thread isn't dropped:
Rules/UseCorrectCasing.cs
Tests/Rules/Issue2205.tests.ps1 — regression test performing a full recursive analysis of the repository with the reported settings under -ErrorAction Stop.
Note for reviewers
This is a defensive workaround for an upstream engine defect, consistent with the earlier precedent in #1523. If the underlying runspace-affinity issue is ever fixed, the retry loop becomes dead weight and can be removed.
More extensive fix applied:
Follow-up to #2206 / issue #2205. CommandInfoCache resolved commands through a RunspacePool(1, 10), allowing up to ten Get-Command invocations to run concurrently across separate runspaces. The PowerShell engine's command discovery state is not thread safe (PowerShell#4003), which surfaced as intermittent CommandNotFoundException for Get-Command itself and non-deterministic diagnostic counts.
A runspace pool doesn't help here: it already guarantees a given runspace is used by only one PowerShell instance at a time, and that per-runspace serialization is precisely the configuration that fails. The unsafe state is engine-global, so the gate has to be global too — at which point additional pooled runspaces are never concurrently active.
Changes
Engine/CommandInfoCache.cs
Tests/Engine/CommandInfoCacheConcurrency.tests.ps1 (new)
Performance
Invoke-ScriptAnalyzer -Path Tests -Recurse, warm, alternating runs:
Steady-state cost is within noise. The pre-change build also produced large outliers and varying diagnostic counts, so serializing appears to trade little or no throughput for determinism.