diff --git a/Engine/Commands/InvokeScriptAnalyzerCommand.cs b/Engine/Commands/InvokeScriptAnalyzerCommand.cs index 936d38925..7e552fc9e 100644 --- a/Engine/Commands/InvokeScriptAnalyzerCommand.cs +++ b/Engine/Commands/InvokeScriptAnalyzerCommand.cs @@ -169,17 +169,18 @@ public SwitchParameter SuppressedOnly private bool suppressedOnly; /// - /// Returns path to the file that contains user profile for ScriptAnalyzer + /// Returns path to the file that contains user profile or hash table for ScriptAnalyzer /// [Alias("Profile")] [Parameter(Mandatory = false)] [ValidateNotNull] - public string Configuration + public object Settings { - get { return configuration; } - set { configuration = value; } + get { return settings; } + set { settings = value; } } - private string configuration; + + private object settings; private bool stopProcessing; @@ -195,7 +196,7 @@ protected override void BeginProcessing() string[] rulePaths = Helper.ProcessCustomRulePaths(customRulePath, this.SessionState, recurseCustomRulePath); - if (!ScriptAnalyzer.Instance.ParseProfile(this.configuration, this.SessionState.Path, this)) + if (!ScriptAnalyzer.Instance.ParseProfile(this.settings, this.SessionState.Path, this)) { stopProcessing = true; return; @@ -208,8 +209,7 @@ protected override void BeginProcessing() this.excludeRule, this.severity, null == rulePaths ? true : this.includeDefaultRules, - this.suppressedOnly, - this.configuration); + this.suppressedOnly); } /// @@ -238,6 +238,18 @@ protected override void ProcessRecord() } } + protected override void EndProcessing() + { + ScriptAnalyzer.Instance.CleanUp(); + base.EndProcessing(); + } + + protected override void StopProcessing() + { + ScriptAnalyzer.Instance.CleanUp(); + base.StopProcessing(); + } + #endregion #region Methods diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index e9241770d..b37fe099d 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -27,6 +27,7 @@ using System.Collections.Concurrent; using System.Threading.Tasks; using System.Collections.ObjectModel; +using System.Collections; namespace Microsoft.Windows.PowerShell.ScriptAnalyzer { @@ -101,8 +102,7 @@ internal void Initialize( string[] excludeRuleNames = null, string[] severity = null, bool includeDefaultRules = false, - bool suppressedOnly = false, - string profile = null) + bool suppressedOnly = false) where TCmdlet : PSCmdlet, IOutputWriter { if (cmdlet == null) @@ -119,8 +119,7 @@ internal void Initialize( excludeRuleNames, severity, includeDefaultRules, - suppressedOnly, - profile); + suppressedOnly); } /// @@ -134,8 +133,7 @@ public void Initialize( string[] excludeRuleNames = null, string[] severity = null, bool includeDefaultRules = false, - bool suppressedOnly = false, - string profile = null) + bool suppressedOnly = false) { if (runspace == null) { @@ -151,161 +149,304 @@ public void Initialize( excludeRuleNames, severity, includeDefaultRules, - suppressedOnly, - profile); + suppressedOnly); } - internal bool ParseProfile(string profile, PathIntrinsics path, IOutputWriter writer) + /// + /// clean up this instance, resetting all properties + /// + public void CleanUp() { - IEnumerable includeRuleList = new List(); - IEnumerable excludeRuleList = new List(); - IEnumerable severityList = new List(); + includeRule = null; + excludeRule = null; + severity = null; + includeRegexList = null; + excludeRegexList = null; + suppressedOnly = false; + } + + internal bool ParseProfile(object profileObject, PathIntrinsics path, IOutputWriter writer) + { + // profile was not given + if (profileObject == null) + { + return true; + } + + if (!(profileObject is string || profileObject is Hashtable)) + { + return false; + } + + List includeRuleList = new List(); + List excludeRuleList = new List(); + List severityList = new List(); bool hasError = false; - if (!String.IsNullOrWhiteSpace(profile)) + Hashtable hashTableProfile = profileObject as Hashtable; + + // checks whether we get a hashtable + if (hashTableProfile != null) { - try + hasError = ParseProfileHashtable(hashTableProfile, path, writer, severityList, includeRuleList, excludeRuleList); + } + else + { + // checks whether we get a string instead + string profile = profileObject as string; + + if (!String.IsNullOrWhiteSpace(profile)) { - profile = path.GetResolvedPSPathFromPSPath(profile).First().Path; + hasError = ParseProfileString(profile, path, writer, severityList, includeRuleList, excludeRuleList); } - catch + } + + if (hasError) + { + return false; + } + + this.severity = (severityList.Count() == 0) ? null : severityList.ToArray(); + this.includeRule = (includeRuleList.Count() == 0) ? null : includeRuleList.ToArray(); + this.excludeRule = (excludeRuleList.Count() == 0) ? null : excludeRuleList.ToArray(); + + return true; + } + + private bool ParseProfileHashtable(Hashtable profile, PathIntrinsics path, IOutputWriter writer, + List severityList, List includeRuleList, List excludeRuleList) + { + bool hasError = false; + + HashSet validKeys = new HashSet(StringComparer.OrdinalIgnoreCase); + validKeys.Add("severity"); + validKeys.Add("includerules"); + validKeys.Add("excluderules"); + + foreach (var obj in profile.Keys) + { + string key = obj as string; + + // key should be a string + if (key == null) { - writer.WriteError(new ErrorRecord(new FileNotFoundException(string.Format(CultureInfo.CurrentCulture, Strings.FileNotFound, profile)), - Strings.ConfigurationFileNotFound, ErrorCategory.ResourceUnavailable, profile)); + writer.WriteError(new ErrorRecord(new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.KeyNotString, key)), + Strings.ConfigurationKeyNotAString, ErrorCategory.InvalidData, profile)); hasError = true; + continue; } - if (File.Exists(profile)) + // checks whether it falls into list of valid keys + if (!validKeys.Contains(key)) { - Token[] parserTokens = null; - ParseError[] parserErrors = null; - Ast profileAst = Parser.ParseFile(profile, out parserTokens, out parserErrors); - IEnumerable hashTableAsts = profileAst.FindAll(item => item is HashtableAst, false); + writer.WriteError(new ErrorRecord( + new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongKeyHashTable, key)), + Strings.WrongConfigurationKey, ErrorCategory.InvalidData, profile)); + hasError = true; + continue; + } - // no hashtable, raise warning - if (hashTableAsts.Count() == 0) + object value = profile[obj]; + + // value must be either string or collections of string or array + if (value == null || !(value is string || value is IEnumerable || value.GetType().IsArray)) + { + writer.WriteError(new ErrorRecord( + new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongValueHashTable, value, key)), + Strings.WrongConfigurationKey, ErrorCategory.InvalidData, profile)); + hasError = true; + continue; + } + + // if we get here then everything is good + + List values = new List(); + + if (value is string) + { + values.Add(value as string); + } + else if (value is IEnumerable) + { + values.Union(value as IEnumerable); + } + else if (value.GetType().IsArray) + { + // for array case, sometimes we won't be able to cast it directly to IEnumerable + foreach (var val in value as IEnumerable) { - writer.WriteError(new ErrorRecord(new ArgumentException(string.Format(CultureInfo.CurrentCulture, Strings.InvalidProfile, profile)), - Strings.ConfigurationFileHasNoHashTable, ErrorCategory.ResourceUnavailable, profile)); - hasError = true; + if (val is string) + { + values.Add(val as string); + } + else + { + writer.WriteError(new ErrorRecord( + new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongValueHashTable, val, key)), + Strings.WrongConfigurationKey, ErrorCategory.InvalidData, profile)); + hasError = true; + continue; + } } - else + } + + // now add to the list + switch (key) + { + case "severity": + severityList.AddRange(values); + break; + case "includerules": + includeRuleList.AddRange(values); + break; + case "excluderules": + excludeRuleList.AddRange(values); + break; + default: + break; + } + } + + return hasError; + } + + private bool ParseProfileString(string profile, PathIntrinsics path, IOutputWriter writer, + List severityList, List includeRuleList, List excludeRuleList) + { + bool hasError = false; + + try + { + profile = path.GetResolvedPSPathFromPSPath(profile).First().Path; + } + catch + { + writer.WriteError(new ErrorRecord(new FileNotFoundException(string.Format(CultureInfo.CurrentCulture, Strings.FileNotFound, profile)), + Strings.ConfigurationFileNotFound, ErrorCategory.ResourceUnavailable, profile)); + hasError = true; + } + + if (File.Exists(profile)) + { + Token[] parserTokens = null; + ParseError[] parserErrors = null; + Ast profileAst = Parser.ParseFile(profile, out parserTokens, out parserErrors); + IEnumerable hashTableAsts = profileAst.FindAll(item => item is HashtableAst, false); + + // no hashtable, raise warning + if (hashTableAsts.Count() == 0) + { + writer.WriteError(new ErrorRecord(new ArgumentException(string.Format(CultureInfo.CurrentCulture, Strings.InvalidProfile, profile)), + Strings.ConfigurationFileHasNoHashTable, ErrorCategory.ResourceUnavailable, profile)); + hasError = true; + } + else + { + HashtableAst hashTableAst = hashTableAsts.First() as HashtableAst; + + foreach (var kvp in hashTableAst.KeyValuePairs) { - HashtableAst hashTableAst = hashTableAsts.First() as HashtableAst; + if (!(kvp.Item1 is StringConstantExpressionAst)) + { + // first item (the key) should be a string + writer.WriteError(new ErrorRecord(new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongKeyFormat, kvp.Item1.Extent.StartLineNumber, kvp.Item1.Extent.StartColumnNumber, profile)), + Strings.ConfigurationKeyNotAString, ErrorCategory.InvalidData, profile)); + hasError = true; + continue; + } - foreach (var kvp in hashTableAst.KeyValuePairs) + // parse the item2 as array + PipelineAst pipeAst = kvp.Item2 as PipelineAst; + List rhsList = new List(); + if (pipeAst != null) { - if (!(kvp.Item1 is StringConstantExpressionAst)) + ExpressionAst pureExp = pipeAst.GetPureExpression(); + if (pureExp is StringConstantExpressionAst) { - // first item (the key) should be a string - writer.WriteError(new ErrorRecord(new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongKeyFormat, kvp.Item1.Extent.StartLineNumber, kvp.Item1.Extent.StartColumnNumber, profile)), - Strings.ConfigurationKeyNotAString, ErrorCategory.InvalidData, profile)); - hasError = true; - continue; + rhsList.Add((pureExp as StringConstantExpressionAst).Value); } - - // parse the item2 as array - PipelineAst pipeAst = kvp.Item2 as PipelineAst; - List rhsList = new List(); - if (pipeAst != null) + else { - ExpressionAst pureExp = pipeAst.GetPureExpression(); - if (pureExp is StringConstantExpressionAst) - { - rhsList.Add((pureExp as StringConstantExpressionAst).Value); - } - else + ArrayLiteralAst arrayLitAst = pureExp as ArrayLiteralAst; + if (arrayLitAst == null && pureExp is ArrayExpressionAst) { - ArrayLiteralAst arrayLitAst = pureExp as ArrayLiteralAst; - if (arrayLitAst == null && pureExp is ArrayExpressionAst) + ArrayExpressionAst arrayExp = pureExp as ArrayExpressionAst; + // Statements property is never null + if (arrayExp.SubExpression != null) { - ArrayExpressionAst arrayExp = pureExp as ArrayExpressionAst; - // Statements property is never null - if (arrayExp.SubExpression != null) + StatementAst stateAst = arrayExp.SubExpression.Statements.FirstOrDefault(); + if (stateAst != null && stateAst is PipelineAst) { - StatementAst stateAst = arrayExp.SubExpression.Statements.FirstOrDefault(); - if (stateAst != null && stateAst is PipelineAst) + CommandBaseAst cmdBaseAst = (stateAst as PipelineAst).PipelineElements.FirstOrDefault(); + if (cmdBaseAst != null && cmdBaseAst is CommandExpressionAst) { - CommandBaseAst cmdBaseAst = (stateAst as PipelineAst).PipelineElements.FirstOrDefault(); - if (cmdBaseAst != null && cmdBaseAst is CommandExpressionAst) + CommandExpressionAst cmdExpAst = cmdBaseAst as CommandExpressionAst; + if (cmdExpAst.Expression is StringConstantExpressionAst) { - CommandExpressionAst cmdExpAst = cmdBaseAst as CommandExpressionAst; - if (cmdExpAst.Expression is StringConstantExpressionAst) - { - rhsList.Add((cmdExpAst.Expression as StringConstantExpressionAst).Value); - } - else - { - arrayLitAst = cmdExpAst.Expression as ArrayLiteralAst; - } + rhsList.Add((cmdExpAst.Expression as StringConstantExpressionAst).Value); + } + else + { + arrayLitAst = cmdExpAst.Expression as ArrayLiteralAst; } } } } + } - if (arrayLitAst != null) + if (arrayLitAst != null) + { + foreach (var element in arrayLitAst.Elements) { - foreach (var element in arrayLitAst.Elements) + // all the values in the array needs to be string + if (!(element is StringConstantExpressionAst)) { - // all the values in the array needs to be string - if (!(element is StringConstantExpressionAst)) - { - writer.WriteError(new ErrorRecord(new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongValueFormat, element.Extent.StartLineNumber, element.Extent.StartColumnNumber, profile)), - Strings.ConfigurationValueNotAString, ErrorCategory.InvalidData, profile)); - hasError = true; - continue; - } - - rhsList.Add((element as StringConstantExpressionAst).Value); + writer.WriteError(new ErrorRecord(new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongValueFormat, element.Extent.StartLineNumber, element.Extent.StartColumnNumber, profile)), + Strings.ConfigurationValueNotAString, ErrorCategory.InvalidData, profile)); + hasError = true; + continue; } + + rhsList.Add((element as StringConstantExpressionAst).Value); } } } + } - if (rhsList.Count == 0) - { - writer.WriteError(new ErrorRecord(new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongValueFormat, kvp.Item2.Extent.StartLineNumber, kvp.Item2.Extent.StartColumnNumber, profile)), - Strings.ConfigurationValueWrongFormat, ErrorCategory.InvalidData, profile)); - hasError = true; - continue; - } + if (rhsList.Count == 0) + { + writer.WriteError(new ErrorRecord(new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongValueFormat, kvp.Item2.Extent.StartLineNumber, kvp.Item2.Extent.StartColumnNumber, profile)), + Strings.ConfigurationValueWrongFormat, ErrorCategory.InvalidData, profile)); + hasError = true; + continue; + } - string key = (kvp.Item1 as StringConstantExpressionAst).Value.ToLower(); + string key = (kvp.Item1 as StringConstantExpressionAst).Value.ToLower(); - switch (key) - { - case "severity": - severityList = severityList.Union(rhsList); - break; - case "includerules": - includeRuleList = includeRuleList.Union(rhsList); - break; - case "excluderules": - excludeRuleList = excludeRuleList.Union(rhsList); - break; - default: - writer.WriteError(new ErrorRecord( - new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongKey, key, kvp.Item1.Extent.StartLineNumber, kvp.Item1.Extent.StartColumnNumber, profile)), - Strings.WrongConfigurationKey, ErrorCategory.InvalidData, profile)); - hasError = true; - break; - } + switch (key) + { + case "severity": + severityList.AddRange(rhsList); + break; + case "includerules": + includeRuleList.AddRange(rhsList); + break; + case "excluderules": + excludeRuleList.AddRange(rhsList); + break; + default: + writer.WriteError(new ErrorRecord( + new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongKey, key, kvp.Item1.Extent.StartLineNumber, kvp.Item1.Extent.StartColumnNumber, profile)), + Strings.WrongConfigurationKey, ErrorCategory.InvalidData, profile)); + hasError = true; + break; } } } } - if (hasError) - { - return false; - } - - this.severity = (severityList.Count() == 0) ? null : severityList.ToArray(); - this.includeRule = (includeRuleList.Count() == 0) ? null : includeRuleList.ToArray(); - this.excludeRule = (excludeRuleList.Count() == 0) ? null : excludeRuleList.ToArray(); - - return true; + return hasError; } private void Initialize( diff --git a/Engine/Strings.Designer.cs b/Engine/Strings.Designer.cs index 0f5dc9d0a..379b35d13 100644 --- a/Engine/Strings.Designer.cs +++ b/Engine/Strings.Designer.cs @@ -88,7 +88,7 @@ internal static string CommandInfoNotFound { } /// - /// Looks up a localized string similar to ConfigurationFileHasNoHashTable. + /// Looks up a localized string similar to SettingsFileHasNoHashTable. /// internal static string ConfigurationFileHasNoHashTable { get { @@ -97,7 +97,7 @@ internal static string ConfigurationFileHasNoHashTable { } /// - /// Looks up a localized string similar to ConfigurationFileNotFound. + /// Looks up a localized string similar to SettingsFileNotFound. /// internal static string ConfigurationFileNotFound { get { @@ -106,7 +106,7 @@ internal static string ConfigurationFileNotFound { } /// - /// Looks up a localized string similar to ConfigurationKeyNotAString. + /// Looks up a localized string similar to SettingsKeyNotAString. /// internal static string ConfigurationKeyNotAString { get { @@ -115,7 +115,7 @@ internal static string ConfigurationKeyNotAString { } /// - /// Looks up a localized string similar to ConfigurationValueNotAString. + /// Looks up a localized string similar to SettingsValueNotAString. /// internal static string ConfigurationValueNotAString { get { @@ -124,7 +124,7 @@ internal static string ConfigurationValueNotAString { } /// - /// Looks up a localized string similar to ConfigurationValueWrongFormat. + /// Looks up a localized string similar to SettingsValueWrongFormat. /// internal static string ConfigurationValueWrongFormat { get { @@ -169,7 +169,7 @@ internal static string InvalidPath { } /// - /// Looks up a localized string similar to Profile file '{0}' is invalid because it does not contain a hashtable.. + /// Looks up a localized string similar to Settings file '{0}' is invalid because it does not contain a hashtable.. /// internal static string InvalidProfile { get { @@ -177,6 +177,15 @@ internal static string InvalidProfile { } } + /// + /// Looks up a localized string similar to Key {0} in the settings is not a string.. + /// + internal static string KeyNotString { + get { + return ResourceManager.GetString("KeyNotString", resourceCulture); + } + } + /// /// Looks up a localized string similar to No loggers found.. /// @@ -367,7 +376,7 @@ internal static string VerboseScriptDefinitionMessage { } /// - /// Looks up a localized string similar to WrongConfigurationKey. + /// Looks up a localized string similar to WrongSettingsKey. /// internal static string WrongConfigurationKey { get { @@ -376,7 +385,7 @@ internal static string WrongConfigurationKey { } /// - /// Looks up a localized string similar to {0} is not a valid key in the profile hashtable: line {1} column {2} in file {3}. Valid keys are ExcludeRules, IncludeRules and Severity.. + /// Looks up a localized string similar to {0} is not a valid key in the settings hashtable: line {1} column {2} in file {3}. Valid keys are ExcludeRules, IncludeRules and Severity.. /// internal static string WrongKey { get { @@ -385,7 +394,7 @@ internal static string WrongKey { } /// - /// Looks up a localized string similar to Key in the profile hashtable should be a string: line {0} column {1} in file {2}. + /// Looks up a localized string similar to Key in the settings hashtable should be a string: line {0} column {1} in file {2}. /// internal static string WrongKeyFormat { get { @@ -393,6 +402,15 @@ internal static string WrongKeyFormat { } } + /// + /// Looks up a localized string similar to {0} is not a valid key in the settings hashtable. Valid keys are ExcludeRules, IncludeRules and Severity.. + /// + internal static string WrongKeyHashTable { + get { + return ResourceManager.GetString("WrongKeyHashTable", resourceCulture); + } + } + /// /// Looks up a localized string similar to Scope can only be either function or class.. /// @@ -403,12 +421,21 @@ internal static string WrongScopeArgumentSuppressionAttributeError { } /// - /// Looks up a localized string similar to Value in the profile hashtable should be a string or an array of strings: line {0} column {1} in file {2}. + /// Looks up a localized string similar to Value in the settings hashtable should be a string or an array of strings: line {0} column {1} in file {2}. /// internal static string WrongValueFormat { get { return ResourceManager.GetString("WrongValueFormat", resourceCulture); } } + + /// + /// Looks up a localized string similar to Value {0} for key {1} has the wrong data type. Value in the settings hashtable should be a string or an array of strings.. + /// + internal static string WrongValueHashTable { + get { + return ResourceManager.GetString("WrongValueHashTable", resourceCulture); + } + } } } diff --git a/Engine/Strings.resx b/Engine/Strings.resx index 2707a02be..aa1a3f779 100644 --- a/Engine/Strings.resx +++ b/Engine/Strings.resx @@ -193,16 +193,16 @@ Cannot find any DiagnosticRecord with the Rule Suppression ID {0}. - {0} is not a valid key in the profile hashtable: line {1} column {2} in file {3}. Valid keys are ExcludeRules, IncludeRules and Severity. + {0} is not a valid key in the settings hashtable: line {1} column {2} in file {3}. Valid keys are ExcludeRules, IncludeRules and Severity. - Key in the profile hashtable should be a string: line {0} column {1} in file {2} + Key in the settings hashtable should be a string: line {0} column {1} in file {2} - Value in the profile hashtable should be a string or an array of strings: line {0} column {1} in file {2} + Value in the settings hashtable should be a string or an array of strings: line {0} column {1} in file {2} - Profile file '{0}' is invalid because it does not contain a hashtable. + Settings file '{0}' is invalid because it does not contain a hashtable. Parse error in script definition: {0} at line {1} column {2}. @@ -217,21 +217,30 @@ Analyzing Script Definition. - ConfigurationFileHasNoHashTable + SettingsFileHasNoHashTable - ConfigurationFileNotFound + SettingsFileNotFound - ConfigurationKeyNotAString + SettingsKeyNotAString - ConfigurationValueNotAString + SettingsValueNotAString - ConfigurationValueWrongFormat + SettingsValueWrongFormat - WrongConfigurationKey + WrongSettingsKey + + + Key {0} in the settings is not a string. + + + {0} is not a valid key in the settings hashtable. Valid keys are ExcludeRules, IncludeRules and Severity. + + + Value {0} for key {1} has the wrong data type. Value in the settings hashtable should be a string or an array of strings. \ No newline at end of file diff --git a/Tests/Engine/GlobalSuppression.test.ps1 b/Tests/Engine/GlobalSuppression.test.ps1 index 9608071ed..285663ec4 100644 --- a/Tests/Engine/GlobalSuppression.test.ps1 +++ b/Tests/Engine/GlobalSuppression.test.ps1 @@ -14,19 +14,29 @@ $suppressionUsingScriptDefinition = Invoke-ScriptAnalyzer -ScriptDefinition (Get Describe "GlobalSuppression" { Context "Exclude Rule" { - It "Raises 1 violation for uninitialized variable and 1 for cmdlet alias" { - $withoutProfile = $violations | Where-Object { $_.RuleName -eq "PSAvoidUsingCmdletAliases" -or $_.RuleName -eq "PSAvoidUninitializedVariable" } + It "Raises 1 violation for cmdlet alias" { + $withoutProfile = $violations | Where-Object { $_.RuleName -eq "PSAvoidUsingCmdletAliases"} $withoutProfile.Count | Should Be 1 - $withoutProfile = $violationsUsingScriptDefinition | Where-Object { $_.RuleName -eq "PSAvoidUsingCmdletAliases" -or $_.RuleName -eq "PSAvoidUninitializedVariable" } + $withoutProfile = $violationsUsingScriptDefinition | Where-Object { $_.RuleName -eq "PSAvoidUsingCmdletAliases"} $withoutProfile.Count | Should Be 1 } - It "Does not raise any violations for uninitialized variable and cmdlet alias with profile" { - $withProfile = $suppression | Where-Object { $_.RuleName -eq "PSAvoidUsingCmdletAliases" -or $_.RuleName -eq "PSAvoidUninitializedVariable" } + It "Does not raise any violations for cmdlet alias with profile" { + $withProfile = $suppression | Where-Object { $_.RuleName -eq "PSAvoidUsingCmdletAliases" } $withProfile.Count | Should be 0 - $withProfile = $suppressionUsingScriptDefinition | Where-Object { $_.RuleName -eq "PSAvoidUsingCmdletAliases" -or $_.RuleName -eq "PSAvoidUninitializedVariable" } + $withProfile = $suppressionUsingScriptDefinition | Where-Object { $_.RuleName -eq "PSAvoidUsingCmdletAliases" } $withProfile.Count | Should be 0 } + + It "Does not raise any violation for cmdlet alias using configuration hashtable" { + $hashtableConfiguration = Invoke-ScriptAnalyzer "$directory\GlobalSuppression.ps1" -Configuration @{"excluderules" = "PSAvoidUsingCmdletAliases"} | + Where-Object { $_.RuleName -eq "PSAvoidUsingCmdletAliases"} + $hashtableConfiguration.Count | Should Be 0 + + $hashtableConfiguration = Invoke-ScriptAnalyzer -ScriptDefinition (Get-Content -Raw "$directory\GlobalSuppression.ps1") -Configuration @{"excluderules" = "PSAvoidUsingCmdletAliases"} | + Where-Object { $_.RuleName -eq "PSAvoidUsingCmdletAliases"} + $hashtableConfiguration.Count | Should Be 0 + } } Context "Include Rule" { @@ -43,6 +53,12 @@ Describe "GlobalSuppression" { $withProfile = $suppressionUsingScriptDefinition | Where-Object { $_.RuleName -eq "PSAvoidUsingComputerNameHardcoded" } $withProfile.Count | Should be 0 } + + It "Does not raise any violation for computername hard-coded using configuration hashtable" { + $hashtableConfiguration = Invoke-ScriptAnalyzer "$directory\GlobalSuppression.ps1" -Settings @{"includerules" = @("PSAvoidUsingCmdletAliases", "PSUseOutputTypeCorrectly")} | + Where-Object { $_.RuleName -eq "PSAvoidUsingComputerNameHardcoded"} + $hashtableConfiguration.Count | Should Be 0 + } } Context "Severity" { @@ -59,25 +75,31 @@ Describe "GlobalSuppression" { $withProfile = $suppressionUsingScriptDefinition | Where-Object { $_.RuleName -eq "PSUseOutputTypeCorrectly" } $withProfile.Count | Should be 0 } + + It "Does not raise any violation for use output type correctly with configuration hashtable" { + $hashtableConfiguration = Invoke-ScriptAnalyzer "$directory\GlobalSuppression.ps1" -Settings @{"severity" = "warning"} | + Where-Object {$_.RuleName -eq "PSUseOutputTypeCorrectly"} + $hashtableConfiguration.Count | should be 0 + } } Context "Error Case" { It "Raises Error for file not found" { - $invokeWithError = Invoke-ScriptAnalyzer "$directory\GlobalSuppression.ps1" -Configuration ".\ThisFileDoesNotExist.ps1" -ErrorAction SilentlyContinue + $invokeWithError = Invoke-ScriptAnalyzer "$directory\GlobalSuppression.ps1" -Settings ".\ThisFileDoesNotExist.ps1" -ErrorAction SilentlyContinue $invokeWithError.Count | should be 0 - $Error[0].FullyQualifiedErrorId | should match "ConfigurationFileNotFound,Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands.InvokeScriptAnalyzerCommand" + $Error[0].FullyQualifiedErrorId | should match "SettingsFileNotFound,Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands.InvokeScriptAnalyzerCommand" } It "Raises Error for file with no hash table" { - $invokeWithError = Invoke-ScriptAnalyzer "$directory\GlobalSuppression.ps1" -Configuration "$directory\GlobalSuppression.ps1" -ErrorAction SilentlyContinue + $invokeWithError = Invoke-ScriptAnalyzer "$directory\GlobalSuppression.ps1" -Settings "$directory\GlobalSuppression.ps1" -ErrorAction SilentlyContinue $invokeWithError.Count | should be 0 - $Error[0].FullyQualifiedErrorId | should match "ConfigurationFileHasNoHashTable,Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands.InvokeScriptAnalyzerCommand" + $Error[0].FullyQualifiedErrorId | should match "SettingsFileHasNoHashTable,Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands.InvokeScriptAnalyzerCommand" } It "Raises Error for wrong profile" { - $invokeWithError = Invoke-ScriptAnalyzer "$directory\GlobalSuppression.ps1" -Configuration "$directory\WrongProfile.ps1" -ErrorAction SilentlyContinue + $invokeWithError = Invoke-ScriptAnalyzer "$directory\GlobalSuppression.ps1" -Settings "$directory\WrongProfile.ps1" -ErrorAction SilentlyContinue $invokeWithError.Count | should be 0 - $Error[0].FullyQualifiedErrorId | should match "WrongConfigurationKey,Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands.InvokeScriptAnalyzerCommand" + $Error[0].FullyQualifiedErrorId | should match "WrongSettingsKey,Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands.InvokeScriptAnalyzerCommand" } } } \ No newline at end of file diff --git a/Tests/Engine/LibraryUsage.tests.ps1 b/Tests/Engine/LibraryUsage.tests.ps1 index 432ac8cee..1ae8eae60 100644 --- a/Tests/Engine/LibraryUsage.tests.ps1 +++ b/Tests/Engine/LibraryUsage.tests.ps1 @@ -39,16 +39,19 @@ function Invoke-ScriptAnalyzer { [switch] $IncludeDefaultRules, [Parameter(Mandatory = $false)] - [switch] $SuppressedOnly, - - [Parameter(Mandatory = $false)] - [string] $Profile = $null + [switch] $SuppressedOnly ) if ($null -eq $CustomRulePath) { $IncludeDefaultRules = $true } + # There is an inconsistency between this implementation and c# implementation of the cmdlet. + # The CustomRulePath parameter here is of "string[]" type whereas in the c# implementation it is of "string" type. + # If we set the CustomRulePath parameter here to "string[]", then the library usage test fails when run as an administrator. + # We want to note that the library usage test doesn't fail when run as a non-admin user. + # The following is the error statement when the test runs as an administrator. + # Assert failed on "Initialize" with "7" argument(s): "Test failed due to terminating error: The module was expected to contain an assembly manifest. (Exception from HRESULT: 0x80131018)" $scriptAnalyzer = New-Object "Microsoft.Windows.PowerShell.ScriptAnalyzer.ScriptAnalyzer"; $scriptAnalyzer.Initialize( @@ -59,8 +62,7 @@ function Invoke-ScriptAnalyzer { $ExcludeRule, $Severity, $IncludeDefaultRules.IsPresent, - $SuppressedOnly.IsPresent, - $Profile + $SuppressedOnly.IsPresent ); if ($PSCmdlet.ParameterSetName -eq "File") {