From fe4bf82961f883500c64ff6df6b58ebf6c3e9c02 Mon Sep 17 00:00:00 2001 From: Quoc Truong Date: Fri, 12 Jun 2015 15:42:59 -0700 Subject: [PATCH 1/7] Improve Performance of ScriptAnalyzer by using BackgroundWorkers --- .../Commands/InvokeScriptAnalyzerCommand.cs | 171 +++++++++++++----- 1 file changed, 123 insertions(+), 48 deletions(-) diff --git a/Engine/Commands/InvokeScriptAnalyzerCommand.cs b/Engine/Commands/InvokeScriptAnalyzerCommand.cs index 02ef89a75..8d56fb732 100644 --- a/Engine/Commands/InvokeScriptAnalyzerCommand.cs +++ b/Engine/Commands/InvokeScriptAnalyzerCommand.cs @@ -12,6 +12,7 @@ using System.Text.RegularExpressions; using System; +using System.ComponentModel; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -20,6 +21,9 @@ using System.Management.Automation.Language; using System.IO; using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; +using System.Threading.Tasks; +using System.Collections.Concurrent; +using System.Threading; namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands { @@ -267,6 +271,14 @@ private void ProcessPath(string path) } + ConcurrentBag diagnostics; + ConcurrentBag suppressed; + Dictionary> ruleSuppressions; + List includeRegexList; + List excludeRegexList; + CountdownEvent cde; + ConcurrentDictionary> ruleDictionary; + /// /// Analyzes a single script file. /// @@ -275,15 +287,16 @@ private void AnalyzeFile(string filePath) { Token[] tokens = null; ParseError[] errors = null; - List diagnostics = new List(); - List suppressed = new List(); + diagnostics = new ConcurrentBag(); + suppressed = new ConcurrentBag(); + ruleDictionary = new ConcurrentDictionary>(); // Use a List of KVP rather than dictionary, since for a script containing inline functions with same signature, keys clash List> cmdInfoTable = new List>(); //Check wild card input for the Include/ExcludeRules and create regex match patterns - List includeRegexList = new List(); - List excludeRegexList = new List(); + includeRegexList = new List(); + excludeRegexList = new List(); if (includeRule != null) { foreach (string rule in includeRule) @@ -331,7 +344,7 @@ private void AnalyzeFile(string filePath) return; } - Dictionary> ruleSuppressions = Helper.Instance.GetRuleSuppression(ast); + ruleSuppressions = Helper.Instance.GetRuleSuppression(ast); foreach (List ruleSuppressionsList in ruleSuppressions.Values) { @@ -360,44 +373,24 @@ private void AnalyzeFile(string filePath) if (ScriptAnalyzer.Instance.ScriptRules != null) { - foreach (IScriptRule scriptRule in ScriptAnalyzer.Instance.ScriptRules) + cde = new CountdownEvent(ScriptAnalyzer.Instance.ScriptRules.Count()); + + foreach (var scriptRule in ScriptAnalyzer.Instance.ScriptRules) { - bool includeRegexMatch = false; - bool excludeRegexMatch = false; - foreach (Regex include in includeRegexList) - { - if (include.IsMatch(scriptRule.GetName())) - { - includeRegexMatch = true; - break; - } - } + BackgroundWorker bg = new BackgroundWorker(); + bg.DoWork += bg_DoWork; + bg.RunWorkerAsync(new object[] { scriptRule }); + } - foreach (Regex exclude in excludeRegexList) - { - if (exclude.IsMatch(scriptRule.GetName())) - { - excludeRegexMatch = true; - break; - } - } + cde.Wait(); - if ((includeRule == null || includeRegexMatch) && (excludeRule == null || !excludeRegexMatch)) + foreach (var rule in ruleDictionary.Keys) + { + List verboseOrErrors = ruleDictionary[rule]; + WriteVerbose(verboseOrErrors[0] as string); + if (verboseOrErrors.Count == 2) { - WriteVerbose(string.Format(CultureInfo.CurrentCulture, Strings.VerboseRunningMessage, scriptRule.GetName())); - - // Ensure that any unhandled errors from Rules are converted to non-terminating errors - // We want the Engine to continue functioning even if one or more Rules throws an exception - try - { - var records = Helper.Instance.SuppressRule(scriptRule.GetName(), ruleSuppressions, scriptRule.AnalyzeScript(ast, filePath).ToList()); - diagnostics.AddRange(records.Item2); - suppressed.AddRange(records.Item1); - } - catch (Exception scriptRuleException) - { - WriteError(new ErrorRecord(scriptRuleException, Strings.RuleErrorMessage, ErrorCategory.InvalidOperation, filePath)); - } + WriteError(verboseOrErrors[1] as ErrorRecord); } } } @@ -437,8 +430,14 @@ private void AnalyzeFile(string filePath) try { var records = Helper.Instance.SuppressRule(tokenRule.GetName(), ruleSuppressions, tokenRule.AnalyzeTokens(tokens, filePath).ToList()); - diagnostics.AddRange(records.Item2); - suppressed.AddRange(records.Item1); + foreach (var record in records.Item2) + { + diagnostics.Add(record); + } + foreach (var suppressedRec in records.Item1) + { + suppressed.Add(suppressedRec); + } } catch (Exception tokenRuleException) { @@ -489,8 +488,14 @@ private void AnalyzeFile(string filePath) try { var records = Helper.Instance.SuppressRule(dscResourceRule.GetName(), ruleSuppressions, dscResourceRule.AnalyzeDSCClass(ast, filePath).ToList()); - diagnostics.AddRange(records.Item2); - suppressed.AddRange(records.Item1); + foreach (var record in records.Item2) + { + diagnostics.Add(record); + } + foreach (var suppressedRec in records.Item1) + { + suppressed.Add(suppressedRec); + } } catch (Exception dscResourceRuleException) { @@ -532,8 +537,14 @@ private void AnalyzeFile(string filePath) try { var records = Helper.Instance.SuppressRule(dscResourceRule.GetName(), ruleSuppressions, dscResourceRule.AnalyzeDSCResource(ast, filePath).ToList()); - diagnostics.AddRange(records.Item2); - suppressed.AddRange(records.Item1); + foreach (var record in records.Item2) + { + diagnostics.Add(record); + } + foreach (var suppressedRec in records.Item1) + { + suppressed.Add(suppressedRec); + } } catch (Exception dscResourceRuleException) { @@ -573,15 +584,20 @@ private void AnalyzeFile(string filePath) } } - diagnostics.AddRange(ScriptAnalyzer.Instance.GetExternalRecord(ast, tokens, exRules.ToArray(), this, fileName)); + foreach (var record in ScriptAnalyzer.Instance.GetExternalRecord(ast, tokens, exRules.ToArray(), this, fileName)) + { + diagnostics.Add(record); + } } #endregion + IEnumerable diagnosticsList = diagnostics; + if (severity != null) { var diagSeverity = severity.Select(item => Enum.Parse(typeof(DiagnosticSeverity), item, true)); - diagnostics = diagnostics.Where(item => diagSeverity.Contains(item.Severity)).ToList(); + diagnosticsList = diagnostics.Where(item => diagSeverity.Contains(item.Severity)); } //Output through loggers @@ -596,7 +612,7 @@ private void AnalyzeFile(string filePath) } else { - foreach (DiagnosticRecord diagnostic in diagnostics) + foreach (DiagnosticRecord diagnostic in diagnosticsList) { logger.LogObject(diagnostic, this); } @@ -604,6 +620,65 @@ private void AnalyzeFile(string filePath) } } + void bg_DoWork(object sender, DoWorkEventArgs e) + { + bool includeRegexMatch = false; + bool excludeRegexMatch = false; + + object[] parameters = e.Argument as object[]; + + IScriptRule scriptRule = parameters[0] as IScriptRule; + + foreach (Regex include in includeRegexList) + { + if (include.IsMatch(scriptRule.GetName())) + { + includeRegexMatch = true; + break; + } + } + + foreach (Regex exclude in excludeRegexList) + { + if (exclude.IsMatch(scriptRule.GetName())) + { + excludeRegexMatch = true; + break; + } + } + + List result = new List(); + + if ((includeRule == null || includeRegexMatch) && (excludeRule == null || !excludeRegexMatch)) + { + //WriteVerbose(string.Format(CultureInfo.CurrentCulture, Strings.VerboseRunningMessage, scriptRule.GetName())); + result.Add(string.Format(CultureInfo.CurrentCulture, Strings.VerboseRunningMessage, scriptRule.GetName())); + + // Ensure that any unhandled errors from Rules are converted to non-terminating errors + // We want the Engine to continue functioning even if one or more Rules throws an exception + try + { + var records = Helper.Instance.SuppressRule(scriptRule.GetName(), ruleSuppressions, scriptRule.AnalyzeScript(ast, ast.Extent.File).ToList()); + foreach (var record in records.Item2) + { + diagnostics.Add(record); + } + foreach (var suppressedRec in records.Item1) + { + suppressed.Add(suppressedRec); + } + } + catch (Exception scriptRuleException) + { + result.Add(new ErrorRecord(scriptRuleException, Strings.RuleErrorMessage, ErrorCategory.InvalidOperation, ast.Extent.File)); + } + } + + ruleDictionary[scriptRule.GetName()] = result; + + cde.Signal(); + } + #endregion } } \ No newline at end of file From 967461a50b0b629b04978145f338da955307f1d4 Mon Sep 17 00:00:00 2001 From: quoctruong Date: Mon, 15 Jun 2015 01:16:30 -0700 Subject: [PATCH 2/7] Fix Session State Error --- Rules/AvoidUsingDeprecatedManifestFields.cs | 3 ++- Rules/MissingModuleManifestField.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Rules/AvoidUsingDeprecatedManifestFields.cs b/Rules/AvoidUsingDeprecatedManifestFields.cs index 964522490..8ec58ca60 100644 --- a/Rules/AvoidUsingDeprecatedManifestFields.cs +++ b/Rules/AvoidUsingDeprecatedManifestFields.cs @@ -41,7 +41,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) if (String.Equals(System.IO.Path.GetExtension(fileName), ".psd1", StringComparison.OrdinalIgnoreCase)) { - var ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace); + var ps = System.Management.Automation.PowerShell.Create(); IEnumerable result = null; try { @@ -73,6 +73,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) } } + ps.Dispose(); } } diff --git a/Rules/MissingModuleManifestField.cs b/Rules/MissingModuleManifestField.cs index 9b0dd5954..6df37980c 100644 --- a/Rules/MissingModuleManifestField.cs +++ b/Rules/MissingModuleManifestField.cs @@ -38,7 +38,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) if (String.Equals(System.IO.Path.GetExtension(fileName), ".psd1", StringComparison.OrdinalIgnoreCase)) { - var ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace); + var ps = System.Management.Automation.PowerShell.Create(); try { @@ -68,6 +68,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) } } + ps.Dispose(); } } From 8241b864aa35831a448c03063bc9346f65718477 Mon Sep 17 00:00:00 2001 From: Quoc Truong Date: Fri, 12 Jun 2015 15:42:59 -0700 Subject: [PATCH 3/7] Improve Performance of ScriptAnalyzer by using BackgroundWorkers --- .../Commands/InvokeScriptAnalyzerCommand.cs | 171 +++++++++++++----- 1 file changed, 123 insertions(+), 48 deletions(-) diff --git a/Engine/Commands/InvokeScriptAnalyzerCommand.cs b/Engine/Commands/InvokeScriptAnalyzerCommand.cs index 02ef89a75..8d56fb732 100644 --- a/Engine/Commands/InvokeScriptAnalyzerCommand.cs +++ b/Engine/Commands/InvokeScriptAnalyzerCommand.cs @@ -12,6 +12,7 @@ using System.Text.RegularExpressions; using System; +using System.ComponentModel; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -20,6 +21,9 @@ using System.Management.Automation.Language; using System.IO; using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; +using System.Threading.Tasks; +using System.Collections.Concurrent; +using System.Threading; namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands { @@ -267,6 +271,14 @@ private void ProcessPath(string path) } + ConcurrentBag diagnostics; + ConcurrentBag suppressed; + Dictionary> ruleSuppressions; + List includeRegexList; + List excludeRegexList; + CountdownEvent cde; + ConcurrentDictionary> ruleDictionary; + /// /// Analyzes a single script file. /// @@ -275,15 +287,16 @@ private void AnalyzeFile(string filePath) { Token[] tokens = null; ParseError[] errors = null; - List diagnostics = new List(); - List suppressed = new List(); + diagnostics = new ConcurrentBag(); + suppressed = new ConcurrentBag(); + ruleDictionary = new ConcurrentDictionary>(); // Use a List of KVP rather than dictionary, since for a script containing inline functions with same signature, keys clash List> cmdInfoTable = new List>(); //Check wild card input for the Include/ExcludeRules and create regex match patterns - List includeRegexList = new List(); - List excludeRegexList = new List(); + includeRegexList = new List(); + excludeRegexList = new List(); if (includeRule != null) { foreach (string rule in includeRule) @@ -331,7 +344,7 @@ private void AnalyzeFile(string filePath) return; } - Dictionary> ruleSuppressions = Helper.Instance.GetRuleSuppression(ast); + ruleSuppressions = Helper.Instance.GetRuleSuppression(ast); foreach (List ruleSuppressionsList in ruleSuppressions.Values) { @@ -360,44 +373,24 @@ private void AnalyzeFile(string filePath) if (ScriptAnalyzer.Instance.ScriptRules != null) { - foreach (IScriptRule scriptRule in ScriptAnalyzer.Instance.ScriptRules) + cde = new CountdownEvent(ScriptAnalyzer.Instance.ScriptRules.Count()); + + foreach (var scriptRule in ScriptAnalyzer.Instance.ScriptRules) { - bool includeRegexMatch = false; - bool excludeRegexMatch = false; - foreach (Regex include in includeRegexList) - { - if (include.IsMatch(scriptRule.GetName())) - { - includeRegexMatch = true; - break; - } - } + BackgroundWorker bg = new BackgroundWorker(); + bg.DoWork += bg_DoWork; + bg.RunWorkerAsync(new object[] { scriptRule }); + } - foreach (Regex exclude in excludeRegexList) - { - if (exclude.IsMatch(scriptRule.GetName())) - { - excludeRegexMatch = true; - break; - } - } + cde.Wait(); - if ((includeRule == null || includeRegexMatch) && (excludeRule == null || !excludeRegexMatch)) + foreach (var rule in ruleDictionary.Keys) + { + List verboseOrErrors = ruleDictionary[rule]; + WriteVerbose(verboseOrErrors[0] as string); + if (verboseOrErrors.Count == 2) { - WriteVerbose(string.Format(CultureInfo.CurrentCulture, Strings.VerboseRunningMessage, scriptRule.GetName())); - - // Ensure that any unhandled errors from Rules are converted to non-terminating errors - // We want the Engine to continue functioning even if one or more Rules throws an exception - try - { - var records = Helper.Instance.SuppressRule(scriptRule.GetName(), ruleSuppressions, scriptRule.AnalyzeScript(ast, filePath).ToList()); - diagnostics.AddRange(records.Item2); - suppressed.AddRange(records.Item1); - } - catch (Exception scriptRuleException) - { - WriteError(new ErrorRecord(scriptRuleException, Strings.RuleErrorMessage, ErrorCategory.InvalidOperation, filePath)); - } + WriteError(verboseOrErrors[1] as ErrorRecord); } } } @@ -437,8 +430,14 @@ private void AnalyzeFile(string filePath) try { var records = Helper.Instance.SuppressRule(tokenRule.GetName(), ruleSuppressions, tokenRule.AnalyzeTokens(tokens, filePath).ToList()); - diagnostics.AddRange(records.Item2); - suppressed.AddRange(records.Item1); + foreach (var record in records.Item2) + { + diagnostics.Add(record); + } + foreach (var suppressedRec in records.Item1) + { + suppressed.Add(suppressedRec); + } } catch (Exception tokenRuleException) { @@ -489,8 +488,14 @@ private void AnalyzeFile(string filePath) try { var records = Helper.Instance.SuppressRule(dscResourceRule.GetName(), ruleSuppressions, dscResourceRule.AnalyzeDSCClass(ast, filePath).ToList()); - diagnostics.AddRange(records.Item2); - suppressed.AddRange(records.Item1); + foreach (var record in records.Item2) + { + diagnostics.Add(record); + } + foreach (var suppressedRec in records.Item1) + { + suppressed.Add(suppressedRec); + } } catch (Exception dscResourceRuleException) { @@ -532,8 +537,14 @@ private void AnalyzeFile(string filePath) try { var records = Helper.Instance.SuppressRule(dscResourceRule.GetName(), ruleSuppressions, dscResourceRule.AnalyzeDSCResource(ast, filePath).ToList()); - diagnostics.AddRange(records.Item2); - suppressed.AddRange(records.Item1); + foreach (var record in records.Item2) + { + diagnostics.Add(record); + } + foreach (var suppressedRec in records.Item1) + { + suppressed.Add(suppressedRec); + } } catch (Exception dscResourceRuleException) { @@ -573,15 +584,20 @@ private void AnalyzeFile(string filePath) } } - diagnostics.AddRange(ScriptAnalyzer.Instance.GetExternalRecord(ast, tokens, exRules.ToArray(), this, fileName)); + foreach (var record in ScriptAnalyzer.Instance.GetExternalRecord(ast, tokens, exRules.ToArray(), this, fileName)) + { + diagnostics.Add(record); + } } #endregion + IEnumerable diagnosticsList = diagnostics; + if (severity != null) { var diagSeverity = severity.Select(item => Enum.Parse(typeof(DiagnosticSeverity), item, true)); - diagnostics = diagnostics.Where(item => diagSeverity.Contains(item.Severity)).ToList(); + diagnosticsList = diagnostics.Where(item => diagSeverity.Contains(item.Severity)); } //Output through loggers @@ -596,7 +612,7 @@ private void AnalyzeFile(string filePath) } else { - foreach (DiagnosticRecord diagnostic in diagnostics) + foreach (DiagnosticRecord diagnostic in diagnosticsList) { logger.LogObject(diagnostic, this); } @@ -604,6 +620,65 @@ private void AnalyzeFile(string filePath) } } + void bg_DoWork(object sender, DoWorkEventArgs e) + { + bool includeRegexMatch = false; + bool excludeRegexMatch = false; + + object[] parameters = e.Argument as object[]; + + IScriptRule scriptRule = parameters[0] as IScriptRule; + + foreach (Regex include in includeRegexList) + { + if (include.IsMatch(scriptRule.GetName())) + { + includeRegexMatch = true; + break; + } + } + + foreach (Regex exclude in excludeRegexList) + { + if (exclude.IsMatch(scriptRule.GetName())) + { + excludeRegexMatch = true; + break; + } + } + + List result = new List(); + + if ((includeRule == null || includeRegexMatch) && (excludeRule == null || !excludeRegexMatch)) + { + //WriteVerbose(string.Format(CultureInfo.CurrentCulture, Strings.VerboseRunningMessage, scriptRule.GetName())); + result.Add(string.Format(CultureInfo.CurrentCulture, Strings.VerboseRunningMessage, scriptRule.GetName())); + + // Ensure that any unhandled errors from Rules are converted to non-terminating errors + // We want the Engine to continue functioning even if one or more Rules throws an exception + try + { + var records = Helper.Instance.SuppressRule(scriptRule.GetName(), ruleSuppressions, scriptRule.AnalyzeScript(ast, ast.Extent.File).ToList()); + foreach (var record in records.Item2) + { + diagnostics.Add(record); + } + foreach (var suppressedRec in records.Item1) + { + suppressed.Add(suppressedRec); + } + } + catch (Exception scriptRuleException) + { + result.Add(new ErrorRecord(scriptRuleException, Strings.RuleErrorMessage, ErrorCategory.InvalidOperation, ast.Extent.File)); + } + } + + ruleDictionary[scriptRule.GetName()] = result; + + cde.Signal(); + } + #endregion } } \ No newline at end of file From bb1cf1d025eb7dac7731acc73c0826599caf368f Mon Sep 17 00:00:00 2001 From: quoctruong Date: Mon, 15 Jun 2015 01:16:30 -0700 Subject: [PATCH 4/7] Fix Session State Error --- Rules/AvoidUsingDeprecatedManifestFields.cs | 3 ++- Rules/MissingModuleManifestField.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Rules/AvoidUsingDeprecatedManifestFields.cs b/Rules/AvoidUsingDeprecatedManifestFields.cs index 964522490..8ec58ca60 100644 --- a/Rules/AvoidUsingDeprecatedManifestFields.cs +++ b/Rules/AvoidUsingDeprecatedManifestFields.cs @@ -41,7 +41,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) if (String.Equals(System.IO.Path.GetExtension(fileName), ".psd1", StringComparison.OrdinalIgnoreCase)) { - var ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace); + var ps = System.Management.Automation.PowerShell.Create(); IEnumerable result = null; try { @@ -73,6 +73,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) } } + ps.Dispose(); } } diff --git a/Rules/MissingModuleManifestField.cs b/Rules/MissingModuleManifestField.cs index 9b0dd5954..6df37980c 100644 --- a/Rules/MissingModuleManifestField.cs +++ b/Rules/MissingModuleManifestField.cs @@ -38,7 +38,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) if (String.Equals(System.IO.Path.GetExtension(fileName), ".psd1", StringComparison.OrdinalIgnoreCase)) { - var ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace); + var ps = System.Management.Automation.PowerShell.Create(); try { @@ -68,6 +68,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) } } + ps.Dispose(); } } From 8ba1409d1beea1f87f45cfef0399c913d5819019 Mon Sep 17 00:00:00 2001 From: Quoc Truong Date: Wed, 17 Jun 2015 17:21:06 -0700 Subject: [PATCH 5/7] Fix test failures and use tasks instead of backgroundworkers --- .../Commands/InvokeScriptAnalyzerCommand.cs | 158 ++++++++---------- .../AvoidGlobalOrUnitializedVars.tests.ps1 | 6 +- .../Rules/AvoidPositionalParameters.tests.ps1 | 2 +- .../AvoidShouldContinueWithoutForce.tests.ps1 | 2 +- .../AvoidUserNameAndPasswordParams.tests.ps1 | 2 +- Tests/Rules/AvoidUsingAlias.tests.ps1 | 2 +- .../AvoidUsingPlainTextForPassword.tests.ps1 | 2 +- .../AvoidUsingUninitializedVariable.Tests.ps1 | 2 +- Tests/Rules/ProvideCommentHelp.tests.ps1 | 2 +- .../ProvideDefaultParameterValue.tests.ps1 | 2 +- ...eturnCorrectTypesForDSCFunctions.tests.ps1 | 2 +- ...eDeclaredVarsMoreThanAssignments.tests.ps1 | 2 +- Tests/Rules/UseOutputTypeCorrectly.tests.ps1 | 2 +- .../UseVerboseMessageInDSCResource.Tests.ps1 | 2 +- 14 files changed, 87 insertions(+), 101 deletions(-) diff --git a/Engine/Commands/InvokeScriptAnalyzerCommand.cs b/Engine/Commands/InvokeScriptAnalyzerCommand.cs index 8d56fb732..1144c8a58 100644 --- a/Engine/Commands/InvokeScriptAnalyzerCommand.cs +++ b/Engine/Commands/InvokeScriptAnalyzerCommand.cs @@ -271,14 +271,6 @@ private void ProcessPath(string path) } - ConcurrentBag diagnostics; - ConcurrentBag suppressed; - Dictionary> ruleSuppressions; - List includeRegexList; - List excludeRegexList; - CountdownEvent cde; - ConcurrentDictionary> ruleDictionary; - /// /// Analyzes a single script file. /// @@ -287,16 +279,16 @@ private void AnalyzeFile(string filePath) { Token[] tokens = null; ParseError[] errors = null; - diagnostics = new ConcurrentBag(); - suppressed = new ConcurrentBag(); - ruleDictionary = new ConcurrentDictionary>(); + ConcurrentBag diagnostics = new ConcurrentBag(); + ConcurrentBag suppressed = new ConcurrentBag(); + BlockingCollection> verboseOrErrors = new BlockingCollection>(); // Use a List of KVP rather than dictionary, since for a script containing inline functions with same signature, keys clash List> cmdInfoTable = new List>(); //Check wild card input for the Include/ExcludeRules and create regex match patterns - includeRegexList = new List(); - excludeRegexList = new List(); + List includeRegexList = new List(); + List excludeRegexList = new List(); if (includeRule != null) { foreach (string rule in includeRule) @@ -344,7 +336,7 @@ private void AnalyzeFile(string filePath) return; } - ruleSuppressions = Helper.Instance.GetRuleSuppression(ast); + Dictionary> ruleSuppressions = Helper.Instance.GetRuleSuppression(ast); foreach (List ruleSuppressionsList in ruleSuppressions.Values) { @@ -373,24 +365,77 @@ private void AnalyzeFile(string filePath) if (ScriptAnalyzer.Instance.ScriptRules != null) { - cde = new CountdownEvent(ScriptAnalyzer.Instance.ScriptRules.Count()); + var tasks = ScriptAnalyzer.Instance.ScriptRules.Select(scriptRule => Task.Factory.StartNew(() => + { + bool includeRegexMatch = false; + bool excludeRegexMatch = false; - foreach (var scriptRule in ScriptAnalyzer.Instance.ScriptRules) - { - BackgroundWorker bg = new BackgroundWorker(); - bg.DoWork += bg_DoWork; - bg.RunWorkerAsync(new object[] { scriptRule }); - } + foreach (Regex include in includeRegexList) + { + if (include.IsMatch(scriptRule.GetName())) + { + includeRegexMatch = true; + break; + } + } + + foreach (Regex exclude in excludeRegexList) + { + if (exclude.IsMatch(scriptRule.GetName())) + { + excludeRegexMatch = true; + break; + } + } + + if ((includeRule == null || includeRegexMatch) && (excludeRule == null || !excludeRegexMatch)) + { + List result = new List(); + + //WriteVerbose(string.Format(CultureInfo.CurrentCulture, Strings.VerboseRunningMessage, scriptRule.GetName())); + result.Add(string.Format(CultureInfo.CurrentCulture, Strings.VerboseRunningMessage, scriptRule.GetName())); + + // Ensure that any unhandled errors from Rules are converted to non-terminating errors + // We want the Engine to continue functioning even if one or more Rules throws an exception + try + { + var records = Helper.Instance.SuppressRule(scriptRule.GetName(), ruleSuppressions, scriptRule.AnalyzeScript(ast, ast.Extent.File).ToList()); + foreach (var record in records.Item2) + { + diagnostics.Add(record); + } + foreach (var suppressedRec in records.Item1) + { + suppressed.Add(suppressedRec); + } + } + catch (Exception scriptRuleException) + { + result.Add(new ErrorRecord(scriptRuleException, Strings.RuleErrorMessage, ErrorCategory.InvalidOperation, ast.Extent.File)); + } - cde.Wait(); + verboseOrErrors.Add(result); + } + })); - foreach (var rule in ruleDictionary.Keys) + Task.Factory.ContinueWhenAll(tasks.ToArray(), t => verboseOrErrors.CompleteAdding()); + + while (!verboseOrErrors.IsCompleted) { - List verboseOrErrors = ruleDictionary[rule]; - WriteVerbose(verboseOrErrors[0] as string); - if (verboseOrErrors.Count == 2) + List data = null; + try + { + data = verboseOrErrors.Take(); + } + catch (InvalidOperationException) { } + + if (data != null) { - WriteError(verboseOrErrors[1] as ErrorRecord); + WriteVerbose(data[0] as string); + if (data.Count == 2) + { + WriteError(data[1] as ErrorRecord); + } } } } @@ -620,65 +665,6 @@ private void AnalyzeFile(string filePath) } } - void bg_DoWork(object sender, DoWorkEventArgs e) - { - bool includeRegexMatch = false; - bool excludeRegexMatch = false; - - object[] parameters = e.Argument as object[]; - - IScriptRule scriptRule = parameters[0] as IScriptRule; - - foreach (Regex include in includeRegexList) - { - if (include.IsMatch(scriptRule.GetName())) - { - includeRegexMatch = true; - break; - } - } - - foreach (Regex exclude in excludeRegexList) - { - if (exclude.IsMatch(scriptRule.GetName())) - { - excludeRegexMatch = true; - break; - } - } - - List result = new List(); - - if ((includeRule == null || includeRegexMatch) && (excludeRule == null || !excludeRegexMatch)) - { - //WriteVerbose(string.Format(CultureInfo.CurrentCulture, Strings.VerboseRunningMessage, scriptRule.GetName())); - result.Add(string.Format(CultureInfo.CurrentCulture, Strings.VerboseRunningMessage, scriptRule.GetName())); - - // Ensure that any unhandled errors from Rules are converted to non-terminating errors - // We want the Engine to continue functioning even if one or more Rules throws an exception - try - { - var records = Helper.Instance.SuppressRule(scriptRule.GetName(), ruleSuppressions, scriptRule.AnalyzeScript(ast, ast.Extent.File).ToList()); - foreach (var record in records.Item2) - { - diagnostics.Add(record); - } - foreach (var suppressedRec in records.Item1) - { - suppressed.Add(suppressedRec); - } - } - catch (Exception scriptRuleException) - { - result.Add(new ErrorRecord(scriptRuleException, Strings.RuleErrorMessage, ErrorCategory.InvalidOperation, ast.Extent.File)); - } - } - - ruleDictionary[scriptRule.GetName()] = result; - - cde.Signal(); - } - #endregion } } \ No newline at end of file diff --git a/Tests/Rules/AvoidGlobalOrUnitializedVars.tests.ps1 b/Tests/Rules/AvoidGlobalOrUnitializedVars.tests.ps1 index 0b90d1b71..47a709f27 100644 --- a/Tests/Rules/AvoidGlobalOrUnitializedVars.tests.ps1 +++ b/Tests/Rules/AvoidGlobalOrUnitializedVars.tests.ps1 @@ -2,14 +2,14 @@ $globalMessage = "Found global variable 'Global:1'." $globalName = "PSAvoidGlobalVars" $nonInitializedName = "PSAvoidUninitializedVariable" -$nonInitializedMessage = "Variable 'a' is not initialized. Non-global variables must be initialized. To fix a violation of this rule, please initialize non-global variables." +$nonInitializedMessage = "Variable 'globalVars' is not initialized. Non-global variables must be initialized. To fix a violation of this rule, please initialize non-global variables." $directory = Split-Path -Parent $MyInvocation.MyCommand.Path $violations = Invoke-ScriptAnalyzer $directory\AvoidGlobalOrUnitializedVars.ps1 $dscResourceViolations = Invoke-ScriptAnalyzer $directory\DSCResources\MSFT_WaitForAny\MSFT_WaitForAny.psm1 | Where-Object {$_.RuleName -eq $nonInitializedName} $globalViolations = $violations | Where-Object {$_.RuleName -eq $globalName} $nonInitializedViolations = $violations | Where-Object {$_.RuleName -eq $nonInitializedName} $noViolations = Invoke-ScriptAnalyzer $directory\AvoidGlobalOrUnitializedVarsNoViolations.ps1 -$noGlobalViolations = $noViolations | Where-Object {$_.RuleName -eq $violationName} +$noGlobalViolations = $noViolations | Where-Object {$_.RuleName -eq $globalName} $noUninitializedViolations = $noViolations | Where-Object {$_.RuleName -eq $nonInitializedName} Describe "AvoidGlobalVars" { @@ -23,7 +23,7 @@ Describe "AvoidGlobalVars" { } It "has the correct description message" { - $violations[0].Message | Should Match $globalMessage + $globalViolations[0].Message | Should Match $globalMessage } } diff --git a/Tests/Rules/AvoidPositionalParameters.tests.ps1 b/Tests/Rules/AvoidPositionalParameters.tests.ps1 index 82be8a726..2e5156edd 100644 --- a/Tests/Rules/AvoidPositionalParameters.tests.ps1 +++ b/Tests/Rules/AvoidPositionalParameters.tests.ps1 @@ -1,5 +1,5 @@ Import-Module PSScriptAnalyzer -$violationMessage = "Cmdlet 'Get-Content' has positional parameter. Please use named parameters instead of positional parameters when calling a command." +$violationMessage = "Cmdlet 'Write-Host' has positional parameter. Please use named parameters instead of positional parameters when calling a command." $violationName = "PSAvoidUsingPositionalParameters" $directory = Split-Path -Parent $MyInvocation.MyCommand.Path $violations = Invoke-ScriptAnalyzer $directory\AvoidPositionalParameters.ps1 | Where-Object {$_.RuleName -eq $violationName} diff --git a/Tests/Rules/AvoidShouldContinueWithoutForce.tests.ps1 b/Tests/Rules/AvoidShouldContinueWithoutForce.tests.ps1 index 4fab2e16b..9daf1cf3e 100644 --- a/Tests/Rules/AvoidShouldContinueWithoutForce.tests.ps1 +++ b/Tests/Rules/AvoidShouldContinueWithoutForce.tests.ps1 @@ -1,5 +1,5 @@ Import-Module PSScriptAnalyzer -$violationMessage = "Function 'Verb-Noun' in file 'AvoidShouldContinueWithoutForce.ps1' uses ShouldContinue but does not have a boolean force parameter. The force parameter will allow users of the script to bypass ShouldContinue prompt" +$violationMessage = "Function 'Verb-Noun2' in file 'AvoidShouldContinueWithoutForce.ps1' uses ShouldContinue but does not have a boolean force parameter. The force parameter will allow users of the script to bypass ShouldContinue prompt" $violationName = "PSAvoidShouldContinueWithoutForce" $directory = Split-Path -Parent $MyInvocation.MyCommand.Path $violations = Invoke-ScriptAnalyzer $directory\AvoidShouldContinueWithoutForce.ps1 | Where-Object {$_.RuleName -eq $violationName} diff --git a/Tests/Rules/AvoidUserNameAndPasswordParams.tests.ps1 b/Tests/Rules/AvoidUserNameAndPasswordParams.tests.ps1 index e50e9fcdc..14f567160 100644 --- a/Tests/Rules/AvoidUserNameAndPasswordParams.tests.ps1 +++ b/Tests/Rules/AvoidUserNameAndPasswordParams.tests.ps1 @@ -1,6 +1,6 @@ Import-Module PSScriptAnalyzer -$violationMessage = "Function 'TestFunction' has both username and password parameters. A credential parameter of type PSCredential should be used." +$violationMessage = "Function 'Verb-Noun' has both username and password parameters. A credential parameter of type PSCredential should be used." $violationName = "PSAvoidUsingUserNameAndPasswordParams" $directory = Split-Path -Parent $MyInvocation.MyCommand.Path $violations = Invoke-ScriptAnalyzer $directory\AvoidUserNameAndPasswordParams.ps1 | Where-Object {$_.RuleName -eq $violationName} diff --git a/Tests/Rules/AvoidUsingAlias.tests.ps1 b/Tests/Rules/AvoidUsingAlias.tests.ps1 index 7c9d74db1..63e8f2780 100644 --- a/Tests/Rules/AvoidUsingAlias.tests.ps1 +++ b/Tests/Rules/AvoidUsingAlias.tests.ps1 @@ -1,5 +1,5 @@ Import-Module PSScriptAnalyzer -$violationMessage = "'iex' is an alias of 'Invoke-Expression'. Alias can introduce possible problems and make scripts hard to maintain. Please consider changing alias to its full content." +$violationMessage = "'cls' is an alias of 'Clear-Host'. Alias can introduce possible problems and make scripts hard to maintain. Please consider changing alias to its full content." $violationName = "PSAvoidUsingCmdletAliases" $directory = Split-Path -Parent $MyInvocation.MyCommand.Path $violations = Invoke-ScriptAnalyzer $directory\AvoidUsingAlias.ps1 | Where-Object {$_.RuleName -eq $violationName} diff --git a/Tests/Rules/AvoidUsingPlainTextForPassword.tests.ps1 b/Tests/Rules/AvoidUsingPlainTextForPassword.tests.ps1 index 2c298a7cf..3dd6ff7c7 100644 --- a/Tests/Rules/AvoidUsingPlainTextForPassword.tests.ps1 +++ b/Tests/Rules/AvoidUsingPlainTextForPassword.tests.ps1 @@ -1,6 +1,6 @@ Import-Module PSScriptAnalyzer -$violationMessage = [regex]::Escape("Parameter '`$passphrases' should use SecureString, otherwise this will expose sensitive information. See ConvertTo-SecureString for more information.") +$violationMessage = [regex]::Escape("Parameter '`$password' should use SecureString, otherwise this will expose sensitive information. See ConvertTo-SecureString for more information.") $violationName = "PSAvoidUsingPlainTextForPassword" $directory = Split-Path -Parent $MyInvocation.MyCommand.Path $violations = Invoke-ScriptAnalyzer $directory\AvoidUsingPlainTextForPassword.ps1 | Where-Object {$_.RuleName -eq $violationName} diff --git a/Tests/Rules/AvoidUsingUninitializedVariable.Tests.ps1 b/Tests/Rules/AvoidUsingUninitializedVariable.Tests.ps1 index 3fb75b360..4f499ed04 100644 --- a/Tests/Rules/AvoidUsingUninitializedVariable.Tests.ps1 +++ b/Tests/Rules/AvoidUsingUninitializedVariable.Tests.ps1 @@ -1,6 +1,6 @@ Import-Module PSScriptAnalyzer $AvoidUninitializedVariable = "PSAvoidUninitializedVariable" -$violationMessage = "Variable 'MyProgressPreference' is not initialized. Non-global variables must be initialized. To fix a violation of this rule, please initialize non-global variables." +$violationMessage = "Variable 'MyVerbosePreference' is not initialized. Non-global variables must be initialized. To fix a violation of this rule, please initialize non-global variables." $directory = Split-Path -Parent $MyInvocation.MyCommand.Path $violations = Invoke-ScriptAnalyzer $directory\AvoidUsingUninitializedVariable.ps1 -IncludeRule $AvoidUninitializedVariable $noViolations = Invoke-ScriptAnalyzer $directory\AvoidUsingUninitializedVariableNoViolations.ps1 -IncludeRule $AvoidUninitializedVariable diff --git a/Tests/Rules/ProvideCommentHelp.tests.ps1 b/Tests/Rules/ProvideCommentHelp.tests.ps1 index 30a183710..a0ab714d4 100644 --- a/Tests/Rules/ProvideCommentHelp.tests.ps1 +++ b/Tests/Rules/ProvideCommentHelp.tests.ps1 @@ -1,5 +1,5 @@ Import-Module PSScriptAnalyzer -$violationMessage = "The cmdlet 'Verb-Files' does not have a help comment." +$violationMessage = "The cmdlet 'Comment' does not have a help comment." $violationName = "PSProvideCommentHelp" $directory = Split-Path -Parent $MyInvocation.MyCommand.Path $violations = Invoke-ScriptAnalyzer $directory\BadCmdlet.ps1 | Where-Object {$_.RuleName -eq $violationName} diff --git a/Tests/Rules/ProvideDefaultParameterValue.tests.ps1 b/Tests/Rules/ProvideDefaultParameterValue.tests.ps1 index c731d7974..5289fdc72 100644 --- a/Tests/Rules/ProvideDefaultParameterValue.tests.ps1 +++ b/Tests/Rules/ProvideDefaultParameterValue.tests.ps1 @@ -1,6 +1,6 @@ Import-Module PSScriptAnalyzer $violationName = "PSProvideDefaultParameterValue" -$violationMessage = "Parameter 'Param2' is not initialized. Parameters must have a default value. To fix a violation of this rule, please specify a default value for all parameters" +$violationMessage = "Parameter 'Param1' is not initialized. Parameters must have a default value. To fix a violation of this rule, please specify a default value for all parameters" $directory = Split-Path -Parent $MyInvocation.MyCommand.Path $violations = Invoke-ScriptAnalyzer $directory\ProvideDefaultParameterValue.ps1 | Where-Object {$_.RuleName -match $violationName} $noViolations = Invoke-ScriptAnalyzer $directory\ProvideDefaultParameterValueNoViolations.ps1 diff --git a/Tests/Rules/ReturnCorrectTypesForDSCFunctions.tests.ps1 b/Tests/Rules/ReturnCorrectTypesForDSCFunctions.tests.ps1 index f45163f88..4ba05667d 100644 --- a/Tests/Rules/ReturnCorrectTypesForDSCFunctions.tests.ps1 +++ b/Tests/Rules/ReturnCorrectTypesForDSCFunctions.tests.ps1 @@ -1,7 +1,7 @@ Import-Module -Verbose PSScriptAnalyzer $violationMessageDSCResource = "Test-TargetResource function in DSC Resource should return object of type System.Boolean instead of System.Collections.Hashtable" -$violationMessageDSCClass = "Test function in DSC Class FileResource should return object of type System.Boolean instead of type System.Int32" +$violationMessageDSCClass = "Get function in DSC Class FileResource should return object of type FileResource instead of type System.Collections.Hashtable" $violationName = "PSDSCReturnCorrectTypesForDSCFunctions" $directory = Split-Path -Parent $MyInvocation.MyCommand.Path $violations = Invoke-ScriptAnalyzer $directory\DSCResources\MSFT_WaitForAll\MSFT_WaitForAll.psm1 | Where-Object {$_.RuleName -eq $violationName} diff --git a/Tests/Rules/UseDeclaredVarsMoreThanAssignments.tests.ps1 b/Tests/Rules/UseDeclaredVarsMoreThanAssignments.tests.ps1 index 61b5b34a2..f1d818893 100644 --- a/Tests/Rules/UseDeclaredVarsMoreThanAssignments.tests.ps1 +++ b/Tests/Rules/UseDeclaredVarsMoreThanAssignments.tests.ps1 @@ -1,5 +1,5 @@ Import-Module PSScriptAnalyzer -$violationMessage = "The variable 'declaredVar' is assigned but never used." +$violationMessage = "The variable 'declaredVar2' is assigned but never used." $violationName = "PSUseDeclaredVarsMoreThanAssigments" $directory = Split-Path -Parent $MyInvocation.MyCommand.Path $violations = Invoke-ScriptAnalyzer $directory\UseDeclaredVarsMoreThanAssignments.ps1 | Where-Object {$_.RuleName -eq $violationName} diff --git a/Tests/Rules/UseOutputTypeCorrectly.tests.ps1 b/Tests/Rules/UseOutputTypeCorrectly.tests.ps1 index 666d281d0..236a5faa1 100644 --- a/Tests/Rules/UseOutputTypeCorrectly.tests.ps1 +++ b/Tests/Rules/UseOutputTypeCorrectly.tests.ps1 @@ -1,5 +1,5 @@ Import-Module PSScriptAnalyzer -$violationMessage = "The cmdlet 'Verb-Files' returns an object of type 'System.Double' but this type is not declared in the OutputType attribute." +$violationMessage = "The cmdlet 'Verb-Files' returns an object of type 'System.Collections.Hashtable' but this type is not declared in the OutputType attribute." $violationName = "PSUseOutputTypeCorrectly" $directory = Split-Path -Parent $MyInvocation.MyCommand.Path $violations = Invoke-ScriptAnalyzer $directory\BadCmdlet.ps1 | Where-Object {$_.RuleName -eq $violationName} diff --git a/Tests/Rules/UseVerboseMessageInDSCResource.Tests.ps1 b/Tests/Rules/UseVerboseMessageInDSCResource.Tests.ps1 index 9b2b3c5b8..bca6904e7 100644 --- a/Tests/Rules/UseVerboseMessageInDSCResource.Tests.ps1 +++ b/Tests/Rules/UseVerboseMessageInDSCResource.Tests.ps1 @@ -1,6 +1,6 @@ Import-Module PSScriptAnalyzer -$violationMessage = "There is no call to Write-Verbose in DSC function ‘Set-TargetResource’. If you are using Write-Verbose in a helper function, suppress this rule application." +$violationMessage = "There is no call to Write-Verbose in DSC function ‘Test-TargetResource’. If you are using Write-Verbose in a helper function, suppress this rule application." $violationName = "PSDSCUseVerboseMessageInDSCResource" $directory = Split-Path -Parent $MyInvocation.MyCommand.Path $violations = Invoke-ScriptAnalyzer $directory\DSCResources\MSFT_WaitForAll\MSFT_WaitForAll.psm1 | Where-Object {$_.RuleName -eq $violationName} From f4b99d483ee341655b44a9f5b1f13fddb4714475 Mon Sep 17 00:00:00 2001 From: Quoc Truong Date: Thu, 18 Jun 2015 10:56:37 -0700 Subject: [PATCH 6/7] Remove countdownevent --- .../Commands/InvokeScriptAnalyzerCommand.cs | 60 ------------------- 1 file changed, 60 deletions(-) diff --git a/Engine/Commands/InvokeScriptAnalyzerCommand.cs b/Engine/Commands/InvokeScriptAnalyzerCommand.cs index 014f01e18..1e5aa0512 100644 --- a/Engine/Commands/InvokeScriptAnalyzerCommand.cs +++ b/Engine/Commands/InvokeScriptAnalyzerCommand.cs @@ -276,7 +276,6 @@ private void ProcessPath(string path) Dictionary> ruleSuppressions; List includeRegexList; List excludeRegexList; - CountdownEvent cde; ConcurrentDictionary> ruleDictionary; /// @@ -673,65 +672,6 @@ private void AnalyzeFile(string filePath) } } - void bg_DoWork(object sender, DoWorkEventArgs e) - { - bool includeRegexMatch = false; - bool excludeRegexMatch = false; - - object[] parameters = e.Argument as object[]; - - IScriptRule scriptRule = parameters[0] as IScriptRule; - - foreach (Regex include in includeRegexList) - { - if (include.IsMatch(scriptRule.GetName())) - { - includeRegexMatch = true; - break; - } - } - - foreach (Regex exclude in excludeRegexList) - { - if (exclude.IsMatch(scriptRule.GetName())) - { - excludeRegexMatch = true; - break; - } - } - - List result = new List(); - - if ((includeRule == null || includeRegexMatch) && (excludeRule == null || !excludeRegexMatch)) - { - //WriteVerbose(string.Format(CultureInfo.CurrentCulture, Strings.VerboseRunningMessage, scriptRule.GetName())); - result.Add(string.Format(CultureInfo.CurrentCulture, Strings.VerboseRunningMessage, scriptRule.GetName())); - - // Ensure that any unhandled errors from Rules are converted to non-terminating errors - // We want the Engine to continue functioning even if one or more Rules throws an exception - try - { - var records = Helper.Instance.SuppressRule(scriptRule.GetName(), ruleSuppressions, scriptRule.AnalyzeScript(ast, ast.Extent.File).ToList()); - foreach (var record in records.Item2) - { - diagnostics.Add(record); - } - foreach (var suppressedRec in records.Item1) - { - suppressed.Add(suppressedRec); - } - } - catch (Exception scriptRuleException) - { - result.Add(new ErrorRecord(scriptRuleException, Strings.RuleErrorMessage, ErrorCategory.InvalidOperation, ast.Extent.File)); - } - } - - ruleDictionary[scriptRule.GetName()] = result; - - cde.Signal(); - } - #endregion } } \ No newline at end of file From 295f9a328757a0986f4cc4d9ec97cec0e2b59812 Mon Sep 17 00:00:00 2001 From: Quoc Truong Date: Thu, 18 Jun 2015 11:47:26 -0700 Subject: [PATCH 7/7] Remove comment --- Engine/Commands/InvokeScriptAnalyzerCommand.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Engine/Commands/InvokeScriptAnalyzerCommand.cs b/Engine/Commands/InvokeScriptAnalyzerCommand.cs index 1e5aa0512..8936e2cd3 100644 --- a/Engine/Commands/InvokeScriptAnalyzerCommand.cs +++ b/Engine/Commands/InvokeScriptAnalyzerCommand.cs @@ -399,7 +399,6 @@ private void AnalyzeFile(string filePath) { List result = new List(); - //WriteVerbose(string.Format(CultureInfo.CurrentCulture, Strings.VerboseRunningMessage, scriptRule.GetName())); result.Add(string.Format(CultureInfo.CurrentCulture, Strings.VerboseRunningMessage, scriptRule.GetName())); // Ensure that any unhandled errors from Rules are converted to non-terminating errors