From 99ca4e635331aaf2b315c72fd4d0927cc6bf60ac Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Wed, 31 Aug 2016 16:02:04 -0700 Subject: [PATCH 1/9] Parse settings file for hashtable type value --- Engine/ScriptAnalyzer.cs | 237 +++++++++++++++++++++++++-------------- 1 file changed, 151 insertions(+), 86 deletions(-) diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index a9d3c1387..4b9096ed4 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -40,6 +40,7 @@ public sealed class ScriptAnalyzer #region Private members private IOutputWriter outputWriter; + private Dictionary settings; #if !CORECLR private CompositionContainer container; #endif // !CORECLR @@ -87,13 +88,10 @@ public static ScriptAnalyzer Instance #else [ImportMany] public IEnumerable ScriptRules { get; private set; } - [ImportMany] public IEnumerable TokenRules { get; private set; } - [ImportMany] public IEnumerable Loggers { get; private set; } - [ImportMany] public IEnumerable DSCResourceRules { get; private set; } // Initializes via ImportMany @@ -273,6 +271,135 @@ private bool AddProfileItem( return true; } + private Dictionary GetDictionaryFromHashTableAst( + HashtableAst hashTableAst, + IOutputWriter writer, + string profile, + out bool hasError) + { + hasError = false; + var output = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var kvp in hashTableAst.KeyValuePairs) + { + var keyAst = kvp.Item1 as StringConstantExpressionAst; + if (keyAst == null) + { + // 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; + } + var key = keyAst.Value; + // parse the item2 as array + PipelineAst pipeAst = kvp.Item2 as PipelineAst; + List rhsList = new List(); + if (pipeAst != null) + { + ExpressionAst pureExp = pipeAst.GetPureExpression(); + if (pureExp is StringConstantExpressionAst) + { + rhsList.Add((pureExp as StringConstantExpressionAst).Value); + } + else if (pureExp is HashtableAst) + { + output[key] = GetDictionaryFromHashTableAst( + pureExp as HashtableAst, + writer, + profile, + out hasError); + continue; + } + else + { + 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) + { + 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) + { + CommandExpressionAst cmdExpAst = cmdBaseAst as CommandExpressionAst; + if (cmdExpAst.Expression is StringConstantExpressionAst) + { + rhsList.Add((cmdExpAst.Expression as StringConstantExpressionAst).Value); + } + else + { + arrayLitAst = cmdExpAst.Expression as ArrayLiteralAst; + } + } + } + } + } + + if (arrayLitAst != null) + { + foreach (var element in arrayLitAst.Elements) + { + // all the values in the array needs to be string + if (!(element is StringConstantExpressionAst)) + { + outputWriter.WriteError( + new ErrorRecord( + new InvalidDataException( + string.Format( + CultureInfo.CurrentCulture, + Strings.WrongValueFormat, + element.Extent.StartLineNumber, + element.Extent.StartColumnNumber, + "")), + Strings.ConfigurationValueNotAString, + ErrorCategory.InvalidData, + null)); + 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; + } + output[key] = rhsList; + } + return output; + } + private bool ParseProfileHashtable(Hashtable profile, PathIntrinsics path, IOutputWriter writer, List severityList, List includeRuleList, List excludeRuleList) { @@ -319,7 +446,6 @@ private bool ParseProfileHashtable(Hashtable profile, PathIntrinsics path, IOutp } // if we get here then everything is good - List values = new List(); if (value is string) @@ -390,91 +516,31 @@ private bool ParseProfileString(string profile, PathIntrinsics path, IOutputWrit else { HashtableAst hashTableAst = hashTableAsts.First() as HashtableAst; - - foreach (var kvp in hashTableAst.KeyValuePairs) + settings = GetDictionaryFromHashTableAst( + hashTableAst, + writer, + profile, + out hasError); + foreach (var key in settings.Keys) { - if (!(kvp.Item1 is StringConstantExpressionAst)) + var rhsList = settings[key] as List; + if (rhsList == null) { - // 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; } - - // parse the item2 as array - PipelineAst pipeAst = kvp.Item2 as PipelineAst; - List rhsList = new List(); - if (pipeAst != null) + if (!AddProfileItem(key, rhsList, severityList, includeRuleList, excludeRuleList)) { - 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) - { - 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) - { - CommandBaseAst cmdBaseAst = (stateAst as PipelineAst).PipelineElements.FirstOrDefault(); - if (cmdBaseAst != null && cmdBaseAst is CommandExpressionAst) - { - CommandExpressionAst cmdExpAst = cmdBaseAst as CommandExpressionAst; - if (cmdExpAst.Expression is StringConstantExpressionAst) - { - rhsList.Add((cmdExpAst.Expression as StringConstantExpressionAst).Value); - } - else - { - arrayLitAst = cmdExpAst.Expression as ArrayLiteralAst; - } - } - } - } - } - - if (arrayLitAst != null) - { - foreach (var element in arrayLitAst.Elements) - { - // 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); - } - } - } - } - - 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(); - - if(!AddProfileItem(key, rhsList, severityList, includeRuleList, excludeRuleList)) - { - 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)); + writer.WriteError( + new ErrorRecord( + new InvalidDataException( + string.Format( + CultureInfo.CurrentCulture, + Strings.WrongKey, + key, + profile)), + Strings.WrongConfigurationKey, + ErrorCategory.InvalidData, + profile)); hasError = true; } } @@ -517,7 +583,6 @@ private void Initialize( // But for Core CLR we need to load it explicitly this.Loggers = GetInterfaceImplementationsFromAssembly(); #endif - #region Initializes Rules var includeRuleList = new List(); @@ -1887,7 +1952,7 @@ public IEnumerable AnalyzeSyntaxTree( } } } - + #endregion // Need to reverse the concurrentbag to ensure that results are sorted in the increasing order of line numbers From ce62cfa7b2d164c0b1126fc1c09a9311ac91a586 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Wed, 31 Aug 2016 16:11:29 -0700 Subject: [PATCH 2/9] Pass parameters to rules via settings file --- .../Commands/InvokeScriptAnalyzerCommand.cs | 6 + Engine/Helper.cs | 103 ++++++++++++++---- Engine/ScriptAnalyzer.cs | 18 ++- Engine/Strings.Designer.cs | 2 +- Engine/Strings.resx | 2 +- 5 files changed, 100 insertions(+), 31 deletions(-) diff --git a/Engine/Commands/InvokeScriptAnalyzerCommand.cs b/Engine/Commands/InvokeScriptAnalyzerCommand.cs index c2cced646..4a7a61413 100644 --- a/Engine/Commands/InvokeScriptAnalyzerCommand.cs +++ b/Engine/Commands/InvokeScriptAnalyzerCommand.cs @@ -210,6 +210,12 @@ public SwitchParameter SaveDscDependency /// protected override void BeginProcessing() { + // Initialize helper + Helper.Instance = new Helper( + SessionState.InvokeCommand, + this); + Helper.Instance.Initialize(); + string[] rulePaths = Helper.ProcessCustomRulePaths(customRulePath, this.SessionState, recurseCustomRulePath); diff --git a/Engine/Helper.cs b/Engine/Helper.cs index fa3419347..c8b26c274 100644 --- a/Engine/Helper.cs +++ b/Engine/Helper.cs @@ -35,6 +35,7 @@ public class Helper private IOutputWriter outputWriter; private Object getCommandLock = new object(); private readonly static Version minSupportedPSVersion = new Version(3, 0); + private Dictionary> ruleArguments; #endregion @@ -113,14 +114,14 @@ internal set /// private Helper() { - + } /// /// Initializes the Helper class. /// /// - /// A CommandInvocationIntrinsics instance for use in gathering + /// A CommandInvocationIntrinsics instance for use in gathering /// information about available commands and aliases. /// /// @@ -145,6 +146,7 @@ public void Initialize() AliasToCmdletDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); KeywordBlockDictionary = new Dictionary>>(StringComparer.OrdinalIgnoreCase); VariableAnalysisDictionary = new Dictionary(); + ruleArguments = new Dictionary>(StringComparer.OrdinalIgnoreCase); IEnumerable aliases = this.invokeCommand.GetCommands("*", CommandTypes.Alias, true); @@ -163,6 +165,59 @@ public void Initialize() } } + /// + /// Returns all the rule arguments + /// + /// Dictionary that maps between rule name to their named arguments + public Dictionary> GetRuleArguments() + { + return ruleArguments; + } + + /// + /// Get the parameters corresponding to the given rule name + /// + /// + /// Dictionary of argument names mapped to values. If ruleName is not a valid key, returns null + public Dictionary GetRuleArguments(string ruleName) + { + if (ruleArguments.ContainsKey(ruleName)) + { + return ruleArguments[ruleName]; + } + return null; + } + + /// + /// Sets the arguments for consumption by rules + /// + /// A hashtable with rule names as keys + public void SetRuleArguments(Dictionary ruleArgs) + { + if (ruleArgs == null) + { + return; + } + + if (ruleArgs.Comparer != StringComparer.OrdinalIgnoreCase) + { + throw new ArgumentException( + "Input dictionary should have OrdinalIgnoreCase comparer.", + "ruleArgs"); + } + var ruleArgsDict = new Dictionary>(); + foreach (var rule in ruleArgs.Keys) + { + var argsDict = ruleArgs[rule] as Dictionary; + if (argsDict == null) + { + return; + } + ruleArgsDict[rule] = argsDict; + } + ruleArguments = ruleArgsDict; + } + /// /// Given a cmdlet, return the list of all the aliases. /// Also include the original name in the list. @@ -231,7 +286,7 @@ public bool IsDscResourceModule(string filePath) return false; } - + /// /// Gets the module manifest /// @@ -471,13 +526,13 @@ private string NameWithoutScope(string name, string[] scopes) if (String.IsNullOrWhiteSpace(name) || scopes == null) { return name; - } + } // checks whether function name starts with scope foreach (string scope in scopes) { // trim the scope part - if (name.IndexOf(scope, StringComparison.OrdinalIgnoreCase) == 0) + if (name.IndexOf(scope, StringComparison.OrdinalIgnoreCase) == 0) { return name.Substring(scope.Length); @@ -598,7 +653,7 @@ public bool PositionalParameterUsed(CommandAst cmdAst, bool moreThanThreePositio { arguments += 1; } - + } // if not the first element in a pipeline, increase the number of arguments by 1 @@ -724,10 +779,10 @@ public IScriptExtent GetScriptExtentForFunctionName(FunctionDefinitionAst functi if (null == functionDefinitionAst) { return null; - } + } var funcNameTokens = Tokens.Where( - token => - ContainsExtent(functionDefinitionAst.Extent, token.Extent) + token => + ContainsExtent(functionDefinitionAst.Extent, token.Extent) && token.Text.Equals(functionDefinitionAst.Name)); var funcNameToken = funcNameTokens.FirstOrDefault(); return funcNameToken == null ? null : funcNameToken.Extent; @@ -919,7 +974,7 @@ internal VariableAnalysis InitializeVariableAnalysisHelper(Ast ast, VariableAnal /// /// /// - + #if PSV3 public string GetTypeFromReturnStatementAst(Ast funcAst, ReturnStatementAst ret) @@ -990,7 +1045,7 @@ public string GetTypeFromReturnStatementAst(Ast funcAst, ReturnStatementAst ret, /// /// /// - + #if PSV3 public string GetTypeFromMemberExpressionAst(MemberExpressionAst memberAst, Ast scopeAst) @@ -1050,9 +1105,9 @@ public string GetTypeFromMemberExpressionAst(MemberExpressionAst memberAst, Ast /// /// /// - + #if PSV3 - + internal string GetTypeFromMemberExpressionAstHelper(MemberExpressionAst memberAst, VariableAnalysisDetails analysisDetails) #else @@ -1241,8 +1296,8 @@ internal List GetSuppressionsClass(TypeDefinitionAst typeAst) if (typeAst.Members == null) { - return result; - } + return result; + } foreach (var member in typeAst.Members) { @@ -1469,11 +1524,11 @@ public static string[] ProcessCustomRulePaths(string[] rulePaths, SessionState s } outPaths.Add(path); } - + return outPaths.ToArray(); - + } - + /// /// Check if the function name starts with one of potentailly state changing verbs /// @@ -1671,8 +1726,8 @@ public static IEnumerable GetDeprecatedModuleManifestKeys() /// /// Get a mapping between string type keys and StatementAsts from module manifest hashtable ast - /// - /// This is a workaround as SafeGetValue is not supported on PS v5 and below. + /// + /// This is a workaround as SafeGetValue is not supported on PS v4 and below. /// /// Hashtable Ast obtained from module manifest /// A dictionary that maps string keys to values of StatementAst type @@ -1693,7 +1748,7 @@ private static Dictionary GetMapFromHashtableAst(Hashtable /// /// Checks if the version is supported - /// + /// /// PowerShell versions with Major greater than 3 are supported /// /// PowerShell version @@ -1910,7 +1965,7 @@ private object VisitStatementHelper(StatementAst statementAst) #if PSV3 statementAst.Visit(this); - + #else TypeDefinitionAst typeAst = statementAst as TypeDefinitionAst; @@ -2664,7 +2719,7 @@ static FindPipelineOutput() /// Find the pipeline output /// /// - + #if PSV3 public FindPipelineOutput(FunctionDefinitionAst ast) @@ -2695,7 +2750,7 @@ public FindPipelineOutput(FunctionDefinitionAst ast, IEnumerable /// - + #if PSV3 public static List> OutputTypes(FunctionDefinitionAst funcAst) diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index 4b9096ed4..06e6ad8aa 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -166,6 +166,13 @@ public void Initialize( throw new ArgumentNullException("runspace"); } + //initialize helper + Helper.Instance = new Helper( + runspace.SessionStateProxy.InvokeCommand, + outputWriter); + Helper.Instance.Initialize(); + + this.Initialize( outputWriter, runspace.SessionStateProxy.Path, @@ -237,7 +244,12 @@ internal bool ParseProfile(object profileObject, PathIntrinsics path, IOutputWri this.severity = (severityList.Count() == 0) ? null : severityList.ToArray(); this.includeRule = (includeRuleList.Count() == 0) ? null : includeRuleList.ToArray(); this.excludeRule = (excludeRuleList.Count() == 0) ? null : excludeRuleList.ToArray(); - + if (settings != null + && settings.ContainsKey("Rules")) + { + var ruleArgs = settings["Rules"] as Dictionary; + Helper.Instance.SetRuleArguments(ruleArgs); + } return true; } @@ -800,10 +812,6 @@ private void LoadRules(Dictionary> result, CommandInvocatio { List paths = new List(); - // Initialize helper - Helper.Instance = new Helper(invokeCommand, this.outputWriter); - Helper.Instance.Initialize(); - // Clear external rules for each invoke. this.ScriptRules = null; this.TokenRules = null; diff --git a/Engine/Strings.Designer.cs b/Engine/Strings.Designer.cs index 379b35d13..7af798344 100644 --- a/Engine/Strings.Designer.cs +++ b/Engine/Strings.Designer.cs @@ -385,7 +385,7 @@ internal static string WrongConfigurationKey { } /// - /// 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.. + /// Looks up a localized string similar to {0} is not a valid key in the settings hashtable: file {3}. Valid keys are ExcludeRules, IncludeRules and Severity.. /// internal static string WrongKey { get { diff --git a/Engine/Strings.resx b/Engine/Strings.resx index aa1a3f779..e1e0de031 100644 --- a/Engine/Strings.resx +++ b/Engine/Strings.resx @@ -193,7 +193,7 @@ Cannot find any DiagnosticRecord with the Rule Suppression ID {0}. - {0} is not a valid key in the settings 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: file {3}. Valid keys are ExcludeRules, IncludeRules and Severity. Key in the settings hashtable should be a string: line {0} column {1} in file {2} From 923c701bc9d4fd90c5a48eb199b28586b02b64c4 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Wed, 31 Aug 2016 13:55:27 -0700 Subject: [PATCH 3/9] Pass whitelist parameter to AvoidAlias rule --- Engine/Helper.cs | 24 ++++++++++++++++++++++++ Rules/AvoidAlias.cs | Bin 12044 -> 7249 bytes 2 files changed, 24 insertions(+) diff --git a/Engine/Helper.cs b/Engine/Helper.cs index c8b26c274..e6abcbbb8 100644 --- a/Engine/Helper.cs +++ b/Engine/Helper.cs @@ -165,6 +165,30 @@ public void Initialize() } } + public Dictionary> GetRuleArguments() + { + return ruleArguments; + } + + public Dictionary GetRuleArguments(string ruleName) + { + if (ruleArguments.ContainsKey(ruleName)) + { + return ruleArguments[ruleName]; + } + return null; + } + + public void SetRuleArguments(Dictionary> ruleArgs) + { + if (ruleArgs.Comparer != StringComparer.OrdinalIgnoreCase) + { + throw new ArgumentException( + "Input dictionary should have OrdinalIgnoreCase comparer.", + "ruleArgs"); + } + ruleArguments = ruleArgs; + } /// /// Returns all the rule arguments /// diff --git a/Rules/AvoidAlias.cs b/Rules/AvoidAlias.cs index c5e325abe5cf1220ba1b7e9a5d88baaf23328c82..a1ebee6f64901648b266b145397034f29d97a043 100644 GIT binary patch literal 7249 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 aIu}ekXj8Q51V5@N810=Rp;m(g|Lrf~EcO`y literal 12044 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 Date: Wed, 31 Aug 2016 16:33:48 -0700 Subject: [PATCH 4/9] Remove duplicate methods in helper --- Engine/Helper.cs | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/Engine/Helper.cs b/Engine/Helper.cs index e6abcbbb8..c8b26c274 100644 --- a/Engine/Helper.cs +++ b/Engine/Helper.cs @@ -165,30 +165,6 @@ public void Initialize() } } - public Dictionary> GetRuleArguments() - { - return ruleArguments; - } - - public Dictionary GetRuleArguments(string ruleName) - { - if (ruleArguments.ContainsKey(ruleName)) - { - return ruleArguments[ruleName]; - } - return null; - } - - public void SetRuleArguments(Dictionary> ruleArgs) - { - if (ruleArgs.Comparer != StringComparer.OrdinalIgnoreCase) - { - throw new ArgumentException( - "Input dictionary should have OrdinalIgnoreCase comparer.", - "ruleArgs"); - } - ruleArguments = ruleArgs; - } /// /// Returns all the rule arguments /// From 28caa63ee5c93730ccdccc7cf988983b55aeae37 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Wed, 31 Aug 2016 17:23:48 -0700 Subject: [PATCH 5/9] Refactor ParseProfileHashtable to read rules key --- Engine/ScriptAnalyzer.cs | 134 ++++++++++++++++++++++++--------------- 1 file changed, 82 insertions(+), 52 deletions(-) diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index 06e6ad8aa..e589aa6d2 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -421,77 +421,107 @@ private bool ParseProfileHashtable(Hashtable profile, PathIntrinsics path, IOutp validKeys.Add("severity"); validKeys.Add("includerules"); validKeys.Add("excluderules"); + validKeys.Add("rules"); foreach (var obj in profile.Keys) { string key = obj as string; - - // key should be a string if (key == null) { - writer.WriteError(new ErrorRecord(new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.KeyNotString, key)), - Strings.ConfigurationKeyNotAString, ErrorCategory.InvalidData, profile)); - hasError = true; - continue; - } - - // checks whether it falls into list of valid keys - if (!validKeys.Contains(key)) - { - writer.WriteError(new ErrorRecord( - new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongKeyHashTable, key)), - Strings.WrongConfigurationKey, ErrorCategory.InvalidData, profile)); + writer.WriteError( + new ErrorRecord( + new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.KeyNotString, key)), + Strings.ConfigurationKeyNotAString, + ErrorCategory.InvalidData, + profile)); hasError = true; continue; } + key = key.ToLower(); 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)) + switch (key) { - 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) - { - if (val is string) + case "severity": + case "includerules": + case "excluderules": + // 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; + break; + } + List values = new List(); + if (value is string) { - values.Add(val as string); + values.Add(value as string); } - else + else if (value is IEnumerable) { - writer.WriteError(new ErrorRecord( - new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongValueHashTable, val, key)), - Strings.WrongConfigurationKey, ErrorCategory.InvalidData, profile)); + 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) + { + 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; + break; + } + } + } + AddProfileItem(key, values, severityList, includeRuleList, excludeRuleList); + settings[key] = values; + break; + + case "rules": + var ruleDict = value as Dictionary; + if (ruleDict == null) + { + writer.WriteError( + new ErrorRecord( + new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongValueHashTable, value, key)), + Strings.WrongConfigurationKey, + ErrorCategory.InvalidData, + profile)); hasError = true; - continue; + break; } - } + settings[key] = ruleDict; + break; + + default: + writer.WriteError( + new ErrorRecord( + new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongKeyHashTable, key)), + Strings.WrongConfigurationKey, + ErrorCategory.InvalidData, + profile)); + hasError = true; + break; } - - AddProfileItem(key, values, severityList, includeRuleList, excludeRuleList); - } - return hasError; } From c7a091adf15c5b79cf37a3c9153ba313f927125b Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Wed, 31 Aug 2016 19:09:41 -0700 Subject: [PATCH 6/9] Extract rules from settings hashtable type input --- Engine/ScriptAnalyzer.cs | 95 ++++++++++++++++++++++++++++------------ 1 file changed, 67 insertions(+), 28 deletions(-) diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index e589aa6d2..041d66388 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -412,18 +412,19 @@ private Dictionary GetDictionaryFromHashTableAst( return output; } - private bool ParseProfileHashtable(Hashtable profile, PathIntrinsics path, IOutputWriter writer, - List severityList, List includeRuleList, List excludeRuleList) + /// + /// Recursively convert hashtable to dictionary + /// + /// + /// Dictionary that maps string to object + private Dictionary GetDictionaryFromHashtable( + Hashtable hashtable, + IOutputWriter writer, + out bool hasError) { - bool hasError = false; - - HashSet validKeys = new HashSet(StringComparer.OrdinalIgnoreCase); - validKeys.Add("severity"); - validKeys.Add("includerules"); - validKeys.Add("excluderules"); - validKeys.Add("rules"); - - foreach (var obj in profile.Keys) + var dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); + hasError = false; + foreach (var obj in hashtable.Keys) { string key = obj as string; if (key == null) @@ -433,13 +434,64 @@ private bool ParseProfileHashtable(Hashtable profile, PathIntrinsics path, IOutp new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.KeyNotString, key)), Strings.ConfigurationKeyNotAString, ErrorCategory.InvalidData, - profile)); + hashtable)); hasError = true; - continue; + return null; + } + var valueHashtableObj = hashtable[obj]; + if (valueHashtableObj == null) + { + writer.WriteError( + new ErrorRecord( + new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongValueHashTable, valueHashtableObj, key)), + Strings.WrongConfigurationKey, + ErrorCategory.InvalidData, + hashtable)); + hasError = true; + return null; + } + var valueHashtable = valueHashtableObj as Hashtable; + if (valueHashtable == null) + { + dictionary.Add(key, valueHashtableObj); + } + else + { + var dict = GetDictionaryFromHashtable(valueHashtable, writer, out hasError); + if (dict != null) + { + dictionary.Add(key, dict); + } + else + { + return null; + } } - key = key.ToLower(); - object value = profile[obj]; + } + return dictionary; + } + + 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"); + validKeys.Add("rules"); + + settings = GetDictionaryFromHashtable(profile, writer, out hasError); + if (hasError) + { + return hasError; + } + foreach (var settingKey in settings.Keys) + { + var key = settingKey.ToLower(); + object value = settings[key]; switch (key) { case "severity": @@ -496,19 +548,6 @@ private bool ParseProfileHashtable(Hashtable profile, PathIntrinsics path, IOutp break; case "rules": - var ruleDict = value as Dictionary; - if (ruleDict == null) - { - writer.WriteError( - new ErrorRecord( - new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongValueHashTable, value, key)), - Strings.WrongConfigurationKey, - ErrorCategory.InvalidData, - profile)); - hasError = true; - break; - } - settings[key] = ruleDict; break; default: From c54634f1797fcdcbc3bcf7f3f16b58bced3db312 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Wed, 31 Aug 2016 19:11:32 -0700 Subject: [PATCH 7/9] Enhance avoidalias setproperty method --- Rules/AvoidAlias.cs | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/Rules/AvoidAlias.cs b/Rules/AvoidAlias.cs index a1ebee6f6..b2a95f3ea 100644 --- a/Rules/AvoidAlias.cs +++ b/Rules/AvoidAlias.cs @@ -62,11 +62,31 @@ private void SetProperties() return; } IEnumerable aliases = obj as IEnumerable; - if (obj == null) + if (aliases == null) { - return; + // try with enumerable objects + var enumerableObjs = obj as IEnumerable; + if (enumerableObjs == null) + { + return; + } + foreach (var x in enumerableObjs) + { + var y = x as string; + if (y == null) + { + return; + } + else + { + whiteList.Add(y); + } + } + } + else + { + whiteList.AddRange(aliases); } - whiteList.AddRange(aliases); } /// From bf9bcb3ebe694278d05e5a70363d746b972d1493 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Thu, 1 Sep 2016 13:18:35 -0700 Subject: [PATCH 8/9] Add tests for avoidalias rule whitelist --- Tests/Rules/AvoidUsingAlias.tests.ps1 | 23 +++++++++++++++++++ .../TestSettings/AvoidAliasSettings.psd1 | 7 ++++++ 2 files changed, 30 insertions(+) create mode 100644 Tests/Rules/TestSettings/AvoidAliasSettings.psd1 diff --git a/Tests/Rules/AvoidUsingAlias.tests.ps1 b/Tests/Rules/AvoidUsingAlias.tests.ps1 index f304b0318..f6d8005d2 100644 --- a/Tests/Rules/AvoidUsingAlias.tests.ps1 +++ b/Tests/Rules/AvoidUsingAlias.tests.ps1 @@ -31,4 +31,27 @@ Describe "AvoidUsingAlias" { $noViolations.Count | Should Be 0 } } + + Context "Settings file provides whitelist" { + $whiteListTestScriptDef = 'gci; cd; iwr' + + It "honors the whitelist provided as hashtable" { + $settings = @{ + 'Rules' = @{ + 'PSAvoidUsingCmdletAliases' = @{ + 'Whitelist' = @('cd') + } + } + } + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $whiteListTestScriptDef -Settings $settings -IncludeRule $violationName + $violations.Count | Should Be 2 + } + + It "honors the whitelist provided through settings file" { + # even though join-path returns string, if we do not use tostring, then invoke-scriptanalyzer cannot cast it to string type + $settingsFilePath = (Join-Path $directory (Join-Path 'TestSettings' 'AvoidAliasSettings.psd1')).ToString() + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $whiteListTestScriptDef -Settings $settingsFilePath -IncludeRule $violationName + $violations.Count | Should be 2 + } + } } \ No newline at end of file diff --git a/Tests/Rules/TestSettings/AvoidAliasSettings.psd1 b/Tests/Rules/TestSettings/AvoidAliasSettings.psd1 new file mode 100644 index 000000000..e5a4cb1af --- /dev/null +++ b/Tests/Rules/TestSettings/AvoidAliasSettings.psd1 @@ -0,0 +1,7 @@ +@{ + 'Rules' = @{ + 'PSAvoidUsingCmdletAliases' = @{ + 'Whitelist' = @('cd') + } + } +} \ No newline at end of file From a40da2338d67995adf25b1bdb5a2eb62778d7f11 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Fri, 2 Sep 2016 13:42:52 -0700 Subject: [PATCH 9/9] Add verbose while processing settings parameter --- Engine/ScriptAnalyzer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index 041d66388..16d650e44 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -204,11 +204,13 @@ internal bool ParseProfile(object profileObject, PathIntrinsics path, IOutputWri // profile was not given if (profileObject == null) { + writer.WriteVerbose("No settings hashtable or file to consume"); return true; } if (!(profileObject is string || profileObject is Hashtable)) { + writer.WriteWarning("Settings parameter should be a hashtable or a filepath"); return false; }