From ea91c50c671b57e9db6ea9dab1750e2410fa9cf0 Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Sat, 13 Jun 2020 13:31:31 +0100 Subject: [PATCH 1/3] UseCorrectCasing: Do not use CommandInfoCache because Parameters property has runspace affinity problems --- Engine/CommandInfoCache.cs | 7 ++++++- Engine/Helper.cs | 5 +++-- Rules/UseCorrectCasing.cs | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Engine/CommandInfoCache.cs b/Engine/CommandInfoCache.cs index 02e926b53..852c8edd3 100644 --- a/Engine/CommandInfoCache.cs +++ b/Engine/CommandInfoCache.cs @@ -57,8 +57,9 @@ protected virtual void Dispose(bool disposing) /// /// Name of the command to get a commandinfo object for. /// What types of command are needed. If omitted, all types are retrieved. + /// When needed due to runspace affinity problems of some PowerShell objects. /// - public CommandInfo GetCommandInfo(string commandName, CommandTypes? commandTypes = null) + public CommandInfo GetCommandInfo(string commandName, CommandTypes? commandTypes = null, bool bypassCache = false) { if (string.IsNullOrWhiteSpace(commandName)) { @@ -67,6 +68,10 @@ public CommandInfo GetCommandInfo(string commandName, CommandTypes? commandTypes var key = new CommandLookupKey(commandName, commandTypes); // Atomically either use PowerShell to query a command info object, or fetch it from the cache + if (bypassCache) + { + return GetCommandInfoInternal(commandName, commandTypes); + } return _commandInfoCache.GetOrAdd(key, new Lazy(() => GetCommandInfoInternal(commandName, commandTypes))).Value; } diff --git a/Engine/Helper.cs b/Engine/Helper.cs index cbd8d0cd3..743cb4d68 100644 --- a/Engine/Helper.cs +++ b/Engine/Helper.cs @@ -673,10 +673,11 @@ public bool PositionalParameterUsed(CommandAst cmdAst, bool moreThanTwoPositiona /// /// /// + /// /// - public CommandInfo GetCommandInfo(string name, CommandTypes? commandType = null) + public CommandInfo GetCommandInfo(string name, CommandTypes? commandType = null, bool bypassCache = false) { - return CommandInfoCache.GetCommandInfo(name, commandTypes: commandType); + return CommandInfoCache.GetCommandInfo(name, commandTypes: commandType, bypassCache: bypassCache); } /// diff --git a/Rules/UseCorrectCasing.cs b/Rules/UseCorrectCasing.cs index d1f579731..5cb45f949 100644 --- a/Rules/UseCorrectCasing.cs +++ b/Rules/UseCorrectCasing.cs @@ -44,7 +44,7 @@ public override IEnumerable AnalyzeScript(Ast ast, string file continue; } - var commandInfo = Helper.Instance.GetCommandInfo(commandName); + var commandInfo = Helper.Instance.GetCommandInfo(commandName, bypassCache: false); if (commandInfo == null || commandInfo.CommandType == CommandTypes.ExternalScript || commandInfo.CommandType == CommandTypes.Application) { continue; From 002243ff3d130c23ec973d15a799f47ebea13a74 Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Sat, 13 Jun 2020 18:48:33 +0100 Subject: [PATCH 2/3] Catch exception and only bypass cache then --- Engine/CommandInfoCache.cs | 2 +- Rules/UseCorrectCasing.cs | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Engine/CommandInfoCache.cs b/Engine/CommandInfoCache.cs index 852c8edd3..dbcb41eda 100644 --- a/Engine/CommandInfoCache.cs +++ b/Engine/CommandInfoCache.cs @@ -67,11 +67,11 @@ public CommandInfo GetCommandInfo(string commandName, CommandTypes? commandTypes } var key = new CommandLookupKey(commandName, commandTypes); - // Atomically either use PowerShell to query a command info object, or fetch it from the cache if (bypassCache) { return GetCommandInfoInternal(commandName, commandTypes); } + // Atomically either use PowerShell to query a command info object, or fetch it from the cache return _commandInfoCache.GetOrAdd(key, new Lazy(() => GetCommandInfoInternal(commandName, commandTypes))).Value; } diff --git a/Rules/UseCorrectCasing.cs b/Rules/UseCorrectCasing.cs index 5cb45f949..9569b1904 100644 --- a/Rules/UseCorrectCasing.cs +++ b/Rules/UseCorrectCasing.cs @@ -44,7 +44,7 @@ public override IEnumerable AnalyzeScript(Ast ast, string file continue; } - var commandInfo = Helper.Instance.GetCommandInfo(commandName, bypassCache: false); + var commandInfo = Helper.Instance.GetCommandInfo(commandName); if (commandInfo == null || commandInfo.CommandType == CommandTypes.ExternalScript || commandInfo.CommandType == CommandTypes.Application) { continue; @@ -69,7 +69,19 @@ public override IEnumerable AnalyzeScript(Ast ast, string file var commandParameterAsts = commandAst.FindAll( testAst => testAst is CommandParameterAst, true).Cast(); - var availableParameters = commandInfo.Parameters; + Dictionary availableParameters; + try + { + availableParameters = commandInfo.Parameters; + } + // It's a known issue that objects from PowerShell can have a runspace affinity, + // therefore if that happens, we query a fresh object instead of using the cache. + // https://github.com/PowerShell/PowerShell/issues/4003 + catch (InvalidOperationException) + { + commandInfo = Helper.Instance.GetCommandInfo(commandName, bypassCache: true); + availableParameters = commandInfo.Parameters; + } foreach (var commandParameterAst in commandParameterAsts) { var parameterName = commandParameterAst.ParameterName; From 303c63fea3790c4042b1a7b7e2898e010a8c1eed Mon Sep 17 00:00:00 2001 From: Christoph Bergmeister Date: Sat, 13 Jun 2020 20:45:42 +0100 Subject: [PATCH 3/3] Add test --- Tests/Rules/UseCorrectCasing.tests.ps1 | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Tests/Rules/UseCorrectCasing.tests.ps1 b/Tests/Rules/UseCorrectCasing.tests.ps1 index 7343677ff..a5280a6e3 100644 --- a/Tests/Rules/UseCorrectCasing.tests.ps1 +++ b/Tests/Rules/UseCorrectCasing.tests.ps1 @@ -73,4 +73,13 @@ Describe "UseCorrectCasing" { Invoke-Formatter 'Get-Process -NonExistingParameterName' -ErrorAction Stop } + It "Does not throw when correcting certain cmdlets (issue 1516)" { + $scriptDefinition = 'Get-Content;Test-Path;Get-ChildItem;Get-Content;Test-Path;Get-ChildItem' + $settings = @{ 'Rules' = @{ 'PSUseCorrectCasing' = @{ 'Enable' = $true } } } + { + 1..100 | + ForEach-Object { $null = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -Settings $settings -ErrorAction Stop } + } | + Should -Not -Throw + } }