From 8e6bf17c94ce363df9655ffcbf279c6dd0dde3c5 Mon Sep 17 00:00:00 2001 From: June Blender Date: Fri, 6 Nov 2015 14:15:34 -0800 Subject: [PATCH 01/53] Edited WriteHost rule strings. --- Rules/Strings.Designer.cs | 4 ++-- Rules/Strings.resx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Rules/Strings.Designer.cs b/Rules/Strings.Designer.cs index b49545e74..99e681f14 100644 --- a/Rules/Strings.Designer.cs +++ b/Rules/Strings.Designer.cs @@ -790,7 +790,7 @@ internal static string AvoidUsingWriteHostCommonName { } /// - /// Looks up a localized string similar to Write-Host or Console.Write should not be used because it may not work in some hosts or there may even be no hosts at all. Use Write-Output instead.. + /// Looks up a localized string similar to Avoid using the Write-Host cmdlet. Instead, use Write-Output, Write-Verbose, or Write-Information. Because Write-Host is host-specific, its implementation might vary unpredictably. Also, prior to PowerShell 5.0, Write-Host did not write to a stream, so users cannot suppress it, capture its value, or redirect it.. /// internal static string AvoidUsingWriteHostDescription { get { @@ -799,7 +799,7 @@ internal static string AvoidUsingWriteHostDescription { } /// - /// Looks up a localized string similar to File '{0}' uses Write-Host. This is not recommended because it may not work in some hosts or there may even be no hosts at all. Use Write-Output instead.. + /// Looks up a localized string similar to File '{0}' uses Write-Host. Avoid using Write-Host because it might not work in all hosts, does not work when there is no host, and (prior to PS 5.0) cannot be suppressed, captured, or redirected. Instead, use Write-Output, Write-Verbose, or Write-Information.. /// internal static string AvoidUsingWriteHostError { get { diff --git a/Rules/Strings.resx b/Rules/Strings.resx index c5b254de5..4657658df 100644 --- a/Rules/Strings.resx +++ b/Rules/Strings.resx @@ -361,10 +361,10 @@ File '{0}' uses Console.'{1}'. Using Console to write is not recommended because it may not work in all hosts or there may even be no hosts at all. Use Write-Output instead. - Write-Host or Console.Write should not be used because it may not work in some hosts or there may even be no hosts at all. Use Write-Output instead. + Avoid using the Write-Host cmdlet. Instead, use Write-Output, Write-Verbose, or Write-Information. Because Write-Host is host-specific, its implementation might vary unpredictably. Also, prior to PowerShell 5.0, Write-Host did not write to a stream, so users cannot suppress it, capture its value, or redirect it. - File '{0}' uses Write-Host. This is not recommended because it may not work in some hosts or there may even be no hosts at all. Use Write-Output instead. + File '{0}' uses Write-Host. Avoid using Write-Host because it might not work in all hosts, does not work when there is no host, and (prior to PS 5.0) cannot be suppressed, captured, or redirected. Instead, use Write-Output, Write-Verbose, or Write-Information. Avoid Using Write-Host From 26dcceba4f432f1f14232c63a7967040c3aeb6cc Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Fri, 6 Nov 2015 15:26:12 -0800 Subject: [PATCH 02/53] Update message in Test case for WriteHost Rule --- Tests/Rules/AvoidUsingWriteHost.tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Rules/AvoidUsingWriteHost.tests.ps1 b/Tests/Rules/AvoidUsingWriteHost.tests.ps1 index 2e28f5c37..c959bc501 100644 --- a/Tests/Rules/AvoidUsingWriteHost.tests.ps1 +++ b/Tests/Rules/AvoidUsingWriteHost.tests.ps1 @@ -1,6 +1,6 @@ Import-Module PSScriptAnalyzer Set-Alias ctss ConvertTo-SecureString -$writeHostMessage = [Regex]::Escape("File 'AvoidUsingWriteHost.ps1' uses Write-Host. This is not recommended because it may not work in some hosts or there may even be no hosts at all. Use Write-Output instead.") +$writeHostMessage = [Regex]::Escape("File 'AvoidUsingWriteHost.ps1' uses Write-Host. Avoid using Write-Host because it might not work in all hosts, does not work when there is no host, and (prior to PS 5.0) cannot be suppressed, captured, or redirected. Instead, use Write-Output, Write-Verbose, or Write-Information.") $writeHostName = "PSAvoidUsingWriteHost" $directory = Split-Path -Parent $MyInvocation.MyCommand.Path $violations = Invoke-ScriptAnalyzer $directory\AvoidUsingWriteHost.ps1 | Where-Object {$_.RuleName -eq $writeHostName} From 7ef6ce7ece7d7f4370000b857b35d5db855f9d26 Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Mon, 9 Nov 2015 14:11:54 -0800 Subject: [PATCH 03/53] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index babe53032..265890fc7 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Announcements ##### ScriptAnalyzer community meeting schedule: - - Next Meeting - 11/10/2015 - 11am to 12pm PDT + - Next Meeting - 12/08/2015 - 11am to 12pm PDT - [iCalender invite](http://1drv.ms/1VvAaxO) - [Notes and recordings from earlier meetings](https://github.com/PowerShell/PSScriptAnalyzer/wiki) From 8e3a0cd5f1f7e5d591e7123960232fccdd438a70 Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Tue, 10 Nov 2015 13:47:40 -0800 Subject: [PATCH 04/53] Updated Engine to use AddCommand to prevent Script Based injection attacks --- Engine/ScriptAnalyzer.cs | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index c6cbcd528..6dfb257b5 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -26,6 +26,7 @@ using System.Globalization; using System.Collections.Concurrent; using System.Threading.Tasks; +using System.Collections.ObjectModel; namespace Microsoft.Windows.PowerShell.ScriptAnalyzer { @@ -545,18 +546,19 @@ private List GetExternalRule(string[] moduleNames) using (System.Management.Automation.PowerShell posh = System.Management.Automation.PowerShell.Create(state)) { - string script = string.Format(CultureInfo.CurrentCulture, "Get-Module -Name '{0}' -ListAvailable", moduleName); - shortModuleName = posh.AddScript(script).Invoke().First().Name; + posh.AddCommand("Get-Module").AddParameter("Name", moduleName).AddParameter("ListAvailable"); + shortModuleName = posh.Invoke().First().Name; // Invokes Update-Help for this module // Required since when invoking Get-Help later on, the cmdlet prompts for Update-Help interactively // By invoking Update-Help first, Get-Help will not prompt for downloading help later - script = string.Format(CultureInfo.CurrentCulture, "Update-Help -Module '{0}' -Force", shortModuleName); - posh.AddScript(script).Invoke(); - + posh.AddCommand("Update-Help").AddParameter("Module", shortModuleName).AddParameter("Force"); + posh.Invoke(); + // Invokes Get-Command and Get-Help for each functions in the module. - script = string.Format(CultureInfo.CurrentCulture, "Get-Command -Module '{0}'", shortModuleName); - var psobjects = posh.AddScript(script).Invoke(); + posh.Commands.Clear(); + posh.AddCommand("Get-Command").AddParameter("Module", shortModuleName); + var psobjects = posh.Invoke(); foreach (PSObject psobject in psobjects) { @@ -570,10 +572,22 @@ private List GetExternalRule(string[] moduleNames) //Only add functions that are defined as rules. if (param != null) { - script = string.Format(CultureInfo.CurrentCulture, "(Get-Help -Name {0}).Description | Out-String", funcInfo.Name); - string desc = posh.AddScript(script).Invoke()[0].ImmediateBaseObject.ToString() - .Replace("\r\n", " ").Trim(); + posh.AddCommand("Get-Help").AddParameter("Name", funcInfo.Name); + Collection helpContent = posh.Invoke(); + + // Retrieve "Description" field in the help content + string desc = String.Empty; + + if ((null != helpContent) && ( 1 == helpContent.Count)) + { + dynamic description = helpContent[0].Properties["Description"].Value; + if (null != description) + { + desc = description[0].Text; + } + } + rules.Add(new ExternalRule(funcInfo.Name, funcInfo.Name, desc, param.Name, param.ParameterType.FullName, funcInfo.ModuleName, funcInfo.Module.Path)); } @@ -784,8 +798,8 @@ public Dictionary> CheckRuleExtension(string[] path, PathIn using (System.Management.Automation.PowerShell posh = System.Management.Automation.PowerShell.Create()) { - string script = string.Format(CultureInfo.CurrentCulture, "Get-Module -Name '{0}' -ListAvailable", resolvedPath); - PSModuleInfo moduleInfo = posh.AddScript(script).Invoke().First(); + posh.AddCommand("Get-Module").AddParameter("Name", resolvedPath).AddParameter("ListAvailable"); + PSModuleInfo moduleInfo = posh.Invoke().First(); // Adds original path, otherwise path.Except(validModPaths) will fail. // It's possible that user can provide something like this: From 4454618b0d7e8bab6869995197d0838b9388df5d Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Tue, 10 Nov 2015 14:26:26 -0800 Subject: [PATCH 05/53] Check for null Description Property in the returned help content --- Engine/ScriptAnalyzer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index 6dfb257b5..d82ebd1d7 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -580,11 +580,11 @@ private List GetExternalRule(string[] moduleNames) if ((null != helpContent) && ( 1 == helpContent.Count)) { - dynamic description = helpContent[0].Properties["Description"].Value; + dynamic description = helpContent[0].Properties["Description"]; if (null != description) { - desc = description[0].Text; + desc = description.Value[0].Text; } } From 5866510fa5db2f507a35fce036a2ab7c0070acfb Mon Sep 17 00:00:00 2001 From: quoctruong Date: Wed, 11 Nov 2015 17:10:16 -0800 Subject: [PATCH 06/53] Initial commit for ScriptDefinition --- .../Commands/InvokeScriptAnalyzerCommand.cs | 52 +++++++++++++--- Engine/Generic/DiagnosticRecord.cs | 11 +++- Engine/Generic/RuleSuppression.cs | 24 ++++++-- Engine/Helper.cs | 14 ++++- Engine/ScriptAnalyzer.cs | 59 +++++++++++++++++-- Engine/Strings.Designer.cs | 36 +++++++++++ Engine/Strings.resx | 12 ++++ PSScriptAnalyzer.sln | 2 +- Rules/AvoidDefaultTrueValueSwitchParameter.cs | 15 ++++- Rules/AvoidShouldContinueWithoutForce.cs | 15 ++++- ...UsingConvertToSecureStringWithPlainText.cs | 11 +++- Rules/AvoidUsingWMICmdlet.cs | 12 +++- Rules/AvoidUsingWriteHost.cs | 15 ++++- Rules/DscExamplesPresent.cs | 12 ++++ Rules/DscTestsPresent.cs | 12 ++++ Rules/ProvideDefaultParameterValue.cs | 2 +- Rules/ScriptAnalyzerBuiltinRules.csproj | 22 +++---- Rules/Strings.Designer.cs | 54 +++++++++++++++++ Rules/Strings.resx | 18 ++++++ Rules/UseBOMForUnicodeEncodedFile.cs | 6 ++ Rules/UseUTF8EncodingForHelpFile.cs | 7 +++ Tests/Engine/GlobalSuppression.test.ps1 | 20 ++++++- Tests/Engine/InvokeScriptAnalyzer.tests.ps1 | 50 ++++++++++++++++ Tests/Engine/RuleSuppression.tests.ps1 | 10 +++- 24 files changed, 440 insertions(+), 51 deletions(-) diff --git a/Engine/Commands/InvokeScriptAnalyzerCommand.cs b/Engine/Commands/InvokeScriptAnalyzerCommand.cs index d89413948..ccf5d5992 100644 --- a/Engine/Commands/InvokeScriptAnalyzerCommand.cs +++ b/Engine/Commands/InvokeScriptAnalyzerCommand.cs @@ -31,14 +31,19 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands /// /// InvokeScriptAnalyzerCommand: Cmdlet to statically check PowerShell scripts. /// - [Cmdlet(VerbsLifecycle.Invoke, "ScriptAnalyzer", HelpUri = "http://go.microsoft.com/fwlink/?LinkId=525914")] + [Cmdlet(VerbsLifecycle.Invoke, + "ScriptAnalyzer", + DefaultParameterSetName="File", + HelpUri = "http://go.microsoft.com/fwlink/?LinkId=525914")] public class InvokeScriptAnalyzerCommand : PSCmdlet, IOutputWriter { #region Parameters /// /// Path: The path to the file or folder to invoke PSScriptAnalyzer on. /// - [Parameter(Position = 0, Mandatory = true)] + [Parameter(Position = 0, + ParameterSetName = "File", + Mandatory = true)] [ValidateNotNull] [Alias("PSPath")] public string Path @@ -48,6 +53,20 @@ public string Path } private string path; + /// + /// ScriptDefinition: a script definition in the form of a string to run rules on. + /// + [Parameter(Position = 0, + ParameterSetName = "ScriptDefinition", + Mandatory = true)] + [ValidateNotNull] + public string ScriptDefinition + { + get { return scriptDefinition; } + set { scriptDefinition = value; } + } + private string scriptDefinition; + /// /// CustomRulePath: The path to the file containing custom rules to run. /// @@ -160,11 +179,18 @@ protected override void BeginProcessing() /// protected override void ProcessRecord() { - // throws Item Not Found Exception - Collection paths = this.SessionState.Path.GetResolvedPSPathFromPSPath(path); - foreach (PathInfo p in paths) + if (String.Equals(this.ParameterSetName, "File", StringComparison.OrdinalIgnoreCase)) { - ProcessPath(this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(p.Path)); + // throws Item Not Found Exception + Collection paths = this.SessionState.Path.GetResolvedPSPathFromPSPath(path); + foreach (PathInfo p in paths) + { + ProcessPathOrScriptDefinition(this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(p.Path)); + } + } + else if (String.Equals(this.ParameterSetName, "ScriptDefinition", StringComparison.OrdinalIgnoreCase)) + { + ProcessPathOrScriptDefinition(scriptDefinition); } } @@ -172,10 +198,18 @@ protected override void ProcessRecord() #region Methods - private void ProcessPath(string path) + private void ProcessPathOrScriptDefinition(string pathOrScriptDefinition) { - IEnumerable diagnosticsList = - ScriptAnalyzer.Instance.AnalyzePath(path, this.recurse); + IEnumerable diagnosticsList = Enumerable.Empty(); + + if (String.Equals(this.ParameterSetName, "File", StringComparison.OrdinalIgnoreCase)) + { + diagnosticsList = ScriptAnalyzer.Instance.AnalyzePath(pathOrScriptDefinition); + } + else if (String.Equals(this.ParameterSetName, "ScriptDefinition", StringComparison.OrdinalIgnoreCase)) + { + diagnosticsList = ScriptAnalyzer.Instance.AnalyzeScriptDefinition(pathOrScriptDefinition); + } //Output through loggers foreach (ILogger logger in ScriptAnalyzer.Instance.Loggers) diff --git a/Engine/Generic/DiagnosticRecord.cs b/Engine/Generic/DiagnosticRecord.cs index d62a5217c..5425d9d9a 100644 --- a/Engine/Generic/DiagnosticRecord.cs +++ b/Engine/Generic/DiagnosticRecord.cs @@ -70,7 +70,16 @@ public string ScriptName { get { return scriptName; } //Trim down to the leaf element of the filePath and pass it to Diagnostic Record - set { scriptName = System.IO.Path.GetFileName(value); } + set { + if (!string.IsNullOrWhiteSpace(value)) + { + scriptName = System.IO.Path.GetFileName(value); + } + else + { + scriptName = string.Empty; + } + } } /// diff --git a/Engine/Generic/RuleSuppression.cs b/Engine/Generic/RuleSuppression.cs index a90f1b88c..5ce24af67 100644 --- a/Engine/Generic/RuleSuppression.cs +++ b/Engine/Generic/RuleSuppression.cs @@ -294,8 +294,15 @@ public RuleSuppression(AttributeAst attrAst, int start, int end) if (!String.IsNullOrWhiteSpace(Error)) { - Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormat, StartAttributeLine, - System.IO.Path.GetFileName(attrAst.Extent.File), Error); + if (String.IsNullOrWhiteSpace(attrAst.Extent.File)) + { + Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormatScriptDefinition, StartAttributeLine, Error); + } + else + { + Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormat, StartAttributeLine, + System.IO.Path.GetFileName(attrAst.Extent.File), Error); + } } } @@ -372,8 +379,17 @@ public static List GetSuppressions(IEnumerable at { if (targetAsts.Count() == 0) { - ruleSupp.Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormat, ruleSupp.StartAttributeLine, - System.IO.Path.GetFileName(scopeAst.Extent.File), String.Format(Strings.TargetCannotBeFoundError, ruleSupp.Target, ruleSupp.Scope)); + if (String.IsNullOrWhiteSpace(scopeAst.Extent.File)) + { + ruleSupp.Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormatScriptDefinition, ruleSupp.StartAttributeLine, + String.Format(Strings.TargetCannotBeFoundError, ruleSupp.Target, ruleSupp.Scope)); + } + else + { + ruleSupp.Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormat, ruleSupp.StartAttributeLine, + System.IO.Path.GetFileName(scopeAst.Extent.File), String.Format(Strings.TargetCannotBeFoundError, ruleSupp.Target, ruleSupp.Scope)); + } + result.Add(ruleSupp); continue; } diff --git a/Engine/Helper.cs b/Engine/Helper.cs index 7297bc4b0..bc2fef74c 100644 --- a/Engine/Helper.cs +++ b/Engine/Helper.cs @@ -956,8 +956,18 @@ public Tuple, List> SuppressRule(string // If we cannot found any error but the rulesuppression has a rulesuppressionid then it must be used wrongly if (!String.IsNullOrWhiteSpace(ruleSuppression.RuleSuppressionID) && suppressionCount == 0) { - ruleSuppression.Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormat, ruleSuppression.StartAttributeLine, - System.IO.Path.GetFileName(diagnostics.First().Extent.File), String.Format(Strings.RuleSuppressionIDError, ruleSuppression.RuleSuppressionID)); + // checks whether are given a string or a file path + if (String.IsNullOrWhiteSpace(diagnostics.First().Extent.File)) + { + ruleSuppression.Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormatScriptDefinition, ruleSuppression.StartAttributeLine, + String.Format(Strings.RuleSuppressionIDError, ruleSuppression.RuleSuppressionID)); + } + else + { + ruleSuppression.Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormat, ruleSuppression.StartAttributeLine, + System.IO.Path.GetFileName(diagnostics.First().Extent.File), String.Format(Strings.RuleSuppressionIDError, ruleSuppression.RuleSuppressionID)); + } + this.outputWriter.WriteError(new ErrorRecord(new ArgumentException(ruleSuppression.Error), ruleSuppression.Error, ErrorCategory.InvalidArgument, ruleSuppression)); } } diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index d82ebd1d7..054db6758 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -883,7 +883,7 @@ public Dictionary> CheckRuleExtension(string[] path, PathIn } #endregion - + /// /// Analyzes a script file or a directory containing script files. @@ -924,6 +924,49 @@ public IEnumerable AnalyzePath(string path, bool searchRecursi } } + /// + /// Analyzes a script definition in the form of a string input + /// + /// The script to be analyzed + /// + public IEnumerable AnalyzeScriptDefinition(string scriptDefinition) + { + ScriptBlockAst scriptAst = null; + Token[] scriptTokens = null; + ParseError[] errors = null; + + this.outputWriter.WriteVerbose(string.Format(CultureInfo.CurrentCulture, Strings.VerboseScriptDefinitionMessage)); + + try + { + scriptAst = Parser.ParseInput(scriptDefinition, out scriptTokens, out errors); + } + catch (Exception e) + { + this.outputWriter.WriteWarning(e.ToString()); + return null; + } + + if (errors != null && errors.Length > 0) + { + foreach (ParseError error in errors) + { + string parseErrorMessage = String.Format(CultureInfo.CurrentCulture, Strings.ParseErrorFormatForScriptDefinition, error.Message.TrimEnd('.'), error.Extent.StartLineNumber, error.Extent.StartColumnNumber); + this.outputWriter.WriteError(new ErrorRecord(new ParseException(parseErrorMessage), parseErrorMessage, ErrorCategory.ParserError, error.ErrorId)); + } + } + + if (errors != null && errors.Length > 10) + { + string manyParseErrorMessage = String.Format(CultureInfo.CurrentCulture, Strings.ParserErrorMessageForScriptDefinition); + this.outputWriter.WriteError(new ErrorRecord(new ParseException(manyParseErrorMessage), manyParseErrorMessage, ErrorCategory.ParserError, scriptDefinition)); + + return new List(); + } + + return this.AnalyzeSyntaxTree(scriptAst, scriptTokens, String.Empty); + } + private void BuildScriptPathList( string path, bool searchRecursively, @@ -1038,7 +1081,9 @@ private IEnumerable AnalyzeFile(string filePath) /// /// The ScriptBlockAst from the parsed script. /// The tokens found in the script. - /// The path to the file that was parsed. + /// The path to the file that was parsed. + /// If AnalyzeSyntaxTree is called from an ast that we get from ParseInput, then this field will be String.Empty + /// /// An enumeration of DiagnosticRecords that were found by rules. public IEnumerable AnalyzeSyntaxTree( ScriptBlockAst scriptAst, @@ -1052,8 +1097,12 @@ public IEnumerable AnalyzeSyntaxTree( // Use a List of KVP rather than dictionary, since for a script containing inline functions with same signature, keys clash List> cmdInfoTable = new List>(); + bool filePathIsNullOrWhiteSpace = String.IsNullOrWhiteSpace(filePath); + filePath = filePathIsNullOrWhiteSpace ? String.Empty : filePath; - bool helpFile = (scriptAst == null) && Helper.Instance.IsHelpFile(filePath); + // check whether the script we are analyzing is a help file or not. + // this step is not applicable for scriptdefinition, whose filepath is null + bool helpFile = (scriptAst == null) && (!filePathIsNullOrWhiteSpace) && Helper.Instance.IsHelpFile(filePath); if (!helpFile) { @@ -1083,7 +1132,7 @@ public IEnumerable AnalyzeSyntaxTree( #region Run ScriptRules //Trim down to the leaf element of the filePath and pass it to Diagnostic Record - string fileName = System.IO.Path.GetFileName(filePath); + string fileName = filePathIsNullOrWhiteSpace ? String.Empty : System.IO.Path.GetFileName(filePath); if (this.ScriptRules != null) { @@ -1285,7 +1334,7 @@ public IEnumerable AnalyzeSyntaxTree( } // Check if the supplied artifact is indeed part of the DSC resource - if (Helper.Instance.IsDscResourceModule(filePath)) + if (!filePathIsNullOrWhiteSpace && Helper.Instance.IsDscResourceModule(filePath)) { // Run all DSC Rules foreach (IDSCResourceRule dscResourceRule in this.DSCResourceRules) diff --git a/Engine/Strings.Designer.cs b/Engine/Strings.Designer.cs index cc892c6c8..619490de5 100644 --- a/Engine/Strings.Designer.cs +++ b/Engine/Strings.Designer.cs @@ -177,6 +177,15 @@ internal static string NullRuleNameError { } } + /// + /// Looks up a localized string similar to Parse error in script definition: {0} at line {1} column {2}.. + /// + internal static string ParseErrorFormatForScriptDefinition { + get { + return ResourceManager.GetString("ParseErrorFormatForScriptDefinition", resourceCulture); + } + } + /// /// Looks up a localized string similar to Parse error in file {0}: {1} at line {2} column {3}.. /// @@ -195,6 +204,15 @@ internal static string ParserErrorMessage { } } + /// + /// Looks up a localized string similar to There are too many parser errors in the script definition. Please correct them before running ScriptAnalyzer.. + /// + internal static string ParserErrorMessageForScriptDefinition { + get { + return ResourceManager.GetString("ParserErrorMessageForScriptDefinition", resourceCulture); + } + } + /// /// Looks up a localized string similar to RULE_ERROR. /// @@ -222,6 +240,15 @@ internal static string RuleSuppressionErrorFormat { } } + /// + /// Looks up a localized string similar to Suppression Message Attribute error at line {0} in script definition : {1}. + /// + internal static string RuleSuppressionErrorFormatScriptDefinition { + get { + return ResourceManager.GetString("RuleSuppressionErrorFormatScriptDefinition", resourceCulture); + } + } + /// /// Looks up a localized string similar to Cannot find any DiagnosticRecord with the Rule Suppression ID {0}.. /// @@ -285,6 +312,15 @@ internal static string VerboseRunningMessage { } } + /// + /// Looks up a localized string similar to Analyzing Script Definition.. + /// + internal static string VerboseScriptDefinitionMessage { + get { + return ResourceManager.GetString("VerboseScriptDefinitionMessage", resourceCulture); + } + } + /// /// Looks up a localized string similar to {0} is not a valid key in the profile hashtable: line {0} column {1} in file {2}. /// diff --git a/Engine/Strings.resx b/Engine/Strings.resx index 87daf264d..9de1cef68 100644 --- a/Engine/Strings.resx +++ b/Engine/Strings.resx @@ -204,4 +204,16 @@ Profile file '{0}' is invalid because it does not contain a hashtable. + + Parse error in script definition: {0} at line {1} column {2}. + + + There are too many parser errors in the script definition. Please correct them before running ScriptAnalyzer. + + + Suppression Message Attribute error at line {0} in script definition : {1} + + + Analyzing Script Definition. + \ No newline at end of file diff --git a/PSScriptAnalyzer.sln b/PSScriptAnalyzer.sln index 45cae24a7..a9dcdc36e 100644 --- a/PSScriptAnalyzer.sln +++ b/PSScriptAnalyzer.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2013 -VisualStudioVersion = 12.0.21005.1 +VisualStudioVersion = 12.0.31101.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScriptAnalyzerEngine", "Engine\ScriptAnalyzerEngine.csproj", "{F4BDE3D0-3EEF-4157-8A3E-722DF7ADEF60}" EndProject diff --git a/Rules/AvoidDefaultTrueValueSwitchParameter.cs b/Rules/AvoidDefaultTrueValueSwitchParameter.cs index 932da65e6..ee5c60a44 100644 --- a/Rules/AvoidDefaultTrueValueSwitchParameter.cs +++ b/Rules/AvoidDefaultTrueValueSwitchParameter.cs @@ -42,9 +42,18 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) if (paramAst.Attributes.Any(attr => attr.TypeName.GetReflectionType() == typeof(System.Management.Automation.SwitchParameter)) && paramAst.DefaultValue != null && String.Equals(paramAst.DefaultValue.Extent.Text, "$true", StringComparison.OrdinalIgnoreCase)) { - yield return new DiagnosticRecord( - String.Format(CultureInfo.CurrentCulture, Strings.AvoidDefaultValueSwitchParameterError, System.IO.Path.GetFileName(fileName)), - paramAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName); + if (String.IsNullOrWhiteSpace(fileName)) + { + yield return new DiagnosticRecord( + String.Format(CultureInfo.CurrentCulture, Strings.AvoidDefaultValueSwitchParameterErrorScriptDefinition), + paramAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName); + } + else + { + yield return new DiagnosticRecord( + String.Format(CultureInfo.CurrentCulture, Strings.AvoidDefaultValueSwitchParameterError, System.IO.Path.GetFileName(fileName)), + paramAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName); + } } } } diff --git a/Rules/AvoidShouldContinueWithoutForce.cs b/Rules/AvoidShouldContinueWithoutForce.cs index 0222200e9..42996c990 100644 --- a/Rules/AvoidShouldContinueWithoutForce.cs +++ b/Rules/AvoidShouldContinueWithoutForce.cs @@ -68,9 +68,18 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) if (String.Equals(typeAst.VariablePath.UserPath, "pscmdlet", StringComparison.OrdinalIgnoreCase) && (String.Equals(imeAst.Member.Extent.Text, "shouldcontinue", StringComparison.OrdinalIgnoreCase))) { - yield return new DiagnosticRecord( - String.Format(CultureInfo.CurrentCulture, Strings.AvoidShouldContinueWithoutForceError, funcAst.Name, System.IO.Path.GetFileName(fileName)), - imeAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName); + if (String.IsNullOrWhiteSpace(fileName)) + { + yield return new DiagnosticRecord( + String.Format(CultureInfo.CurrentCulture, Strings.AvoidShouldContinueWithoutForceErrorScriptDefinition, funcAst.Name), + imeAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName); + } + else + { + yield return new DiagnosticRecord( + String.Format(CultureInfo.CurrentCulture, Strings.AvoidShouldContinueWithoutForceError, funcAst.Name, + System.IO.Path.GetFileName(fileName)), imeAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName); + } } } } diff --git a/Rules/AvoidUsingConvertToSecureStringWithPlainText.cs b/Rules/AvoidUsingConvertToSecureStringWithPlainText.cs index d9105235a..0105680c8 100644 --- a/Rules/AvoidUsingConvertToSecureStringWithPlainText.cs +++ b/Rules/AvoidUsingConvertToSecureStringWithPlainText.cs @@ -60,9 +60,16 @@ public override bool ParameterCondition(CommandAst CmdAst, CommandElementAst CeA /// /// /// - public override string GetError(string FileName, CommandAst CmdAst) + public override string GetError(string fileName, CommandAst cmdAst) { - return String.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingConvertToSecureStringWithPlainTextError, System.IO.Path.GetFileName(FileName)); + if (String.IsNullOrWhiteSpace(fileName)) + { + return String.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingConvertToSecureStringWithPlainTextErrorScriptDefinition); + } + else + { + return String.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingConvertToSecureStringWithPlainTextError, System.IO.Path.GetFileName(fileName)); + } } /// diff --git a/Rules/AvoidUsingWMICmdlet.cs b/Rules/AvoidUsingWMICmdlet.cs index d5104a298..4ac3b4bea 100644 --- a/Rules/AvoidUsingWMICmdlet.cs +++ b/Rules/AvoidUsingWMICmdlet.cs @@ -50,8 +50,16 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) || String.Equals(cmdAst.GetCommandName(), "set-wmiinstance", StringComparison.OrdinalIgnoreCase)) ) { - yield return new DiagnosticRecord(String.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingWMICmdletError, System.IO.Path.GetFileName(fileName)), - cmdAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName); + if (String.IsNullOrWhiteSpace(fileName)) + { + yield return new DiagnosticRecord(String.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingWMICmdletErrorScriptDefinition), + cmdAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName); + } + else + { + yield return new DiagnosticRecord(String.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingWMICmdletError, + System.IO.Path.GetFileName(fileName)), cmdAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName); + } } } } diff --git a/Rules/AvoidUsingWriteHost.cs b/Rules/AvoidUsingWriteHost.cs index e2339cb75..bd5cd3a6a 100644 --- a/Rules/AvoidUsingWriteHost.cs +++ b/Rules/AvoidUsingWriteHost.cs @@ -78,8 +78,16 @@ public override AstVisitAction VisitCommand(CommandAst cmdAst) if (cmdAst.GetCommandName() != null && String.Equals(cmdAst.GetCommandName(), "write-host", StringComparison.OrdinalIgnoreCase)) { - records.Add(new DiagnosticRecord(String.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingWriteHostError, System.IO.Path.GetFileName(fileName)), - cmdAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName)); + if (String.IsNullOrWhiteSpace(fileName)) + { + records.Add(new DiagnosticRecord(String.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingWriteHostErrorScriptDefinition), + cmdAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName)); + } + else + { + records.Add(new DiagnosticRecord(String.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingWriteHostError, + System.IO.Path.GetFileName(fileName)), cmdAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName)); + } } return AstVisitAction.Continue; @@ -102,7 +110,8 @@ public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressio if (typeAst.TypeName.FullName.EndsWith("console", StringComparison.OrdinalIgnoreCase) && !String.IsNullOrWhiteSpace(imeAst.Member.Extent.Text) && imeAst.Member.Extent.Text.StartsWith("Write", StringComparison.OrdinalIgnoreCase)) { - records.Add(new DiagnosticRecord(String.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingConsoleWriteError, System.IO.Path.GetFileName(fileName), imeAst.Member.Extent.Text), + records.Add(new DiagnosticRecord(String.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingConsoleWriteError, + String.IsNullOrWhiteSpace(fileName) ? Strings.ScriptDefinitionName : System.IO.Path.GetFileName(fileName), imeAst.Member.Extent.Text), imeAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName)); } diff --git a/Rules/DscExamplesPresent.cs b/Rules/DscExamplesPresent.cs index e0f9b53a1..a659c9cb6 100644 --- a/Rules/DscExamplesPresent.cs +++ b/Rules/DscExamplesPresent.cs @@ -39,6 +39,12 @@ public class DscExamplesPresent : IDSCResourceRule /// The results of the analysis public IEnumerable AnalyzeDSCResource(Ast ast, string fileName) { + // we are given a script definition, do not analyze + if (String.IsNullOrWhiteSpace(fileName)) + { + yield break; + } + String fileNameOnly = Path.GetFileName(fileName); String resourceName = Path.GetFileNameWithoutExtension(fileNameOnly); String examplesQuery = String.Format("*{0}*", resourceName); @@ -72,6 +78,12 @@ public IEnumerable AnalyzeDSCResource(Ast ast, string fileName /// public IEnumerable AnalyzeDSCClass(Ast ast, string fileName) { + // we are given a script definition, do not analyze + if (String.IsNullOrWhiteSpace(fileName)) + { + yield break; + } + String resourceName = null; IEnumerable dscClasses = ast.FindAll(item => diff --git a/Rules/DscTestsPresent.cs b/Rules/DscTestsPresent.cs index 9a25ece36..7b355cb3b 100644 --- a/Rules/DscTestsPresent.cs +++ b/Rules/DscTestsPresent.cs @@ -39,6 +39,12 @@ public class DscTestsPresent : IDSCResourceRule /// The results of the analysis public IEnumerable AnalyzeDSCResource(Ast ast, string fileName) { + // we are given a script definition, do not analyze + if (String.IsNullOrWhiteSpace(fileName)) + { + yield break; + } + String fileNameOnly = Path.GetFileName(fileName); String resourceName = Path.GetFileNameWithoutExtension(fileNameOnly); String testsQuery = String.Format("*{0}*", resourceName); @@ -72,6 +78,12 @@ public IEnumerable AnalyzeDSCResource(Ast ast, string fileName /// public IEnumerable AnalyzeDSCClass(Ast ast, string fileName) { + // we are given a script definition, do not analyze + if (String.IsNullOrWhiteSpace(fileName)) + { + yield break; + } + String resourceName = null; IEnumerable dscClasses = ast.FindAll(item => diff --git a/Rules/ProvideDefaultParameterValue.cs b/Rules/ProvideDefaultParameterValue.cs index 3d5c24023..c67c14133 100644 --- a/Rules/ProvideDefaultParameterValue.cs +++ b/Rules/ProvideDefaultParameterValue.cs @@ -37,7 +37,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) IEnumerable functionAsts = ast.FindAll(testAst => testAst is FunctionDefinitionAst, true); // Checks whether this is a dsc resource file (we don't raise this rule for get, set and test-target resource - bool isDscResourceFile = Helper.Instance.IsDscResourceModule(fileName); + bool isDscResourceFile = !String.IsNullOrWhiteSpace(fileName) && Helper.Instance.IsDscResourceModule(fileName); List targetResourcesFunctions = new List(new string[] { "get-targetresource", "set-targetresource", "test-targetresource" }); diff --git a/Rules/ScriptAnalyzerBuiltinRules.csproj b/Rules/ScriptAnalyzerBuiltinRules.csproj index 9328f21d1..6f8058d4b 100644 --- a/Rules/ScriptAnalyzerBuiltinRules.csproj +++ b/Rules/ScriptAnalyzerBuiltinRules.csproj @@ -70,17 +70,17 @@ + + True + True + Strings.resx + - - True - True - Strings.resx - @@ -94,6 +94,12 @@ + + + {f4bde3d0-3eef-4157-8a3e-722df7adef60} + ScriptAnalyzerEngine + + Designer @@ -101,12 +107,6 @@ Strings.Designer.cs - - - {f4bde3d0-3eef-4157-8a3e-722df7adef60} - ScriptAnalyzerEngine - - diff --git a/Rules/Strings.Designer.cs b/Rules/Strings.Designer.cs index 99e681f14..9128ebedd 100644 --- a/Rules/Strings.Designer.cs +++ b/Rules/Strings.Designer.cs @@ -123,6 +123,15 @@ internal static string AvoidDefaultValueSwitchParameterError { } } + /// + /// Looks up a localized string similar to Script definition has a switch parameter default to true.. + /// + internal static string AvoidDefaultValueSwitchParameterErrorScriptDefinition { + get { + return ResourceManager.GetString("AvoidDefaultValueSwitchParameterErrorScriptDefinition", resourceCulture); + } + } + /// /// Looks up a localized string similar to AvoidDefaultValueSwitchParameter. /// @@ -240,6 +249,15 @@ internal static string AvoidShouldContinueWithoutForceError { } } + /// + /// Looks up a localized string similar to Function '{0}' in script definition uses ShouldContinue but does not have a boolean force parameter. The force parameter will allow users of the script to bypass ShouldContinue prompt. + /// + internal static string AvoidShouldContinueWithoutForceErrorScriptDefinition { + get { + return ResourceManager.GetString("AvoidShouldContinueWithoutForceErrorScriptDefinition", resourceCulture); + } + } + /// /// Looks up a localized string similar to AvoidShouldContinueWithoutForce. /// @@ -501,6 +519,15 @@ internal static string AvoidUsingConvertToSecureStringWithPlainTextError { } } + /// + /// Looks up a localized string similar to Script definition uses ConvertTo-SecureString with plaintext. This will expose secure information. Encrypted standard strings should be used instead.. + /// + internal static string AvoidUsingConvertToSecureStringWithPlainTextErrorScriptDefinition { + get { + return ResourceManager.GetString("AvoidUsingConvertToSecureStringWithPlainTextErrorScriptDefinition", resourceCulture); + } + } + /// /// Looks up a localized string similar to AvoidUsingConvertToSecureStringWithPlainText. /// @@ -771,6 +798,15 @@ internal static string AvoidUsingWMICmdletError { } } + /// + /// Looks up a localized string similar to Script definition uses WMI cmdlet. For PowerShell 3.0 and above, use CIM cmdlet which perform the same tasks as the WMI cmdlets. The CIM cmdlets comply with WS-Management (WSMan) standards and with the Common Information Model (CIM) standard, which enables the cmdlets to use the same techniques to manage Windows computers and those running other operating systems.. + /// + internal static string AvoidUsingWMICmdletErrorScriptDefinition { + get { + return ResourceManager.GetString("AvoidUsingWMICmdletErrorScriptDefinition", resourceCulture); + } + } + /// /// Looks up a localized string similar to AvoidUsingWMICmdlet. /// @@ -807,6 +843,15 @@ internal static string AvoidUsingWriteHostError { } } + /// + /// Looks up a localized string similar to Script definition uses Write-Host. Avoid using Write-Host because it might not work in all hosts, does not work when there is no host, and (prior to PS 5.0) cannot be suppressed, captured, or redirected. Instead, use Write-Output, Write-Verbose, or Write-Information.. + /// + internal static string AvoidUsingWriteHostErrorScriptDefinition { + get { + return ResourceManager.GetString("AvoidUsingWriteHostErrorScriptDefinition", resourceCulture); + } + } + /// /// Looks up a localized string similar to AvoidUsingWriteHost. /// @@ -1338,6 +1383,15 @@ internal static string ReturnCorrectTypesForSetTargetResourceFunctionsDSCError { } } + /// + /// Looks up a localized string similar to ScriptDefinition. + /// + internal static string ScriptDefinitionName { + get { + return ResourceManager.GetString("ScriptDefinitionName", resourceCulture); + } + } + /// /// Looks up a localized string similar to www.sharepoint.com. /// diff --git a/Rules/Strings.resx b/Rules/Strings.resx index 4657658df..2679b9b34 100644 --- a/Rules/Strings.resx +++ b/Rules/Strings.resx @@ -756,4 +756,22 @@ UseBOMForUnicodeEncodedFile + + Script definition has a switch parameter default to true. + + + Function '{0}' in script definition uses ShouldContinue but does not have a boolean force parameter. The force parameter will allow users of the script to bypass ShouldContinue prompt + + + Script definition uses ConvertTo-SecureString with plaintext. This will expose secure information. Encrypted standard strings should be used instead. + + + Script definition uses WMI cmdlet. For PowerShell 3.0 and above, use CIM cmdlet which perform the same tasks as the WMI cmdlets. The CIM cmdlets comply with WS-Management (WSMan) standards and with the Common Information Model (CIM) standard, which enables the cmdlets to use the same techniques to manage Windows computers and those running other operating systems. + + + Script definition uses Write-Host. Avoid using Write-Host because it might not work in all hosts, does not work when there is no host, and (prior to PS 5.0) cannot be suppressed, captured, or redirected. Instead, use Write-Output, Write-Verbose, or Write-Information. + + + ScriptDefinition + \ No newline at end of file diff --git a/Rules/UseBOMForUnicodeEncodedFile.cs b/Rules/UseBOMForUnicodeEncodedFile.cs index 7ea1d79eb..eeeddf90c 100644 --- a/Rules/UseBOMForUnicodeEncodedFile.cs +++ b/Rules/UseBOMForUnicodeEncodedFile.cs @@ -33,6 +33,12 @@ public class UseBOMForUnicodeEncodedFile : IScriptRule /// public IEnumerable AnalyzeScript(Ast ast, string fileName) { + // we are given a script definition, do not analyze + if (String.IsNullOrWhiteSpace(fileName)) + { + yield break; + } + byte[] byteStream = File.ReadAllBytes(fileName); if (null == GetByteStreamEncoding(byteStream)) diff --git a/Rules/UseUTF8EncodingForHelpFile.cs b/Rules/UseUTF8EncodingForHelpFile.cs index 7f301abc8..05989d546 100644 --- a/Rules/UseUTF8EncodingForHelpFile.cs +++ b/Rules/UseUTF8EncodingForHelpFile.cs @@ -30,6 +30,13 @@ public class UseUTF8EncodingForHelpFile : IScriptRule /// public IEnumerable AnalyzeScript(Ast ast, string fileName) { + // we are given a script definition, do not analyze + // this rule is not applicable for that + if (String.IsNullOrWhiteSpace(fileName)) + { + yield break; + } + if (!String.IsNullOrWhiteSpace(fileName) && Helper.Instance.IsHelpFile(fileName)) { using (var reader = new System.IO.StreamReader(fileName, true)) diff --git a/Tests/Engine/GlobalSuppression.test.ps1 b/Tests/Engine/GlobalSuppression.test.ps1 index 11b030756..e5aeb87cd 100644 --- a/Tests/Engine/GlobalSuppression.test.ps1 +++ b/Tests/Engine/GlobalSuppression.test.ps1 @@ -7,19 +7,25 @@ if (!(Get-Module PSScriptAnalyzer) -and !$testingLibraryUsage) } $directory = Split-Path -Parent $MyInvocation.MyCommand.Path -$violations = Invoke-ScriptAnalyzer $directory\GlobalSuppression.ps1 -$suppression = Invoke-ScriptAnalyzer $directory\GlobalSuppression.ps1 -Profile $directory\Profile.ps1 +$violations = Invoke-ScriptAnalyzer "$directory\GlobalSuppression.ps1" +$violationsUsingScriptDefinition = Invoke-ScriptAnalyzer -ScriptDefinition (Get-Content -Raw "$directory\GlobalSuppression.ps1") +$suppression = Invoke-ScriptAnalyzer "$directory\GlobalSuppression.ps1" -Profile "$directory\Profile.ps1" +$suppressionUsingScriptDefinition = Invoke-ScriptAnalyzer -ScriptDefinition (Get-Content -Raw "$directory\GlobalSuppression.ps1") -Profile "$directory\Profile.ps1" 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" } - $withoutProfile.Count | Should Be 2 + $withoutProfile.Count | Should Be 1 + $withoutProfile = $violationsUsingScriptDefinition | Where-Object { $_.RuleName -eq "PSAvoidUsingCmdletAliases" -or $_.RuleName -eq "PSAvoidUninitializedVariable" } + $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" } $withProfile.Count | Should be 0 + $withProfile = $suppressionUsingScriptDefinition | Where-Object { $_.RuleName -eq "PSAvoidUsingCmdletAliases" -or $_.RuleName -eq "PSAvoidUninitializedVariable" } + $withProfile.Count | Should be 0 } } @@ -27,11 +33,15 @@ Describe "GlobalSuppression" { It "Raises 1 violation for computername hard-coded" { $withoutProfile = $violations | Where-Object { $_.RuleName -eq "PSAvoidUsingComputerNameHardcoded" } $withoutProfile.Count | Should Be 1 + $withoutProfile = $violationsUsingScriptDefinition | Where-Object { $_.RuleName -eq "PSAvoidUsingComputerNameHardcoded" } + $withoutProfile.Count | Should Be 1 } It "Does not raise any violations for computername hard-coded" { $withProfile = $suppression | Where-Object { $_.RuleName -eq "PSAvoidUsingComputerNameHardcoded" } $withProfile.Count | Should be 0 + $withProfile = $suppressionUsingScriptDefinition | Where-Object { $_.RuleName -eq "PSAvoidUsingComputerNameHardcoded" } + $withProfile.Count | Should be 0 } } @@ -39,11 +49,15 @@ Describe "GlobalSuppression" { It "Raises 1 violation for internal url without profile" { $withoutProfile = $violations | Where-Object { $_.RuleName -eq "PSAvoidUsingInternalURLs" } $withoutProfile.Count | Should Be 1 + $withoutProfile = $violationsUsingScriptDefinition | Where-Object { $_.RuleName -eq "PSAvoidUsingInternalURLs" } + $withoutProfile.Count | Should Be 1 } It "Does not raise any violations for internal urls with profile" { $withProfile = $suppression | Where-Object { $_.RuleName -eq "PSAvoidUsingInternalURLs" } $withProfile.Count | Should be 0 + $withProfile = $suppressionUsingScriptDefinition | Where-Object { $_.RuleName -eq "PSAvoidUsingInternalURLs" } + $withProfile.Count | Should be 0 } } } \ No newline at end of file diff --git a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 index 467a71723..c85152b70 100644 --- a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 +++ b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 @@ -25,6 +25,16 @@ Describe "Test available parameters" { } } + Context "Path parameter" { + It "has a ScriptDefinition parameter" { + $params.ContainsKey("ScriptDefinition") | Should Be $true + } + + It "accepts string" { + $params["ScriptDefinition"].ParameterType.FullName | Should Be "System.String" + } + } + Context "CustomizedRulePath parameters" { It "has a CustomizedRulePath parameter" { $params.ContainsKey("CustomizedRulePath") | Should Be $true @@ -54,6 +64,46 @@ Describe "Test available parameters" { $params["Severity"].ParameterType.FullName | Should Be "System.String[]" } } + + Context "It has 2 parameter sets: File and ScriptDefinition" { + It "Has 2 parameter sets" { + $sa.ParameterSets.Count | Should Be 2 + } + + It "Has File parameter set" { + $hasFile = $false + foreach ($paramSet in $sa.ParameterSets) { + if ($paramSet.Name -eq "File") { + $hasFile = $true + break + } + } + + $hasFile | Should Be $true + } + + It "Has ScriptDefinition parameter set" { + $hasFile = $false + foreach ($paramSet in $sa.ParameterSets) { + if ($paramSet.Name -eq "ScriptDefinition") { + $hasFile = $true + break + } + } + + $hasFile | Should Be $true + } + + } +} + +Describe "Test ScriptDefinition" { + Context "When given a script definition" { + It "Does not run rules on script with more than 10 parser errors" { + $moreThanTenErrors = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue -ScriptDefinition (Get-Content -Raw "$directory\CSharp.ps1") + $moreThanTenErrors.Count | Should Be 0 + } + } } Describe "Test Path" { diff --git a/Tests/Engine/RuleSuppression.tests.ps1 b/Tests/Engine/RuleSuppression.tests.ps1 index 7902648d7..49b67cebe 100644 --- a/Tests/Engine/RuleSuppression.tests.ps1 +++ b/Tests/Engine/RuleSuppression.tests.ps1 @@ -7,12 +7,14 @@ if (!(Get-Module PSScriptAnalyzer) -and !$testingLibraryUsage) } $directory = Split-Path -Parent $MyInvocation.MyCommand.Path -$violations = Invoke-ScriptAnalyzer $directory\RuleSuppression.ps1 +$violationsUsingScriptDefinition = Invoke-ScriptAnalyzer -ScriptDefinition (Get-Content -Raw "$directory\RuleSuppression.ps1") +$violations = Invoke-ScriptAnalyzer "$directory\RuleSuppression.ps1" Describe "RuleSuppressionWithoutScope" { Context "Function" { It "Does not raise violations" { $suppression = $violations | Where-Object { $_.RuleName -eq "PSProvideCommentHelp" } + $suppression = $violationsUsingScriptDefinition | Where-Object { $_.RuleName -eq "PSProvideCommentHelp" } $suppression.Count | Should Be 0 } } @@ -20,6 +22,7 @@ Describe "RuleSuppressionWithoutScope" { Context "Class" { It "Does not raise violations" { $suppression = $violations | Where-Object {$_.RuleName -eq "PSAvoidUsingInvokeExpression" } + $suppression = $violationsUsingScriptDefinition | Where-Object {$_.RuleName -eq "PSAvoidUsingInvokeExpression" } $suppression.Count | Should Be 0 } } @@ -27,6 +30,7 @@ Describe "RuleSuppressionWithoutScope" { Context "FunctionInClass" { It "Does not raise violations" { $suppression = $violations | Where-Object {$_.RuleName -eq "PSAvoidUsingCmdletAliases" } + $suppression = $violationsUsingScriptDefinition | Where-Object {$_.RuleName -eq "PSAvoidUsingCmdletAliases" } $suppression.Count | Should Be 0 } } @@ -34,6 +38,7 @@ Describe "RuleSuppressionWithoutScope" { Context "Script" { It "Does not raise violations" { $suppression = $violations | Where-Object {$_.RuleName -eq "PSProvideCommentHelp" } + $suppression = $violationsUsingScriptDefinition | Where-Object {$_.RuleName -eq "PSProvideCommentHelp" } $suppression.Count | Should Be 0 } } @@ -41,6 +46,7 @@ Describe "RuleSuppressionWithoutScope" { Context "RuleSuppressionID" { It "Only suppress violations for that ID" { $suppression = $violations | Where-Object {$_.RuleName -eq "PSProvideDefaultParameterValue" } + $suppression = $violationsUsingScriptDefinition | Where-Object {$_.RuleName -eq "PSProvideDefaultParameterValue" } $suppression.Count | Should Be 1 } } @@ -50,6 +56,7 @@ Describe "RuleSuppressionWithScope" { Context "FunctionScope" { It "Does not raise violations" { $suppression = $violations | Where-Object {$_.RuleName -eq "PSAvoidUsingPositionalParameters" } + $suppression = $violationsUsingScriptDefinition | Where-Object {$_.RuleName -eq "PSAvoidUsingPositionalParameters" } $suppression.Count | Should Be 0 } } @@ -57,6 +64,7 @@ Describe "RuleSuppressionWithScope" { Context "ClassScope" { It "Does not raise violations" { $suppression = $violations | Where-Object {$_.RuleName -eq "PSAvoidUsingConvertToSecureStringWithPlainText" } + $suppression = $violationsUsingScriptDefinition | Where-Object {$_.RuleName -eq "PSAvoidUsingConvertToSecureStringWithPlainText" } $suppression.Count | Should Be 0 } } From 5701167794183a1943e7a74aa53d829c468d0762 Mon Sep 17 00:00:00 2001 From: June Blender Date: Wed, 11 Nov 2015 21:50:40 -0700 Subject: [PATCH 07/53] First draft of cmdlet help file --- ...ows.PowerShell.ScriptAnalyzer.dll-Help.xml | 675 ++++++++++++++++++ 1 file changed, 675 insertions(+) create mode 100644 Engine/Microsoft.Windows.PowerShell.ScriptAnalyzer.dll-Help.xml diff --git a/Engine/Microsoft.Windows.PowerShell.ScriptAnalyzer.dll-Help.xml b/Engine/Microsoft.Windows.PowerShell.ScriptAnalyzer.dll-Help.xml new file mode 100644 index 000000000..5c176a9f2 --- /dev/null +++ b/Engine/Microsoft.Windows.PowerShell.ScriptAnalyzer.dll-Help.xml @@ -0,0 +1,675 @@ + + + + + + + + + Get-ScriptAnalyzerRule + + Gets the script analyzer rules on the local computer. + + + + + Get + ScriptAnalyzerRule + + + + Gets the script analyzer rules on the local computer. You can select rules by Name, Severity, Source, or SourceType, or even particular words in the rule description. + +Use this cmdlet to create collections of rules to include and exclude when running the Invoke-ScriptAnalyzer cmdlet. +To get information about the rules, see the value of the Description property of each rule. + +The PSScriptAnalyzer module tests the Windows PowerShell code in a script, module, or DSC resource to determine whether, and to what extent, it fulfils best practice standards. For more information about PSScriptAnalyzer, type: Get-Help about_PSScriptAnalyzer. + +PSScriptAnalyzer is an open-source project. To contribute or file an issue, see GitHub.com\PowerShell\PSScriptAnalyzer. + + + + + Get-ScriptAnalyzerRule + + CustomizedRulePath + + Gets the Script Analyzer rules in the specified path. By default, PSScriptAnalyzer gets the rules specified in the Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules.dll file in the module. + +You can create customized rules by using a custom .NET assembly or a Windows PowerShell module, such as the Community Analyzer Rules in +https://github.com/PowerShell/PSScriptAnalyzer/blob/development/Tests/Engine/CommunityAnalyzerRules/CommunityAnalyzerRules.psm1. + + String[] + The rules in Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules.dll. + + + Name + + Gets only rules with the specified name or name pattern. By default, Get-ScriptAnalyzerRule gets all rules in the current rule path. Wildcards are supported. + + String[] + All rules + + + Severity + + Gets only rules with the specified severity values. Valid values are Information, Warning, and Error. By default, Get-ScriptAnalyzerRule gets all rules in the current rule path. + + String[] + All rules + + + + + + + CustomizedRulePath + + Gets the Script Analyzer rules in the specified path. By default, PSScriptAnalyzer gets the rules specified in the Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules.dll file in the module. + +You can create customized rules by using a custom .NET assembly or a Windows PowerShell module, such as the Community Analyzer Rules in +https://github.com/PowerShell/PSScriptAnalyzer/blob/development/Tests/Engine/CommunityAnalyzerRules/CommunityAnalyzerRules.psm1. + + String[] + + String[] + + + The rules in Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules.dll. + + + Name + + Gets only rules with the specified name or name pattern. By default, Get-ScriptAnalyzerRule gets all rules in the current rule path. Wildcards are supported. + + String[] + + String[] + + + All rules + + + Severity + + Gets only rules with the specified severity values. Valid values are Information, Warning, and Error. By default, Get-ScriptAnalyzerRule gets all rules in the current rule path. + + String[] + + String[] + + + All rules + + + + + + + You cannot pipe input to this cmdlet. + + + None + + + + + + + + + + The RuleInfo object is a custom object created especially for Script Analyzer. It is not documented in MSDN. + + + Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.RuleInfo + + + + + + + + -------------------------- EXAMPLE 1 -------------------------- + + PS C:\> + + Get-ScriptAnalyzerRule + + This command gets all script analyzer rules on the local computer. + + + + -------------------------- EXAMPLE 2 -------------------------- + + PS C:\> + + Get-ScriptAnalyzerRule -Severity Error + + This command gets only rules with the Error severity. + + + + -------------------------- EXAMPLE 3 -------------------------- + + PS C:\> + + $DSCError = Get-ScriptAnalyzerRule -Severity Error | Where SourceName -eq PSDSC + +PS C:\>$Path = "$home\Documents\WindowsPowerShell\Modules\MyDSCModule\*" + +PS C:\> Invoke-ScriptAnalyzerRule -Path $Path -IncludeRule $DSCError -Recurse + + + This example runs only the DSC rules with the Error severity on the files in the MyDSCModule module. + +Using the IncludeRule parameter of Invoke-ScriptAnalyzerRule is much more efficient than using its Severity parameter, which is applied only after using all rules to analyze all module files. + + + + -------------------------- EXAMPLE 4 -------------------------- + + PS C:\> + + $TestParameters = Get-ScriptAnalyzerRule -Severity Error, Warning -Name *Parameter* + + This command gets rules with "Parameter" in the name that generate an Error or Warning. Use this set of rules to test the parameters of your script or module. + + + + + + Online version: + http://go.microsoft.com/fwlink/?LinkId=525913 + + + + Invoke-ScriptAnalyzer + + + about_PSScriptAnalyzer + + + PSScriptAnalyzer on GitHub + https://github.com/PowerShell/PSScriptAnalyzer + + + + + + + Invoke-ScriptAnalyzer + + Evaluates a script or module based on selected best practice rules + + + + + Invoke + ScriptAnalyzer + + + + Invoke-ScriptAnalyzer evaluates a script or module based on a collection of best practice rules and returns objects that represent rule violations. In each evaluation, you can run all rules or use the IncludeRule and ExcludeRule parameters to run only selected rules. Invoke-ScriptAnalyzer includes special rules to analyze DSC resources. + +Invoke-ScriptAnalyzer evaluates only .ps1 and .psm1 files. If you specify a path with multiple file types, the .ps1 and .psm1 files are tested; all other file types are ignored. + +Invoke-ScriptAnalzyer comes with a set of built-in rules, but you can also use customized rules that you write in Windows PowerShell scripts, or compile in assemblies by using C#. Just as with the built-in rules, you can add the ExcludeRule and IncludeRule parameters to your Invoke-ScriptAnalyzer command to exclude or include custom rules. + +To analyze your script or module, begin by using the Get-ScriptAnalyzerRule cmdlet to examine and select the rules you want to include and/or exclude from the evaluation. + +You can also include a rule in the analysis, but suppress the output of that rule for selected functions or scripts. This feature should be used only when absolutely necessary. To get rules that were suppressed, run Invoke-ScriptAnalyzer with the SuppressedOnly parameter. For instructions on suppressing a rule, see the description of the SuppressedOnly parameter. + +The PSScriptAnalyzer module tests the Windows PowerShell code in a script, module, or DSC resource to determine whether, and to what extent, it fulfils best practice standards. For more information about PSScriptAnalyzer, type: Get-Help about_PSScriptAnalyzer. + +PSScriptAnalyzer is an open-source project. To contribute or file an issue, see GitHub.com\PowerShell\PSScriptAnalyzer. + + + + + Invoke-ScriptAnalyzer + + Path + + Specifies the path to the scripts or module to be analyzed. Wildcard characters are supported. + +Enter the path to a script (.ps1) or module file (.psm1) or to a directory that contains scripts or modules. If the directory contains other types of files, they are ignored. + +To analyze files that are not in the root directory of the specified path, use a wildcard character (C:\Modules\MyModule\*) or the Recurse parameter. + + String + + + + + CustomizedRulePath + + Adds the custom rules defined in the specified paths to the analysis. Enter one or more paths to Windows PowerShell modules or .NET assemblies that define rules. + +By default, Invoke-ScriptAnalyzer uses only rules defined in the Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules.dll file in the PSScriptAnalyzer module. + +If Invoke-ScriptAnalyzer cannot find rules in the CustomizedRulePath, it runs the standard rules without notice. + + String[] + The rules in Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules.dll. + + + ExcludeRule + + Omits the specified rules from the Script Analyzer test. Wildcard characters are supported. + +Enter a comma-separated list of rule names, a variable that contains rule names, or a command that gets rule names. You can also specify a list of excluded rules in a Script Analyzer profile file. You can exclude standard rules and rules in a custom rule path. + +When you exclude a rule, the rule does not run on any of the files in the path. To exclude a rule on a particular line, parameter, function, script, or class, adjust the Path parameter or suppress the rule. For information about suppressing a rule, see the examples. + +If a rule is specified in both the ExcludeRule and IncludeRule collections, the rule is excluded. + + String[] + All rules are included. + + + IncludeRule + + Runs only the specified rules in the Script Analyzer test. By default, PSScriptAnalyzer runs all rules. + +Enter a comma-separated list of rule names, a variable that contains rule names, or a command that gets rule names. Wildcard characters are supported. You can also specify rule names in a Script Analyzer profile file. + +When you use the CustomizedRulePath parameter, you can use this parameter to include standard rules and rules in the custom rule paths. + +If a rule is specified in both the ExcludeRule and IncludeRule collections, the rule is excluded. + +Also, Severity takes precedence over IncludeRule. For example, if Severity is Error, you cannot use IncludeRule to include a Warning rule. + + String[] + All rules are included. + + + Severity + + After running Script Analyzer with all rules, this parameter selects rule violations with the specified severity. + +Valid values are: Error, Warning, and Information. You can specify one ore more severity values. + +Because this parameter filters the rules only after running with all rules, it is not an efficient filter. To filter rules efficiently, use Get-ScriptAnalyzer rule to get the rules you want to run or exclude and then use the ExcludeRule or IncludeRule parameters. + +Also, Severity takes precedence over IncludeRule. For example, if Severity is Error, you cannot use IncludeRule to include a Warning rule. + + String[] + All rule violations + + + Recurse + + Runs Script Analyzer on the files in the Path directory and all subdirectories recursively. + +Recurse applies only to the Path parameter value; not to the CustomizedRulePath parameter. + + SwitchParameter + False + + + SuppressedOnly + + Returns rules that are suppressed, instead of analyzing the files in the path. + +When you used SuppressedOnly, Invoke-ScriptAnalyzer returns a SuppressedRecord object (Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.SuppressedRecord). + +To suppress a rule, use the SuppressMessageAttribute. For help, see the examples. + + SwitchParameter + False + + + Profile + + Runs Invoke-ScriptAnalyzer with the parameters and values specified in a Script Analyzer profile file. Enter the path to the profile file. + +If the path, the file, or its content is invalid, it is ignored. The parameters and values in the profile take precedence over the same parameter and values specified at the command line. + +A Script Analyzer profile file is a text file that contains a hash table with one or more of the following keys: +-- Severity +-- IncludeRules +-- ExcludeRules + +The keys and values in the profile are interpreted as if they were standard parameters and parameter values. + +To specify a single value, enclose the value in quotation marks. For example: + + @{ Severity = 'Error'} + +To specify multiple values, enclose the values in an array. For example: + + @{ Severity = 'Error', 'Warning'} + + String + Uses the parameters and values specified at the command line. + + + + + + + Path + + Specifies the path to the scripts or module to be analyzed. Wildcard characters are supported. + +Enter the path to a script (.ps1) or module file (.psm1) or to a directory that contains scripts or modules. If the directory contains other types of files, they are ignored. + +To analyze files that are not in the root directory of the specified path, use a wildcard character (C:\Modules\MyModule\*) or the Recurse parameter. + + String + + String + + + + + + + CustomizedRulePath + + Adds the custom rules defined in the specified paths to the analysis. Enter one or more paths to Windows PowerShell modules or .NET assemblies that define rules. + +By default, Invoke-ScriptAnalyzer uses only rules defined in the Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules.dll file in the PSScriptAnalyzer module. + +If Invoke-ScriptAnalyzer cannot find rules in the CustomizedRulePath, it runs the standard rules without notice. + + String[] + + String[] + + + The rules in Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules.dll. + + + ExcludeRule + + Omits the specified rules from the Script Analyzer test. Wildcard characters are supported. + +Enter a comma-separated list of rule names, a variable that contains rule names, or a command that gets rule names. You can also specify a list of excluded rules in a Script Analyzer profile file. You can exclude standard rules and rules in a custom rule path. + +When you exclude a rule, the rule does not run on any of the files in the path. To exclude a rule on a particular line, parameter, function, script, or class, adjust the Path parameter or suppress the rule. For information about suppressing a rule, see the examples. + +If a rule is specified in both the ExcludeRule and IncludeRule collections, the rule is excluded. + + String[] + + String[] + + + All rules are included. + + + IncludeRule + + Runs only the specified rules in the Script Analyzer test. By default, PSScriptAnalyzer runs all rules. + +Enter a comma-separated list of rule names, a variable that contains rule names, or a command that gets rule names. Wildcard characters are supported. You can also specify rule names in a Script Analyzer profile file. + +When you use the CustomizedRulePath parameter, you can use this parameter to include standard rules and rules in the custom rule paths. + +If a rule is specified in both the ExcludeRule and IncludeRule collections, the rule is excluded. + +Also, Severity takes precedence over IncludeRule. For example, if Severity is Error, you cannot use IncludeRule to include a Warning rule. + + String[] + + String[] + + + All rules are included. + + + Severity + + After running Script Analyzer with all rules, this parameter selects rule violations with the specified severity. + +Valid values are: Error, Warning, and Information. You can specify one ore more severity values. + +Because this parameter filters the rules only after running with all rules, it is not an efficient filter. To filter rules efficiently, use Get-ScriptAnalyzer rule to get the rules you want to run or exclude and then use the ExcludeRule or IncludeRule parameters. + +Also, Severity takes precedence over IncludeRule. For example, if Severity is Error, you cannot use IncludeRule to include a Warning rule. + + String[] + + String[] + + + All rule violations + + + Recurse + + Runs Script Analyzer on the files in the Path directory and all subdirectories recursively. + +Recurse applies only to the Path parameter value; not to the CustomizedRulePath parameter. + + SwitchParameter + + SwitchParameter + + + False + + + SuppressedOnly + + Returns rules that are suppressed, instead of analyzing the files in the path. + +When you used SuppressedOnly, Invoke-ScriptAnalyzer returns a SuppressedRecord object (Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.SuppressedRecord). + +To suppress a rule, use the SuppressMessageAttribute. For help, see the examples. + + SwitchParameter + + SwitchParameter + + + False + + + Profile + + Runs Invoke-ScriptAnalyzer with the parameters and values specified in a Script Analyzer profile file. Enter the path to the profile file. + +If the path, the file, or its content is invalid, it is ignored. The parameters and values in the profile take precedence over the same parameter and values specified at the command line. + +A Script Analyzer profile file is a text file that contains a hash table with one or more of the following keys: +-- Severity +-- IncludeRules +-- ExcludeRules + +The keys and values in the profile are interpreted as if they were standard parameters and parameter values. + +To specify a single value, enclose the value in quotation marks. For example: + + @{ Severity = 'Error'} + +To specify multiple values, enclose the values in an array. For example: + + @{ Severity = 'Error', 'Warning'} + + String + + String + + + Uses the parameters and values specified at the command line. + + + + + + + You cannot pipe input to this cmdlet. + + + None + + + + + + + + + + + By default, Invoke-ScriptAnalyzer returns one DiagnosticRecord object to report a rule violation. If you use the SuppressedOnly parameter, Invoke-ScriptAnalyzer instead returns a SuppressedRecord object. + + + Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord, Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.SuppressedRecord + + + + + + + + -------------------------- EXAMPLE 1 -------------------------- + + PS C:\> + + Invoke-ScriptAnalyzer -Path $pshome\Modules\PSDiagnostics\PSDiagnostics.psm1 + + This command runs all Script Analyzer rules on the .psm1 file of the PSDiagnostics module. + + + + -------------------------- EXAMPLE 2 -------------------------- + + PS C:\> + + Invoke-ScriptAnalyzer -Path C:\Scripts -Recurse + + This command runs all Script Analyzer rules on all .ps1 and .psm1 files in the C:\Scripts directory and its subdirectories. + + + + -------------------------- EXAMPLE 3 -------------------------- + + PS C:\> + + Invoke-ScriptAnalyzer -Path C:\Windows\System32\WindowsPowerShell\v1.0\Modules\PSDiagnostics\PSDiagnostics.psm1 -IncludeRule PSAvoidUsingPositionalParameters + + This command runs only the PSAvoidUsingPositionalParameters rule. You might use a command like this to find all instances of a particular rule violation while working to eliminate it. + + + + -------------------------- EXAMPLE 4 -------------------------- + + PS C:\> + + Invoke-ScriptAnalyzer -Path C:\ps-test\MyModule -Recurse -ExcludeRule PSAvoidUsingCmdletAliases, PSAvoidUsingInternalURLs + + + This command runs Script Analyzer on the .ps1 and .psm1 files in the MyModules directory (and its subdirectories) with all rules except for PSAvoidUsingCmdletAliases and PSAvoidUsingInternalURLs. + + + + -------------------------- EXAMPLE 5 -------------------------- + + PS C:\> + + Invoke-ScriptAnalyzer -Path D:\test_scripts\Test-Script.ps1 -CustomizedRulePath C:\CommunityAnalyzerRules + + This command runs Script Analyzer with the standard rules and rules in the C:\CommunityAnalyzerRules path. + + + + -------------------------- EXAMPLE 6 -------------------------- + + PS C:\> + + $DSCError = Get-ScriptAnalyzerRule -Severity Error | Where SourceName -eq PSDSC + +PS C:\>$Path = "$home\Documents\WindowsPowerShell\Modules\MyDSCModule" + +PS C:\> Invoke-ScriptAnalyzerRule -Path $Path -IncludeRule $DSCError -Recurse + + This example runs only the rules that are Error severity and have the PSDSC source name. + + + + -------------------------- EXAMPLE 7 -------------------------- + + PS C:\> + + function Get-Widgets +{ + [CmdletBinding()] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns")] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingCmdletAliases", Justification="Resolution in progress.")] + Param() + + dir $pshome + ... +} + +PS C:\> Invoke-ScriptAnalyzer -Path .\Get-Widgets.ps1 + +RuleName Severity FileName Line Message +-------- -------- -------- ---- ------- +PSProvideCommentHelp Information ManageProf 14 The cmdlet 'Get-Widget' does not have a help comment. + iles.psm1 + +PS C:\> Invoke-ScriptAnalyzer -Path .\Get-Widgets.ps1 -SuppressedOnly + +Rule Name Severity File Name Line Justification +--------- -------- --------- ---- ------------- +PSAvoidUsingCmdletAliases Warning ManageProf 21 Resolution in progress. + iles.psm1 +PSUseSingularNouns Warning ManageProf 14 + iles.psm1 + + This example shows how to suppress the reporting of rule violations in a function and how to discover rule violations that are suppressed. + +The example uses the SuppressMessageAttribute attribute to suppress the PSUseSingularNouns and PSAvoidUsingCmdletAliases rules for the Get-Widgets function in the Get-Widgets.ps1 script. You can use this attribute to suppress a rule for a module, script, class, function, parameter, or line. + +The first command runs Script Analyzer on the script that contains the Get-Widgets function. The output reports a rule violation, but neither of the suppressed rules is listed, even though they are violated. + +The second command uses the SuppressedOnly parameter to discover the rules that are supressed in the Get-Widgets.ps1 file. The output reports the suppressed rules. + + + + -------------------------- EXAMPLE 8 -------------------------- + + PS C:\> + + # In .\ScriptAnalyzerProfile.txt +@{ + Severity = @('Error', 'Warning') + IncludeRules = 'PSAvoid*' + ExcludeRules = '*WriteHost' +} + +PS C:\> Invoke-ScriptAnalyzer -Path $pshome\Modules\BitLocker -Profile .\ScriptAnalyzerProfile.txt + + In this example, we create a Script Analyzer profile and save it in the ScriptAnalyzerProfile.txt file in the local directory. + +Next, we run Invoke-ScriptAnalyzer on the BitLocker module files. The value of the Profile parameter is the path to the Script Analyzer profile. + +If you include a conflicting parameter in the Invoke-ScriptAnalyzer command, such as '-Severity Error', Invoke-ScriptAnalyzer uses the profile value and ignores the parameter. + + + + + + + Online version: + http://go.microsoft.com/fwlink/?LinkId=525914 + + + Get-ScriptAnalyzerRule + + + about_PSScriptAnalyzer + + + PSScriptAnalyzer on GitHub + https://github.com/PowerShell/PSScriptAnalyzer + + + + + \ No newline at end of file From 006d9578000c8ae9094a6b495b5899bf47d7e28e Mon Sep 17 00:00:00 2001 From: Quoc Truong Date: Thu, 12 Nov 2015 13:31:00 -0800 Subject: [PATCH 08/53] Fix test failures --- Tests/Engine/LibraryUsage.tests.ps1 | 14 ++++++++++++-- Tests/Engine/RuleSuppression.tests.ps1 | 7 +++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/Tests/Engine/LibraryUsage.tests.ps1 b/Tests/Engine/LibraryUsage.tests.ps1 index 37137c832..5e3c9694b 100644 --- a/Tests/Engine/LibraryUsage.tests.ps1 +++ b/Tests/Engine/LibraryUsage.tests.ps1 @@ -6,10 +6,15 @@ $directory = Split-Path -Parent $MyInvocation.MyCommand.Path # wraps the usage of ScriptAnalyzer as a .NET library function Invoke-ScriptAnalyzer { param ( - [parameter(Mandatory = $true, Position = 0)] + [CmdletBinding(DefaultParameterSetName="File")] + + [parameter(Mandatory = $true, Position = 0, ParameterSetName="File")] [Alias("PSPath")] [string] $Path, + [parameter(Mandatory = $true, ParameterSetName="ScriptDefinition")] + [string] $ScriptDefinition, + [Parameter(Mandatory = $false)] [string[]] $CustomizedRulePath = $null, @@ -41,7 +46,12 @@ function Invoke-ScriptAnalyzer { $SuppressedOnly.IsPresent ); - return $scriptAnalyzer.AnalyzePath($Path, $Recurse.IsPresent); + if ($PSCmdlet.ParameterSetName -eq "File") { + return $scriptAnalyzer.AnalyzePath($Path, $Recurse.IsPresent); + } + else { + return $scriptAnalyzer.AnalyzeScriptDefinition($ScriptDefinition); + } } # Define an implementation of the IOutputWriter interface diff --git a/Tests/Engine/RuleSuppression.tests.ps1 b/Tests/Engine/RuleSuppression.tests.ps1 index 49b67cebe..35a4f15c7 100644 --- a/Tests/Engine/RuleSuppression.tests.ps1 +++ b/Tests/Engine/RuleSuppression.tests.ps1 @@ -14,6 +14,7 @@ Describe "RuleSuppressionWithoutScope" { Context "Function" { It "Does not raise violations" { $suppression = $violations | Where-Object { $_.RuleName -eq "PSProvideCommentHelp" } + $suppression.Count | Should Be 0 $suppression = $violationsUsingScriptDefinition | Where-Object { $_.RuleName -eq "PSProvideCommentHelp" } $suppression.Count | Should Be 0 } @@ -22,6 +23,7 @@ Describe "RuleSuppressionWithoutScope" { Context "Class" { It "Does not raise violations" { $suppression = $violations | Where-Object {$_.RuleName -eq "PSAvoidUsingInvokeExpression" } + $suppression.Count | Should Be 0 $suppression = $violationsUsingScriptDefinition | Where-Object {$_.RuleName -eq "PSAvoidUsingInvokeExpression" } $suppression.Count | Should Be 0 } @@ -30,6 +32,7 @@ Describe "RuleSuppressionWithoutScope" { Context "FunctionInClass" { It "Does not raise violations" { $suppression = $violations | Where-Object {$_.RuleName -eq "PSAvoidUsingCmdletAliases" } + $suppression.Count | Should Be 0 $suppression = $violationsUsingScriptDefinition | Where-Object {$_.RuleName -eq "PSAvoidUsingCmdletAliases" } $suppression.Count | Should Be 0 } @@ -38,6 +41,7 @@ Describe "RuleSuppressionWithoutScope" { Context "Script" { It "Does not raise violations" { $suppression = $violations | Where-Object {$_.RuleName -eq "PSProvideCommentHelp" } + $suppression.Count | Should Be 0 $suppression = $violationsUsingScriptDefinition | Where-Object {$_.RuleName -eq "PSProvideCommentHelp" } $suppression.Count | Should Be 0 } @@ -46,6 +50,7 @@ Describe "RuleSuppressionWithoutScope" { Context "RuleSuppressionID" { It "Only suppress violations for that ID" { $suppression = $violations | Where-Object {$_.RuleName -eq "PSProvideDefaultParameterValue" } + $suppression.Count | Should Be 1 $suppression = $violationsUsingScriptDefinition | Where-Object {$_.RuleName -eq "PSProvideDefaultParameterValue" } $suppression.Count | Should Be 1 } @@ -56,6 +61,7 @@ Describe "RuleSuppressionWithScope" { Context "FunctionScope" { It "Does not raise violations" { $suppression = $violations | Where-Object {$_.RuleName -eq "PSAvoidUsingPositionalParameters" } + $suppression.Count | Should Be 0 $suppression = $violationsUsingScriptDefinition | Where-Object {$_.RuleName -eq "PSAvoidUsingPositionalParameters" } $suppression.Count | Should Be 0 } @@ -64,6 +70,7 @@ Describe "RuleSuppressionWithScope" { Context "ClassScope" { It "Does not raise violations" { $suppression = $violations | Where-Object {$_.RuleName -eq "PSAvoidUsingConvertToSecureStringWithPlainText" } + $suppression.Count | Should Be 0 $suppression = $violationsUsingScriptDefinition | Where-Object {$_.RuleName -eq "PSAvoidUsingConvertToSecureStringWithPlainText" } $suppression.Count | Should Be 0 } From c48f74ba077877c190b5498672d72af89f598a02 Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Wed, 18 Nov 2015 11:54:34 -0800 Subject: [PATCH 09/53] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 265890fc7..d2de4d679 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Announcements ##### ScriptAnalyzer community meeting schedule: - - Next Meeting - 12/08/2015 - 11am to 12pm PDT + - Next Meeting - Dec 8 2015 - 11am to 12pm PDT - [iCalender invite](http://1drv.ms/1VvAaxO) - [Notes and recordings from earlier meetings](https://github.com/PowerShell/PSScriptAnalyzer/wiki) From d28f8cce70f27e95dee63ac2f5bfaf50afb4d489 Mon Sep 17 00:00:00 2001 From: kborle Date: Tue, 17 Nov 2015 16:26:55 -0800 Subject: [PATCH 10/53] Renames CustomizedRulePath parameter and modifies its behavior. * Renames CustomizedRulePath parameter to CustomRulePath. * Adds CustromizedRulePath as an alias of CustomRulePath. * Adds RecurseCustomRulePath switch to recursively retrieve custom rules. * Fixes a bug: CustomRulePath can now accept paths with a trailing backslash. * CustomRulePath parameter now takes only one string, instead of an array of strings. --- .../Commands/GetScriptAnalyzerRuleCommand.cs | 25 +++++-- .../Commands/InvokeScriptAnalyzerCommand.cs | 26 +++++-- Engine/Helper.cs | 37 ++++++++++ Engine/ScriptAnalyzer.cs | 10 +-- Tests/Engine/CustomizedRule.tests.ps1 | 73 ++++++++++++++++++- Tests/Engine/GetScriptAnalyzerRule.tests.ps1 | 8 +- Tests/Engine/InvokeScriptAnalyzer.tests.ps1 | 16 ++-- Tests/Engine/LibraryUsage.tests.ps1 | 8 +- Tests/Engine/samplerule/samplerule1.psm1 | 47 ++++++++++++ .../samplerule/samplerule2/samplerule2.psm1 | 47 ++++++++++++ .../samplerule2/samplerule3/samplerule3.psm1 | 47 ++++++++++++ 11 files changed, 315 insertions(+), 29 deletions(-) create mode 100644 Tests/Engine/samplerule/samplerule1.psm1 create mode 100644 Tests/Engine/samplerule/samplerule2/samplerule2.psm1 create mode 100644 Tests/Engine/samplerule/samplerule2/samplerule3/samplerule3.psm1 diff --git a/Engine/Commands/GetScriptAnalyzerRuleCommand.cs b/Engine/Commands/GetScriptAnalyzerRuleCommand.cs index a914e1f59..f9c1934bc 100644 --- a/Engine/Commands/GetScriptAnalyzerRuleCommand.cs +++ b/Engine/Commands/GetScriptAnalyzerRuleCommand.cs @@ -33,12 +33,25 @@ public class GetScriptAnalyzerRuleCommand : PSCmdlet, IOutputWriter [Parameter(Mandatory = false)] [ValidateNotNullOrEmpty] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public string[] CustomizedRulePath + [Alias("CustomizedRulePath")] + public string CustomRulePath { - get { return customizedRulePath; } - set { customizedRulePath = value; } + get { return customRulePath; } + set { customRulePath = value; } } - private string[] customizedRulePath; + private string customRulePath; + + /// + /// RecurseCustomRulePath: Find rules within subfolders under the path + /// + [Parameter(Mandatory = false)] + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + public SwitchParameter RecurseCustomRulePath + { + get { return recurseCustomRulePath; } + set { recurseCustomRulePath = value; } + } + private bool recurseCustomRulePath; /// /// Name: The name of a specific rule to list. @@ -76,7 +89,9 @@ public string[] Severity /// protected override void BeginProcessing() { - ScriptAnalyzer.Instance.Initialize(this, customizedRulePath); + string[] rulePaths = Helper.ProcessCustomRulePaths(customRulePath, + this.SessionState, recurseCustomRulePath); + ScriptAnalyzer.Instance.Initialize(this, rulePaths); } /// diff --git a/Engine/Commands/InvokeScriptAnalyzerCommand.cs b/Engine/Commands/InvokeScriptAnalyzerCommand.cs index ccf5d5992..afbfc46b7 100644 --- a/Engine/Commands/InvokeScriptAnalyzerCommand.cs +++ b/Engine/Commands/InvokeScriptAnalyzerCommand.cs @@ -73,12 +73,25 @@ public string ScriptDefinition [Parameter(Mandatory = false)] [ValidateNotNull] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public string[] CustomizedRulePath + [Alias("CustomizedRulePath")] + public string CustomRulePath { - get { return customizedRulePath; } - set { customizedRulePath = value; } + get { return customRulePath; } + set { customRulePath = value; } } - private string[] customizedRulePath; + private string customRulePath; + + /// + /// RecurseCustomRulePath: Find rules within subfolders under the path + /// + [Parameter(Mandatory = false)] + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + public SwitchParameter RecurseCustomRulePath + { + get { return recurseCustomRulePath; } + set { recurseCustomRulePath = value; } + } + private bool recurseCustomRulePath; /// /// ExcludeRule: Array of names of rules to be disabled. @@ -164,9 +177,12 @@ public string Profile /// protected override void BeginProcessing() { + string[] rulePaths = Helper.ProcessCustomRulePaths(customRulePath, + this.SessionState, recurseCustomRulePath); + ScriptAnalyzer.Instance.Initialize( this, - customizedRulePath, + rulePaths, this.includeRule, this.excludeRule, this.severity, diff --git a/Engine/Helper.cs b/Engine/Helper.cs index bc2fef74c..ef10cefc5 100644 --- a/Engine/Helper.cs +++ b/Engine/Helper.cs @@ -12,6 +12,7 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Management.Automation; @@ -983,9 +984,45 @@ public Tuple, List> SuppressRule(string return result; } + public static string[] ProcessCustomRulePaths(string rulePath, SessionState sessionState, bool recurse = false) + { + //if directory is given, list all the psd1 files + List outPaths = new List(); + if (rulePath == null) + { + return null; + } + try + { + Collection pathInfo = sessionState.Path.GetResolvedPSPathFromPSPath(rulePath); + foreach (PathInfo pinfo in pathInfo) + { + string path = pinfo.Path; + if (Directory.Exists(path)) + { + path = path.TrimEnd('\\'); + if (recurse) + { + outPaths.AddRange(Directory.GetDirectories(pinfo.Path, "*", SearchOption.AllDirectories)); + } + } + outPaths.Add(path); + } + return outPaths.ToArray(); + } + catch (Exception ex) + { + // need to do this as the path validation takes place later in the hierarchy. + outPaths.Add(rulePath); + return outPaths.ToArray(); + } + } + + #endregion } + internal class TupleComparer : IComparer> { public int Compare(Tuple t1, Tuple t2) diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index 054db6758..b385fe4ae 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -108,7 +108,7 @@ internal void Initialize( { throw new ArgumentNullException("cmdlet"); } - + this.Initialize( cmdlet, cmdlet.SessionState.Path, @@ -188,7 +188,7 @@ private void Initialize( if (!String.IsNullOrWhiteSpace(profile)) { try - { + { profile = path.GetResolvedPSPathFromPSPath(profile).First().Path; } catch @@ -784,7 +784,7 @@ public Dictionary> CheckRuleExtension(string[] path, PathIn // We have to identify the childPath is really a directory or just a module name. // You can also consider following two commands. // Get-ScriptAnalyzerRule -RuleExtension "ContosoAnalyzerRules" - // Get-ScriptAnalyzerRule -RuleExtension "%USERPROFILE%\WindowsPowerShell\Modules\ContosoAnalyzerRules" + // Get-ScriptAnalyzerRule -RuleExtension "%USERPROFILE%\WindowsPowerShell\Modules\ContosoAnalyzerRules" if (Path.GetDirectoryName(childPath) == string.Empty) { resolvedPath = childPath; @@ -797,14 +797,14 @@ public Dictionary> CheckRuleExtension(string[] path, PathIn using (System.Management.Automation.PowerShell posh = System.Management.Automation.PowerShell.Create()) - { + { posh.AddCommand("Get-Module").AddParameter("Name", resolvedPath).AddParameter("ListAvailable"); PSModuleInfo moduleInfo = posh.Invoke().First(); // Adds original path, otherwise path.Except(validModPaths) will fail. // It's possible that user can provide something like this: // "..\..\..\ScriptAnalyzer.UnitTest\modules\CommunityAnalyzerRules\CommunityAnalyzerRules.psd1" - if (moduleInfo.ExportedFunctions.Count > 0) validModPaths.Add(childPath); + if (moduleInfo.ExportedFunctions.Count > 0) validModPaths.Add(resolvedPath); } } catch diff --git a/Tests/Engine/CustomizedRule.tests.ps1 b/Tests/Engine/CustomizedRule.tests.ps1 index cf87ed818..2ef337c88 100644 --- a/Tests/Engine/CustomizedRule.tests.ps1 +++ b/Tests/Engine/CustomizedRule.tests.ps1 @@ -51,18 +51,83 @@ Describe "Test importing correct customized rules" { } Context "Test Get-ScriptAnalyzer with customized rules" { - It "will show the customized rule" { + It "will show the custom rule" { $customizedRulePath = Get-ScriptAnalyzerRule -CustomizedRulePath $directory\samplerule\samplerule.psm1 | Where-Object {$_.RuleName -eq $measure} $customizedRulePath.Count | Should Be 1 } - + + It "will show the custom rule when given a rule folder path" { + $customizedRulePath = Get-ScriptAnalyzerRule -CustomizedRulePath $directory\samplerule | Where-Object {$_.RuleName -eq $measure} + $customizedRulePath.Count | Should Be 1 + } + + if (!$testingLibraryUsage) + { + It "will show the custom rule when given a rule folder path with trailing backslash" { + $customizedRulePath = Get-ScriptAnalyzerRule -CustomizedRulePath $directory\samplerule\ | Where-Object {$_.RuleName -eq $measure} + $customizedRulePath.Count | Should Be 1 + } + + It "will show the custom rules when given a glob" { + $customizedRulePath = Get-ScriptAnalyzerRule -CustomizedRulePath $directory\samplerule\samplerule* | Where-Object {$_.RuleName -match $measure} + $customizedRulePath.Count | Should be 4 + } + + It "will show the custom rules when given recurse switch" { + $customizedRulePath = Get-ScriptAnalyzerRule -RecurseCustomRulePath -CustomizedRulePath $directory\samplerule | Where-Object {$_.RuleName -eq $measure} + $customizedRulePath.Count | Should be 3 + } + + it "will show the custom rules when given glob with recurse switch" { + $customizedRulePath = Get-ScriptAnalyzerRule -RecurseCustomRulePath -CustomizedRulePath $directory\samplerule\samplerule* | Where-Object {$_.RuleName -eq $measure} + $customizedRulePath.Count | Should be 5 + } + + it "will show the custom rules when given glob with recurse switch" { + $customizedRulePath = Get-ScriptAnalyzerRule -RecurseCustomRulePath -CustomizedRulePath $directory\samplerule* | Where-Object {$_.RuleName -eq $measure} + $customizedRulePath.Count | Should be 3 + } + } } Context "Test Invoke-ScriptAnalyzer with customized rules" { - It "will show the customized rule in the results" { + It "will show the custom rule in the results" { $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomizedRulePath $directory\samplerule\samplerule.psm1 | Where-Object {$_.Message -eq $message} $customizedRulePath.Count | Should Be 1 } + + It "will show the custom rule in the results when given a rule folder path" { + $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomizedRulePath $directory\samplerule | Where-Object {$_.Message -eq $message} + $customizedRulePath.Count | Should Be 1 + } + + if (!$testingLibraryUsage) + { + It "will show the custom rule in the results when given a rule folder path with trailing backslash" { + $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomizedRulePath $directory\samplerule\ | Where-Object {$_.Message -eq $message} + $customizedRulePath.Count | Should Be 1 + } + + It "will show the custom rules when given a glob" { + $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomizedRulePath $directory\samplerule\samplerule* | Where-Object {$_.Message -eq $message} + $customizedRulePath.Count | Should be 3 + } + + It "will show the custom rules when given recurse switch" { + $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -RecurseCustomRulePath -CustomizedRulePath $directory\samplerule | Where-Object {$_.Message -eq $message} + $customizedRulePath.Count | Should be 3 + } + + it "will show the custom rules when given glob with recurse switch" { + $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -RecurseCustomRulePath -CustomizedRulePath $directory\samplerule\samplerule* | Where-Object {$_.Message -eq $message} + $customizedRulePath.Count | Should be 4 + } + + it "will show the custom rules when given glob with recurse switch" { + $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -RecurseCustomRulePath -CustomizedRulePath $directory\samplerule* | Where-Object {$_.Message -eq $message} + $customizedRulePath.Count | Should be 3 + } + } } +} -} \ No newline at end of file diff --git a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 index 63895955c..32624e216 100644 --- a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 +++ b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 @@ -19,12 +19,16 @@ Describe "Test available parameters" { Context "RuleExtension parameters" { It "has a RuleExtension parameter" { - $params.ContainsKey("CustomizedRulePath") | Should Be $true + $params.ContainsKey("CustomRulePath") | Should Be $true } It "accepts string array" { - $params["CustomizedRulePath"].ParameterType.FullName | Should Be "System.String[]" + $params["CustomRulePath"].ParameterType.FullName | Should Be "System.String" } + + It "takes CustomizedRulePath parameter as an alias of CustomRulePath paramter" { + $params.CustomRulePath.Aliases.Contains("CustomizedRulePath") | Should be $true + } } } diff --git a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 index c85152b70..32db99599 100644 --- a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 +++ b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 @@ -30,19 +30,23 @@ Describe "Test available parameters" { $params.ContainsKey("ScriptDefinition") | Should Be $true } - It "accepts string" { + It "accepts string" { $params["ScriptDefinition"].ParameterType.FullName | Should Be "System.String" } } - Context "CustomizedRulePath parameters" { - It "has a CustomizedRulePath parameter" { - $params.ContainsKey("CustomizedRulePath") | Should Be $true + Context "CustomRulePath parameters" { + It "has a CustomRulePath parameter" { + $params.ContainsKey("CustomRulePath") | Should Be $true } - It "accepts string array" { - $params["CustomizedRulePath"].ParameterType.FullName | Should Be "System.String[]" + It "accepts a string" { + $params["CustomRulePath"].ParameterType.FullName | Should Be "System.String" } + + It "has a CustomizedRulePath alias"{ + $params.CustomRulePath.Aliases.Contains("CustomizedRulePath") | Should be $true + } } Context "IncludeRule parameters" { diff --git a/Tests/Engine/LibraryUsage.tests.ps1 b/Tests/Engine/LibraryUsage.tests.ps1 index 5e3c9694b..6b5664a5d 100644 --- a/Tests/Engine/LibraryUsage.tests.ps1 +++ b/Tests/Engine/LibraryUsage.tests.ps1 @@ -16,7 +16,11 @@ function Invoke-ScriptAnalyzer { [string] $ScriptDefinition, [Parameter(Mandatory = $false)] - [string[]] $CustomizedRulePath = $null, + [Alias("CustomizedRulePath")] + [string] $CustomRulePath = $null, + + [Parameter(Mandatory = $false)] + [switch] $RecurseCustomRulePath, [Parameter(Mandatory=$false)] [string[]] $ExcludeRule = $null, @@ -39,7 +43,7 @@ function Invoke-ScriptAnalyzer { $scriptAnalyzer.Initialize( $runspace, $testOutputWriter, - $CustomizedRulePath, + $CustomRulePath, $IncludeRule, $ExcludeRule, $Severity, diff --git a/Tests/Engine/samplerule/samplerule1.psm1 b/Tests/Engine/samplerule/samplerule1.psm1 new file mode 100644 index 000000000..cc94c6b1b --- /dev/null +++ b/Tests/Engine/samplerule/samplerule1.psm1 @@ -0,0 +1,47 @@ +#Requires -Version 3.0 + +<# +.SYNOPSIS + Uses #Requires -RunAsAdministrator instead of your own methods. +.DESCRIPTION + The #Requires statement prevents a script from running unless the Windows PowerShell version, modules, snap-ins, and module and snap-in version prerequisites are met. + From Windows PowerShell 4.0, the #Requires statement let script developers require that sessions be run with elevated user rights (run as Administrator). + Script developers does not need to write their own methods any more. + To fix a violation of this rule, please consider to use #Requires -RunAsAdministrator instead of your own methods. +.EXAMPLE + Measure-RequiresRunAsAdministrator -ScriptBlockAst $ScriptBlockAst +.INPUTS + [System.Management.Automation.Language.ScriptBlockAst] +.OUTPUTS + [OutputType([PSCustomObject[])] +.NOTES + None +#> +function Measure-RequiresRunAsAdministrator +{ + [CmdletBinding()] + [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])] + Param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.Language.ScriptBlockAst] + $testAst + ) + + + $results = @() + + $result = [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]@{"Message" = "this is help"; + "Extent" = $ast.Extent; + "RuleName" = $PSCmdlet.MyInvocation.InvocationName; + "Severity" = "Warning"} + + $results += $result + + + return $results + + +} +Export-ModuleMember -Function Measure* \ No newline at end of file diff --git a/Tests/Engine/samplerule/samplerule2/samplerule2.psm1 b/Tests/Engine/samplerule/samplerule2/samplerule2.psm1 new file mode 100644 index 000000000..cc94c6b1b --- /dev/null +++ b/Tests/Engine/samplerule/samplerule2/samplerule2.psm1 @@ -0,0 +1,47 @@ +#Requires -Version 3.0 + +<# +.SYNOPSIS + Uses #Requires -RunAsAdministrator instead of your own methods. +.DESCRIPTION + The #Requires statement prevents a script from running unless the Windows PowerShell version, modules, snap-ins, and module and snap-in version prerequisites are met. + From Windows PowerShell 4.0, the #Requires statement let script developers require that sessions be run with elevated user rights (run as Administrator). + Script developers does not need to write their own methods any more. + To fix a violation of this rule, please consider to use #Requires -RunAsAdministrator instead of your own methods. +.EXAMPLE + Measure-RequiresRunAsAdministrator -ScriptBlockAst $ScriptBlockAst +.INPUTS + [System.Management.Automation.Language.ScriptBlockAst] +.OUTPUTS + [OutputType([PSCustomObject[])] +.NOTES + None +#> +function Measure-RequiresRunAsAdministrator +{ + [CmdletBinding()] + [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])] + Param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.Language.ScriptBlockAst] + $testAst + ) + + + $results = @() + + $result = [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]@{"Message" = "this is help"; + "Extent" = $ast.Extent; + "RuleName" = $PSCmdlet.MyInvocation.InvocationName; + "Severity" = "Warning"} + + $results += $result + + + return $results + + +} +Export-ModuleMember -Function Measure* \ No newline at end of file diff --git a/Tests/Engine/samplerule/samplerule2/samplerule3/samplerule3.psm1 b/Tests/Engine/samplerule/samplerule2/samplerule3/samplerule3.psm1 new file mode 100644 index 000000000..cc94c6b1b --- /dev/null +++ b/Tests/Engine/samplerule/samplerule2/samplerule3/samplerule3.psm1 @@ -0,0 +1,47 @@ +#Requires -Version 3.0 + +<# +.SYNOPSIS + Uses #Requires -RunAsAdministrator instead of your own methods. +.DESCRIPTION + The #Requires statement prevents a script from running unless the Windows PowerShell version, modules, snap-ins, and module and snap-in version prerequisites are met. + From Windows PowerShell 4.0, the #Requires statement let script developers require that sessions be run with elevated user rights (run as Administrator). + Script developers does not need to write their own methods any more. + To fix a violation of this rule, please consider to use #Requires -RunAsAdministrator instead of your own methods. +.EXAMPLE + Measure-RequiresRunAsAdministrator -ScriptBlockAst $ScriptBlockAst +.INPUTS + [System.Management.Automation.Language.ScriptBlockAst] +.OUTPUTS + [OutputType([PSCustomObject[])] +.NOTES + None +#> +function Measure-RequiresRunAsAdministrator +{ + [CmdletBinding()] + [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])] + Param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.Language.ScriptBlockAst] + $testAst + ) + + + $results = @() + + $result = [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]@{"Message" = "this is help"; + "Extent" = $ast.Extent; + "RuleName" = $PSCmdlet.MyInvocation.InvocationName; + "Severity" = "Warning"} + + $results += $result + + + return $results + + +} +Export-ModuleMember -Function Measure* \ No newline at end of file From 36bd5778541eb8fcaf001063a45f6fc245fb2d76 Mon Sep 17 00:00:00 2001 From: kborle Date: Fri, 20 Nov 2015 16:24:07 -0800 Subject: [PATCH 11/53] Fixes a bug in library usage test cmdlet --- Tests/Engine/LibraryUsage.tests.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Engine/LibraryUsage.tests.ps1 b/Tests/Engine/LibraryUsage.tests.ps1 index 6b5664a5d..f93e91f11 100644 --- a/Tests/Engine/LibraryUsage.tests.ps1 +++ b/Tests/Engine/LibraryUsage.tests.ps1 @@ -38,12 +38,12 @@ function Invoke-ScriptAnalyzer { [Parameter(Mandatory = $false)] [switch] $SuppressedOnly ) - + $customRulePathArr = @($CustomRulePath); $scriptAnalyzer = New-Object "Microsoft.Windows.PowerShell.ScriptAnalyzer.ScriptAnalyzer" $scriptAnalyzer.Initialize( $runspace, $testOutputWriter, - $CustomRulePath, + $customRulePathArr, $IncludeRule, $ExcludeRule, $Severity, From 900de1118d2ab68ab39e59d4f89404ca77296f0e Mon Sep 17 00:00:00 2001 From: kborle Date: Fri, 20 Nov 2015 16:36:31 -0800 Subject: [PATCH 12/53] Modifies library usage test cmdlet. --- Tests/Engine/LibraryUsage.tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Engine/LibraryUsage.tests.ps1 b/Tests/Engine/LibraryUsage.tests.ps1 index f93e91f11..cf138b44b 100644 --- a/Tests/Engine/LibraryUsage.tests.ps1 +++ b/Tests/Engine/LibraryUsage.tests.ps1 @@ -38,7 +38,7 @@ function Invoke-ScriptAnalyzer { [Parameter(Mandatory = $false)] [switch] $SuppressedOnly ) - $customRulePathArr = @($CustomRulePath); + [string[]]$customRulePathArr = @($CustomRulePath); $scriptAnalyzer = New-Object "Microsoft.Windows.PowerShell.ScriptAnalyzer.ScriptAnalyzer" $scriptAnalyzer.Initialize( $runspace, From 25ba9bd9e6ea9c5cf656e4e45df68a6c74fe68ec Mon Sep 17 00:00:00 2001 From: kborle Date: Fri, 20 Nov 2015 20:35:26 -0800 Subject: [PATCH 13/53] Fixes customRulePath parameter in library test --- Tests/Engine/LibraryUsage.tests.ps1 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Tests/Engine/LibraryUsage.tests.ps1 b/Tests/Engine/LibraryUsage.tests.ps1 index cf138b44b..103c36d55 100644 --- a/Tests/Engine/LibraryUsage.tests.ps1 +++ b/Tests/Engine/LibraryUsage.tests.ps1 @@ -39,7 +39,12 @@ function Invoke-ScriptAnalyzer { [switch] $SuppressedOnly ) [string[]]$customRulePathArr = @($CustomRulePath); - $scriptAnalyzer = New-Object "Microsoft.Windows.PowerShell.ScriptAnalyzer.ScriptAnalyzer" + if ($CustomRulePath -eq $null) + { + $customRulePathArr = $null; + } + + $scriptAnalyzer = New-Object "Microsoft.Windows.PowerShell.ScriptAnalyzer.ScriptAnalyzer"; $scriptAnalyzer.Initialize( $runspace, $testOutputWriter, From 50ea0560aea2eb8b87a257199cb0c463476fa807 Mon Sep 17 00:00:00 2001 From: kborle Date: Fri, 20 Nov 2015 20:57:58 -0800 Subject: [PATCH 14/53] Adds profile parameter to the cmdlet in library tests --- Tests/Engine/LibraryUsage.tests.ps1 | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Tests/Engine/LibraryUsage.tests.ps1 b/Tests/Engine/LibraryUsage.tests.ps1 index 103c36d55..e7187337e 100644 --- a/Tests/Engine/LibraryUsage.tests.ps1 +++ b/Tests/Engine/LibraryUsage.tests.ps1 @@ -36,7 +36,10 @@ function Invoke-ScriptAnalyzer { [switch] $Recurse, [Parameter(Mandatory = $false)] - [switch] $SuppressedOnly + [switch] $SuppressedOnly, + + [Parameter(Mandatory = $false)] + [string] $Profile = $null ) [string[]]$customRulePathArr = @($CustomRulePath); if ($CustomRulePath -eq $null) @@ -52,7 +55,8 @@ function Invoke-ScriptAnalyzer { $IncludeRule, $ExcludeRule, $Severity, - $SuppressedOnly.IsPresent + $SuppressedOnly.IsPresent, + $Profile ); if ($PSCmdlet.ParameterSetName -eq "File") { From dbe3143dc0a0ee97d2877f499ba141117ec73291 Mon Sep 17 00:00:00 2001 From: Kapil Borle Date: Sat, 21 Nov 2015 01:13:06 -0800 Subject: [PATCH 15/53] Creates a workaround the failing tests. We have set the CustomRulePath parameter in the cmdlet implementation in LibraryUsage.tests.ps1 to "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. Even if we create a "[string[]]" type object and pass it to Initialize method, the tests fail to run as an admin with the following error message. 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) --- Tests/Engine/InvokeScriptAnalyzer.tests.ps1 | 9 ++++++++- Tests/Engine/LibraryUsage.tests.ps1 | 15 ++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 index 32db99599..ab1d5dc7b 100644 --- a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 +++ b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 @@ -41,7 +41,14 @@ Describe "Test available parameters" { } It "accepts a string" { - $params["CustomRulePath"].ParameterType.FullName | Should Be "System.String" + if ($testingLibraryUsage) + { + $params["CustomRulePath"].ParameterType.FullName | Should Be "System.String[]" + } + else + { + $params["CustomRulePath"].ParameterType.FullName | Should Be "System.String" + } } It "has a CustomizedRulePath alias"{ diff --git a/Tests/Engine/LibraryUsage.tests.ps1 b/Tests/Engine/LibraryUsage.tests.ps1 index e7187337e..49a1c6bdc 100644 --- a/Tests/Engine/LibraryUsage.tests.ps1 +++ b/Tests/Engine/LibraryUsage.tests.ps1 @@ -17,7 +17,7 @@ function Invoke-ScriptAnalyzer { [Parameter(Mandatory = $false)] [Alias("CustomizedRulePath")] - [string] $CustomRulePath = $null, + [string[]] $CustomRulePath = $null, [Parameter(Mandatory = $false)] [switch] $RecurseCustomRulePath, @@ -41,17 +41,18 @@ function Invoke-ScriptAnalyzer { [Parameter(Mandatory = $false)] [string] $Profile = $null ) - [string[]]$customRulePathArr = @($CustomRulePath); - if ($CustomRulePath -eq $null) - { - $customRulePathArr = $null; - } + # 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( $runspace, $testOutputWriter, - $customRulePathArr, + $CustomRulePath, $IncludeRule, $ExcludeRule, $Severity, From 91b61bd1259e1cf48469014585412cb58a71a24c Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Tue, 24 Nov 2015 11:21:00 -0800 Subject: [PATCH 16/53] Bug Fixes when using Recurse functionality. Fix to Binplace cmdlet help file --- .../Commands/InvokeScriptAnalyzerCommand.cs | 2 +- Engine/Helper.cs | 2 +- Engine/ScriptAnalyzer.cs | 29 ++++++++++++++----- Engine/ScriptAnalyzerEngine.csproj | 3 +- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/Engine/Commands/InvokeScriptAnalyzerCommand.cs b/Engine/Commands/InvokeScriptAnalyzerCommand.cs index afbfc46b7..7cf5f9b5e 100644 --- a/Engine/Commands/InvokeScriptAnalyzerCommand.cs +++ b/Engine/Commands/InvokeScriptAnalyzerCommand.cs @@ -220,7 +220,7 @@ private void ProcessPathOrScriptDefinition(string pathOrScriptDefinition) if (String.Equals(this.ParameterSetName, "File", StringComparison.OrdinalIgnoreCase)) { - diagnosticsList = ScriptAnalyzer.Instance.AnalyzePath(pathOrScriptDefinition); + diagnosticsList = ScriptAnalyzer.Instance.AnalyzePath(pathOrScriptDefinition, this.recurse); } else if (String.Equals(this.ParameterSetName, "ScriptDefinition", StringComparison.OrdinalIgnoreCase)) { diff --git a/Engine/Helper.cs b/Engine/Helper.cs index ef10cefc5..45f4c3204 100644 --- a/Engine/Helper.cs +++ b/Engine/Helper.cs @@ -1010,7 +1010,7 @@ public static string[] ProcessCustomRulePaths(string rulePath, SessionState sess } return outPaths.ToArray(); } - catch (Exception ex) + catch { // need to do this as the path validation takes place later in the hierarchy. outPaths.Add(rulePath); diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index b385fe4ae..5841bfcf2 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -466,15 +466,16 @@ private void LoadRules(Dictionary> result, CommandInvocatio { container.ComposeParts(this); } - catch (CompositionException compositionException) - { - this.outputWriter.WriteWarning(compositionException.ToString()); + catch + { } } // Gets external rules. if (result.ContainsKey("ValidModPaths") && result["ValidModPaths"].Count > 0) + { ExternalRules = GetExternalRule(result["ValidModPaths"].ToArray()); + } } internal string[] GetValidModulePaths() @@ -495,8 +496,12 @@ public IEnumerable GetRule(string[] moduleNames, string[] ruleNames) IEnumerable externalRules = null; // Combines C# rules. - IEnumerable rules = ScriptRules.Union(TokenRules) - .Union(DSCResourceRules); + IEnumerable rules = Enumerable.Empty(); + + if (null != ScriptRules) + { + rules = ScriptRules.Union(TokenRules).Union(DSCResourceRules); + } // Gets PowerShell Rules. if (moduleNames != null) @@ -565,9 +570,17 @@ private List GetExternalRule(string[] moduleNames) posh.Commands.Clear(); FunctionInfo funcInfo = (FunctionInfo)psobject.ImmediateBaseObject; - ParameterMetadata param = funcInfo.Parameters.Values - .First(item => item.Name.EndsWith("ast", StringComparison.OrdinalIgnoreCase) || - item.Name.EndsWith("token", StringComparison.OrdinalIgnoreCase)); + ParameterMetadata param = null; + + // Ignore any exceptions associated with finding functions that are ScriptAnalyzer rules + try + { + param = funcInfo.Parameters.Values.First(item => item.Name.EndsWith("ast", StringComparison.OrdinalIgnoreCase) || + item.Name.EndsWith("token", StringComparison.OrdinalIgnoreCase)); + } + catch + { + } //Only add functions that are defined as rules. if (param != null) diff --git a/Engine/ScriptAnalyzerEngine.csproj b/Engine/ScriptAnalyzerEngine.csproj index 80e91a809..63a053aca 100644 --- a/Engine/ScriptAnalyzerEngine.csproj +++ b/Engine/ScriptAnalyzerEngine.csproj @@ -106,6 +106,7 @@ copy /y "$(ProjectDir)\*.ps1xml" "$(SolutionDir)$(SolutionName)" copy /y "$(ProjectDir)\*.psd1" "$(SolutionDir)$(SolutionName)" copy /y "$(TargetPath)" "$(SolutionDir)$(SolutionName)" mkdir "$(SolutionDir)$(SolutionName)\en-US" -copy /y "$(ProjectDir)\about_*.help.txt" "$(SolutionDir)$(SolutionName)\en-US" +copy /y "$(ProjectDir)\about_*.help.txt" "$(SolutionDir)$(SolutionName)\en-US" +copy /y "$(ProjectDir)\*Help.xml" "$(SolutionDir)$(SolutionName)\en-US" \ No newline at end of file From dc6c18fd9a85ec7caea6ff3149fe6942788001fe Mon Sep 17 00:00:00 2001 From: Quoc Truong Date: Tue, 24 Nov 2015 11:27:24 -0800 Subject: [PATCH 17/53] Give profile alias configuration and provide some sample configurations --- Engine/Commands/InvokeScriptAnalyzerCommand.cs | 1 + Engine/Configurations/CmdletDesign.psd1 | 10 ++++++++++ Engine/Configurations/DSC.psd1 | 3 +++ Engine/Configurations/ScriptFunctions.psd1 | 11 +++++++++++ Engine/Configurations/ScriptSecurity.psd1 | 8 ++++++++ Engine/Configurations/ScriptingStyle.psd1 | 4 ++++ Engine/ScriptAnalyzerEngine.csproj | 2 ++ 7 files changed, 39 insertions(+) create mode 100644 Engine/Configurations/CmdletDesign.psd1 create mode 100644 Engine/Configurations/DSC.psd1 create mode 100644 Engine/Configurations/ScriptFunctions.psd1 create mode 100644 Engine/Configurations/ScriptSecurity.psd1 create mode 100644 Engine/Configurations/ScriptingStyle.psd1 diff --git a/Engine/Commands/InvokeScriptAnalyzerCommand.cs b/Engine/Commands/InvokeScriptAnalyzerCommand.cs index afbfc46b7..15d348785 100644 --- a/Engine/Commands/InvokeScriptAnalyzerCommand.cs +++ b/Engine/Commands/InvokeScriptAnalyzerCommand.cs @@ -159,6 +159,7 @@ public SwitchParameter SuppressedOnly /// /// Returns path to the file that contains user profile for ScriptAnalyzer /// + [Alias("Configuration")] [Parameter(Mandatory = false)] [ValidateNotNull] public string Profile diff --git a/Engine/Configurations/CmdletDesign.psd1 b/Engine/Configurations/CmdletDesign.psd1 new file mode 100644 index 000000000..e90ff47ad --- /dev/null +++ b/Engine/Configurations/CmdletDesign.psd1 @@ -0,0 +1,10 @@ +@{ + IncludeRules=@('PSUseApprovedVerbs', + 'PSReservedCmdletChar', + 'PSReservedParams', + 'PSShouldProcess', + 'PSUseShouldProcessForStateChangingFunctions', + 'PSUseSingularNouns', + 'PSMissingModuleManifestField', + 'PSAvoidDefaultValueSwitchParameter') +} \ No newline at end of file diff --git a/Engine/Configurations/DSC.psd1 b/Engine/Configurations/DSC.psd1 new file mode 100644 index 000000000..0fa2ac9c6 --- /dev/null +++ b/Engine/Configurations/DSC.psd1 @@ -0,0 +1,3 @@ +@{ + IncludeRules=@('PSDSC*') +} \ No newline at end of file diff --git a/Engine/Configurations/ScriptFunctions.psd1 b/Engine/Configurations/ScriptFunctions.psd1 new file mode 100644 index 000000000..b394abddf --- /dev/null +++ b/Engine/Configurations/ScriptFunctions.psd1 @@ -0,0 +1,11 @@ +@{ + IncludeRules=@('PSAvoidUsingCmdletAliases', + 'PSAvoidUsingWMICmdlet', + 'PSAvoidUsingEmptyCatchBlock', + 'PSUseCmdletCorrectly', + 'PSUseShouldProcessForStateChangingFunctions', + 'PSAvoidUsingPositionalParameters', + 'PSAvoidGlobalVars', + 'PSUseDeclaredVarsMoreThanAssignments', + 'PSAvoidUsingInvokeExpression') +} \ No newline at end of file diff --git a/Engine/Configurations/ScriptSecurity.psd1 b/Engine/Configurations/ScriptSecurity.psd1 new file mode 100644 index 000000000..3be041013 --- /dev/null +++ b/Engine/Configurations/ScriptSecurity.psd1 @@ -0,0 +1,8 @@ +@{ + IncludeRules=@('PSAvoidUsingPlainTextForPassword', + 'PSAvoidUsingComputerNameHardcoded', + 'PSAvoidUsingConvertToSecureStringWithPlainText', + 'PSUsePSCredentialType', + 'PSAvoidUsingUserNameAndPasswordParams', + 'PSAvoidUsingFilePath') +} \ No newline at end of file diff --git a/Engine/Configurations/ScriptingStyle.psd1 b/Engine/Configurations/ScriptingStyle.psd1 new file mode 100644 index 000000000..a25571566 --- /dev/null +++ b/Engine/Configurations/ScriptingStyle.psd1 @@ -0,0 +1,4 @@ +@{ + IncludeRules=@('PSProvideCommentHelp', + 'PSAvoidUsingWriteHost') +} \ No newline at end of file diff --git a/Engine/ScriptAnalyzerEngine.csproj b/Engine/ScriptAnalyzerEngine.csproj index 80e91a809..8dc4aeffb 100644 --- a/Engine/ScriptAnalyzerEngine.csproj +++ b/Engine/ScriptAnalyzerEngine.csproj @@ -104,6 +104,8 @@ mkdir "$(SolutionDir)$(SolutionName)" copy /y "$(ProjectDir)\*.ps1xml" "$(SolutionDir)$(SolutionName)" copy /y "$(ProjectDir)\*.psd1" "$(SolutionDir)$(SolutionName)" +mkdir "$(SolutionDir)$(SolutionName)\Configurations" +copy /y "$(ProjectDir)\Configurations\*.psd1" "$(SolutionDir)$(SolutionName)\Configurations" copy /y "$(TargetPath)" "$(SolutionDir)$(SolutionName)" mkdir "$(SolutionDir)$(SolutionName)\en-US" copy /y "$(ProjectDir)\about_*.help.txt" "$(SolutionDir)$(SolutionName)\en-US" From 25bbd2d7cbfc80199e481aa868d86f3072579d37 Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Tue, 24 Nov 2015 12:05:58 -0800 Subject: [PATCH 18/53] Fix to emit Filename when using Script Based Rules --- Engine/ScriptAnalyzer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index 5841bfcf2..e648a6408 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -759,7 +759,7 @@ internal IEnumerable GetExternalRecord(Ast ast, Token[] token, if (!string.IsNullOrEmpty(message)) { - diagnostics.Add(new DiagnosticRecord(message, extent, ruleName, severity, null)); + diagnostics.Add(new DiagnosticRecord(message, extent, ruleName, severity, filePath)); } } } From 99e4c5ea6aa48b5ba728ede83792de9303e9ca0d Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Tue, 24 Nov 2015 12:21:52 -0800 Subject: [PATCH 19/53] Reintroduced warnigns on issues with Composition --- Engine/ScriptAnalyzer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index e648a6408..4324c3626 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -466,8 +466,9 @@ private void LoadRules(Dictionary> result, CommandInvocatio { container.ComposeParts(this); } - catch - { + catch (CompositionException compositionException) + { + this.outputWriter.WriteWarning(compositionException.ToString()); } } From bb5ee2722dfedbfacd11595814b88d591d31bf25 Mon Sep 17 00:00:00 2001 From: Quoc Truong Date: Tue, 24 Nov 2015 15:03:38 -0800 Subject: [PATCH 20/53] Switch profile and configuration --- Engine/Commands/InvokeScriptAnalyzerCommand.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Engine/Commands/InvokeScriptAnalyzerCommand.cs b/Engine/Commands/InvokeScriptAnalyzerCommand.cs index d00a1b3ed..5136555ed 100644 --- a/Engine/Commands/InvokeScriptAnalyzerCommand.cs +++ b/Engine/Commands/InvokeScriptAnalyzerCommand.cs @@ -159,15 +159,15 @@ public SwitchParameter SuppressedOnly /// /// Returns path to the file that contains user profile for ScriptAnalyzer /// - [Alias("Configuration")] + [Alias("Profile")] [Parameter(Mandatory = false)] [ValidateNotNull] - public string Profile + public string Configuration { - get { return profile; } - set { profile = value; } + get { return configuration; } + set { configuration = value; } } - private string profile; + private string configuration; #endregion Parameters @@ -188,7 +188,7 @@ protected override void BeginProcessing() this.excludeRule, this.severity, this.suppressedOnly, - this.profile); + this.configuration); } /// From d650bcd383031c758567865a5b6f409d8c6e7b2b Mon Sep 17 00:00:00 2001 From: June Blender Date: Tue, 24 Nov 2015 21:35:16 -0700 Subject: [PATCH 21/53] Customize/CustomRulePath, RecurseCustomRulePath, ScriptDefinition --- ...ows.PowerShell.ScriptAnalyzer.dll-Help.xml | 569 ++++++++++++++---- 1 file changed, 446 insertions(+), 123 deletions(-) diff --git a/Engine/Microsoft.Windows.PowerShell.ScriptAnalyzer.dll-Help.xml b/Engine/Microsoft.Windows.PowerShell.ScriptAnalyzer.dll-Help.xml index 5c176a9f2..f9548dd6b 100644 --- a/Engine/Microsoft.Windows.PowerShell.ScriptAnalyzer.dll-Help.xml +++ b/Engine/Microsoft.Windows.PowerShell.ScriptAnalyzer.dll-Help.xml @@ -1,7 +1,11 @@ - + + @@ -21,9 +25,10 @@ Gets the script analyzer rules on the local computer. You can select rules by Name, Severity, Source, or SourceType, or even particular words in the rule description. Use this cmdlet to create collections of rules to include and exclude when running the Invoke-ScriptAnalyzer cmdlet. -To get information about the rules, see the value of the Description property of each rule. -The PSScriptAnalyzer module tests the Windows PowerShell code in a script, module, or DSC resource to determine whether, and to what extent, it fulfils best practice standards. For more information about PSScriptAnalyzer, type: Get-Help about_PSScriptAnalyzer. +To get information about the rules, see the value of the Description property of each rule. + +The PSScriptAnalyzer module tests the Windows PowerShell code in a script, module, or DSC resource to determine whether, and to what extent, it fulfils best practice standards. For more information about PSScriptAnalyzer, type: Get-Help about_PSScriptAnalyzer. PSScriptAnalyzer is an open-source project. To contribute or file an issue, see GitHub.com\PowerShell\PSScriptAnalyzer. @@ -31,21 +36,33 @@ PSScriptAnalyzer is an open-source project. To contribute or file an issue, see Get-ScriptAnalyzerRule - - CustomizedRulePath + + CustomRulePath - Gets the Script Analyzer rules in the specified path. By default, PSScriptAnalyzer gets the rules specified in the Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules.dll file in the module. + Gets the Script Analyzer rules in the specified path in addition to the standard Script Analyzer rules. By default, PSScriptAnalyzer gets only the standard rules specified in the Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules.dll file in the module. -You can create customized rules by using a custom .NET assembly or a Windows PowerShell module, such as the Community Analyzer Rules in +Enter the path to a .NET assembly or module that contains Script Analyzer rules. You can enter only one value, but wildcards are supported. To get rules in subdirectories of the path, use the RecurseCustomRulePath parameter. + +You can create custom rules by using a custom .NET assembly or a Windows PowerShell module, such as the Community Analyzer Rules in https://github.com/PowerShell/PSScriptAnalyzer/blob/development/Tests/Engine/CommunityAnalyzerRules/CommunityAnalyzerRules.psm1. - String[] + String The rules in Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules.dll. + RecurseCustomRulePath + + Searches the CustomRulePath location recursively to add rules defined in files in subdirectories of the path. By default, Get-ScriptAnalyzerRule adds only the custom rules in the specified path. + + SwitchParameter + + + Name - Gets only rules with the specified name or name pattern. By default, Get-ScriptAnalyzerRule gets all rules in the current rule path. Wildcards are supported. + Gets only rules with the specified names or name patterns. Wildcards are supported. If you list multiple names or patterns, it gets rules that match any of the name patterns, as though the name patterns were joined by an OR. + +By default, Get-ScriptAnalyzerRule gets all rules. String[] All rules @@ -53,7 +70,7 @@ https://github.com/PowerShell/PSScriptAnalyzer/blob/development/Tests/Engine/Com Severity - Gets only rules with the specified severity values. Valid values are Information, Warning, and Error. By default, Get-ScriptAnalyzerRule gets all rules in the current rule path. + Gets only rules with the specified severity values. Valid values are Information, Warning, and Error. By default, Get-ScriptAnalyzerRule gets all rules. String[] All rules @@ -62,25 +79,41 @@ https://github.com/PowerShell/PSScriptAnalyzer/blob/development/Tests/Engine/Com - - CustomizedRulePath + + CustomRulePath - Gets the Script Analyzer rules in the specified path. By default, PSScriptAnalyzer gets the rules specified in the Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules.dll file in the module. + Gets the Script Analyzer rules in the specified path in addition to the standard Script Analyzer rules. By default, PSScriptAnalyzer gets only the standard rules specified in the Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules.dll file in the module. + +Enter the path to a .NET assembly or module that contains Script Analyzer rules. You can enter only one value, but wildcards are supported. To get rules in subdirectories of the path, use the RecurseCustomRulePath parameter. -You can create customized rules by using a custom .NET assembly or a Windows PowerShell module, such as the Community Analyzer Rules in +You can create custom rules by using a custom .NET assembly or a Windows PowerShell module, such as the Community Analyzer Rules in https://github.com/PowerShell/PSScriptAnalyzer/blob/development/Tests/Engine/CommunityAnalyzerRules/CommunityAnalyzerRules.psm1. - String[] + String - String[] + String The rules in Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules.dll. + + RecurseCustomRulePath + + Searches the CustomRulePath location recursively to add rules defined in files in subdirectories of the path. By default, Get-ScriptAnalyzerRule adds only the custom rules in the specified path. + + SwitchParameter + + SwitchParameter + + + + Name - Gets only rules with the specified name or name pattern. By default, Get-ScriptAnalyzerRule gets all rules in the current rule path. Wildcards are supported. + Gets only rules with the specified names or name patterns. Wildcards are supported. If you list multiple names or patterns, it gets rules that match any of the name patterns, as though the name patterns were joined by an OR. + +By default, Get-ScriptAnalyzerRule gets all rules. String[] @@ -92,7 +125,7 @@ https://github.com/PowerShell/PSScriptAnalyzer/blob/development/Tests/Engine/Com Severity - Gets only rules with the specified severity values. Valid values are Information, Warning, and Error. By default, Get-ScriptAnalyzerRule gets all rules in the current rule path. + Gets only rules with the specified severity values. Valid values are Information, Warning, and Error. By default, Get-ScriptAnalyzerRule gets all rules. String[] @@ -105,21 +138,37 @@ https://github.com/PowerShell/PSScriptAnalyzer/blob/development/Tests/Engine/Com - - You cannot pipe input to this cmdlet. - - None - + None + + You cannot pipe input to this cmdlet. + + + - The RuleInfo object is a custom object created especially for Script Analyzer. It is not documented in MSDN. + The RuleInfo object is a custom object created especially for Script Analyzer. It is not documented in MSDN. + +Name MemberType Definition +---- ---------- ---------- +Equals Method bool Equals(System.Object obj) +GetHashCode Method int GetHashCode() +GetType Method type GetType() +ToString Method string ToString() +CommonName Property string CommonName {get;} +Description Property string Description {get;} +RuleName Property string RuleName {get;} +Severity Property Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.RuleSeverity Severity {get;} +SourceName Property string SourceName {get;} +SourceType Property Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.SourceType SourceType {get;} + + Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.RuleInfo @@ -136,7 +185,7 @@ https://github.com/PowerShell/PSScriptAnalyzer/blob/development/Tests/Engine/Com Get-ScriptAnalyzerRule - This command gets all script analyzer rules on the local computer. + This command gets all Script Analyzer rules on the local computer. @@ -158,10 +207,9 @@ https://github.com/PowerShell/PSScriptAnalyzer/blob/development/Tests/Engine/Com PS C:\>$Path = "$home\Documents\WindowsPowerShell\Modules\MyDSCModule\*" -PS C:\> Invoke-ScriptAnalyzerRule -Path $Path -IncludeRule $DSCError -Recurse - +PS C:\> Invoke-ScriptAnalyzerRule -Path $Path -IncludeRule $DSCError -Recurse - This example runs only the DSC rules with the Error severity on the files in the MyDSCModule module. + This example runs only the DSC rules with the Error severity on the files in the MyDSCModule module. Using the IncludeRule parameter of Invoke-ScriptAnalyzerRule is much more efficient than using its Severity parameter, which is applied only after using all rules to analyze all module files. @@ -171,23 +219,35 @@ Using the IncludeRule parameter of Invoke-ScriptAnalyzerRule is much more effici PS C:\> - $TestParameters = Get-ScriptAnalyzerRule -Severity Error, Warning -Name *Parameter* + $TestParameters = Get-ScriptAnalyzerRule -Severity Error, Warning -Name *Parameter*, *Alias* - This command gets rules with "Parameter" in the name that generate an Error or Warning. Use this set of rules to test the parameters of your script or module. + This command gets rules with "Parameter" or "Alias" in the name that generate an Error or Warning. Use this set of rules to test the parameters of your script or module. + + + + -------------------------- EXAMPLE 5 -------------------------- + + PS C:\> + + Get-ScriptAnalyzerRule -CustomRulePath $home\Documents\WindowsPowerShell\Modules\*StrictRules -RecurseCustomRulePath + + This command gets the standard rules and the rules in the VeryStrictRules and ExtremelyStrictRules modules. The command uses the RecurseCustomRulePath parameter to get rules defined in subdirectories of the matching paths. + Online version: http://go.microsoft.com/fwlink/?LinkId=525913 - Invoke-ScriptAnalyzer + about_PSScriptAnalyzer + PSScriptAnalyzer on GitHub @@ -210,17 +270,28 @@ Using the IncludeRule parameter of Invoke-ScriptAnalyzerRule is much more effici - Invoke-ScriptAnalyzer evaluates a script or module based on a collection of best practice rules and returns objects that represent rule violations. In each evaluation, you can run all rules or use the IncludeRule and ExcludeRule parameters to run only selected rules. Invoke-ScriptAnalyzer includes special rules to analyze DSC resources. + Invoke-ScriptAnalyzer evaluates a script or module based on a collection of best practice rules and returns objects +that represent rule violations. In each evaluation, you can run all rules or use the IncludeRule and ExcludeRule +parameters to run only selected rules. Invoke-ScriptAnalyzer includes special rules to analyze DSC resources. -Invoke-ScriptAnalyzer evaluates only .ps1 and .psm1 files. If you specify a path with multiple file types, the .ps1 and .psm1 files are tested; all other file types are ignored. +Invoke-ScriptAnalyzer evaluates only .ps1 and .psm1 files. If you specify a path with multiple file types, the .ps1 and +.psm1 files are tested; all other file types are ignored. -Invoke-ScriptAnalzyer comes with a set of built-in rules, but you can also use customized rules that you write in Windows PowerShell scripts, or compile in assemblies by using C#. Just as with the built-in rules, you can add the ExcludeRule and IncludeRule parameters to your Invoke-ScriptAnalyzer command to exclude or include custom rules. +Invoke-ScriptAnalzyer comes with a set of built-in rules, but you can also use customized rules that you write in +Windows PowerShell scripts, or compile in assemblies by using C#. Just as with the built-in rules, you can add the +ExcludeRule and IncludeRule parameters to your Invoke-ScriptAnalyzer command to exclude or include custom rules. -To analyze your script or module, begin by using the Get-ScriptAnalyzerRule cmdlet to examine and select the rules you want to include and/or exclude from the evaluation. +To analyze your script or module, begin by using the Get-ScriptAnalyzerRule cmdlet to examine and select the rules you +want to include and/or exclude from the evaluation. -You can also include a rule in the analysis, but suppress the output of that rule for selected functions or scripts. This feature should be used only when absolutely necessary. To get rules that were suppressed, run Invoke-ScriptAnalyzer with the SuppressedOnly parameter. For instructions on suppressing a rule, see the description of the SuppressedOnly parameter. +You can also include a rule in the analysis, but suppress the output of that rule for selected functions or scripts. +This feature should be used only when absolutely necessary. To get rules that were suppressed, run +Invoke-ScriptAnalyzer with the SuppressedOnly parameter. For instructions on suppressing a rule, see the description of +the SuppressedOnly parameter. -The PSScriptAnalyzer module tests the Windows PowerShell code in a script, module, or DSC resource to determine whether, and to what extent, it fulfils best practice standards. For more information about PSScriptAnalyzer, type: Get-Help about_PSScriptAnalyzer. +The PSScriptAnalyzer module tests the Windows PowerShell code in a script, module, or DSC resource to determine +whether, and to what extent, it fulfils best practice standards. For more information about PSScriptAnalyzer, type: +Get-Help about_PSScriptAnalyzer. PSScriptAnalyzer is an open-source project. To contribute or file an issue, see GitHub.com\PowerShell\PSScriptAnalyzer. @@ -228,110 +299,265 @@ PSScriptAnalyzer is an open-source project. To contribute or file an issue, see Invoke-ScriptAnalyzer - + Path Specifies the path to the scripts or module to be analyzed. Wildcard characters are supported. -Enter the path to a script (.ps1) or module file (.psm1) or to a directory that contains scripts or modules. If the directory contains other types of files, they are ignored. +Enter the path to a script (.ps1) or module file (.psm1) or to a directory that contains scripts or modules. If the +directory contains other types of files, they are ignored. + +To analyze files that are not in the root directory of the specified path, use a wildcard character +(C:\Modules\MyModule\*) or the Recurse parameter. + + String + + + CustomRulePath + + Adds the custom rules defined in the specified paths to the analysis. + +Enter the path to a file that defines rules or a directory that contains files that define rules. Wildcard characters are supported. To add rules defined in subdirectories of the path, use the RecurseCustomRulePath parameter. + +By default, Invoke-ScriptAnalyzer uses only rules defined in the Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules.dll file in the PSScriptAnalyzer module. + +If Invoke-ScriptAnalyzer cannot find rules in the CustomRulePath, it runs the standard rules without notice. + + String + + + + RecurseCustomRulePath + + Adds rules defined in in subdirectories of the CustomRulePath location. By default, Invoke-ScriptAnalyzer adds only the custom rules defined in the specified file or directory. + + SwitchParameter + + + + ExcludeRule + + Omits the specified rules from the Script Analyzer test. Wildcard characters are supported. + +Enter a comma-separated list of rule names, a variable that contains rule names, or a command that gets rule names. You +can also specify a list of excluded rules in a Script Analyzer profile file. You can exclude standard rules and rules +in a custom rule path. + +When you exclude a rule, the rule does not run on any of the files in the path. To exclude a rule on a particular line, +parameter, function, script, or class, adjust the Path parameter or suppress the rule. For information about +suppressing a rule, see the examples. + +If a rule is specified in both the ExcludeRule and IncludeRule collections, the rule is excluded. + + String[] + + + IncludeRule + + Runs only the specified rules in the Script Analyzer test. By default, PSScriptAnalyzer runs all rules. + +Enter a comma-separated list of rule names, a variable that contains rule names, or a command that gets rule names. +Wildcard characters are supported. You can also specify rule names in a Script Analyzer profile file. + +When you use the CustomizedRulePath parameter, you can use this parameter to include standard rules and rules in the +custom rule paths. + +If a rule is specified in both the ExcludeRule and IncludeRule collections, the rule is excluded. + +Also, Severity takes precedence over IncludeRule. For example, if Severity is Error, you cannot use IncludeRule to +include a Warning rule. + + String[] + + + Severity + + After running Script Analyzer with all rules, this parameter selects rule violations with the specified severity. + +Valid values are: Error, Warning, and Information. You can specify one ore more severity values. + +Because this parameter filters the rules only after running with all rules, it is not an efficient filter. To filter +rules efficiently, use Get-ScriptAnalyzer rule to get the rules you want to run or exclude and then use the ExcludeRule +or IncludeRule parameters. + +Also, Severity takes precedence over IncludeRule. For example, if Severity is Error, you cannot use IncludeRule to +include a Warning rule. + + String[] + + + Recurse + + Runs Script Analyzer on the files in the Path directory and all subdirectories recursively. + +Recurse applies only to the Path parameter value. To search the CustomRulePath recursively, use the RecurseCustomRulePath parameter. + + SwitchParameter + False + + + SuppressedOnly + + Returns rules that are suppressed, instead of analyzing the files in the path. + +When you used SuppressedOnly, Invoke-ScriptAnalyzer returns a SuppressedRecord object +(Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.SuppressedRecord). + +To suppress a rule, use the SuppressMessageAttribute. For help, see the examples. + + SwitchParameter + + + Profile + + Runs Invoke-ScriptAnalyzer with the parameters and values specified in a Script Analyzer profile file. Enter the path +to the profile file. + +If the path, the file, or its content is invalid, it is ignored. The parameters and values in the profile take +precedence over the same parameter and values specified at the command line. + +A Script Analyzer profile file is a text file that contains a hash table with one or more of the following keys: +-- Severity +-- IncludeRules +-- ExcludeRules + +The keys and values in the profile are interpreted as if they were standard parameters and parameter values of Invoke-ScriptAnalyzer. -To analyze files that are not in the root directory of the specified path, use a wildcard character (C:\Modules\MyModule\*) or the Recurse parameter. +To specify a single value, enclose the value in quotation marks. For example: + + @{ Severity = 'Error'} + +To specify multiple values, enclose the values in an array. For example: + + @{ Severity = 'Error', 'Warning'} + + String + Uses the parameters and values specified at the command line. + + + + Invoke-ScriptAnalyzer + + ScriptDefinition + + Runs Invoke-ScriptAnalyzer on commands, functions, or expressions in a string. You can use this feature to analyze statements, expressions, and functions, independent of their script context. + +Unlike ScriptBlock parameters, the ScriptDefinition parameter requires a string value. String - - CustomizedRulePath + + CustomRulePath - Adds the custom rules defined in the specified paths to the analysis. Enter one or more paths to Windows PowerShell modules or .NET assemblies that define rules. + Adds the custom rules defined in the specified paths to the analysis. + +Enter the path to a file that defines rules or a directory that contains files that define rules. Wildcard characters are supported. To add rules defined in subdirectories of the path, use the RecurseCustomRulePath parameter. By default, Invoke-ScriptAnalyzer uses only rules defined in the Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules.dll file in the PSScriptAnalyzer module. -If Invoke-ScriptAnalyzer cannot find rules in the CustomizedRulePath, it runs the standard rules without notice. +If Invoke-ScriptAnalyzer cannot find rules in the CustomRulePath, it runs the standard rules without notice. - String[] - The rules in Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules.dll. + String + + RecurseCustomRulePath + + Adds rules defined in in subdirectories of the CustomRulePath location. By default, Invoke-ScriptAnalyzer adds only the custom rules defined in the specified file or directory. + + SwitchParameter + + + ExcludeRule Omits the specified rules from the Script Analyzer test. Wildcard characters are supported. -Enter a comma-separated list of rule names, a variable that contains rule names, or a command that gets rule names. You can also specify a list of excluded rules in a Script Analyzer profile file. You can exclude standard rules and rules in a custom rule path. +Enter a comma-separated list of rule names, a variable that contains rule names, or a command that gets rule names. You +can also specify a list of excluded rules in a Script Analyzer profile file. You can exclude standard rules and rules +in a custom rule path. -When you exclude a rule, the rule does not run on any of the files in the path. To exclude a rule on a particular line, parameter, function, script, or class, adjust the Path parameter or suppress the rule. For information about suppressing a rule, see the examples. +When you exclude a rule, the rule does not run on any of the files in the path. To exclude a rule on a particular line, +parameter, function, script, or class, adjust the Path parameter or suppress the rule. For information about +suppressing a rule, see the examples. If a rule is specified in both the ExcludeRule and IncludeRule collections, the rule is excluded. String[] - All rules are included. - + IncludeRule Runs only the specified rules in the Script Analyzer test. By default, PSScriptAnalyzer runs all rules. -Enter a comma-separated list of rule names, a variable that contains rule names, or a command that gets rule names. Wildcard characters are supported. You can also specify rule names in a Script Analyzer profile file. +Enter a comma-separated list of rule names, a variable that contains rule names, or a command that gets rule names. +Wildcard characters are supported. You can also specify rule names in a Script Analyzer profile file. -When you use the CustomizedRulePath parameter, you can use this parameter to include standard rules and rules in the custom rule paths. +When you use the CustomizedRulePath parameter, you can use this parameter to include standard rules and rules in the +custom rule paths. If a rule is specified in both the ExcludeRule and IncludeRule collections, the rule is excluded. -Also, Severity takes precedence over IncludeRule. For example, if Severity is Error, you cannot use IncludeRule to include a Warning rule. +Also, Severity takes precedence over IncludeRule. For example, if Severity is Error, you cannot use IncludeRule to +include a Warning rule. String[] - All rules are included. - + Severity After running Script Analyzer with all rules, this parameter selects rule violations with the specified severity. Valid values are: Error, Warning, and Information. You can specify one ore more severity values. -Because this parameter filters the rules only after running with all rules, it is not an efficient filter. To filter rules efficiently, use Get-ScriptAnalyzer rule to get the rules you want to run or exclude and then use the ExcludeRule or IncludeRule parameters. +Because this parameter filters the rules only after running with all rules, it is not an efficient filter. To filter +rules efficiently, use Get-ScriptAnalyzer rule to get the rules you want to run or exclude and then use the ExcludeRule +or IncludeRule parameters. -Also, Severity takes precedence over IncludeRule. For example, if Severity is Error, you cannot use IncludeRule to include a Warning rule. +Also, Severity takes precedence over IncludeRule. For example, if Severity is Error, you cannot use IncludeRule to +include a Warning rule. String[] - All rule violations Recurse Runs Script Analyzer on the files in the Path directory and all subdirectories recursively. -Recurse applies only to the Path parameter value; not to the CustomizedRulePath parameter. +Recurse applies only to the Path parameter value. To search the CustomRulePath recursively, use the RecurseCustomRulePath parameter. SwitchParameter False - + SuppressedOnly Returns rules that are suppressed, instead of analyzing the files in the path. -When you used SuppressedOnly, Invoke-ScriptAnalyzer returns a SuppressedRecord object (Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.SuppressedRecord). +When you used SuppressedOnly, Invoke-ScriptAnalyzer returns a SuppressedRecord object +(Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.SuppressedRecord). To suppress a rule, use the SuppressMessageAttribute. For help, see the examples. SwitchParameter - False Profile - Runs Invoke-ScriptAnalyzer with the parameters and values specified in a Script Analyzer profile file. Enter the path to the profile file. + Runs Invoke-ScriptAnalyzer with the parameters and values specified in a Script Analyzer profile file. Enter the path +to the profile file. -If the path, the file, or its content is invalid, it is ignored. The parameters and values in the profile take precedence over the same parameter and values specified at the command line. +If the path, the file, or its content is invalid, it is ignored. The parameters and values in the profile take +precedence over the same parameter and values specified at the command line. A Script Analyzer profile file is a text file that contains a hash table with one or more of the following keys: -- Severity -- IncludeRules -- ExcludeRules -The keys and values in the profile are interpreted as if they were standard parameters and parameter values. +The keys and values in the profile are interpreted as if they were standard parameters and parameter values of Invoke-ScriptAnalyzer. To specify a single value, enclose the value in quotation marks. For example: @@ -348,47 +574,66 @@ To specify multiple values, enclose the values in an array. For example: - + Path Specifies the path to the scripts or module to be analyzed. Wildcard characters are supported. -Enter the path to a script (.ps1) or module file (.psm1) or to a directory that contains scripts or modules. If the directory contains other types of files, they are ignored. +Enter the path to a script (.ps1) or module file (.psm1) or to a directory that contains scripts or modules. If the +directory contains other types of files, they are ignored. -To analyze files that are not in the root directory of the specified path, use a wildcard character (C:\Modules\MyModule\*) or the Recurse parameter. +To analyze files that are not in the root directory of the specified path, use a wildcard character +(C:\Modules\MyModule\*) or the Recurse parameter. String String - - + - - CustomizedRulePath + + CustomRulePath - Adds the custom rules defined in the specified paths to the analysis. Enter one or more paths to Windows PowerShell modules or .NET assemblies that define rules. + Adds the custom rules defined in the specified paths to the analysis. + +Enter the path to a file that defines rules or a directory that contains files that define rules. Wildcard characters are supported. To add rules defined in subdirectories of the path, use the RecurseCustomRulePath parameter. By default, Invoke-ScriptAnalyzer uses only rules defined in the Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules.dll file in the PSScriptAnalyzer module. -If Invoke-ScriptAnalyzer cannot find rules in the CustomizedRulePath, it runs the standard rules without notice. +If Invoke-ScriptAnalyzer cannot find rules in the CustomRulePath, it runs the standard rules without notice. - String[] + String - String[] + String - The rules in Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules.dll. + + RecurseCustomRulePath + + Adds rules defined in in subdirectories of the CustomRulePath location. By default, Invoke-ScriptAnalyzer adds only the custom rules defined in the specified file or directory. + + SwitchParameter + + SwitchParameter + + + + + ExcludeRule Omits the specified rules from the Script Analyzer test. Wildcard characters are supported. -Enter a comma-separated list of rule names, a variable that contains rule names, or a command that gets rule names. You can also specify a list of excluded rules in a Script Analyzer profile file. You can exclude standard rules and rules in a custom rule path. +Enter a comma-separated list of rule names, a variable that contains rule names, or a command that gets rule names. You +can also specify a list of excluded rules in a Script Analyzer profile file. You can exclude standard rules and rules +in a custom rule path. -When you exclude a rule, the rule does not run on any of the files in the path. To exclude a rule on a particular line, parameter, function, script, or class, adjust the Path parameter or suppress the rule. For information about suppressing a rule, see the examples. +When you exclude a rule, the rule does not run on any of the files in the path. To exclude a rule on a particular line, +parameter, function, script, or class, adjust the Path parameter or suppress the rule. For information about +suppressing a rule, see the examples. If a rule is specified in both the ExcludeRule and IncludeRule collections, the rule is excluded. @@ -399,18 +644,21 @@ If a rule is specified in both the ExcludeRule and IncludeRule collections, the All rules are included. - + IncludeRule Runs only the specified rules in the Script Analyzer test. By default, PSScriptAnalyzer runs all rules. -Enter a comma-separated list of rule names, a variable that contains rule names, or a command that gets rule names. Wildcard characters are supported. You can also specify rule names in a Script Analyzer profile file. +Enter a comma-separated list of rule names, a variable that contains rule names, or a command that gets rule names. +Wildcard characters are supported. You can also specify rule names in a Script Analyzer profile file. -When you use the CustomizedRulePath parameter, you can use this parameter to include standard rules and rules in the custom rule paths. +When you use the CustomizedRulePath parameter, you can use this parameter to include standard rules and rules in the +custom rule paths. If a rule is specified in both the ExcludeRule and IncludeRule collections, the rule is excluded. -Also, Severity takes precedence over IncludeRule. For example, if Severity is Error, you cannot use IncludeRule to include a Warning rule. +Also, Severity takes precedence over IncludeRule. For example, if Severity is Error, you cannot use IncludeRule to +include a Warning rule. String[] @@ -419,16 +667,19 @@ Also, Severity takes precedence over IncludeRule. For example, if Severity is Er All rules are included. - + Severity After running Script Analyzer with all rules, this parameter selects rule violations with the specified severity. Valid values are: Error, Warning, and Information. You can specify one ore more severity values. -Because this parameter filters the rules only after running with all rules, it is not an efficient filter. To filter rules efficiently, use Get-ScriptAnalyzer rule to get the rules you want to run or exclude and then use the ExcludeRule or IncludeRule parameters. +Because this parameter filters the rules only after running with all rules, it is not an efficient filter. To filter +rules efficiently, use Get-ScriptAnalyzer rule to get the rules you want to run or exclude and then use the ExcludeRule +or IncludeRule parameters. -Also, Severity takes precedence over IncludeRule. For example, if Severity is Error, you cannot use IncludeRule to include a Warning rule. +Also, Severity takes precedence over IncludeRule. For example, if Severity is Error, you cannot use IncludeRule to +include a Warning rule. String[] @@ -442,7 +693,7 @@ Also, Severity takes precedence over IncludeRule. For example, if Severity is Er Runs Script Analyzer on the files in the Path directory and all subdirectories recursively. -Recurse applies only to the Path parameter value; not to the CustomizedRulePath parameter. +Recurse applies only to the Path parameter value. To search the CustomRulePath recursively, use the RecurseCustomRulePath parameter. SwitchParameter @@ -451,12 +702,13 @@ Recurse applies only to the Path parameter value; not to the CustomizedRulePath False - + SuppressedOnly Returns rules that are suppressed, instead of analyzing the files in the path. -When you used SuppressedOnly, Invoke-ScriptAnalyzer returns a SuppressedRecord object (Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.SuppressedRecord). +When you used SuppressedOnly, Invoke-ScriptAnalyzer returns a SuppressedRecord object +(Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.SuppressedRecord). To suppress a rule, use the SuppressMessageAttribute. For help, see the examples. @@ -470,16 +722,18 @@ To suppress a rule, use the SuppressMessageAttribute. For help, see the examples Profile - Runs Invoke-ScriptAnalyzer with the parameters and values specified in a Script Analyzer profile file. Enter the path to the profile file. + Runs Invoke-ScriptAnalyzer with the parameters and values specified in a Script Analyzer profile file. Enter the path +to the profile file. -If the path, the file, or its content is invalid, it is ignored. The parameters and values in the profile take precedence over the same parameter and values specified at the command line. +If the path, the file, or its content is invalid, it is ignored. The parameters and values in the profile take +precedence over the same parameter and values specified at the command line. A Script Analyzer profile file is a text file that contains a hash table with one or more of the following keys: -- Severity -- IncludeRules -- ExcludeRules -The keys and values in the profile are interpreted as if they were standard parameters and parameter values. +The keys and values in the profile are interpreted as if they were standard parameters and parameter values of Invoke-ScriptAnalyzer. To specify a single value, enclose the value in quotation marks. For example: @@ -496,29 +750,66 @@ To specify multiple values, enclose the values in an array. For example: Uses the parameters and values specified at the command line. + + ScriptDefinition + + Runs Invoke-ScriptAnalyzer on commands, functions, or expressions in a string. You can use this feature to analyze statements, expressions, and functions, independent of their script context. + +Unlike ScriptBlock parameters, the ScriptDefinition parameter requires a string value. + + String + + String + + + + + + + None + + You cannot pipe input to this cmdlet. + + - - None - - - - - By default, Invoke-ScriptAnalyzer returns one DiagnosticRecord object to report a rule violation. If you use the SuppressedOnly parameter, Invoke-ScriptAnalyzer instead returns a SuppressedRecord object. + By default, Invoke-ScriptAnalyzer returns one DiagnosticRecord object to report a rule violation. If you use the SuppressedOnly parameter, Invoke-ScriptAnalyzer instead returns a SuppressedRecord object. + +Name MemberType Definition +---- ---------- ---------- +Extent Property System.Management.Automation.Language.IScriptExtent Extent {get;set;} +Message Property string Message {get;set;} +RuleName Property string RuleName {get;set;} +RuleSuppressionID Property string RuleSuppressionID {get;set;} +ScriptName Property string ScriptName {get;set;} +Severity Property Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticSeverity Severity {get;set;} +Column ScriptProperty System.Object Column {get=$this.Extent.StartColumnNumber;} +Line ScriptProperty System.Object Line {get=$this.Extent.StartLineNumber;} + + - Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord, Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.SuppressedRecord + Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord + + + + + + + + + Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.SuppressedRecord @@ -530,9 +821,9 @@ To specify multiple values, enclose the values in an array. For example: PS C:\> - Invoke-ScriptAnalyzer -Path $pshome\Modules\PSDiagnostics\PSDiagnostics.psm1 + Invoke-ScriptAnalyzer -Path C:\Scripts\Get-LogData.ps1 - This command runs all Script Analyzer rules on the .psm1 file of the PSDiagnostics module. + This command runs all Script Analyzer rules on the Get-LogData.ps1 script. @@ -540,9 +831,10 @@ To specify multiple values, enclose the values in an array. For example: PS C:\> - Invoke-ScriptAnalyzer -Path C:\Scripts -Recurse + Invoke-ScriptAnalyzer -Path $home\Documents\WindowsPowerShell\Modules -Recurse - This command runs all Script Analyzer rules on all .ps1 and .psm1 files in the C:\Scripts directory and its subdirectories. + This command runs all Script Analyzer rules on all .ps1 and .psm1 files in the Modules directory and its +subdirectories. @@ -550,9 +842,9 @@ To specify multiple values, enclose the values in an array. For example: PS C:\> - Invoke-ScriptAnalyzer -Path C:\Windows\System32\WindowsPowerShell\v1.0\Modules\PSDiagnostics\PSDiagnostics.psm1 -IncludeRule PSAvoidUsingPositionalParameters + Invoke-ScriptAnalyzer -Path C:\Windows\System32\WindowsPowerShell\v1.0\Modules\PSDiagnostics -IncludeRule PSAvoidUsingPositionalParameters - This command runs only the PSAvoidUsingPositionalParameters rule. You might use a command like this to find all instances of a particular rule violation while working to eliminate it. + This command runs only the PSAvoidUsingPositionalParameters rule on the files in the PSDiagnostics module. You might use a command like this to find all instances of a particular rule violation while working to eliminate it. @@ -560,10 +852,9 @@ To specify multiple values, enclose the values in an array. For example: PS C:\> - Invoke-ScriptAnalyzer -Path C:\ps-test\MyModule -Recurse -ExcludeRule PSAvoidUsingCmdletAliases, PSAvoidUsingInternalURLs - + Invoke-ScriptAnalyzer -Path C:\ps-test\MyModule -Recurse -ExcludeRule PSAvoidUsingCmdletAliases, PSAvoidUsingInternalURLs - This command runs Script Analyzer on the .ps1 and .psm1 files in the MyModules directory (and its subdirectories) with all rules except for PSAvoidUsingCmdletAliases and PSAvoidUsingInternalURLs. + This command runs Script Analyzer on the .ps1 and .psm1 files in the MyModules directory, including the scripts in its subdirectories, with all rules except for PSAvoidUsingCmdletAliases and PSAvoidUsingInternalURLs. @@ -571,9 +862,9 @@ To specify multiple values, enclose the values in an array. For example: PS C:\> - Invoke-ScriptAnalyzer -Path D:\test_scripts\Test-Script.ps1 -CustomizedRulePath C:\CommunityAnalyzerRules + Invoke-ScriptAnalyzer -Path D:\test_scripts\Test-Script.ps1 -CustomRulePath C:\CommunityAnalyzerRules - This command runs Script Analyzer with the standard rules and rules in the C:\CommunityAnalyzerRules path. + This command runs Script Analyzer on Test-Script.ps1 with the standard rules and rules in the C:\CommunityAnalyzerRules path. @@ -610,7 +901,7 @@ PS C:\> Invoke-ScriptAnalyzer -Path .\Get-Widgets.ps1 RuleName Severity FileName Line Message -------- -------- -------- ---- ------- -PSProvideCommentHelp Information ManageProf 14 The cmdlet 'Get-Widget' does not have a help comment. +PSProvideCommentHelp Information ManageProf 14 The cmdlet 'Get-Widget' does not have a help comment. iles.psm1 PS C:\> Invoke-ScriptAnalyzer -Path .\Get-Widgets.ps1 -SuppressedOnly @@ -622,13 +913,18 @@ PSAvoidUsingCmdletAliases Warning ManageProf 21 Resolution in PSUseSingularNouns Warning ManageProf 14 iles.psm1 - This example shows how to suppress the reporting of rule violations in a function and how to discover rule violations that are suppressed. + This example shows how to suppress the reporting of rule violations in a function and how to discover rule violations +that are suppressed. -The example uses the SuppressMessageAttribute attribute to suppress the PSUseSingularNouns and PSAvoidUsingCmdletAliases rules for the Get-Widgets function in the Get-Widgets.ps1 script. You can use this attribute to suppress a rule for a module, script, class, function, parameter, or line. +The example uses the SuppressMessageAttribute attribute to suppress the PSUseSingularNouns and +PSAvoidUsingCmdletAliases rules for the Get-Widgets function in the Get-Widgets.ps1 script. You can use this attribute +to suppress a rule for a module, script, class, function, parameter, or line. -The first command runs Script Analyzer on the script that contains the Get-Widgets function. The output reports a rule violation, but neither of the suppressed rules is listed, even though they are violated. +The first command runs Script Analyzer on the script that contains the Get-Widgets function. The output reports a rule +violation, but neither of the suppressed rules is listed, even though they are violated. -The second command uses the SuppressedOnly parameter to discover the rules that are supressed in the Get-Widgets.ps1 file. The output reports the suppressed rules. +The second command uses the SuppressedOnly parameter to discover the rules that are supressed in the Get-Widgets.ps1 +file. The output reports the suppressed rules. @@ -638,18 +934,43 @@ The second command uses the SuppressedOnly parameter to discover the rules that # In .\ScriptAnalyzerProfile.txt @{ - Severity = @('Error', 'Warning') - IncludeRules = 'PSAvoid*' - ExcludeRules = '*WriteHost' + Severity = @('Error', 'Warning') + IncludeRules = 'PSAvoid*' + ExcludeRules = '*WriteHost' } PS C:\> Invoke-ScriptAnalyzer -Path $pshome\Modules\BitLocker -Profile .\ScriptAnalyzerProfile.txt - In this example, we create a Script Analyzer profile and save it in the ScriptAnalyzerProfile.txt file in the local directory. + In this example, we create a Script Analyzer profile and save it in the ScriptAnalyzerProfile.txt file in the local +directory. + +Next, we run Invoke-ScriptAnalyzer on the BitLocker module files. The value of the Profile parameter is the path to the +Script Analyzer profile. + +If you include a conflicting parameter in the Invoke-ScriptAnalyzer command, such as '-Severity Error', +Invoke-ScriptAnalyzer uses the profile value and ignores the parameter. + + + + -------------------------- EXAMPLE 9 -------------------------- + + PS C:\> + + Invoke-ScriptAnalyzer -ScriptDefinition "function Get-Widgets {Write-Host 'Hello'}" -Next, we run Invoke-ScriptAnalyzer on the BitLocker module files. The value of the Profile parameter is the path to the Script Analyzer profile. +RuleName Severity FileName Line Message +-------- -------- -------- ---- ------- +PSAvoidUsingWriteHost Warning 1 Script + because + there i + suppres + Write-O +PSUseSingularNouns Warning 1 The cmd + noun sh + + This command uses the ScriptDefinition parameter to analyze a function at the command line. The function string is enclosed in quotation marks. -If you include a conflicting parameter in the Invoke-ScriptAnalyzer command, such as '-Severity Error', Invoke-ScriptAnalyzer uses the profile value and ignores the parameter. +When you use the ScriptDefinition parameter, the FileName property of the DiagnosticRecord object is $null. @@ -661,9 +982,11 @@ If you include a conflicting parameter in the Invoke-ScriptAnalyzer command, suc Get-ScriptAnalyzerRule + about_PSScriptAnalyzer + PSScriptAnalyzer on GitHub @@ -671,5 +994,5 @@ If you include a conflicting parameter in the Invoke-ScriptAnalyzer command, suc - + \ No newline at end of file From 02dad541e7510129332a4490e26208674354e687 Mon Sep 17 00:00:00 2001 From: Ryan Yates Date: Wed, 25 Nov 2015 17:43:06 +0000 Subject: [PATCH 22/53] Fix for #371 --- Rules/AvoidUsingPlainTextForPassword.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rules/AvoidUsingPlainTextForPassword.cs b/Rules/AvoidUsingPlainTextForPassword.cs index ffec527f5..4586b27e0 100644 --- a/Rules/AvoidUsingPlainTextForPassword.cs +++ b/Rules/AvoidUsingPlainTextForPassword.cs @@ -37,7 +37,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) // Finds all ParamAsts. IEnumerable paramAsts = ast.FindAll(testAst => testAst is ParameterAst, true); - List passwords = new List() {"Password", "Passphrase", "Auth", "Cred", "Credential"}; + List passwords = new List() {"Password", "Passphrase", "Cred", "Credential"}; // Iterrates all ParamAsts and check if their names are on the list. foreach (ParameterAst paramAst in paramAsts) From bf3023418064df01e0a1fb08d8912d5b1ba306cc Mon Sep 17 00:00:00 2001 From: Ryan Yates Date: Wed, 25 Nov 2015 19:20:20 +0000 Subject: [PATCH 23/53] Updated Pester test that I forgot in last commit --- Tests/Rules/AvoidUsingPlainTextForPassword.tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Rules/AvoidUsingPlainTextForPassword.tests.ps1 b/Tests/Rules/AvoidUsingPlainTextForPassword.tests.ps1 index 51b2696e0..28f6539c6 100644 --- a/Tests/Rules/AvoidUsingPlainTextForPassword.tests.ps1 +++ b/Tests/Rules/AvoidUsingPlainTextForPassword.tests.ps1 @@ -9,7 +9,7 @@ $noViolations = Invoke-ScriptAnalyzer $directory\AvoidUsingPlainTextForPasswordN Describe "AvoidUsingPlainTextForPassword" { Context "When there are violations" { It "has 3 avoid using plain text for password violations" { - $violations.Count | Should Be 5 + $violations.Count | Should Be 4 } It "has the correct violation message" { From ef6d8a62111aeeced4d8c33967b26e22e50d6c4c Mon Sep 17 00:00:00 2001 From: Quoc Truong Date: Wed, 25 Nov 2015 12:00:13 -0800 Subject: [PATCH 24/53] Change Provide Default Parameter Value to Avoid Default Value For Mandatory Parameter --- ...AvoidDefaultValueForMandatoryParameter.cs} | 78 ++++++++++--------- Rules/ScriptAnalyzerBuiltinRules.csproj | 2 +- Rules/Strings.Designer.cs | 72 ++++++++--------- Rules/Strings.resx | 16 ++-- ...AvoidDefaultValueForMandatoryParameter.ps1 | 42 ++++++++++ ...efaultValueForMandatoryParameter.tests.ps1 | 24 ++++++ ...alueForMandatoryParameterNoViolations.ps1} | 0 Tests/Rules/ProvideDefaultParameterValue.ps1 | 20 ----- .../ProvideDefaultParameterValue.tests.ps1 | 24 ------ 9 files changed, 153 insertions(+), 125 deletions(-) rename Rules/{ProvideDefaultParameterValue.cs => AvoidDefaultValueForMandatoryParameter.cs} (59%) create mode 100644 Tests/Rules/AvoidDefaultValueForMandatoryParameter.ps1 create mode 100644 Tests/Rules/AvoidDefaultValueForMandatoryParameter.tests.ps1 rename Tests/Rules/{ProvideDefaultParameterValueNoViolations.ps1 => AvoidDefaultValueForMandatoryParameterNoViolations.ps1} (100%) delete mode 100644 Tests/Rules/ProvideDefaultParameterValue.ps1 delete mode 100644 Tests/Rules/ProvideDefaultParameterValue.tests.ps1 diff --git a/Rules/ProvideDefaultParameterValue.cs b/Rules/AvoidDefaultValueForMandatoryParameter.cs similarity index 59% rename from Rules/ProvideDefaultParameterValue.cs rename to Rules/AvoidDefaultValueForMandatoryParameter.cs index c67c14133..5db8bfe6f 100644 --- a/Rules/ProvideDefaultParameterValue.cs +++ b/Rules/AvoidDefaultValueForMandatoryParameter.cs @@ -13,10 +13,11 @@ using System; using System.Linq; using System.Collections.Generic; +using System.Management.Automation; using System.Management.Automation.Language; -using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; using System.ComponentModel.Composition; using System.Globalization; +using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules { @@ -24,7 +25,7 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules /// ProvideDefaultParameterValue: Check if any uninitialized variable is used. /// [Export(typeof(IScriptRule))] - public class ProvideDefaultParameterValue : IScriptRule + public class AvoidDefaultValueForMandatoryParameter : IScriptRule { /// /// AnalyzeScript: Check if any uninitialized variable is used. @@ -36,48 +37,53 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) // Finds all functionAst IEnumerable functionAsts = ast.FindAll(testAst => testAst is FunctionDefinitionAst, true); - // Checks whether this is a dsc resource file (we don't raise this rule for get, set and test-target resource - bool isDscResourceFile = !String.IsNullOrWhiteSpace(fileName) && Helper.Instance.IsDscResourceModule(fileName); - - List targetResourcesFunctions = new List(new string[] { "get-targetresource", "set-targetresource", "test-targetresource" }); - - foreach (FunctionDefinitionAst funcAst in functionAsts) { - // Finds all ParamAsts. - IEnumerable varAsts = funcAst.FindAll(testAst => testAst is VariableExpressionAst, true); - - // Iterrates all ParamAsts and check if their names are on the list. - - HashSet dscVariables = new HashSet(); - if (isDscResourceFile && targetResourcesFunctions.Contains(funcAst.Name, StringComparer.OrdinalIgnoreCase)) + if (funcAst.Body != null && funcAst.Body.ParamBlock != null + && funcAst.Body.ParamBlock.Attributes != null && funcAst.Body.ParamBlock.Parameters != null) { - // don't raise the rules for variables in the param block. - if (funcAst.Body != null && funcAst.Body.ParamBlock != null && funcAst.Body.ParamBlock.Parameters != null) + // only raise this rule for function with cmdletbinding + if (!funcAst.Body.ParamBlock.Attributes.Any(attr => attr.TypeName.GetReflectionType() == typeof(CmdletBindingAttribute))) { - dscVariables.UnionWith(funcAst.Body.ParamBlock.Parameters.Select(paramAst => paramAst.Name.VariablePath.UserPath)); + continue; } - } - // only raise the rules for variables in the param block. - if (funcAst.Body != null && funcAst.Body.ParamBlock != null && funcAst.Body.ParamBlock.Parameters != null) - { + foreach (var paramAst in funcAst.Body.ParamBlock.Parameters) { - if (Helper.Instance.IsUninitialized(paramAst.Name, funcAst) && !dscVariables.Contains(paramAst.Name.VariablePath.UserPath)) + bool mandatory = false; + + // check that param is mandatory + foreach (var paramAstAttribute in paramAst.Attributes) { - yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.ProvideDefaultParameterValueError, paramAst.Name.VariablePath.UserPath), - paramAst.Name.Extent, GetName(), DiagnosticSeverity.Warning, fileName, paramAst.Name.VariablePath.UserPath); + if (paramAstAttribute is AttributeAst) + { + var namedArguments = (paramAstAttribute as AttributeAst).NamedArguments; + if (namedArguments != null) + { + foreach (NamedAttributeArgumentAst namedArgument in namedArguments) + { + if (String.Equals(namedArgument.ArgumentName, "mandatory", StringComparison.OrdinalIgnoreCase)) + { + // 2 cases: [Parameter(Mandatory)] and [Parameter(Mandatory=$true)] + if (namedArgument.ExpressionOmitted || (!namedArgument.ExpressionOmitted && String.Equals(namedArgument.Argument.Extent.Text, "$true", StringComparison.OrdinalIgnoreCase))) + { + mandatory = true; + break; + } + } + } + } + } } - } - } - if (funcAst.Parameters != null) - { - foreach (var paramAst in funcAst.Parameters) - { - if (Helper.Instance.IsUninitialized(paramAst.Name, funcAst) && !dscVariables.Contains(paramAst.Name.VariablePath.UserPath)) + if (!mandatory) + { + break; + } + + if (paramAst.DefaultValue != null) { - yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.ProvideDefaultParameterValueError, paramAst.Name.VariablePath.UserPath), + yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.AvoidDefaultValueForMandatoryParameterError, paramAst.Name.VariablePath.UserPath), paramAst.Name.Extent, GetName(), DiagnosticSeverity.Warning, fileName, paramAst.Name.VariablePath.UserPath); } } @@ -91,7 +97,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) /// The name of this rule public string GetName() { - return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.ProvideDefaultParameterValueName); + return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.AvoidDefaultValueForMandatoryParameterName); } /// @@ -100,7 +106,7 @@ public string GetName() /// The common name of this rule public string GetCommonName() { - return string.Format(CultureInfo.CurrentCulture, Strings.ProvideDefaultParameterValueCommonName); + return string.Format(CultureInfo.CurrentCulture, Strings.AvoidDefaultValueForMandatoryParameterCommonName); } /// @@ -109,7 +115,7 @@ public string GetCommonName() /// The description of this rule public string GetDescription() { - return string.Format(CultureInfo.CurrentCulture, Strings.ProvideDefaultParameterValueDescription); + return string.Format(CultureInfo.CurrentCulture, Strings.AvoidDefaultValueForMandatoryParameterDescription); } /// diff --git a/Rules/ScriptAnalyzerBuiltinRules.csproj b/Rules/ScriptAnalyzerBuiltinRules.csproj index 6f8058d4b..48154170e 100644 --- a/Rules/ScriptAnalyzerBuiltinRules.csproj +++ b/Rules/ScriptAnalyzerBuiltinRules.csproj @@ -59,7 +59,7 @@ - + diff --git a/Rules/Strings.Designer.cs b/Rules/Strings.Designer.cs index 9128ebedd..448fb0efe 100644 --- a/Rules/Strings.Designer.cs +++ b/Rules/Strings.Designer.cs @@ -96,6 +96,42 @@ internal static string AvoidComputerNameHardcodedName { } } + /// + /// Looks up a localized string similar to Avoid Default Value For Mandatory Parameter. + /// + internal static string AvoidDefaultValueForMandatoryParameterCommonName { + get { + return ResourceManager.GetString("AvoidDefaultValueForMandatoryParameterCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Mandatory parameter should not be initialized with a default value in the param block because this value will be ignored.. To fix a violation of this rule, please avoid initializing a value for the mandatory parameter in the param block.. + /// + internal static string AvoidDefaultValueForMandatoryParameterDescription { + get { + return ResourceManager.GetString("AvoidDefaultValueForMandatoryParameterDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Mandatory Parameter '{0}' is initialized in the Param block. To fix a violation of this rule, please leave it unintialized.. + /// + internal static string AvoidDefaultValueForMandatoryParameterError { + get { + return ResourceManager.GetString("AvoidDefaultValueForMandatoryParameterError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AvoidDefaultValueForMandatoryParameter. + /// + internal static string AvoidDefaultValueForMandatoryParameterName { + get { + return ResourceManager.GetString("AvoidDefaultValueForMandatoryParameterName", resourceCulture); + } + } + /// /// Looks up a localized string similar to Switch Parameters Should Not Default To True. /// @@ -1176,42 +1212,6 @@ internal static string ProvideCommentHelpName { } } - /// - /// Looks up a localized string similar to Default Parameter Values. - /// - internal static string ProvideDefaultParameterValueCommonName { - get { - return ResourceManager.GetString("ProvideDefaultParameterValueCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Parameters must have a default value. To fix a violation of this rule, please specify a default value for all parameters. - /// - internal static string ProvideDefaultParameterValueDescription { - get { - return ResourceManager.GetString("ProvideDefaultParameterValueDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Parameter '{0}' is not initialized. Parameters must have a default value. To fix a violation of this rule, please specify a default value for all parameters. - /// - internal static string ProvideDefaultParameterValueError { - get { - return ResourceManager.GetString("ProvideDefaultParameterValueError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to ProvideDefaultParameterValue. - /// - internal static string ProvideDefaultParameterValueName { - get { - return ResourceManager.GetString("ProvideDefaultParameterValueName", resourceCulture); - } - } - /// /// Looks up a localized string similar to Reserved Cmdlet Chars. /// diff --git a/Rules/Strings.resx b/Rules/Strings.resx index 2679b9b34..f297f16b0 100644 --- a/Rules/Strings.resx +++ b/Rules/Strings.resx @@ -711,17 +711,17 @@ No examples found for resource '{0}' - - Default Parameter Values + + Avoid Default Value For Mandatory Parameter - - Parameters must have a default value. To fix a violation of this rule, please specify a default value for all parameters + + Mandatory parameter should not be initialized with a default value in the param block because this value will be ignored.. To fix a violation of this rule, please avoid initializing a value for the mandatory parameter in the param block. - - Parameter '{0}' is not initialized. Parameters must have a default value. To fix a violation of this rule, please specify a default value for all parameters + + Mandatory Parameter '{0}' is initialized in the Param block. To fix a violation of this rule, please leave it unintialized. - - ProvideDefaultParameterValue + + AvoidDefaultValueForMandatoryParameter Avoid Using Deprecated Manifest Fields diff --git a/Tests/Rules/AvoidDefaultValueForMandatoryParameter.ps1 b/Tests/Rules/AvoidDefaultValueForMandatoryParameter.ps1 new file mode 100644 index 000000000..d1cccd86b --- /dev/null +++ b/Tests/Rules/AvoidDefaultValueForMandatoryParameter.ps1 @@ -0,0 +1,42 @@ +function BadFunc +{ + [CmdletBinding()] + param( + # this one has default value + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $Param1="String", + # this parameter has no default value + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [string] + $Param2 + ) + $Param1 + $Param1 = "test" +} + +function GoodFunc1($Param1) +{ + $Param1 +} + +# same as BadFunc but this one has no cmdletbinding +function GoodFunc2 +{ + param( + # this one has default value + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $Param1="String", + # this parameter has no default value + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [string] + $Param2 + ) + $Param1 + $Param1 = "test" +} \ No newline at end of file diff --git a/Tests/Rules/AvoidDefaultValueForMandatoryParameter.tests.ps1 b/Tests/Rules/AvoidDefaultValueForMandatoryParameter.tests.ps1 new file mode 100644 index 000000000..f29a98699 --- /dev/null +++ b/Tests/Rules/AvoidDefaultValueForMandatoryParameter.tests.ps1 @@ -0,0 +1,24 @@ +Import-Module PSScriptAnalyzer +$violationName = "PSAvoidDefaultValueForMandatoryParameter" +$violationMessage = "Mandatory Parameter 'Param1' is initialized in the Param block. To fix a violation of this rule, please leave it unintialized." +$directory = Split-Path -Parent $MyInvocation.MyCommand.Path +$violations = Invoke-ScriptAnalyzer "$directory\AvoidDefaultValueForMandatoryParameter.ps1" | Where-Object {$_.RuleName -match $violationName} +$noViolations = Invoke-ScriptAnalyzer "$directory\AvoidDefaultValueForMandatoryParameterNoViolations.ps1" + +Describe "AvoidDefaultValueForMandatoryParameter" { + Context "When there are violations" { + It "has 1 provide default value for mandatory parameter violation" { + $violations.Count | Should Be 1 + } + + It "has the correct description message" { + $violations[0].Message | Should Match $violationMessage + } + } + + Context "When there are no violations" { + It "returns no violations" { + $noViolations.Count | Should Be 0 + } + } +} \ No newline at end of file diff --git a/Tests/Rules/ProvideDefaultParameterValueNoViolations.ps1 b/Tests/Rules/AvoidDefaultValueForMandatoryParameterNoViolations.ps1 similarity index 100% rename from Tests/Rules/ProvideDefaultParameterValueNoViolations.ps1 rename to Tests/Rules/AvoidDefaultValueForMandatoryParameterNoViolations.ps1 diff --git a/Tests/Rules/ProvideDefaultParameterValue.ps1 b/Tests/Rules/ProvideDefaultParameterValue.ps1 deleted file mode 100644 index 706ba9e6c..000000000 --- a/Tests/Rules/ProvideDefaultParameterValue.ps1 +++ /dev/null @@ -1,20 +0,0 @@ -function BadFunc -{ - param( - [Parameter(Mandatory=$true)] - [ValidateNotNullOrEmpty()] - [string] - $Param1, - [Parameter(Mandatory=$false)] - [ValidateNotNullOrEmpty()] - [string] - $Param2 - ) - $Param1 - $Param1 = "test" -} - -function BadFunc2($Param1) -{ - $Param1 -} \ No newline at end of file diff --git a/Tests/Rules/ProvideDefaultParameterValue.tests.ps1 b/Tests/Rules/ProvideDefaultParameterValue.tests.ps1 deleted file mode 100644 index 5289fdc72..000000000 --- a/Tests/Rules/ProvideDefaultParameterValue.tests.ps1 +++ /dev/null @@ -1,24 +0,0 @@ -Import-Module PSScriptAnalyzer -$violationName = "PSProvideDefaultParameterValue" -$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 - -Describe "ProvideDefaultParameters" { - Context "When there are violations" { - It "has 2 provide default parameter value violation" { - $violations.Count | Should Be 2 - } - - It "has the correct description message" { - $violations[0].Message | Should Match $violationMessage - } - } - - Context "When there are no violations" { - It "returns no violations" { - $noViolations.Count | Should Be 0 - } - } -} \ No newline at end of file From da45614efe31f7495415960c62b7c8f554685c61 Mon Sep 17 00:00:00 2001 From: Quoc Truong Date: Wed, 25 Nov 2015 13:30:13 -0800 Subject: [PATCH 25/53] Fix test failures --- Tests/Engine/RuleSuppression.ps1 | 28 +++++++++++++++++++++----- Tests/Engine/RuleSuppression.tests.ps1 | 4 ++-- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/Tests/Engine/RuleSuppression.ps1 b/Tests/Engine/RuleSuppression.ps1 index b63aef84c..02651b0ee 100644 --- a/Tests/Engine/RuleSuppression.ps1 +++ b/Tests/Engine/RuleSuppression.ps1 @@ -6,8 +6,17 @@ Param( function SuppressMe () { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSProvideCommentHelp")] - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSProvideDefaultParameterValue", "unused1")] - Param([string]$unUsed1, [int] $unUsed2) + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidDefaultValueForMandatoryParameter", "unused1")] + [CmdletBinding()] + Param( + [Parameter(Mandatory=$true)] + [string] + $unUsed1="unused", + + [Parameter(Mandatory=$true)] + [int] + $unUsed2=3 + ) { Write-Host "I do nothing" } @@ -16,9 +25,18 @@ function SuppressMe () function SuppressTwoVariables() { - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSProvideDefaultParameterValue", "b")] - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSProvideDefaultParameterValue", "a")] - Param([string]$a, [int]$b) + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidDefaultValueForMandatoryParameter", "b")] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidDefaultValueForMandatoryParameter", "a")] + [CmdletBinding()] + Param( + [Parameter(Mandatory=$true)] + [string] + $a="unused", + + [Parameter(Mandatory=$true)] + [int] + $b=3 + ) { } } diff --git a/Tests/Engine/RuleSuppression.tests.ps1 b/Tests/Engine/RuleSuppression.tests.ps1 index 35a4f15c7..555b770c5 100644 --- a/Tests/Engine/RuleSuppression.tests.ps1 +++ b/Tests/Engine/RuleSuppression.tests.ps1 @@ -49,9 +49,9 @@ Describe "RuleSuppressionWithoutScope" { Context "RuleSuppressionID" { It "Only suppress violations for that ID" { - $suppression = $violations | Where-Object {$_.RuleName -eq "PSProvideDefaultParameterValue" } + $suppression = $violations | Where-Object {$_.RuleName -eq "PSAvoidDefaultValueForMandatoryParameter" } $suppression.Count | Should Be 1 - $suppression = $violationsUsingScriptDefinition | Where-Object {$_.RuleName -eq "PSProvideDefaultParameterValue" } + $suppression = $violationsUsingScriptDefinition | Where-Object {$_.RuleName -eq "PSAvoidDefaultValueForMandatoryParameter" } $suppression.Count | Should Be 1 } } From 80b788a5324399b63ab85c23bfcf63052ee3955d Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Mon, 30 Nov 2015 15:18:03 -0800 Subject: [PATCH 26/53] Script Analyzer shouldn't automatically update help for modules --- Engine/ScriptAnalyzer.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index 4324c3626..6790bd434 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -554,13 +554,7 @@ private List GetExternalRule(string[] moduleNames) { posh.AddCommand("Get-Module").AddParameter("Name", moduleName).AddParameter("ListAvailable"); shortModuleName = posh.Invoke().First().Name; - - // Invokes Update-Help for this module - // Required since when invoking Get-Help later on, the cmdlet prompts for Update-Help interactively - // By invoking Update-Help first, Get-Help will not prompt for downloading help later - posh.AddCommand("Update-Help").AddParameter("Module", shortModuleName).AddParameter("Force"); - posh.Invoke(); - + // Invokes Get-Command and Get-Help for each functions in the module. posh.Commands.Clear(); posh.AddCommand("Get-Command").AddParameter("Module", shortModuleName); @@ -586,6 +580,11 @@ private List GetExternalRule(string[] moduleNames) //Only add functions that are defined as rules. if (param != null) { + // On a new image, when Get-Help is run the first time, PowerShell offers to download updated help content + // using Update-Help. This results in an interactive prompt - which we cannot handle + // Workaround to prevent Update-Help from running is to set the following reg key + // HKLM:\Software\Microsoft\PowerShell\DisablePromptToUpdateHelp + // OR execute Update-Help in an elevated admin mode before running ScriptAnalyzer posh.AddCommand("Get-Help").AddParameter("Name", funcInfo.Name); Collection helpContent = posh.Invoke(); From 028af94018808919e0c955287c8296d39a8b82fc Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Mon, 30 Nov 2015 16:11:14 -0800 Subject: [PATCH 27/53] Added Warning + updated failing tests --- Engine/ScriptAnalyzer.cs | 14 +++++++++++--- Tests/Engine/CustomizedRule.tests.ps1 | 16 ++++++++++++++-- Tests/Engine/LibraryUsage.tests.ps1 | 13 +++++++++++++ 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index 6790bd434..c8f797a1c 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -584,9 +584,17 @@ private List GetExternalRule(string[] moduleNames) // using Update-Help. This results in an interactive prompt - which we cannot handle // Workaround to prevent Update-Help from running is to set the following reg key // HKLM:\Software\Microsoft\PowerShell\DisablePromptToUpdateHelp - // OR execute Update-Help in an elevated admin mode before running ScriptAnalyzer - posh.AddCommand("Get-Help").AddParameter("Name", funcInfo.Name); - Collection helpContent = posh.Invoke(); + // OR execute Update-Help in an elevated admin mode before running ScriptAnalyzer + Collection helpContent = null; + try + { + posh.AddCommand("Get-Help").AddParameter("Name", funcInfo.Name); + helpContent = posh.Invoke(); + } + catch (Exception getHelpException) + { + this.outputWriter.WriteWarning(getHelpException.Message.ToString()); + } // Retrieve "Description" field in the help content string desc = String.Empty; diff --git a/Tests/Engine/CustomizedRule.tests.ps1 b/Tests/Engine/CustomizedRule.tests.ps1 index 2ef337c88..30153537b 100644 --- a/Tests/Engine/CustomizedRule.tests.ps1 +++ b/Tests/Engine/CustomizedRule.tests.ps1 @@ -46,8 +46,20 @@ Describe "Test importing correct customized rules" { $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomizedRulePath $directory\samplerule\samplerule.psm1 | Where-Object {$_.Message -eq $message} $customizedRulePath.Count | Should Be 1 - } - + + # Force Get-Help not to prompt for interactive input to download help using Update-Help + # By adding this registry key we force to turn off Get-Help interactivity logic during ScriptRule parsing + $null,"Wow6432Node" | ForEach-Object { + try + { + Set-ItemProperty -Name "DisablePromptToUpdateHelp" -Path "HKLM:\SOFTWARE\$($_)\Microsoft\PowerShell" -Value 1 -Force + } + catch + { + # Ignore for cases when tests are running in non-elevated more or registry key does not exist or not accessible + } + } + } } Context "Test Get-ScriptAnalyzer with customized rules" { diff --git a/Tests/Engine/LibraryUsage.tests.ps1 b/Tests/Engine/LibraryUsage.tests.ps1 index 49a1c6bdc..41d2de3d5 100644 --- a/Tests/Engine/LibraryUsage.tests.ps1 +++ b/Tests/Engine/LibraryUsage.tests.ps1 @@ -134,6 +134,19 @@ $runspace.Open(); # Let other test scripts know we are testing library usage now $testingLibraryUsage = $true +# Force Get-Help not to prompt for interactive input to download help using Update-Help +# By adding this registry key we force to turn off Get-Help interactivity logic during ScriptRule parsing +$null,"Wow6432Node" | ForEach-Object { + try + { + Set-ItemProperty -Name "DisablePromptToUpdateHelp" -Path "HKLM:\SOFTWARE\$($_)\Microsoft\PowerShell" -Value 1 -Force + } + catch + { + # Ignore for cases when tests are running in non-elevated more or registry key does not exist or not accessible + } +} + # Invoke existing test files that use Invoke-ScriptAnalyzer . $directory\InvokeScriptAnalyzer.tests.ps1 . $directory\RuleSuppression.tests.ps1 From bc3cdb01d321645bed99e12716a5154bcd60e2d7 Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Mon, 30 Nov 2015 16:21:12 -0800 Subject: [PATCH 28/53] Cleaned up more warnings in tests --- Tests/Engine/CustomizedRule.tests.ps1 | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Tests/Engine/CustomizedRule.tests.ps1 b/Tests/Engine/CustomizedRule.tests.ps1 index 30153537b..a3bcb7f6b 100644 --- a/Tests/Engine/CustomizedRule.tests.ps1 +++ b/Tests/Engine/CustomizedRule.tests.ps1 @@ -6,6 +6,18 @@ if (!(Get-Module PSScriptAnalyzer) -and !$testingLibraryUsage) Import-Module PSScriptAnalyzer } +# Force Get-Help not to prompt for interactive input to download help using Update-Help +# By adding this registry key we turn off Get-Help interactivity logic during ScriptRule parsing +$null,"Wow6432Node" | ForEach-Object { +try +{ + Set-ItemProperty -Name "DisablePromptToUpdateHelp" -Path "HKLM:\SOFTWARE\$($_)\Microsoft\PowerShell" -Value 1 -Force +} +catch +{ + # Ignore for cases when tests are running in non-elevated more or registry key does not exist or not accessible +} + $directory = Split-Path -Parent $MyInvocation.MyCommand.Path $message = "this is help" $measure = "Measure-RequiresRunAsAdministrator" @@ -48,7 +60,7 @@ Describe "Test importing correct customized rules" { $customizedRulePath.Count | Should Be 1 # Force Get-Help not to prompt for interactive input to download help using Update-Help - # By adding this registry key we force to turn off Get-Help interactivity logic during ScriptRule parsing + # By adding this registry key we turn off Get-Help interactivity logic during ScriptRule parsing $null,"Wow6432Node" | ForEach-Object { try { From 466b04c570337a21e52336a90f37dfad882a4752 Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Mon, 30 Nov 2015 16:28:31 -0800 Subject: [PATCH 29/53] Fixed Syntax issue in the test script --- Tests/Engine/CustomizedRule.tests.ps1 | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Tests/Engine/CustomizedRule.tests.ps1 b/Tests/Engine/CustomizedRule.tests.ps1 index a3bcb7f6b..1e7f098de 100644 --- a/Tests/Engine/CustomizedRule.tests.ps1 +++ b/Tests/Engine/CustomizedRule.tests.ps1 @@ -9,13 +9,14 @@ if (!(Get-Module PSScriptAnalyzer) -and !$testingLibraryUsage) # Force Get-Help not to prompt for interactive input to download help using Update-Help # By adding this registry key we turn off Get-Help interactivity logic during ScriptRule parsing $null,"Wow6432Node" | ForEach-Object { -try -{ - Set-ItemProperty -Name "DisablePromptToUpdateHelp" -Path "HKLM:\SOFTWARE\$($_)\Microsoft\PowerShell" -Value 1 -Force -} -catch -{ - # Ignore for cases when tests are running in non-elevated more or registry key does not exist or not accessible + try + { + Set-ItemProperty -Name "DisablePromptToUpdateHelp" -Path "HKLM:\SOFTWARE\$($_)\Microsoft\PowerShell" -Value 1 -Force + } + catch + { + # Ignore for cases when tests are running in non-elevated more or registry key does not exist or not accessible + } } $directory = Split-Path -Parent $MyInvocation.MyCommand.Path From a1b452874af2fab9711573b6157ea735942febfd Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Tue, 1 Dec 2015 10:39:37 -0800 Subject: [PATCH 30/53] Deprecate Internal Url Rule --- {Rules => DeprecatedRules}/AvoidUsingInternalURLs.cs | 0 Rules/ScriptAnalyzerBuiltinRules.csproj | 1 - Tests/{Rules => DisabledRules}/AvoidUsingInternalURLs.ps1 | 0 Tests/{Rules => DisabledRules}/AvoidUsingInternalURLs.tests.ps1 | 0 .../AvoidUsingInternalURLsNoViolations.ps1 | 0 5 files changed, 1 deletion(-) rename {Rules => DeprecatedRules}/AvoidUsingInternalURLs.cs (100%) rename Tests/{Rules => DisabledRules}/AvoidUsingInternalURLs.ps1 (100%) rename Tests/{Rules => DisabledRules}/AvoidUsingInternalURLs.tests.ps1 (100%) rename Tests/{Rules => DisabledRules}/AvoidUsingInternalURLsNoViolations.ps1 (100%) diff --git a/Rules/AvoidUsingInternalURLs.cs b/DeprecatedRules/AvoidUsingInternalURLs.cs similarity index 100% rename from Rules/AvoidUsingInternalURLs.cs rename to DeprecatedRules/AvoidUsingInternalURLs.cs diff --git a/Rules/ScriptAnalyzerBuiltinRules.csproj b/Rules/ScriptAnalyzerBuiltinRules.csproj index 48154170e..0908618ea 100644 --- a/Rules/ScriptAnalyzerBuiltinRules.csproj +++ b/Rules/ScriptAnalyzerBuiltinRules.csproj @@ -63,7 +63,6 @@ - diff --git a/Tests/Rules/AvoidUsingInternalURLs.ps1 b/Tests/DisabledRules/AvoidUsingInternalURLs.ps1 similarity index 100% rename from Tests/Rules/AvoidUsingInternalURLs.ps1 rename to Tests/DisabledRules/AvoidUsingInternalURLs.ps1 diff --git a/Tests/Rules/AvoidUsingInternalURLs.tests.ps1 b/Tests/DisabledRules/AvoidUsingInternalURLs.tests.ps1 similarity index 100% rename from Tests/Rules/AvoidUsingInternalURLs.tests.ps1 rename to Tests/DisabledRules/AvoidUsingInternalURLs.tests.ps1 diff --git a/Tests/Rules/AvoidUsingInternalURLsNoViolations.ps1 b/Tests/DisabledRules/AvoidUsingInternalURLsNoViolations.ps1 similarity index 100% rename from Tests/Rules/AvoidUsingInternalURLsNoViolations.ps1 rename to Tests/DisabledRules/AvoidUsingInternalURLsNoViolations.ps1 From 07ae0958be50f56786dd717936c130172f5f5fd5 Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Tue, 1 Dec 2015 10:45:15 -0800 Subject: [PATCH 31/53] Fix failing test --- Tests/Engine/GetScriptAnalyzerRule.tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 index 32624e216..96b793fc8 100644 --- a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 +++ b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 @@ -122,7 +122,7 @@ Describe "TestSeverity" { It "filters rules based on multiple severity inputs"{ $rules = Get-ScriptAnalyzerRule -Severity Error,Information - $rules.Count | Should be 14 + $rules.Count | Should be 13 } It "takes lower case inputs" { From be025c8ca73fe4741a2cd9fd463c391daae1bd38 Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Tue, 1 Dec 2015 13:26:28 -0800 Subject: [PATCH 32/53] Added localhost exceptions for HardCodedComputerName Rule --- Rules/AvoidUsingComputerNameHardcoded.cs | 8 +++++++- .../Rules/AvoidUsingComputerNameHardcodedNoViolations.ps1 | 6 +++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Rules/AvoidUsingComputerNameHardcoded.cs b/Rules/AvoidUsingComputerNameHardcoded.cs index f0454d412..ae6fd4462 100644 --- a/Rules/AvoidUsingComputerNameHardcoded.cs +++ b/Rules/AvoidUsingComputerNameHardcoded.cs @@ -15,6 +15,7 @@ using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; using System.ComponentModel.Composition; using System.Globalization; +using System.Collections.Generic; namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules { @@ -48,7 +49,12 @@ public override bool ParameterCondition(CommandAst CmdAst, CommandElementAst CeA if (String.Equals(cmdParamAst.ParameterName, "computername", StringComparison.OrdinalIgnoreCase)) { - return cmdParamAst.Argument is ConstantExpressionAst || GetComputerNameArg(CmdAst, cmdParamAst.Extent.StartOffset) is ConstantExpressionAst; + Ast computerNameArgument = GetComputerNameArg(CmdAst, cmdParamAst.Extent.StartOffset); + List localhostExceptions = new List{ "localhost", ".", "::1", "127.0.0.1" }; + if ((null != computerNameArgument) && (!localhostExceptions.Contains(computerNameArgument.Extent.Text))) + { + return cmdParamAst.Argument is ConstantExpressionAst || computerNameArgument is ConstantExpressionAst; + } } } diff --git a/Tests/Rules/AvoidUsingComputerNameHardcodedNoViolations.ps1 b/Tests/Rules/AvoidUsingComputerNameHardcodedNoViolations.ps1 index a2bdfbea7..cd14ca34f 100644 --- a/Tests/Rules/AvoidUsingComputerNameHardcodedNoViolations.ps1 +++ b/Tests/Rules/AvoidUsingComputerNameHardcodedNoViolations.ps1 @@ -1,2 +1,6 @@ Invoke-Command -ComputerName $comp -Invoke-Command -ComputerName $env:COMPUTERNAME \ No newline at end of file +Invoke-Command -ComputerName $env:COMPUTERNAME +Invoke-Command -ComputerName localhost +Invoke-Command -ComputerName . +Invoke-Command -ComputerName ::1 +Invoke-Command -ComputerName 127.0.0.1 \ No newline at end of file From 86b936fce42c886ddf9fe51d9d12a238c2afe39f Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Tue, 1 Dec 2015 16:39:06 -0800 Subject: [PATCH 33/53] Fixed issues in the original rule, added more testing --- Rules/AvoidUsingComputerNameHardcoded.cs | 20 +++++++++++++++---- ...UsingComputerNameHardcodedNoViolations.ps1 | 6 +++++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/Rules/AvoidUsingComputerNameHardcoded.cs b/Rules/AvoidUsingComputerNameHardcoded.cs index ae6fd4462..95ec0a454 100644 --- a/Rules/AvoidUsingComputerNameHardcoded.cs +++ b/Rules/AvoidUsingComputerNameHardcoded.cs @@ -16,6 +16,7 @@ using System.ComponentModel.Composition; using System.Globalization; using System.Collections.Generic; +using System.Linq; namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules { @@ -49,11 +50,22 @@ public override bool ParameterCondition(CommandAst CmdAst, CommandElementAst CeA if (String.Equals(cmdParamAst.ParameterName, "computername", StringComparison.OrdinalIgnoreCase)) { - Ast computerNameArgument = GetComputerNameArg(CmdAst, cmdParamAst.Extent.StartOffset); - List localhostExceptions = new List{ "localhost", ".", "::1", "127.0.0.1" }; - if ((null != computerNameArgument) && (!localhostExceptions.Contains(computerNameArgument.Extent.Text))) + List localhostRepresentations = new List { "localhost", ".", "::1", "127.0.0.1" }; + Ast computerNameArgument = GetComputerNameArg(CmdAst, cmdParamAst.Extent.StartOffset); + + if (null != computerNameArgument) + { + if (!localhostRepresentations.Contains(computerNameArgument.Extent.Text.ToLower())) + { + return computerNameArgument is ConstantExpressionAst; + } + + return false; + } + + if (null != cmdParamAst.Argument && !localhostRepresentations.Contains(cmdParamAst.Argument.ToString().Replace("\"", "").Replace("'", "").ToLower())) { - return cmdParamAst.Argument is ConstantExpressionAst || computerNameArgument is ConstantExpressionAst; + return cmdParamAst.Argument is ConstantExpressionAst; } } } diff --git a/Tests/Rules/AvoidUsingComputerNameHardcodedNoViolations.ps1 b/Tests/Rules/AvoidUsingComputerNameHardcodedNoViolations.ps1 index cd14ca34f..fc179a9e1 100644 --- a/Tests/Rules/AvoidUsingComputerNameHardcodedNoViolations.ps1 +++ b/Tests/Rules/AvoidUsingComputerNameHardcodedNoViolations.ps1 @@ -3,4 +3,8 @@ Invoke-Command -ComputerName $env:COMPUTERNAME Invoke-Command -ComputerName localhost Invoke-Command -ComputerName . Invoke-Command -ComputerName ::1 -Invoke-Command -ComputerName 127.0.0.1 \ No newline at end of file +Invoke-Command -ComputerName 127.0.0.1 +Invoke-Command -ComputerName:'localhost' +Invoke-Command -ComputerName:"." +Invoke-Command -ComputerName:'::1' +Invoke-Command -ComputerName:"127.0.0.1" \ No newline at end of file From f880cf1997bce1c730590a1968e693a7ab07e64a Mon Sep 17 00:00:00 2001 From: Quoc Truong Date: Tue, 1 Dec 2015 16:45:28 -0800 Subject: [PATCH 34/53] Add an extra check for CredentialAttribute for the credential rules --- Rules/AvoidUserNameAndPasswordParams.cs | 8 +++++++ Rules/Strings.Designer.cs | 10 ++++----- Rules/Strings.resx | 10 ++++----- Rules/UsePSCredentialType.cs | 13 +++++++++--- .../AvoidUserNameAndPasswordParams.tests.ps1 | 2 +- ...dUserNameAndPasswordParamsNoViolations.ps1 | 21 +++++++++++++++++++ Tests/Rules/PSCredentialType.tests.ps1 | 2 +- Tests/Rules/PSCredentialTypeNoViolations.ps1 | 18 +++++++++++++++- 8 files changed, 68 insertions(+), 16 deletions(-) diff --git a/Rules/AvoidUserNameAndPasswordParams.cs b/Rules/AvoidUserNameAndPasswordParams.cs index 823ca6e3c..de76533de 100644 --- a/Rules/AvoidUserNameAndPasswordParams.cs +++ b/Rules/AvoidUserNameAndPasswordParams.cs @@ -11,6 +11,7 @@ // using System; +using System.Linq; using System.Collections.Generic; using System.Management.Automation; using System.Management.Automation.Language; @@ -55,11 +56,18 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) TypeInfo paramType = (TypeInfo)paramAst.StaticType; String paramName = paramAst.Name.VariablePath.ToString(); + // if this is pscredential type, skip if (paramType == typeof(PSCredential) || (paramType.IsArray && paramType.GetElementType() == typeof (PSCredential))) { continue; } + // if this has credential attribute, skip + if (paramAst.Attributes.Any(paramAttribute => paramAttribute.TypeName.GetReflectionType() == typeof(CredentialAttribute))) + { + continue; + } + foreach (String password in passwords) { if (paramName.IndexOf(password, StringComparison.OrdinalIgnoreCase) != -1) diff --git a/Rules/Strings.Designer.cs b/Rules/Strings.Designer.cs index 448fb0efe..0a0cd2095 100644 --- a/Rules/Strings.Designer.cs +++ b/Rules/Strings.Designer.cs @@ -421,7 +421,7 @@ internal static string AvoidUsernameAndPasswordParamsCommonName { } /// - /// Looks up a localized string similar to Functions should only take in a credential parameter of type PSCredential instead of username and password parameters.. + /// Looks up a localized string similar to Functions should only take in a credential parameter of type PSCredential or has a CredentialAttribute instead of username and password parameters.. /// internal static string AvoidUsernameAndPasswordParamsDescription { get { @@ -430,7 +430,7 @@ internal static string AvoidUsernameAndPasswordParamsDescription { } /// - /// Looks up a localized string similar to Function '{0}' has both username and password parameters. A credential parameter of type PSCredential should be used.. + /// Looks up a localized string similar to Function '{0}' has both username and password parameters. A credential parameter of type PSCredential or has a CredentialAttribute should be used.. /// internal static string AvoidUsernameAndPasswordParamsError { get { @@ -1771,7 +1771,7 @@ internal static string UsePSCredentialTypeCommonName { } /// - /// Looks up a localized string similar to Checks that cmdlets that have a Credential parameter accept PSCredential. This comes from the PowerShell teams best practices.. + /// Looks up a localized string similar to Checks that cmdlets that have a Credential parameter accept PSCredential or has a CredentialAttribute. This comes from the PowerShell teams best practices.. /// internal static string UsePSCredentialTypeDescription { get { @@ -1780,7 +1780,7 @@ internal static string UsePSCredentialTypeDescription { } /// - /// Looks up a localized string similar to The Credential parameter in '{0}' must be of the type PSCredential.. + /// Looks up a localized string similar to The Credential parameter in '{0}' must be of the type PSCredential or has a CredentialAttribute.. /// internal static string UsePSCredentialTypeError { get { @@ -1789,7 +1789,7 @@ internal static string UsePSCredentialTypeError { } /// - /// Looks up a localized string similar to The Credential parameter in a found script block must be of the type PSCredential.. + /// Looks up a localized string similar to The Credential parameter in a found script block must be of the type PSCredential or has a CredentialAttribute.. /// internal static string UsePSCredentialTypeErrorSB { get { diff --git a/Rules/Strings.resx b/Rules/Strings.resx index f297f16b0..b4150441a 100644 --- a/Rules/Strings.resx +++ b/Rules/Strings.resx @@ -217,13 +217,13 @@ One Char - Checks that cmdlets that have a Credential parameter accept PSCredential. This comes from the PowerShell teams best practices. + Checks that cmdlets that have a Credential parameter accept PSCredential or has a CredentialAttribute. This comes from the PowerShell teams best practices. - The Credential parameter in '{0}' must be of the type PSCredential. + The Credential parameter in '{0}' must be of the type PSCredential or has a CredentialAttribute. - The Credential parameter in a found script block must be of the type PSCredential. + The Credential parameter in a found script block must be of the type PSCredential or has a CredentialAttribute. PSCredential @@ -511,10 +511,10 @@ Avoid Using Username and Password Parameters - Functions should only take in a credential parameter of type PSCredential instead of username and password parameters. + Functions should only take in a credential parameter of type PSCredential or has a CredentialAttribute instead of username and password parameters. - Function '{0}' has both username and password parameters. A credential parameter of type PSCredential should be used. + Function '{0}' has both username and password parameters. A credential parameter of type PSCredential or has a CredentialAttribute should be used. AvoidUsingUserNameAndPassWordParams diff --git a/Rules/UsePSCredentialType.cs b/Rules/UsePSCredentialType.cs index 99ddbd70f..df19a028a 100644 --- a/Rules/UsePSCredentialType.cs +++ b/Rules/UsePSCredentialType.cs @@ -11,6 +11,7 @@ // using System; +using System.Linq; using System.Collections.Generic; using System.Management.Automation; using System.Management.Automation.Language; @@ -50,7 +51,9 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) { foreach (ParameterAst parameter in funcDefAst.Parameters) { - if (parameter.Name.VariablePath.UserPath.Equals("Credential", StringComparison.OrdinalIgnoreCase) && parameter.StaticType != typeof(PSCredential)) + if (parameter.Name.VariablePath.UserPath.Equals("Credential", StringComparison.OrdinalIgnoreCase) + && parameter.StaticType != typeof(PSCredential) + && !parameter.Attributes.Any(paramAttribute => paramAttribute.TypeName.GetReflectionType() == typeof(CredentialAttribute))) { yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.UsePSCredentialTypeError, funcName), funcDefAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName); } @@ -61,7 +64,9 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) { foreach (ParameterAst parameter in funcDefAst.Body.ParamBlock.Parameters) { - if (parameter.Name.VariablePath.UserPath.Equals("Credential", StringComparison.OrdinalIgnoreCase) && parameter.StaticType != typeof(PSCredential)) + if (parameter.Name.VariablePath.UserPath.Equals("Credential", StringComparison.OrdinalIgnoreCase) + && parameter.StaticType != typeof(PSCredential) + && !parameter.Attributes.Any(paramAttribute => paramAttribute.TypeName.GetReflectionType() == typeof(CredentialAttribute))) { yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.UsePSCredentialTypeError, funcName), funcDefAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName); } @@ -75,7 +80,9 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) { foreach (ParameterAst parameter in scriptBlockAst.ParamBlock.Parameters) { - if (parameter.Name.VariablePath.UserPath.Equals("Credential", StringComparison.OrdinalIgnoreCase) && parameter.StaticType != typeof(PSCredential)) + if (parameter.Name.VariablePath.UserPath.Equals("Credential", StringComparison.OrdinalIgnoreCase) + && parameter.StaticType != typeof(PSCredential) + && !parameter.Attributes.Any(paramAttribute => paramAttribute.TypeName.GetReflectionType() == typeof(CredentialAttribute))) { yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.UsePSCredentialTypeErrorSB), scriptBlockAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName); } diff --git a/Tests/Rules/AvoidUserNameAndPasswordParams.tests.ps1 b/Tests/Rules/AvoidUserNameAndPasswordParams.tests.ps1 index 14f567160..0f383927a 100644 --- a/Tests/Rules/AvoidUserNameAndPasswordParams.tests.ps1 +++ b/Tests/Rules/AvoidUserNameAndPasswordParams.tests.ps1 @@ -1,6 +1,6 @@ Import-Module PSScriptAnalyzer -$violationMessage = "Function 'Verb-Noun' 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 or has a CredentialAttribute 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/AvoidUserNameAndPasswordParamsNoViolations.ps1 b/Tests/Rules/AvoidUserNameAndPasswordParamsNoViolations.ps1 index f964458cf..1ed59f853 100644 --- a/Tests/Rules/AvoidUserNameAndPasswordParamsNoViolations.ps1 +++ b/Tests/Rules/AvoidUserNameAndPasswordParamsNoViolations.ps1 @@ -11,3 +11,24 @@ function MyFunction2 ($param1, $passwords) function MyFunction3 ([PSCredential]$username, $passwords) { } + +function MyFunction4 +{ + [CmdletBinding()] + [Alias()] + [OutputType([int])] + Param + ( + # Param1 help description + [Parameter(Mandatory=$true, + ValueFromPipelineByPropertyName=$true, + Position=0)] + [System.Management.Automation.CredentialAttribute()] + $UserName, + + # Param2 help description + [int] + [System.Management.Automation.CredentialAttribute()] + $Password + ) +} diff --git a/Tests/Rules/PSCredentialType.tests.ps1 b/Tests/Rules/PSCredentialType.tests.ps1 index 50a5f083c..8e09ea6db 100644 --- a/Tests/Rules/PSCredentialType.tests.ps1 +++ b/Tests/Rules/PSCredentialType.tests.ps1 @@ -1,5 +1,5 @@ Import-Module PSScriptAnalyzer -$violationMessage = "The Credential parameter in 'Credential' must be of the type PSCredential." +$violationMessage = "The Credential parameter in 'Credential' must be of the type PSCredential or has a CredentialAttribute." $violationName = "PSUsePSCredentialType" $directory = Split-Path -Parent $MyInvocation.MyCommand.Path $violations = Invoke-ScriptAnalyzer $directory\PSCredentialType.ps1 | Where-Object {$_.RuleName -eq $violationName} diff --git a/Tests/Rules/PSCredentialTypeNoViolations.ps1 b/Tests/Rules/PSCredentialTypeNoViolations.ps1 index ebe613883..62368066d 100644 --- a/Tests/Rules/PSCredentialTypeNoViolations.ps1 +++ b/Tests/Rules/PSCredentialTypeNoViolations.ps1 @@ -1,3 +1,19 @@ function Credential([pscredential]$credential) { -} \ No newline at end of file +} + +function Credential2 +{ + [CmdletBinding()] + [Alias()] + [OutputType([int])] + Param + ( + # Param1 help description + [Parameter(Mandatory=$true, + ValueFromPipelineByPropertyName=$true, + Position=0)] + [System.Management.Automation.CredentialAttribute()] + $Credential + ) +} From 21269c811a84036d2d96b53f508da8f981fca944 Mon Sep 17 00:00:00 2001 From: Quoc Truong Date: Wed, 2 Dec 2015 10:50:16 -0800 Subject: [PATCH 35/53] Change logic to check for both CredentialAttribute and PSCredential type --- Rules/AvoidUserNameAndPasswordParams.cs | 11 ++----- Rules/Strings.Designer.cs | 10 +++--- Rules/Strings.resx | 10 +++--- Rules/UsePSCredentialType.cs | 31 +++++++++++++------ .../AvoidUserNameAndPasswordParams.tests.ps1 | 2 +- ...dUserNameAndPasswordParamsNoViolations.ps1 | 9 ++---- Tests/Rules/PSCredentialType.tests.ps1 | 2 +- Tests/Rules/PSCredentialTypeNoViolations.ps1 | 9 ++---- 8 files changed, 43 insertions(+), 41 deletions(-) diff --git a/Rules/AvoidUserNameAndPasswordParams.cs b/Rules/AvoidUserNameAndPasswordParams.cs index de76533de..422b58080 100644 --- a/Rules/AvoidUserNameAndPasswordParams.cs +++ b/Rules/AvoidUserNameAndPasswordParams.cs @@ -56,14 +56,9 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) TypeInfo paramType = (TypeInfo)paramAst.StaticType; String paramName = paramAst.Name.VariablePath.ToString(); - // if this is pscredential type, skip - if (paramType == typeof(PSCredential) || (paramType.IsArray && paramType.GetElementType() == typeof (PSCredential))) - { - continue; - } - - // if this has credential attribute, skip - if (paramAst.Attributes.Any(paramAttribute => paramAttribute.TypeName.GetReflectionType() == typeof(CredentialAttribute))) + // if this is pscredential type with credential attribute, skip + if ((paramType == typeof(PSCredential) || (paramType.IsArray && paramType.GetElementType() == typeof (PSCredential))) + && paramAst.Attributes.Any(paramAttribute => paramAttribute.TypeName.GetReflectionType() == typeof(CredentialAttribute))) { continue; } diff --git a/Rules/Strings.Designer.cs b/Rules/Strings.Designer.cs index 0a0cd2095..22b8a9191 100644 --- a/Rules/Strings.Designer.cs +++ b/Rules/Strings.Designer.cs @@ -421,7 +421,7 @@ internal static string AvoidUsernameAndPasswordParamsCommonName { } /// - /// Looks up a localized string similar to Functions should only take in a credential parameter of type PSCredential or has a CredentialAttribute instead of username and password parameters.. + /// Looks up a localized string similar to Functions should only take in a credential parameter of type PSCredential with CredentialAttribute instead of username and password parameters.. /// internal static string AvoidUsernameAndPasswordParamsDescription { get { @@ -430,7 +430,7 @@ internal static string AvoidUsernameAndPasswordParamsDescription { } /// - /// Looks up a localized string similar to Function '{0}' has both username and password parameters. A credential parameter of type PSCredential or has a CredentialAttribute should be used.. + /// Looks up a localized string similar to Function '{0}' has both username and password parameters. A credential parameter of type PSCredential with a CredentialAttribute should be used.. /// internal static string AvoidUsernameAndPasswordParamsError { get { @@ -1771,7 +1771,7 @@ internal static string UsePSCredentialTypeCommonName { } /// - /// Looks up a localized string similar to Checks that cmdlets that have a Credential parameter accept PSCredential or has a CredentialAttribute. This comes from the PowerShell teams best practices.. + /// Looks up a localized string similar to Checks that cmdlets that have a Credential parameter accept PSCredential with CredentialAttribute. This comes from the PowerShell teams best practices.. /// internal static string UsePSCredentialTypeDescription { get { @@ -1780,7 +1780,7 @@ internal static string UsePSCredentialTypeDescription { } /// - /// Looks up a localized string similar to The Credential parameter in '{0}' must be of the type PSCredential or has a CredentialAttribute.. + /// Looks up a localized string similar to The Credential parameter in '{0}' must be of the type PSCredential with CredentialAttribute.. /// internal static string UsePSCredentialTypeError { get { @@ -1789,7 +1789,7 @@ internal static string UsePSCredentialTypeError { } /// - /// Looks up a localized string similar to The Credential parameter in a found script block must be of the type PSCredential or has a CredentialAttribute.. + /// Looks up a localized string similar to The Credential parameter in a found script block must be of the type PSCredential with CredentialAttribute.. /// internal static string UsePSCredentialTypeErrorSB { get { diff --git a/Rules/Strings.resx b/Rules/Strings.resx index b4150441a..d27bbb8d7 100644 --- a/Rules/Strings.resx +++ b/Rules/Strings.resx @@ -217,13 +217,13 @@ One Char - Checks that cmdlets that have a Credential parameter accept PSCredential or has a CredentialAttribute. This comes from the PowerShell teams best practices. + Checks that cmdlets that have a Credential parameter accept PSCredential with CredentialAttribute. This comes from the PowerShell teams best practices. - The Credential parameter in '{0}' must be of the type PSCredential or has a CredentialAttribute. + The Credential parameter in '{0}' must be of the type PSCredential with CredentialAttribute. - The Credential parameter in a found script block must be of the type PSCredential or has a CredentialAttribute. + The Credential parameter in a found script block must be of the type PSCredential with CredentialAttribute. PSCredential @@ -511,10 +511,10 @@ Avoid Using Username and Password Parameters - Functions should only take in a credential parameter of type PSCredential or has a CredentialAttribute instead of username and password parameters. + Functions should only take in a credential parameter of type PSCredential with CredentialAttribute instead of username and password parameters. - Function '{0}' has both username and password parameters. A credential parameter of type PSCredential or has a CredentialAttribute should be used. + Function '{0}' has both username and password parameters. A credential parameter of type PSCredential with a CredentialAttribute should be used. AvoidUsingUserNameAndPassWordParams diff --git a/Rules/UsePSCredentialType.cs b/Rules/UsePSCredentialType.cs index df19a028a..57e4309a0 100644 --- a/Rules/UsePSCredentialType.cs +++ b/Rules/UsePSCredentialType.cs @@ -11,6 +11,7 @@ // using System; +using System.Reflection; using System.Linq; using System.Collections.Generic; using System.Management.Automation; @@ -51,9 +52,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) { foreach (ParameterAst parameter in funcDefAst.Parameters) { - if (parameter.Name.VariablePath.UserPath.Equals("Credential", StringComparison.OrdinalIgnoreCase) - && parameter.StaticType != typeof(PSCredential) - && !parameter.Attributes.Any(paramAttribute => paramAttribute.TypeName.GetReflectionType() == typeof(CredentialAttribute))) + if (WrongCredentialUsage(parameter)) { yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.UsePSCredentialTypeError, funcName), funcDefAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName); } @@ -64,9 +63,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) { foreach (ParameterAst parameter in funcDefAst.Body.ParamBlock.Parameters) { - if (parameter.Name.VariablePath.UserPath.Equals("Credential", StringComparison.OrdinalIgnoreCase) - && parameter.StaticType != typeof(PSCredential) - && !parameter.Attributes.Any(paramAttribute => paramAttribute.TypeName.GetReflectionType() == typeof(CredentialAttribute))) + if (WrongCredentialUsage(parameter)) { yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.UsePSCredentialTypeError, funcName), funcDefAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName); } @@ -80,9 +77,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) { foreach (ParameterAst parameter in scriptBlockAst.ParamBlock.Parameters) { - if (parameter.Name.VariablePath.UserPath.Equals("Credential", StringComparison.OrdinalIgnoreCase) - && parameter.StaticType != typeof(PSCredential) - && !parameter.Attributes.Any(paramAttribute => paramAttribute.TypeName.GetReflectionType() == typeof(CredentialAttribute))) + if (WrongCredentialUsage(parameter)) { yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.UsePSCredentialTypeErrorSB), scriptBlockAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName); } @@ -91,6 +86,24 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) } } + private bool WrongCredentialUsage(ParameterAst parameter) + { + if (parameter.Name.VariablePath.UserPath.Equals("Credential", StringComparison.OrdinalIgnoreCase)) + { + TypeInfo paramType = (TypeInfo)parameter.StaticType; + + if ((paramType == typeof(PSCredential) || (paramType.IsArray && paramType.GetElementType() == typeof(PSCredential))) + && parameter.Attributes.Any(paramAttribute => paramAttribute.TypeName.GetReflectionType() == typeof(CredentialAttribute))) + { + return false; + } + + return true; + } + + return false; + } + /// /// GetName: Retrieves the name of this rule. /// diff --git a/Tests/Rules/AvoidUserNameAndPasswordParams.tests.ps1 b/Tests/Rules/AvoidUserNameAndPasswordParams.tests.ps1 index 0f383927a..358997fe8 100644 --- a/Tests/Rules/AvoidUserNameAndPasswordParams.tests.ps1 +++ b/Tests/Rules/AvoidUserNameAndPasswordParams.tests.ps1 @@ -1,6 +1,6 @@ Import-Module PSScriptAnalyzer -$violationMessage = "Function 'Verb-Noun' has both username and password parameters. A credential parameter of type PSCredential or has a CredentialAttribute should be used." +$violationMessage = "Function 'Verb-Noun' has both username and password parameters. A credential parameter of type PSCredential with a CredentialAttribute 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/AvoidUserNameAndPasswordParamsNoViolations.ps1 b/Tests/Rules/AvoidUserNameAndPasswordParamsNoViolations.ps1 index 1ed59f853..b909d7b59 100644 --- a/Tests/Rules/AvoidUserNameAndPasswordParamsNoViolations.ps1 +++ b/Tests/Rules/AvoidUserNameAndPasswordParamsNoViolations.ps1 @@ -8,11 +8,7 @@ function MyFunction2 ($param1, $passwords) } -function MyFunction3 ([PSCredential]$username, $passwords) -{ -} - -function MyFunction4 +function MyFunction3 { [CmdletBinding()] [Alias()] @@ -24,10 +20,11 @@ function MyFunction4 ValueFromPipelineByPropertyName=$true, Position=0)] [System.Management.Automation.CredentialAttribute()] + [pscredential] $UserName, # Param2 help description - [int] + [pscredential] [System.Management.Automation.CredentialAttribute()] $Password ) diff --git a/Tests/Rules/PSCredentialType.tests.ps1 b/Tests/Rules/PSCredentialType.tests.ps1 index 8e09ea6db..ccf13b6ef 100644 --- a/Tests/Rules/PSCredentialType.tests.ps1 +++ b/Tests/Rules/PSCredentialType.tests.ps1 @@ -1,5 +1,5 @@ Import-Module PSScriptAnalyzer -$violationMessage = "The Credential parameter in 'Credential' must be of the type PSCredential or has a CredentialAttribute." +$violationMessage = "The Credential parameter in 'Credential' must be of the type PSCredential with CredentialAttribute." $violationName = "PSUsePSCredentialType" $directory = Split-Path -Parent $MyInvocation.MyCommand.Path $violations = Invoke-ScriptAnalyzer $directory\PSCredentialType.ps1 | Where-Object {$_.RuleName -eq $violationName} diff --git a/Tests/Rules/PSCredentialTypeNoViolations.ps1 b/Tests/Rules/PSCredentialTypeNoViolations.ps1 index 62368066d..b907929b8 100644 --- a/Tests/Rules/PSCredentialTypeNoViolations.ps1 +++ b/Tests/Rules/PSCredentialTypeNoViolations.ps1 @@ -1,8 +1,4 @@ -function Credential([pscredential]$credential) { - -} - -function Credential2 +function Credential { [CmdletBinding()] [Alias()] @@ -14,6 +10,7 @@ function Credential2 ValueFromPipelineByPropertyName=$true, Position=0)] [System.Management.Automation.CredentialAttribute()] + [pscredential] $Credential ) -} +} \ No newline at end of file From f258a87b3523cf70c3f5bb62b1bcd682b154618d Mon Sep 17 00:00:00 2001 From: Quoc Truong Date: Wed, 2 Dec 2015 13:32:37 -0800 Subject: [PATCH 36/53] Throw error instead of warning for profiles --- .../Commands/InvokeScriptAnalyzerCommand.cs | 14 ++ Engine/ScriptAnalyzer.cs | 153 ++++++++++-------- Engine/Strings.Designer.cs | 47 +++++- Engine/Strings.resx | 17 +- Tests/Engine/GlobalSuppression.ps1 | 78 ++++++++- Tests/Engine/GlobalSuppression.test.ps1 | 32 +++- Tests/Engine/WrongProfile.ps1 | 9 ++ 7 files changed, 270 insertions(+), 80 deletions(-) create mode 100644 Tests/Engine/WrongProfile.ps1 diff --git a/Engine/Commands/InvokeScriptAnalyzerCommand.cs b/Engine/Commands/InvokeScriptAnalyzerCommand.cs index 5136555ed..dd9e2542d 100644 --- a/Engine/Commands/InvokeScriptAnalyzerCommand.cs +++ b/Engine/Commands/InvokeScriptAnalyzerCommand.cs @@ -169,6 +169,8 @@ public string Configuration } private string configuration; + private bool stopProcessing; + #endregion Parameters #region Overrides @@ -181,6 +183,12 @@ protected override void BeginProcessing() string[] rulePaths = Helper.ProcessCustomRulePaths(customRulePath, this.SessionState, recurseCustomRulePath); + if (!ScriptAnalyzer.Instance.ParseProfile(this.configuration, this.SessionState.Path, this)) + { + stopProcessing = true; + return; + } + ScriptAnalyzer.Instance.Initialize( this, rulePaths, @@ -196,6 +204,12 @@ protected override void BeginProcessing() /// protected override void ProcessRecord() { + if (stopProcessing) + { + stopProcessing = false; + return; + } + if (String.Equals(this.ParameterSetName, "File", StringComparison.OrdinalIgnoreCase)) { // throws Item Not Found Exception diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index c8f797a1c..ce3310985 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -151,49 +151,25 @@ public void Initialize( profile); } - private void Initialize( - IOutputWriter outputWriter, - PathIntrinsics path, - CommandInvocationIntrinsics invokeCommand, - string[] customizedRulePath, - string[] includeRuleNames, - string[] excludeRuleNames, - string[] severity, - bool suppressedOnly = false, - string profile = null) + internal bool ParseProfile(string profile, PathIntrinsics path, IOutputWriter writer) { - if (outputWriter == null) - { - throw new ArgumentNullException("outputWriter"); - } - - this.outputWriter = outputWriter; - - #region Verifies rule extensions and loggers path - - List paths = this.GetValidCustomRulePaths(customizedRulePath, path); + IEnumerable includeRuleList = new List(); + IEnumerable excludeRuleList = new List(); + IEnumerable severityList = new List(); - #endregion - - #region Initializes Rules - - this.severity = severity; - this.suppressedOnly = suppressedOnly; - this.includeRule = includeRuleNames; - this.excludeRule = excludeRuleNames; - this.includeRegexList = new List(); - this.excludeRegexList = new List(); + bool hasError = false; - #region ParseProfile if (!String.IsNullOrWhiteSpace(profile)) { try - { + { profile = path.GetResolvedPSPathFromPSPath(profile).First().Path; } catch { - this.outputWriter.WriteWarning(string.Format(CultureInfo.CurrentCulture, Strings.FileNotFound, profile)); + writer.WriteError(new ErrorRecord(new FileNotFoundException(string.Format(CultureInfo.CurrentCulture, Strings.FileNotFound, profile)), + Strings.ConfigurationFileNotFound, ErrorCategory.ResourceUnavailable, profile)); + hasError = true; } if (File.Exists(profile)) @@ -206,7 +182,9 @@ private void Initialize( // no hashtable, raise warning if (hashTableAsts.Count() == 0) { - this.outputWriter.WriteWarning(string.Format(CultureInfo.CurrentCulture, Strings.InvalidProfile, profile)); + writer.WriteError(new ErrorRecord(new ArgumentException(string.Format(CultureInfo.CurrentCulture, Strings.InvalidProfile, profile)), + Strings.ConfigurationFileHasNoHashTable, ErrorCategory.ResourceUnavailable, profile)); + hasError = true; } else { @@ -217,8 +195,9 @@ private void Initialize( if (!(kvp.Item1 is StringConstantExpressionAst)) { // first item (the key) should be a string - this.outputWriter.WriteWarning( - string.Format(CultureInfo.CurrentCulture, Strings.WrongKeyFormat, kvp.Item1.Extent.StartLineNumber, kvp.Item1.Extent.StartColumnNumber, profile)); + 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; } @@ -241,10 +220,10 @@ private void Initialize( // Statements property is never null if (arrayExp.SubExpression != null) { - StatementAst stateAst = arrayExp.SubExpression.Statements.First(); + StatementAst stateAst = arrayExp.SubExpression.Statements.FirstOrDefault(); if (stateAst != null && stateAst is PipelineAst) { - CommandBaseAst cmdBaseAst = (stateAst as PipelineAst).PipelineElements.First(); + CommandBaseAst cmdBaseAst = (stateAst as PipelineAst).PipelineElements.FirstOrDefault(); if (cmdBaseAst != null && cmdBaseAst is CommandExpressionAst) { CommandExpressionAst cmdExpAst = cmdBaseAst as CommandExpressionAst; @@ -268,8 +247,9 @@ private void Initialize( // all the values in the array needs to be string if (!(element is StringConstantExpressionAst)) { - this.outputWriter.WriteWarning( - string.Format(CultureInfo.CurrentCulture, Strings.WrongValueFormat, element.Extent.StartLineNumber, element.Extent.StartColumnNumber, profile)); + 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; } @@ -281,46 +261,30 @@ private void Initialize( if (rhsList.Count == 0) { - this.outputWriter.WriteWarning( - string.Format(CultureInfo.CurrentCulture, Strings.WrongValueFormat, kvp.Item2.Extent.StartLineNumber, kvp.Item2.Extent.StartColumnNumber, profile)); - break; + 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; } - switch ((kvp.Item1 as StringConstantExpressionAst).Value.ToLower()) + string key = (kvp.Item1 as StringConstantExpressionAst).Value.ToLower(); + + switch (key) { case "severity": - if (this.severity == null) - { - this.severity = rhsList.ToArray(); - } - else - { - this.severity = this.severity.Union(rhsList).ToArray(); - } + severityList = severityList.Union(rhsList); break; case "includerules": - if (this.includeRule == null) - { - this.includeRule = rhsList.ToArray(); - } - else - { - this.includeRule = this.includeRule.Union(rhsList).ToArray(); - } + includeRuleList = includeRuleList.Union(rhsList); break; case "excluderules": - if (this.excludeRule == null) - { - this.excludeRule = rhsList.ToArray(); - } - else - { - this.excludeRule = this.excludeRule.Union(rhsList).ToArray(); - } + excludeRuleList = excludeRuleList.Union(rhsList); break; default: - this.outputWriter.WriteWarning( - string.Format(CultureInfo.CurrentCulture, Strings.WrongKey, kvp.Item1.Extent.StartLineNumber, kvp.Item1.Extent.StartColumnNumber, profile)); + // keep writing warning here, we only stop processing if existing keys are wrong + writer.WriteWarning( + string.Format(CultureInfo.CurrentCulture, Strings.WrongKey, key, + kvp.Item1.Extent.StartLineNumber, kvp.Item1.Extent.StartColumnNumber, profile)); break; } } @@ -328,8 +292,51 @@ private void Initialize( } } + 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 void Initialize( + IOutputWriter outputWriter, + PathIntrinsics path, + CommandInvocationIntrinsics invokeCommand, + string[] customizedRulePath, + string[] includeRuleNames, + string[] excludeRuleNames, + string[] severity, + bool suppressedOnly = false, + string profile = null) + { + if (outputWriter == null) + { + throw new ArgumentNullException("outputWriter"); + } + + this.outputWriter = outputWriter; + + #region Verifies rule extensions and loggers path + + List paths = this.GetValidCustomRulePaths(customizedRulePath, path); + #endregion + #region Initializes Rules + + this.suppressedOnly = suppressedOnly; + this.severity = this.severity == null ? severity : this.severity.Union(severity ?? new String[0]).ToArray(); + this.includeRule = this.includeRule == null ? includeRuleNames : this.includeRule.Union(includeRuleNames ?? new String[0]).ToArray(); + this.excludeRule = this.excludeRule == null ? excludeRuleNames : this.excludeRule.Union(excludeRuleNames ?? new String[0]).ToArray(); + this.includeRegexList = new List(); + this.excludeRegexList = new List(); + //Check wild card input for the Include/ExcludeRules and create regex match patterns if (this.includeRule != null) { @@ -339,6 +346,7 @@ private void Initialize( this.includeRegexList.Add(includeRegex); } } + if (this.excludeRule != null) { foreach (string rule in excludeRule) @@ -1446,7 +1454,10 @@ public IEnumerable AnalyzeSyntaxTree( if (severity != null) { var diagSeverity = severity.Select(item => Enum.Parse(typeof(DiagnosticSeverity), item, true)); - diagnosticsList = diagnostics.Where(item => diagSeverity.Contains(item.Severity)); + if (diagSeverity.Count() != 0) + { + diagnosticsList = diagnostics.Where(item => diagSeverity.Contains(item.Severity)); + } } return this.suppressedOnly ? diff --git a/Engine/Strings.Designer.cs b/Engine/Strings.Designer.cs index 619490de5..5b6596ba1 100644 --- a/Engine/Strings.Designer.cs +++ b/Engine/Strings.Designer.cs @@ -87,6 +87,51 @@ internal static string CommandInfoNotFound { } } + /// + /// Looks up a localized string similar to ConfigurationFileHasNoHashTable. + /// + internal static string ConfigurationFileHasNoHashTable { + get { + return ResourceManager.GetString("ConfigurationFileHasNoHashTable", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ConfigurationFileNotFound. + /// + internal static string ConfigurationFileNotFound { + get { + return ResourceManager.GetString("ConfigurationFileNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ConfigurationKeyNotAString. + /// + internal static string ConfigurationKeyNotAString { + get { + return ResourceManager.GetString("ConfigurationKeyNotAString", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ConfigurationValueNotAString. + /// + internal static string ConfigurationValueNotAString { + get { + return ResourceManager.GetString("ConfigurationValueNotAString", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ConfigurationValueWrongFormat. + /// + internal static string ConfigurationValueWrongFormat { + get { + return ResourceManager.GetString("ConfigurationValueWrongFormat", resourceCulture); + } + } + /// /// Looks up a localized string similar to Writes all diagnostics to WriteObject.. /// @@ -322,7 +367,7 @@ internal static string VerboseScriptDefinitionMessage { } /// - /// Looks up a localized string similar to {0} is not a valid key in the profile hashtable: line {0} column {1} in file {2}. + /// Looks up a localized string similar to {0} is not a valid key in the profile hashtable: line {1} column {2} in file {3}. This key value pair will not be processed.. /// internal static string WrongKey { get { diff --git a/Engine/Strings.resx b/Engine/Strings.resx index 9de1cef68..c998d8b36 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 profile hashtable: line {0} column {1} in file {2} + {0} is not a valid key in the profile hashtable: line {1} column {2} in file {3}. This key value pair will not be processed. Key in the profile hashtable should be a string: line {0} column {1} in file {2} @@ -216,4 +216,19 @@ Analyzing Script Definition. + + ConfigurationFileHasNoHashTable + + + ConfigurationFileNotFound + + + ConfigurationKeyNotAString + + + ConfigurationValueNotAString + + + ConfigurationValueWrongFormat + \ No newline at end of file diff --git a/Tests/Engine/GlobalSuppression.ps1 b/Tests/Engine/GlobalSuppression.ps1 index a777debc5..910007e7d 100644 --- a/Tests/Engine/GlobalSuppression.ps1 +++ b/Tests/Engine/GlobalSuppression.ps1 @@ -4,4 +4,80 @@ gcm "blah" $a = "//internal" -Get-Alias -ComputerName dfd \ No newline at end of file +Get-Alias -ComputerName dfd + +<# +.Synopsis + Short description +.DESCRIPTION + Long description +.EXAMPLE + Example of how to use this cmdlet +.EXAMPLE + Another example of how to use this cmdlet +.INPUTS + Inputs to this cmdlet (if any) +.OUTPUTS + Output from this cmdlet (if any) +.NOTES + General notes +.COMPONENT + The component this cmdlet belongs to +.ROLE + The role this cmdlet belongs to +.FUNCTIONALITY + The functionality that best describes this cmdlet +#> +function Verb-Noun +{ + [CmdletBinding(DefaultParameterSetName='Parameter Set 1', + SupportsShouldProcess=$true, + PositionalBinding=$false, + HelpUri = 'http://www.microsoft.com/', + ConfirmImpact='Medium')] + [Alias()] + Param + ( + # Param1 help description + [Parameter(Mandatory=$true, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + ValueFromRemainingArguments=$false, + Position=0, + ParameterSetName='Parameter Set 1')] + [ValidateNotNull()] + [ValidateNotNullOrEmpty()] + [ValidateCount(0,5)] + [ValidateSet("sun", "moon", "earth")] + [Alias("p1")] + $Param1, + + # Param2 help description + [Parameter(ParameterSetName='Parameter Set 1')] + [AllowNull()] + [AllowEmptyCollection()] + [AllowEmptyString()] + [ValidateScript({$true})] + [ValidateRange(0,5)] + [int] + $Param2, + + # Param3 help description + [Parameter(ParameterSetName='Another Parameter Set')] + [ValidatePattern("[a-z]*")] + [ValidateLength(0,15)] + [String] + $Param3 + ) + + Begin + { + } + Process + { + return 1 + } + End + { + } +} \ No newline at end of file diff --git a/Tests/Engine/GlobalSuppression.test.ps1 b/Tests/Engine/GlobalSuppression.test.ps1 index e5aeb87cd..e4eb88050 100644 --- a/Tests/Engine/GlobalSuppression.test.ps1 +++ b/Tests/Engine/GlobalSuppression.test.ps1 @@ -46,18 +46,38 @@ Describe "GlobalSuppression" { } Context "Severity" { - It "Raises 1 violation for internal url without profile" { - $withoutProfile = $violations | Where-Object { $_.RuleName -eq "PSAvoidUsingInternalURLs" } + It "Raises 1 violation for use output type correctly without profile" { + $withoutProfile = $violations | Where-Object { $_.RuleName -eq "PSUseOutputTypeCorrectly" } $withoutProfile.Count | Should Be 1 - $withoutProfile = $violationsUsingScriptDefinition | Where-Object { $_.RuleName -eq "PSAvoidUsingInternalURLs" } + $withoutProfile = $violationsUsingScriptDefinition | Where-Object { $_.RuleName -eq "PSUseOutputTypeCorrectly" } $withoutProfile.Count | Should Be 1 } - It "Does not raise any violations for internal urls with profile" { - $withProfile = $suppression | Where-Object { $_.RuleName -eq "PSAvoidUsingInternalURLs" } + It "Does not raise any violations for use output type correctly with profile" { + $withProfile = $suppression | Where-Object { $_.RuleName -eq "PSUseOutputTypeCorrectly" } $withProfile.Count | Should be 0 - $withProfile = $suppressionUsingScriptDefinition | Where-Object { $_.RuleName -eq "PSAvoidUsingInternalURLs" } + $withProfile = $suppressionUsingScriptDefinition | Where-Object { $_.RuleName -eq "PSUseOutputTypeCorrectly" } $withProfile.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.Count | should be 0 + $Error[0].FullyQualifiedErrorId | should match "ConfigurationFileNotFound,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.Count | should be 0 + $Error[0].FullyQualifiedErrorId | should match "ConfigurationFileHasNoHashTable,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.Count | should be 0 + $Error[0].FullyQualifiedErrorId | should match "ConfigurationValueWrongFormat,Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands.InvokeScriptAnalyzerCommand" + } + } } \ No newline at end of file diff --git a/Tests/Engine/WrongProfile.ps1 b/Tests/Engine/WrongProfile.ps1 new file mode 100644 index 000000000..b11fbd07b --- /dev/null +++ b/Tests/Engine/WrongProfile.ps1 @@ -0,0 +1,9 @@ +@{ + Severity='Warning' + IncludeRules=@('PSAvoidUsingCmdletAliases', + 'PSAvoidUsingPositionalParameters', + 'PSAvoidUsingInternalURLs' + 'PSAvoidUninitializedVariable') + ExcludeRules=@(1) + Exclude=@('blah') +} \ No newline at end of file From ac11a2d7fa1116c8c521914b125587df7cb26d31 Mon Sep 17 00:00:00 2001 From: Quoc Truong Date: Wed, 2 Dec 2015 15:05:27 -0800 Subject: [PATCH 37/53] Change remaining warning to error --- Engine/ScriptAnalyzer.cs | 8 ++++---- Engine/Strings.Designer.cs | 11 ++++++++++- Engine/Strings.resx | 5 ++++- Tests/Engine/GlobalSuppression.test.ps1 | 2 +- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index ce3310985..ae4dde1de 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -281,10 +281,10 @@ internal bool ParseProfile(string profile, PathIntrinsics path, IOutputWriter wr excludeRuleList = excludeRuleList.Union(rhsList); break; default: - // keep writing warning here, we only stop processing if existing keys are wrong - writer.WriteWarning( - string.Format(CultureInfo.CurrentCulture, Strings.WrongKey, key, - kvp.Item1.Extent.StartLineNumber, kvp.Item1.Extent.StartColumnNumber, profile)); + 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; } } diff --git a/Engine/Strings.Designer.cs b/Engine/Strings.Designer.cs index 5b6596ba1..a0966e77a 100644 --- a/Engine/Strings.Designer.cs +++ b/Engine/Strings.Designer.cs @@ -367,7 +367,16 @@ internal static string VerboseScriptDefinitionMessage { } /// - /// Looks up a localized string similar to {0} is not a valid key in the profile hashtable: line {1} column {2} in file {3}. This key value pair will not be processed.. + /// Looks up a localized string similar to WrongConfigurationKey. + /// + internal static string WrongConfigurationKey { + get { + return ResourceManager.GetString("WrongConfigurationKey", resourceCulture); + } + } + + /// + /// 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.. /// internal static string WrongKey { get { diff --git a/Engine/Strings.resx b/Engine/Strings.resx index c998d8b36..7907b2efa 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 profile hashtable: line {1} column {2} in file {3}. This key value pair will not be processed. + {0} is not a valid key in the profile 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} @@ -231,4 +231,7 @@ ConfigurationValueWrongFormat + + WrongConfigurationKey + \ No newline at end of file diff --git a/Tests/Engine/GlobalSuppression.test.ps1 b/Tests/Engine/GlobalSuppression.test.ps1 index e4eb88050..9608071ed 100644 --- a/Tests/Engine/GlobalSuppression.test.ps1 +++ b/Tests/Engine/GlobalSuppression.test.ps1 @@ -77,7 +77,7 @@ Describe "GlobalSuppression" { It "Raises Error for wrong profile" { $invokeWithError = Invoke-ScriptAnalyzer "$directory\GlobalSuppression.ps1" -Configuration "$directory\WrongProfile.ps1" -ErrorAction SilentlyContinue $invokeWithError.Count | should be 0 - $Error[0].FullyQualifiedErrorId | should match "ConfigurationValueWrongFormat,Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands.InvokeScriptAnalyzerCommand" + $Error[0].FullyQualifiedErrorId | should match "WrongConfigurationKey,Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands.InvokeScriptAnalyzerCommand" } } } \ No newline at end of file From dfb70f6c8a09803599925f9520b16e877866a908 Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Wed, 2 Dec 2015 16:16:37 -0800 Subject: [PATCH 38/53] Exclude Default RuleSet when using with CustomRule feature --- Engine/ScriptAnalyzer.cs | 9 +++++-- Engine/Strings.Designer.cs | 2 +- Engine/Strings.resx | 2 +- Tests/Engine/GetScriptAnalyzerRule.tests.ps1 | 20 +++++++++------- Tests/Engine/InvokeScriptAnalyzer.tests.ps1 | 25 ++++++++++---------- 5 files changed, 33 insertions(+), 25 deletions(-) diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index c8f797a1c..d83d402ad 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -350,7 +350,7 @@ private void Initialize( try { - this.LoadRules(this.validationResults, invokeCommand); + this.LoadRules(this.validationResults, invokeCommand, null == customizedRulePath ? true : false); } catch (Exception ex) { @@ -418,7 +418,7 @@ private List GetValidCustomRulePaths(string[] customizedRulePath, PathIn return paths; } - private void LoadRules(Dictionary> result, CommandInvocationIntrinsics invokeCommand) + private void LoadRules(Dictionary> result, CommandInvocationIntrinsics invokeCommand, bool loadBuiltInRules) { List paths = new List(); @@ -472,6 +472,11 @@ private void LoadRules(Dictionary> result, CommandInvocatio } } + if (!loadBuiltInRules) + { + this.ScriptRules = null; + } + // Gets external rules. if (result.ContainsKey("ValidModPaths") && result["ValidModPaths"].Count > 0) { diff --git a/Engine/Strings.Designer.cs b/Engine/Strings.Designer.cs index 619490de5..d2cea769b 100644 --- a/Engine/Strings.Designer.cs +++ b/Engine/Strings.Designer.cs @@ -223,7 +223,7 @@ internal static string RuleErrorMessage { } /// - /// Looks up a localized string similar to Cannot find analyzer rules.. + /// Looks up a localized string similar to Cannot find ScriptAnalyzer rules in the specified path. /// internal static string RulesNotFound { get { diff --git a/Engine/Strings.resx b/Engine/Strings.resx index 9de1cef68..f3e571553 100644 --- a/Engine/Strings.resx +++ b/Engine/Strings.resx @@ -160,7 +160,7 @@ RULE_ERROR - Cannot find analyzer rules. + Cannot find ScriptAnalyzer rules in the specified path Suppression Message Attribute error at line {0} in {1} : {2} diff --git a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 index 96b793fc8..a48de4997 100644 --- a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 +++ b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 @@ -91,24 +91,28 @@ Describe "Test RuleExtension" { It "with Name of a built-in rules" { $ruleExtension = Get-ScriptAnalyzerRule -CustomizedRulePath $directory\CommunityAnalyzerRules\CommunityAnalyzerRules.psm1 -Name $singularNouns - $ruleExtension.Count | Should Be 1 - $ruleExtension[0].RuleName | Should Be $singularNouns + $ruleExtension.Count | Should Be 0 } It "with Names of built-in, DSC and non-built-in rules" { $ruleExtension = Get-ScriptAnalyzerRule -CustomizedRulePath $directory\CommunityAnalyzerRules\CommunityAnalyzerRules.psm1 -Name $singularNouns, $measureRequired, $dscIdentical - $ruleExtension.Count | Should be 3 + $ruleExtension.Count | Should be 1 ($ruleExtension | Where-Object {$_.RuleName -eq $measureRequired}).Count | Should Be 1 - ($ruleExtension | Where-Object {$_.RuleName -eq $singularNouns}).Count | Should Be 1 - ($ruleExtension | Where-Object {$_.RuleName -eq $dscIdentical}).Count | Should Be 1 + ($ruleExtension | Where-Object {$_.RuleName -eq $singularNouns}).Count | Should Be 0 + ($ruleExtension | Where-Object {$_.RuleName -eq $dscIdentical}).Count | Should Be 0 } } Context "When used incorrectly" { It "file cannot be found" { - $wrongFile = Get-ScriptAnalyzerRule -CustomizedRulePath "This is a wrong rule" 3>&1 - ($wrongFile | Select-Object -First 1) | Should Match "Cannot find rule extension 'This is a wrong rule'." - ($wrongFile | Where-Object {$_.RuleName -eq $singularNouns}).Count | Should Be 1 + try + { + Get-ScriptAnalyzerRule -CustomizedRulePath "Invalid CustomRulePath" + } + catch + { + $Error[0].FullyQualifiedErrorId | should match "Cannot find ScriptAnalyzer rules in the specified path,Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands.GetScriptAnalyzerRuleCommand" + } } } diff --git a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 index ab1d5dc7b..c72380527 100644 --- a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 +++ b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 @@ -318,19 +318,18 @@ Describe "Test CustomizedRulePath" { } Context "When used incorrectly" { - It "file cannot be found" { - $wrongRule = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomizedRulePath "This is a wrong rule" 3>&1 | Select-Object -First 1 - - if ($testingLibraryUsage) - { - # Special case for library usage testing: warning output written - # with PSHost.UI.WriteWarningLine does not get redirected correctly - # so we can't use this approach for checking the warning message. - # Instead, reach into the test IOutputWriter implementation to find it. - $wrongRule = $testOutputWriter.MostRecentWarningMessage - } - - $wrongRule | Should Match "Cannot find rule extension 'This is a wrong rule'." + It "file cannot be found" { + try + { + Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomRulePath "Invalid CustomRulePath" + } + catch + { + if (-not $testingLibraryUsage) + { + $Error[0].FullyQualifiedErrorId | should match "Cannot find ScriptAnalyzer rules in the specified path,Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands.InvokeScriptAnalyzerCommand" + } + } } } } \ No newline at end of file From 03beec11e5fedfe4805a26715f983a3aa6f4d518 Mon Sep 17 00:00:00 2001 From: Quoc Truong Date: Thu, 3 Dec 2015 12:43:41 -0800 Subject: [PATCH 39/53] Enforce that pscredential attribute must come before credentialattribute --- Rules/AvoidUserNameAndPasswordParams.cs | 12 ++++++--- Rules/Strings.Designer.cs | 10 +++---- Rules/Strings.resx | 10 +++---- Rules/UsePSCredentialType.cs | 15 ++++++++--- .../Rules/AvoidUserNameAndPasswordParams.ps1 | 26 ++++++++++++++++++- .../AvoidUserNameAndPasswordParams.tests.ps1 | 8 +++--- ...dUserNameAndPasswordParamsNoViolations.ps1 | 2 +- Tests/Rules/PSCredentialType.ps1 | 18 +++++++++++++ Tests/Rules/PSCredentialType.tests.ps1 | 8 +++--- Tests/Rules/PSCredentialTypeNoViolations.ps1 | 2 +- 10 files changed, 83 insertions(+), 28 deletions(-) diff --git a/Rules/AvoidUserNameAndPasswordParams.cs b/Rules/AvoidUserNameAndPasswordParams.cs index 422b58080..4fd643d46 100644 --- a/Rules/AvoidUserNameAndPasswordParams.cs +++ b/Rules/AvoidUserNameAndPasswordParams.cs @@ -53,12 +53,16 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) // Iterrates all ParamAsts and check if their names are on the list. foreach (ParameterAst paramAst in paramAsts) { - TypeInfo paramType = (TypeInfo)paramAst.StaticType; + var psCredentialType = paramAst.Attributes.FirstOrDefault(paramAttribute => + (paramAttribute.TypeName.IsArray && (paramAttribute.TypeName as ArrayTypeName).ElementType.GetReflectionType() == typeof(PSCredential)) + || paramAttribute.TypeName.GetReflectionType() == typeof(PSCredential)); + + var credentialAttribute = paramAst.Attributes.FirstOrDefault(paramAttribute => paramAttribute.TypeName.GetReflectionType() == typeof(CredentialAttribute)); + String paramName = paramAst.Name.VariablePath.ToString(); - // if this is pscredential type with credential attribute, skip - if ((paramType == typeof(PSCredential) || (paramType.IsArray && paramType.GetElementType() == typeof (PSCredential))) - && paramAst.Attributes.Any(paramAttribute => paramAttribute.TypeName.GetReflectionType() == typeof(CredentialAttribute))) + // if this is pscredential type with credential attribute where pscredential type comes first + if (psCredentialType != null && credentialAttribute != null && psCredentialType.Extent.EndOffset < credentialAttribute.Extent.StartOffset) { continue; } diff --git a/Rules/Strings.Designer.cs b/Rules/Strings.Designer.cs index 22b8a9191..55a65fd0a 100644 --- a/Rules/Strings.Designer.cs +++ b/Rules/Strings.Designer.cs @@ -421,7 +421,7 @@ internal static string AvoidUsernameAndPasswordParamsCommonName { } /// - /// Looks up a localized string similar to Functions should only take in a credential parameter of type PSCredential with CredentialAttribute instead of username and password parameters.. + /// Looks up a localized string similar to Functions should only take in a credential parameter of type PSCredential with CredentialAttribute where PSCredential comes before CredentialAttribute instead of username and password parameters.. /// internal static string AvoidUsernameAndPasswordParamsDescription { get { @@ -430,7 +430,7 @@ internal static string AvoidUsernameAndPasswordParamsDescription { } /// - /// Looks up a localized string similar to Function '{0}' has both username and password parameters. A credential parameter of type PSCredential with a CredentialAttribute should be used.. + /// Looks up a localized string similar to Function '{0}' has both username and password parameters. A credential parameter of type PSCredential with a CredentialAttribute where PSCredential comes before CredentialAttribute should be used.. /// internal static string AvoidUsernameAndPasswordParamsError { get { @@ -1771,7 +1771,7 @@ internal static string UsePSCredentialTypeCommonName { } /// - /// Looks up a localized string similar to Checks that cmdlets that have a Credential parameter accept PSCredential with CredentialAttribute. This comes from the PowerShell teams best practices.. + /// Looks up a localized string similar to Checks that cmdlets that have a Credential parameter accept PSCredential with CredentialAttribute where PSCredential comes before CredentialAttribute.. This comes from the PowerShell teams best practices.. /// internal static string UsePSCredentialTypeDescription { get { @@ -1780,7 +1780,7 @@ internal static string UsePSCredentialTypeDescription { } /// - /// Looks up a localized string similar to The Credential parameter in '{0}' must be of the type PSCredential with CredentialAttribute.. + /// Looks up a localized string similar to The Credential parameter in '{0}' must be of the type PSCredential with CredentialAttribute where PSCredential comes before CredentialAttribute.. /// internal static string UsePSCredentialTypeError { get { @@ -1789,7 +1789,7 @@ internal static string UsePSCredentialTypeError { } /// - /// Looks up a localized string similar to The Credential parameter in a found script block must be of the type PSCredential with CredentialAttribute.. + /// Looks up a localized string similar to The Credential parameter in a found script block must be of the type PSCredential with CredentialAttribute where PSCredential comes before CredentialAttribute.. /// internal static string UsePSCredentialTypeErrorSB { get { diff --git a/Rules/Strings.resx b/Rules/Strings.resx index d27bbb8d7..319d72d27 100644 --- a/Rules/Strings.resx +++ b/Rules/Strings.resx @@ -217,13 +217,13 @@ One Char - Checks that cmdlets that have a Credential parameter accept PSCredential with CredentialAttribute. This comes from the PowerShell teams best practices. + Checks that cmdlets that have a Credential parameter accept PSCredential with CredentialAttribute where PSCredential comes before CredentialAttribute.. This comes from the PowerShell teams best practices. - The Credential parameter in '{0}' must be of the type PSCredential with CredentialAttribute. + The Credential parameter in '{0}' must be of the type PSCredential with CredentialAttribute where PSCredential comes before CredentialAttribute. - The Credential parameter in a found script block must be of the type PSCredential with CredentialAttribute. + The Credential parameter in a found script block must be of the type PSCredential with CredentialAttribute where PSCredential comes before CredentialAttribute. PSCredential @@ -511,10 +511,10 @@ Avoid Using Username and Password Parameters - Functions should only take in a credential parameter of type PSCredential with CredentialAttribute instead of username and password parameters. + Functions should only take in a credential parameter of type PSCredential with CredentialAttribute where PSCredential comes before CredentialAttribute instead of username and password parameters. - Function '{0}' has both username and password parameters. A credential parameter of type PSCredential with a CredentialAttribute should be used. + Function '{0}' has both username and password parameters. A credential parameter of type PSCredential with a CredentialAttribute where PSCredential comes before CredentialAttribute should be used. AvoidUsingUserNameAndPassWordParams diff --git a/Rules/UsePSCredentialType.cs b/Rules/UsePSCredentialType.cs index 57e4309a0..bddc3bf66 100644 --- a/Rules/UsePSCredentialType.cs +++ b/Rules/UsePSCredentialType.cs @@ -73,6 +73,12 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) foreach (ScriptBlockAst scriptBlockAst in scriptBlockAsts) { + // check for the case where it's parent is function, in that case we already processed above + if (scriptBlockAst.Parent != null && scriptBlockAst.Parent is FunctionDefinitionAst) + { + continue; + } + if (scriptBlockAst.ParamBlock != null && scriptBlockAst.ParamBlock.Parameters != null) { foreach (ParameterAst parameter in scriptBlockAst.ParamBlock.Parameters) @@ -90,10 +96,13 @@ private bool WrongCredentialUsage(ParameterAst parameter) { if (parameter.Name.VariablePath.UserPath.Equals("Credential", StringComparison.OrdinalIgnoreCase)) { - TypeInfo paramType = (TypeInfo)parameter.StaticType; + var psCredentialType = parameter.Attributes.FirstOrDefault(paramAttribute => (paramAttribute.TypeName.IsArray && (paramAttribute.TypeName as ArrayTypeName).ElementType.GetReflectionType() == typeof(PSCredential)) + || paramAttribute.TypeName.GetReflectionType() == typeof(PSCredential)); + + var credentialAttribute = parameter.Attributes.FirstOrDefault(paramAttribute => paramAttribute.TypeName.GetReflectionType() == typeof(CredentialAttribute)); - if ((paramType == typeof(PSCredential) || (paramType.IsArray && paramType.GetElementType() == typeof(PSCredential))) - && parameter.Attributes.Any(paramAttribute => paramAttribute.TypeName.GetReflectionType() == typeof(CredentialAttribute))) + // check that both exists and pscredentialtype comes before credential attribute + if (psCredentialType != null && credentialAttribute != null && psCredentialType.Extent.EndOffset < credentialAttribute.Extent.StartOffset) { return false; } diff --git a/Tests/Rules/AvoidUserNameAndPasswordParams.ps1 b/Tests/Rules/AvoidUserNameAndPasswordParams.ps1 index b764af199..cc89dcfe3 100644 --- a/Tests/Rules/AvoidUserNameAndPasswordParams.ps1 +++ b/Tests/Rules/AvoidUserNameAndPasswordParams.ps1 @@ -31,5 +31,29 @@ } } +function MyFunction3 +{ + [CmdletBinding()] + [Alias()] + [OutputType([int])] + Param + ( + # Param1 help description + [Parameter(Mandatory=$true, + ValueFromPipelineByPropertyName=$true, + Position=0)] + [System.Management.Automation.CredentialAttribute()] + [pscredential] + $UserName, + + # Param2 help description + [System.Management.Automation.CredentialAttribute()] + [pscredential] + $Password + ) +} + function TestFunction($password, [PSCredential[]]$passwords, $username){ -} \ No newline at end of file +} + + diff --git a/Tests/Rules/AvoidUserNameAndPasswordParams.tests.ps1 b/Tests/Rules/AvoidUserNameAndPasswordParams.tests.ps1 index 358997fe8..35895ebcd 100644 --- a/Tests/Rules/AvoidUserNameAndPasswordParams.tests.ps1 +++ b/Tests/Rules/AvoidUserNameAndPasswordParams.tests.ps1 @@ -1,6 +1,6 @@ Import-Module PSScriptAnalyzer -$violationMessage = "Function 'Verb-Noun' has both username and password parameters. A credential parameter of type PSCredential with a CredentialAttribute should be used." +$violationMessage = "Function 'Verb-Noun' has both username and password parameters. A credential parameter of type PSCredential with a CredentialAttribute where PSCredential comes before CredentialAttribute should be used." $violationName = "PSAvoidUsingUserNameAndPasswordParams" $directory = Split-Path -Parent $MyInvocation.MyCommand.Path $violations = Invoke-ScriptAnalyzer $directory\AvoidUserNameAndPasswordParams.ps1 | Where-Object {$_.RuleName -eq $violationName} @@ -8,12 +8,12 @@ $noViolations = Invoke-ScriptAnalyzer $directory\AvoidUserNameAndPasswordParamsN Describe "AvoidUserNameAndPasswordParams" { Context "When there are violations" { - It "has 2 avoid username and password parameter violations" { - $violations.Count | Should Be 2 + It "has 3 avoid username and password parameter violations" { + $violations.Count | Should Be 3 } It "has the correct violation message" { - $violations[1].Message | Should Match $violationMessage + $violations[2].Message | Should Match $violationMessage } } diff --git a/Tests/Rules/AvoidUserNameAndPasswordParamsNoViolations.ps1 b/Tests/Rules/AvoidUserNameAndPasswordParamsNoViolations.ps1 index b909d7b59..2ea0db9cc 100644 --- a/Tests/Rules/AvoidUserNameAndPasswordParamsNoViolations.ps1 +++ b/Tests/Rules/AvoidUserNameAndPasswordParamsNoViolations.ps1 @@ -19,8 +19,8 @@ function MyFunction3 [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, Position=0)] - [System.Management.Automation.CredentialAttribute()] [pscredential] + [System.Management.Automation.CredentialAttribute()] $UserName, # Param2 help description diff --git a/Tests/Rules/PSCredentialType.ps1 b/Tests/Rules/PSCredentialType.ps1 index ddcae8231..3e2d9e730 100644 --- a/Tests/Rules/PSCredentialType.ps1 +++ b/Tests/Rules/PSCredentialType.ps1 @@ -1,3 +1,21 @@ function Credential([string]$credential) { +} + +# this one is wrong because pscredential should come first +function Credential2 +{ + [CmdletBinding()] + [Alias()] + [OutputType([int])] + Param + ( + # Param1 help description + [Parameter(Mandatory=$true, + ValueFromPipelineByPropertyName=$true, + Position=0)] + [System.Management.Automation.CredentialAttribute()] + [pscredential] + $Credential + ) } \ No newline at end of file diff --git a/Tests/Rules/PSCredentialType.tests.ps1 b/Tests/Rules/PSCredentialType.tests.ps1 index ccf13b6ef..ea642ce49 100644 --- a/Tests/Rules/PSCredentialType.tests.ps1 +++ b/Tests/Rules/PSCredentialType.tests.ps1 @@ -1,5 +1,5 @@ Import-Module PSScriptAnalyzer -$violationMessage = "The Credential parameter in 'Credential' must be of the type PSCredential with CredentialAttribute." +$violationMessage = "The Credential parameter in 'Credential' must be of the type PSCredential with CredentialAttribute where PSCredential comes before CredentialAttribute." $violationName = "PSUsePSCredentialType" $directory = Split-Path -Parent $MyInvocation.MyCommand.Path $violations = Invoke-ScriptAnalyzer $directory\PSCredentialType.ps1 | Where-Object {$_.RuleName -eq $violationName} @@ -7,12 +7,12 @@ $noViolations = Invoke-ScriptAnalyzer $directory\PSCredentialTypeNoViolations.ps Describe "PSCredentialType" { Context "When there are violations" { - It "has 1 PSCredential type violation" { - $violations.Count | Should Be 1 + It "has 2 PSCredential type violation" { + $violations.Count | Should Be 2 } It "has the correct description message" { - $violations.Message | Should Match $violationMessage + $violations[1].Message | Should Match $violationMessage } } diff --git a/Tests/Rules/PSCredentialTypeNoViolations.ps1 b/Tests/Rules/PSCredentialTypeNoViolations.ps1 index b907929b8..7b8f09bc0 100644 --- a/Tests/Rules/PSCredentialTypeNoViolations.ps1 +++ b/Tests/Rules/PSCredentialTypeNoViolations.ps1 @@ -9,8 +9,8 @@ [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, Position=0)] - [System.Management.Automation.CredentialAttribute()] [pscredential] + [System.Management.Automation.CredentialAttribute()] $Credential ) } \ No newline at end of file From efdfba9cd13de86e4dec932a1d49ea0755f6051d Mon Sep 17 00:00:00 2001 From: Kory Gill Date: Tue, 8 Dec 2015 20:54:10 -0800 Subject: [PATCH 40/53] Improve post-build command, fix spelling mistakes. --- Engine/Generic/AvoidCmdletGeneric.cs | 4 +- Engine/Generic/AvoidParameterGeneric.cs | 4 +- Engine/Generic/ILogger.cs | 2 +- Engine/Generic/IScriptRule.cs | 2 +- Engine/Generic/SourceType.cs | 2 +- Engine/PSScriptAnalyzer.psd1 | 2 +- Engine/ScriptAnalyzer.cs | 2 +- Engine/VariableAnalysis.cs | 2 +- Engine/VariableAnalysisBase.cs | 12 +- Engine/about_PSScriptAnalyzer.help.txt | 2 +- Rules/AvoidAlias.cs | 2 +- Rules/AvoidDefaultTrueValueSwitchParameter.cs | 2 +- Rules/AvoidEmptyCatchBlock.cs | 2 +- Rules/AvoidPositionalParameters.cs | 2 +- Rules/AvoidShouldContinueWithoutForce.cs | 2 +- Rules/AvoidUserNameAndPasswordParams.cs | 2 +- Rules/AvoidUsingPlainTextForPassword.cs | 2 +- Rules/ScriptAnalyzerBuiltinRules.csproj | 232 +++++++++--------- Rules/UseDeclaredVarsMoreThanAssignments.cs | 2 +- 19 files changed, 141 insertions(+), 141 deletions(-) diff --git a/Engine/Generic/AvoidCmdletGeneric.cs b/Engine/Generic/AvoidCmdletGeneric.cs index 5546295bf..b23d85679 100644 --- a/Engine/Generic/AvoidCmdletGeneric.cs +++ b/Engine/Generic/AvoidCmdletGeneric.cs @@ -24,7 +24,7 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic public abstract class AvoidCmdletGeneric : IScriptRule { /// - /// AnalyzeScript: Analyzes the given Ast and returns DiagnosticRecords based on the anaylsis. + /// AnalyzeScript: Analyzes the given Ast and returns DiagnosticRecords based on the analysis. /// /// The script's ast /// The name of the script file being analyzed @@ -38,7 +38,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) List cmdletNameAndAliases = Microsoft.Windows.PowerShell.ScriptAnalyzer.Helper.Instance.CmdletNameAndAliases(GetCmdletName()); - // Iterrates all CommandAsts and check the command name. + // Iterates all CommandAsts and check the command name. foreach (CommandAst cmdAst in commandAsts) { if (cmdAst.GetCommandName() == null) continue; diff --git a/Engine/Generic/AvoidParameterGeneric.cs b/Engine/Generic/AvoidParameterGeneric.cs index b61835056..2f108b07d 100644 --- a/Engine/Generic/AvoidParameterGeneric.cs +++ b/Engine/Generic/AvoidParameterGeneric.cs @@ -23,7 +23,7 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic public abstract class AvoidParameterGeneric : IScriptRule { /// - /// AnalyzeScript: Analyzes the given Ast and returns DiagnosticRecords based on the anaylsis. + /// AnalyzeScript: Analyzes the given Ast and returns DiagnosticRecords based on the analysis. /// /// The script's ast /// The name of the script file being analyzed @@ -35,7 +35,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) // Finds all CommandAsts. IEnumerable commandAsts = ast.FindAll(testAst => testAst is CommandAst, true); - // Iterrates all CommandAsts and check the condition. + // Iterates all CommandAsts and check the condition. foreach (CommandAst cmdAst in commandAsts) { if (CommandCondition(cmdAst) && cmdAst.CommandElements != null) diff --git a/Engine/Generic/ILogger.cs b/Engine/Generic/ILogger.cs index 9f0adac0a..8b620e018 100644 --- a/Engine/Generic/ILogger.cs +++ b/Engine/Generic/ILogger.cs @@ -34,7 +34,7 @@ public interface ILogger string GetName(); /// - /// GetDescription: Retrives the description of the logger. + /// GetDescription: Retrieves the description of the logger. /// /// The description of the logger string GetDescription(); diff --git a/Engine/Generic/IScriptRule.cs b/Engine/Generic/IScriptRule.cs index 15bafbe87..df7de53fa 100644 --- a/Engine/Generic/IScriptRule.cs +++ b/Engine/Generic/IScriptRule.cs @@ -25,7 +25,7 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic public interface IScriptRule : IRule { /// - /// AnalyzeScript: Analyzes the given Ast and returns DiagnosticRecords based on the anaylsis. + /// AnalyzeScript: Analyzes the given Ast and returns DiagnosticRecords based on the analysis. /// /// The script's ast /// The name of the script file being analyzed diff --git a/Engine/Generic/SourceType.cs b/Engine/Generic/SourceType.cs index dfff87f2a..df8e49843 100644 --- a/Engine/Generic/SourceType.cs +++ b/Engine/Generic/SourceType.cs @@ -23,7 +23,7 @@ public enum SourceType : uint Builtin = 0, /// - /// MANAGED: Indicates the script analyzer rule is contirbuted as a managed rule. + /// MANAGED: Indicates the script analyzer rule is contributed as a managed rule. /// Managed = 1, diff --git a/Engine/PSScriptAnalyzer.psd1 b/Engine/PSScriptAnalyzer.psd1 index c68e1d647..00f513f22 100644 --- a/Engine/PSScriptAnalyzer.psd1 +++ b/Engine/PSScriptAnalyzer.psd1 @@ -23,7 +23,7 @@ CompanyName = 'Microsoft Corporation' Copyright = '(c) Microsoft Corporation 2015. All rights reserved.' # Description of the functionality provided by this module -Description = 'PSScriptAnalyzer provides script analysis and checks for potential code defects in the scripts by applying a group of builtin or customized rules on the scripts being analyzed.' +Description = 'PSScriptAnalyzer provides script analysis and checks for potential code defects in the scripts by applying a group of built-in or customized rules on the scripts being analyzed.' # Minimum version of the Windows PowerShell engine required by this module PowerShellVersion = '5.0' diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index 2c5c5ea80..c0e685c40 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -888,7 +888,7 @@ public Dictionary> CheckRuleExtension(string[] path, PathIn } } - // Resloves relative paths. + // Resolves relative paths. try { for (int i = 0; i < validModPaths.Count; i++) diff --git a/Engine/VariableAnalysis.cs b/Engine/VariableAnalysis.cs index 314457ec3..de45e92e9 100644 --- a/Engine/VariableAnalysis.cs +++ b/Engine/VariableAnalysis.cs @@ -133,7 +133,7 @@ private void ProcessParameters(IEnumerable parameters) } /// - /// Used to analyze scriptbloct, functionmemberast or functiondefinitionast + /// Used to analyze scriptblock, functionmemberast or functiondefinitionast /// /// /// diff --git a/Engine/VariableAnalysisBase.cs b/Engine/VariableAnalysisBase.cs index f1d40ca66..eb7f3182d 100644 --- a/Engine/VariableAnalysisBase.cs +++ b/Engine/VariableAnalysisBase.cs @@ -552,7 +552,7 @@ internal static Block[] SetDominators(Block entry, List Blocks) Block dom = block._predecessors[0]; - // Get first proccessed node + // Get first processed node foreach (var pred in block._predecessors) { if (dominators[pred.PostOrder] != null) @@ -828,14 +828,14 @@ internal static Tuple, Dictionary, Dictionary public object VisitParameter(ParameterAst parameterAst) { - // Nothing to do now, we've already allocated parameters in the first pass looking for all variable naems. + // Nothing to do now, we've already allocated parameters in the first pass looking for all variable names. System.Diagnostics.Debug.Assert(false, "Code is unreachable"); return null; } @@ -2812,7 +2812,7 @@ public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) // left operand is always evaluated, visit it's expression in the current block. binaryExpressionAst.Left.Visit(this.Decorator); - // The right operand is condtionally evaluated. We aren't generating any code here, just + // The right operand is conditionally evaluated. We aren't generating any code here, just // modeling the flow graph, so we just visit the right operand in a new block, and have // both the current and new blocks both flow to a post-expression block. var targetBlock = new Block(); diff --git a/Engine/about_PSScriptAnalyzer.help.txt b/Engine/about_PSScriptAnalyzer.help.txt index a20df5e5d..43bf6e88c 100644 --- a/Engine/about_PSScriptAnalyzer.help.txt +++ b/Engine/about_PSScriptAnalyzer.help.txt @@ -88,7 +88,7 @@ RUNNING SCRIPT ANALYZER ON AN EXISTING SCRIPT, MODULE OR DSC RESOURCE see if the output is "manageable". If it isn't, then you will want to "ease into" things by starting with the most serious violations first - errors. - You may be temtped to use the Invoke-ScriptAnalyzer command's Severity + You may be tempted to use the Invoke-ScriptAnalyzer command's Severity parameter with the argument Error to do this - don't. This will run every built-in rule and then filter the results during output. The more rules the script analyzer runs, the longer it will take to analyze a file. You can diff --git a/Rules/AvoidAlias.cs b/Rules/AvoidAlias.cs index 5e9626923..1b3e05779 100644 --- a/Rules/AvoidAlias.cs +++ b/Rules/AvoidAlias.cs @@ -35,7 +35,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) // Finds all CommandAsts. IEnumerable foundAsts = ast.FindAll(testAst => testAst is CommandAst, true); - // Iterrates all CommandAsts and check the command name. + // Iterates all CommandAsts and check the command name. foreach (Ast foundAst in foundAsts) { CommandAst cmdAst = (CommandAst)foundAst; diff --git a/Rules/AvoidDefaultTrueValueSwitchParameter.cs b/Rules/AvoidDefaultTrueValueSwitchParameter.cs index ee5c60a44..c170308a4 100644 --- a/Rules/AvoidDefaultTrueValueSwitchParameter.cs +++ b/Rules/AvoidDefaultTrueValueSwitchParameter.cs @@ -36,7 +36,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) // Finds all ParamAsts. IEnumerable paramAsts = ast.FindAll(testAst => testAst is ParameterAst, true); - // Iterrates all ParamAsts and check if any are switch. + // Iterates all ParamAsts and check if any are switch. foreach (ParameterAst paramAst in paramAsts) { if (paramAst.Attributes.Any(attr => attr.TypeName.GetReflectionType() == typeof(System.Management.Automation.SwitchParameter)) diff --git a/Rules/AvoidEmptyCatchBlock.cs b/Rules/AvoidEmptyCatchBlock.cs index 6ee312d76..41b1bd45a 100644 --- a/Rules/AvoidEmptyCatchBlock.cs +++ b/Rules/AvoidEmptyCatchBlock.cs @@ -35,7 +35,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) // Finds all CommandAsts. IEnumerable foundAsts = ast.FindAll(testAst => testAst is CatchClauseAst, true); - // Iterrates all CatchClauseAst and check the statements count. + // Iterates all CatchClauseAst and check the statements count. foreach (Ast foundAst in foundAsts) { CatchClauseAst catchAst = (CatchClauseAst)foundAst; diff --git a/Rules/AvoidPositionalParameters.cs b/Rules/AvoidPositionalParameters.cs index 9744096bf..02a375ff4 100644 --- a/Rules/AvoidPositionalParameters.cs +++ b/Rules/AvoidPositionalParameters.cs @@ -35,7 +35,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) // Finds all CommandAsts. IEnumerable foundAsts = ast.FindAll(testAst => testAst is CommandAst, true); - // Iterrates all CommandAsts and check the command name. + // Iterates all CommandAsts and check the command name. foreach (Ast foundAst in foundAsts) { CommandAst cmdAst = (CommandAst)foundAst; diff --git a/Rules/AvoidShouldContinueWithoutForce.cs b/Rules/AvoidShouldContinueWithoutForce.cs index 42996c990..a9025827d 100644 --- a/Rules/AvoidShouldContinueWithoutForce.cs +++ b/Rules/AvoidShouldContinueWithoutForce.cs @@ -37,7 +37,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) // Finds all ParamAsts. IEnumerable funcAsts = ast.FindAll(testAst => testAst is FunctionDefinitionAst, true); - // Iterrates all ParamAsts and check if there are any force. + // Iterates all ParamAsts and check if there are any force. foreach (FunctionDefinitionAst funcAst in funcAsts) { IEnumerable paramAsts = funcAst.FindAll(testAst => testAst is ParameterAst, true); diff --git a/Rules/AvoidUserNameAndPasswordParams.cs b/Rules/AvoidUserNameAndPasswordParams.cs index 4fd643d46..a0b4aec87 100644 --- a/Rules/AvoidUserNameAndPasswordParams.cs +++ b/Rules/AvoidUserNameAndPasswordParams.cs @@ -50,7 +50,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) // Finds all ParamAsts. IEnumerable paramAsts = funcAst.FindAll(testAst => testAst is ParameterAst, true); - // Iterrates all ParamAsts and check if their names are on the list. + // Iterates all ParamAsts and check if their names are on the list. foreach (ParameterAst paramAst in paramAsts) { var psCredentialType = paramAst.Attributes.FirstOrDefault(paramAttribute => diff --git a/Rules/AvoidUsingPlainTextForPassword.cs b/Rules/AvoidUsingPlainTextForPassword.cs index 4586b27e0..766aa78ee 100644 --- a/Rules/AvoidUsingPlainTextForPassword.cs +++ b/Rules/AvoidUsingPlainTextForPassword.cs @@ -39,7 +39,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) List passwords = new List() {"Password", "Passphrase", "Cred", "Credential"}; - // Iterrates all ParamAsts and check if their names are on the list. + // Iterates all ParamAsts and check if their names are on the list. foreach (ParameterAst paramAst in paramAsts) { TypeInfo paramType = (TypeInfo) paramAst.StaticType; diff --git a/Rules/ScriptAnalyzerBuiltinRules.csproj b/Rules/ScriptAnalyzerBuiltinRules.csproj index 0908618ea..ded1db911 100644 --- a/Rules/ScriptAnalyzerBuiltinRules.csproj +++ b/Rules/ScriptAnalyzerBuiltinRules.csproj @@ -1,117 +1,117 @@ - - - - Debug - AnyCPU - {C33B6B9D-E61C-45A3-9103-895FD82A5C1E} - Library - false - Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules - v4.5 - 512 - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules - - - - - - - - - - - False - ..\..\..\..\..\..\fbl_srv2_ci_mgmt.binaries.amd64chk\monad\System.Management.Automation.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Strings.resx - - - - - - - - - - - - - - - - - - - - - - - {f4bde3d0-3eef-4157-8a3e-722df7adef60} - ScriptAnalyzerEngine - - - - - Designer - ResXFileCodeGenerator - Strings.Designer.cs - - - - - - - - mkdir "$(SolutionDir)$(SolutionName)" -copy /y "$(TargetPath)" "$(SolutionDir)$(SolutionName)" - + + + + Debug + AnyCPU + {C33B6B9D-E61C-45A3-9103-895FD82A5C1E} + Library + false + Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules + v4.5 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + + + Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules + + + + + + + + + + + False + ..\..\..\..\..\..\fbl_srv2_ci_mgmt.binaries.amd64chk\monad\System.Management.Automation.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + Strings.resx + + + + + + + + + + + + + + + + + + + + + + + {f4bde3d0-3eef-4157-8a3e-722df7adef60} + ScriptAnalyzerEngine + + + + + Designer + ResXFileCodeGenerator + Strings.Designer.cs + + + + + + + + if not exist "$(SolutionDir)$(SolutionName)" mkdir "$(SolutionDir)$(SolutionName)" +copy /y "$(TargetPath)" "$(SolutionDir)$(SolutionName)" + \ No newline at end of file diff --git a/Rules/UseDeclaredVarsMoreThanAssignments.cs b/Rules/UseDeclaredVarsMoreThanAssignments.cs index afc47410e..e00538fd3 100644 --- a/Rules/UseDeclaredVarsMoreThanAssignments.cs +++ b/Rules/UseDeclaredVarsMoreThanAssignments.cs @@ -87,7 +87,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) { assignments.Remove(varKey); } - //Check if variable belongs to PowerShell builtin variables + //Check if variable belongs to PowerShell built-in variables if (Helper.Instance.HasSpecialVars(varKey)) { assignments.Remove(varKey); From 3735e375f777f430dbb537e93874ffb633acd5bb Mon Sep 17 00:00:00 2001 From: Kory Gill Date: Wed, 9 Dec 2015 11:54:20 -0800 Subject: [PATCH 41/53] Fix post-build in both csproj files. --- Engine/ScriptAnalyzerEngine.csproj | 227 ++++++++++++------------ Rules/ScriptAnalyzerBuiltinRules.csproj | 6 +- 2 files changed, 118 insertions(+), 115 deletions(-) diff --git a/Engine/ScriptAnalyzerEngine.csproj b/Engine/ScriptAnalyzerEngine.csproj index 3e0b3ce67..46c607751 100644 --- a/Engine/ScriptAnalyzerEngine.csproj +++ b/Engine/ScriptAnalyzerEngine.csproj @@ -1,114 +1,115 @@ - - - - Debug - AnyCPU - {F4BDE3D0-3EEF-4157-8A3E-722DF7ADEF60} - Library - false - Microsoft.Windows.PowerShell.ScriptAnalyzer - v4.5 - 512 - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - Microsoft.Windows.PowerShell.ScriptAnalyzer - - - - - - - - - - False - ..\..\..\..\..\..\fbl_srv2_ci_mgmt.binaries.amd64chk\monad\System.Management.Automation.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Strings.resx - - - - - - - - - - - - ResXFileCodeGenerator - Strings.Designer.cs - - - - - - - - - - - mkdir "$(SolutionDir)$(SolutionName)" -copy /y "$(ProjectDir)\*.ps1xml" "$(SolutionDir)$(SolutionName)" -copy /y "$(ProjectDir)\*.psd1" "$(SolutionDir)$(SolutionName)" -mkdir "$(SolutionDir)$(SolutionName)\Configurations" -copy /y "$(ProjectDir)\Configurations\*.psd1" "$(SolutionDir)$(SolutionName)\Configurations" -copy /y "$(TargetPath)" "$(SolutionDir)$(SolutionName)" -mkdir "$(SolutionDir)$(SolutionName)\en-US" -copy /y "$(ProjectDir)\about_*.help.txt" "$(SolutionDir)$(SolutionName)\en-US" -copy /y "$(ProjectDir)\*Help.xml" "$(SolutionDir)$(SolutionName)\en-US" - + + + + Debug + AnyCPU + {F4BDE3D0-3EEF-4157-8A3E-722DF7ADEF60} + Library + false + Microsoft.Windows.PowerShell.ScriptAnalyzer + v4.5 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + + + Microsoft.Windows.PowerShell.ScriptAnalyzer + + + + + + + + + + False + ..\..\..\..\..\..\fbl_srv2_ci_mgmt.binaries.amd64chk\monad\System.Management.Automation.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + Strings.resx + + + + + + + + + + + + ResXFileCodeGenerator + Strings.Designer.cs + + + + + + + + + + + + if not exist "$(SolutionDir)$(SolutionName)" mkdir "$(SolutionDir)$(SolutionName)" + copy /y "$(ProjectDir)*.ps1xml" "$(SolutionDir)$(SolutionName)" + copy /y "$(ProjectDir)*.psd1" "$(SolutionDir)$(SolutionName)" + if not exist "$(SolutionDir)$(SolutionName)\Configurations" mkdir "$(SolutionDir)$(SolutionName)\Configurations" + copy /y "$(ProjectDir)Configurations\*.psd1" "$(SolutionDir)$(SolutionName)\Configurations" + copy /y "$(TargetPath)" "$(SolutionDir)$(SolutionName)" + if not exist "$(SolutionDir)$(SolutionName)\en-US" mkdir "$(SolutionDir)$(SolutionName)\en-US" + copy /y "$(ProjectDir)about_*.help.txt" "$(SolutionDir)$(SolutionName)\en-US" + copy /y "$(ProjectDir)*Help.xml" "$(SolutionDir)$(SolutionName)\en-US" + \ No newline at end of file diff --git a/Rules/ScriptAnalyzerBuiltinRules.csproj b/Rules/ScriptAnalyzerBuiltinRules.csproj index ded1db911..73ca65257 100644 --- a/Rules/ScriptAnalyzerBuiltinRules.csproj +++ b/Rules/ScriptAnalyzerBuiltinRules.csproj @@ -111,7 +111,9 @@ - if not exist "$(SolutionDir)$(SolutionName)" mkdir "$(SolutionDir)$(SolutionName)" -copy /y "$(TargetPath)" "$(SolutionDir)$(SolutionName)" + + if not exist "$(SolutionDir)$(SolutionName)" mkdir "$(SolutionDir)$(SolutionName)" + copy /y "$(TargetPath)" "$(SolutionDir)$(SolutionName)" + \ No newline at end of file From 84e7b432a5facac1ce0b1e36f565df91aa27d796 Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Mon, 14 Dec 2015 13:39:44 -0800 Subject: [PATCH 42/53] Add ability to supply collection of custom rule paths --- .../Commands/GetScriptAnalyzerRuleCommand.cs | 4 +- .../Commands/InvokeScriptAnalyzerCommand.cs | 4 +- Engine/Helper.cs | 42 +++++++++++-------- Tests/Engine/GetScriptAnalyzerRule.tests.ps1 | 8 ++-- Tests/Engine/InvokeScriptAnalyzer.tests.ps1 | 23 ++++++---- 5 files changed, 46 insertions(+), 35 deletions(-) diff --git a/Engine/Commands/GetScriptAnalyzerRuleCommand.cs b/Engine/Commands/GetScriptAnalyzerRuleCommand.cs index f9c1934bc..cb38d7b55 100644 --- a/Engine/Commands/GetScriptAnalyzerRuleCommand.cs +++ b/Engine/Commands/GetScriptAnalyzerRuleCommand.cs @@ -34,12 +34,12 @@ public class GetScriptAnalyzerRuleCommand : PSCmdlet, IOutputWriter [ValidateNotNullOrEmpty] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] [Alias("CustomizedRulePath")] - public string CustomRulePath + public string[] CustomRulePath { get { return customRulePath; } set { customRulePath = value; } } - private string customRulePath; + private string[] customRulePath; /// /// RecurseCustomRulePath: Find rules within subfolders under the path diff --git a/Engine/Commands/InvokeScriptAnalyzerCommand.cs b/Engine/Commands/InvokeScriptAnalyzerCommand.cs index dd9e2542d..ada75920b 100644 --- a/Engine/Commands/InvokeScriptAnalyzerCommand.cs +++ b/Engine/Commands/InvokeScriptAnalyzerCommand.cs @@ -74,12 +74,12 @@ public string ScriptDefinition [ValidateNotNull] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] [Alias("CustomizedRulePath")] - public string CustomRulePath + public string[] CustomRulePath { get { return customRulePath; } set { customRulePath = value; } } - private string customRulePath; + private string[] customRulePath; /// /// RecurseCustomRulePath: Find rules within subfolders under the path diff --git a/Engine/Helper.cs b/Engine/Helper.cs index 45f4c3204..78e43dc2a 100644 --- a/Engine/Helper.cs +++ b/Engine/Helper.cs @@ -984,38 +984,44 @@ public Tuple, List> SuppressRule(string return result; } - public static string[] ProcessCustomRulePaths(string rulePath, SessionState sessionState, bool recurse = false) + public static string[] ProcessCustomRulePaths(string[] rulePaths, SessionState sessionState, bool recurse = false) { //if directory is given, list all the psd1 files List outPaths = new List(); - if (rulePath == null) + if (rulePaths == null) { return null; } - try + + Collection pathInfo = new Collection(); + foreach (string rulePath in rulePaths) { - Collection pathInfo = sessionState.Path.GetResolvedPSPathFromPSPath(rulePath); - foreach (PathInfo pinfo in pathInfo) + Collection pathInfosForRulePath = sessionState.Path.GetResolvedPSPathFromPSPath(rulePath); + if (null != pathInfosForRulePath) { - string path = pinfo.Path; - if (Directory.Exists(path)) + foreach (PathInfo pathInfoForRulePath in pathInfosForRulePath) { - path = path.TrimEnd('\\'); - if (recurse) - { - outPaths.AddRange(Directory.GetDirectories(pinfo.Path, "*", SearchOption.AllDirectories)); - } + pathInfo.Add(pathInfoForRulePath); } - outPaths.Add(path); } - return outPaths.ToArray(); } - catch + + foreach (PathInfo pinfo in pathInfo) { - // need to do this as the path validation takes place later in the hierarchy. - outPaths.Add(rulePath); - return outPaths.ToArray(); + string path = pinfo.Path; + if (Directory.Exists(path)) + { + path = path.TrimEnd('\\'); + if (recurse) + { + outPaths.AddRange(Directory.GetDirectories(pinfo.Path, "*", SearchOption.AllDirectories)); + } + } + outPaths.Add(path); } + + return outPaths.ToArray(); + } diff --git a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 index a48de4997..fdc945f37 100644 --- a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 +++ b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 @@ -23,10 +23,10 @@ Describe "Test available parameters" { } It "accepts string array" { - $params["CustomRulePath"].ParameterType.FullName | Should Be "System.String" + $params["CustomRulePath"].ParameterType.FullName | Should Be "System.String[]" } - It "takes CustomizedRulePath parameter as an alias of CustomRulePath paramter" { + It "takes CustomizedRulePath parameter as an alias of CustomRulePath parameter" { $params.CustomRulePath.Aliases.Contains("CustomizedRulePath") | Should be $true } } @@ -107,11 +107,11 @@ Describe "Test RuleExtension" { It "file cannot be found" { try { - Get-ScriptAnalyzerRule -CustomizedRulePath "Invalid CustomRulePath" + Get-ScriptAnalyzerRule -CustomRulePath "Invalid CustomRulePath" } catch { - $Error[0].FullyQualifiedErrorId | should match "Cannot find ScriptAnalyzer rules in the specified path,Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands.GetScriptAnalyzerRuleCommand" + $Error[0].FullyQualifiedErrorId | should match "PathNotFound,Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands.GetScriptAnalyzerRuleCommand" } } diff --git a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 index c72380527..1f60f93de 100644 --- a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 +++ b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 @@ -40,15 +40,8 @@ Describe "Test available parameters" { $params.ContainsKey("CustomRulePath") | Should Be $true } - It "accepts a string" { - if ($testingLibraryUsage) - { + It "accepts a string array" { $params["CustomRulePath"].ParameterType.FullName | Should Be "System.String[]" - } - else - { - $params["CustomRulePath"].ParameterType.FullName | Should Be "System.String" - } } It "has a CustomizedRulePath alias"{ @@ -315,8 +308,20 @@ Describe "Test CustomizedRulePath" { $customizedRulePathExclude = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomizedRulePath $directory\CommunityAnalyzerRules\CommunityAnalyzerRules.psm1 -ExcludeRule "Measure-RequiresModules" | Where-Object {$_.RuleName -eq $measureRequired} $customizedRulePathExclude.Count | Should be 0 } + + It "When supplied with a collection of paths" { + $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomRulePath ("$directory\CommunityAnalyzerRules", "$directory\SampleRule", "$directory\SampleRule\SampleRule2") + $customizedRulePath.Count | Should Be 3 + } + + It "When supplied with a collection of paths recursively" { + $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomRulePath ("$directory\CommunityAnalyzerRules", "$directory\SampleRule", "$directory\SampleRule\SampleRule2") -RecurseCustomRulePath + $customizedRulePath.Count | Should Be 6 + } + } + Context "When used incorrectly" { It "file cannot be found" { try @@ -327,7 +332,7 @@ Describe "Test CustomizedRulePath" { { if (-not $testingLibraryUsage) { - $Error[0].FullyQualifiedErrorId | should match "Cannot find ScriptAnalyzer rules in the specified path,Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands.InvokeScriptAnalyzerCommand" + $Error[0].FullyQualifiedErrorId | should match "PathNotFound,Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands.InvokeScriptAnalyzerCommand" } } } From 0e111e48fa36a4fe85e729c06c40b7b55544aa1e Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Mon, 14 Dec 2015 13:48:38 -0800 Subject: [PATCH 43/53] Debug failing test --- Tests/Engine/InvokeScriptAnalyzer.tests.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 index 1f60f93de..e52ec650d 100644 --- a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 +++ b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 @@ -316,6 +316,7 @@ Describe "Test CustomizedRulePath" { It "When supplied with a collection of paths recursively" { $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomRulePath ("$directory\CommunityAnalyzerRules", "$directory\SampleRule", "$directory\SampleRule\SampleRule2") -RecurseCustomRulePath + $customizedRulePath $customizedRulePath.Count | Should Be 6 } From 3ba1596a0dac474e454295d1742006a5874617cc Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Mon, 14 Dec 2015 14:15:42 -0800 Subject: [PATCH 44/53] Updates to Test --- Tests/Engine/CustomizedRule.tests.ps1 | 4 ++-- Tests/Engine/InvokeScriptAnalyzer.tests.ps1 | 8 +------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/Tests/Engine/CustomizedRule.tests.ps1 b/Tests/Engine/CustomizedRule.tests.ps1 index 1e7f098de..7f7ded15c 100644 --- a/Tests/Engine/CustomizedRule.tests.ps1 +++ b/Tests/Engine/CustomizedRule.tests.ps1 @@ -99,8 +99,8 @@ Describe "Test importing correct customized rules" { } It "will show the custom rules when given recurse switch" { - $customizedRulePath = Get-ScriptAnalyzerRule -RecurseCustomRulePath -CustomizedRulePath $directory\samplerule | Where-Object {$_.RuleName -eq $measure} - $customizedRulePath.Count | Should be 3 + $customizedRulePath = Get-ScriptAnalyzerRule -RecurseCustomRulePath -CustomizedRulePath "$directory\samplerule", "$directory\samplerule\samplerule2" | Where-Object {$_.RuleName -eq $measure} + $customizedRulePath.Count | Should be 5 } it "will show the custom rules when given glob with recurse switch" { diff --git a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 index e52ec650d..2b1b04917 100644 --- a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 +++ b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 @@ -312,13 +312,7 @@ Describe "Test CustomizedRulePath" { It "When supplied with a collection of paths" { $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomRulePath ("$directory\CommunityAnalyzerRules", "$directory\SampleRule", "$directory\SampleRule\SampleRule2") $customizedRulePath.Count | Should Be 3 - } - - It "When supplied with a collection of paths recursively" { - $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomRulePath ("$directory\CommunityAnalyzerRules", "$directory\SampleRule", "$directory\SampleRule\SampleRule2") -RecurseCustomRulePath - $customizedRulePath - $customizedRulePath.Count | Should Be 6 - } + } } From d849e11e30988b0f2cacfe2ef6b9c3863efe0c10 Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Mon, 14 Dec 2015 16:39:37 -0800 Subject: [PATCH 45/53] Add a new switch parameter to run default rules along with custom ones --- Engine/Commands/GetScriptAnalyzerRuleCommand.cs | 2 +- Engine/Commands/InvokeScriptAnalyzerCommand.cs | 13 +++++++++++++ Engine/ScriptAnalyzer.cs | 15 ++++++++++----- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/Engine/Commands/GetScriptAnalyzerRuleCommand.cs b/Engine/Commands/GetScriptAnalyzerRuleCommand.cs index cb38d7b55..95ed669cc 100644 --- a/Engine/Commands/GetScriptAnalyzerRuleCommand.cs +++ b/Engine/Commands/GetScriptAnalyzerRuleCommand.cs @@ -91,7 +91,7 @@ protected override void BeginProcessing() { string[] rulePaths = Helper.ProcessCustomRulePaths(customRulePath, this.SessionState, recurseCustomRulePath); - ScriptAnalyzer.Instance.Initialize(this, rulePaths); + ScriptAnalyzer.Instance.Initialize(this, rulePaths, null, null, null, null == rulePaths ? true : false); } /// diff --git a/Engine/Commands/InvokeScriptAnalyzerCommand.cs b/Engine/Commands/InvokeScriptAnalyzerCommand.cs index ada75920b..936d38925 100644 --- a/Engine/Commands/InvokeScriptAnalyzerCommand.cs +++ b/Engine/Commands/InvokeScriptAnalyzerCommand.cs @@ -93,6 +93,18 @@ public SwitchParameter RecurseCustomRulePath } private bool recurseCustomRulePath; + /// + /// IncludeDefaultRules: Invoke default rules along with Custom rules + /// + [Parameter(Mandatory = false)] + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + public SwitchParameter IncludeDefaultRules + { + get { return includeDefaultRules; } + set { includeDefaultRules = value; } + } + private bool includeDefaultRules; + /// /// ExcludeRule: Array of names of rules to be disabled. /// @@ -195,6 +207,7 @@ protected override void BeginProcessing() this.includeRule, this.excludeRule, this.severity, + null == rulePaths ? true : this.includeDefaultRules, this.suppressedOnly, this.configuration); } diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index c0e685c40..71a00f27c 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -96,10 +96,11 @@ public static ScriptAnalyzer Instance /// internal void Initialize( TCmdlet cmdlet, - string[] customizedRulePath = null, + string[] customizedRulePath = null, string[] includeRuleNames = null, string[] excludeRuleNames = null, string[] severity = null, + bool includeDefaultRules = false, bool suppressedOnly = false, string profile = null) where TCmdlet : PSCmdlet, IOutputWriter @@ -117,6 +118,7 @@ internal void Initialize( includeRuleNames, excludeRuleNames, severity, + includeDefaultRules, suppressedOnly, profile); } @@ -127,10 +129,11 @@ internal void Initialize( public void Initialize( Runspace runspace, IOutputWriter outputWriter, - string[] customizedRulePath = null, + string[] customizedRulePath = null, string[] includeRuleNames = null, string[] excludeRuleNames = null, string[] severity = null, + bool includeDefaultRules = false, bool suppressedOnly = false, string profile = null) { @@ -147,6 +150,7 @@ public void Initialize( includeRuleNames, excludeRuleNames, severity, + includeDefaultRules, suppressedOnly, profile); } @@ -308,10 +312,11 @@ private void Initialize( IOutputWriter outputWriter, PathIntrinsics path, CommandInvocationIntrinsics invokeCommand, - string[] customizedRulePath, - string[] includeRuleNames, + string[] customizedRulePath, + string[] includeRuleNames, string[] excludeRuleNames, string[] severity, + bool includeDefaultRules = false, bool suppressedOnly = false, string profile = null) { @@ -358,7 +363,7 @@ private void Initialize( try { - this.LoadRules(this.validationResults, invokeCommand, null == customizedRulePath ? true : false); + this.LoadRules(this.validationResults, invokeCommand, includeDefaultRules); } catch (Exception ex) { From 23b22e7c381081cc90d0f8d8519ed002254ac7df Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Tue, 15 Dec 2015 10:08:17 -0800 Subject: [PATCH 46/53] Added Test cases, plugged Test Holes --- Tests/Engine/CustomizedRule.tests.ps1 | 100 ++++++++++--------- Tests/Engine/GetScriptAnalyzerRule.tests.ps1 | 5 + 2 files changed, 60 insertions(+), 45 deletions(-) diff --git a/Tests/Engine/CustomizedRule.tests.ps1 b/Tests/Engine/CustomizedRule.tests.ps1 index 7f7ded15c..6c22076cd 100644 --- a/Tests/Engine/CustomizedRule.tests.ps1 +++ b/Tests/Engine/CustomizedRule.tests.ps1 @@ -86,33 +86,30 @@ Describe "Test importing correct customized rules" { $customizedRulePath.Count | Should Be 1 } - if (!$testingLibraryUsage) - { - It "will show the custom rule when given a rule folder path with trailing backslash" { - $customizedRulePath = Get-ScriptAnalyzerRule -CustomizedRulePath $directory\samplerule\ | Where-Object {$_.RuleName -eq $measure} - $customizedRulePath.Count | Should Be 1 - } + It "will show the custom rule when given a rule folder path with trailing backslash" { + $customizedRulePath = Get-ScriptAnalyzerRule -CustomizedRulePath $directory\samplerule\ | Where-Object {$_.RuleName -eq $measure} + $customizedRulePath.Count | Should Be 1 + } - It "will show the custom rules when given a glob" { - $customizedRulePath = Get-ScriptAnalyzerRule -CustomizedRulePath $directory\samplerule\samplerule* | Where-Object {$_.RuleName -match $measure} - $customizedRulePath.Count | Should be 4 - } + It "will show the custom rules when given a glob" { + $customizedRulePath = Get-ScriptAnalyzerRule -CustomizedRulePath $directory\samplerule\samplerule* | Where-Object {$_.RuleName -match $measure} + $customizedRulePath.Count | Should be 4 + } - It "will show the custom rules when given recurse switch" { - $customizedRulePath = Get-ScriptAnalyzerRule -RecurseCustomRulePath -CustomizedRulePath "$directory\samplerule", "$directory\samplerule\samplerule2" | Where-Object {$_.RuleName -eq $measure} - $customizedRulePath.Count | Should be 5 - } + It "will show the custom rules when given recurse switch" { + $customizedRulePath = Get-ScriptAnalyzerRule -RecurseCustomRulePath -CustomizedRulePath "$directory\samplerule", "$directory\samplerule\samplerule2" | Where-Object {$_.RuleName -eq $measure} + $customizedRulePath.Count | Should be 5 + } - it "will show the custom rules when given glob with recurse switch" { - $customizedRulePath = Get-ScriptAnalyzerRule -RecurseCustomRulePath -CustomizedRulePath $directory\samplerule\samplerule* | Where-Object {$_.RuleName -eq $measure} - $customizedRulePath.Count | Should be 5 - } - - it "will show the custom rules when given glob with recurse switch" { - $customizedRulePath = Get-ScriptAnalyzerRule -RecurseCustomRulePath -CustomizedRulePath $directory\samplerule* | Where-Object {$_.RuleName -eq $measure} - $customizedRulePath.Count | Should be 3 - } + It "will show the custom rules when given glob with recurse switch" { + $customizedRulePath = Get-ScriptAnalyzerRule -RecurseCustomRulePath -CustomizedRulePath $directory\samplerule\samplerule* | Where-Object {$_.RuleName -eq $measure} + $customizedRulePath.Count | Should be 5 } + + It "will show the custom rules when given glob with recurse switch" { + $customizedRulePath = Get-ScriptAnalyzerRule -RecurseCustomRulePath -CustomizedRulePath $directory\samplerule* | Where-Object {$_.RuleName -eq $measure} + $customizedRulePath.Count | Should be 3 + } } Context "Test Invoke-ScriptAnalyzer with customized rules" { @@ -126,33 +123,46 @@ Describe "Test importing correct customized rules" { $customizedRulePath.Count | Should Be 1 } - if (!$testingLibraryUsage) - { - It "will show the custom rule in the results when given a rule folder path with trailing backslash" { - $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomizedRulePath $directory\samplerule\ | Where-Object {$_.Message -eq $message} - $customizedRulePath.Count | Should Be 1 - } + It "will show the custom rule in the results when given a rule folder path with trailing backslash" { + $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomizedRulePath $directory\samplerule\ | Where-Object {$_.Message -eq $message} + $customizedRulePath.Count | Should Be 1 + } - It "will show the custom rules when given a glob" { - $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomizedRulePath $directory\samplerule\samplerule* | Where-Object {$_.Message -eq $message} - $customizedRulePath.Count | Should be 3 - } + It "will show the custom rules when given a glob" { + $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomizedRulePath $directory\samplerule\samplerule* | Where-Object {$_.Message -eq $message} + $customizedRulePath.Count | Should be 3 + } - It "will show the custom rules when given recurse switch" { - $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -RecurseCustomRulePath -CustomizedRulePath $directory\samplerule | Where-Object {$_.Message -eq $message} - $customizedRulePath.Count | Should be 3 - } + It "will show the custom rules when given recurse switch" { + $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -RecurseCustomRulePath -CustomizedRulePath $directory\samplerule | Where-Object {$_.Message -eq $message} + $customizedRulePath.Count | Should be 3 + } - it "will show the custom rules when given glob with recurse switch" { - $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -RecurseCustomRulePath -CustomizedRulePath $directory\samplerule\samplerule* | Where-Object {$_.Message -eq $message} - $customizedRulePath.Count | Should be 4 - } + It "will show the custom rules when given glob with recurse switch" { + $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -RecurseCustomRulePath -CustomizedRulePath $directory\samplerule\samplerule* | Where-Object {$_.Message -eq $message} + $customizedRulePath.Count | Should be 4 + } - it "will show the custom rules when given glob with recurse switch" { - $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -RecurseCustomRulePath -CustomizedRulePath $directory\samplerule* | Where-Object {$_.Message -eq $message} - $customizedRulePath.Count | Should be 3 - } + It "will show the custom rules when given glob with recurse switch" { + $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -RecurseCustomRulePath -CustomizedRulePath $directory\samplerule* | Where-Object {$_.Message -eq $message} + $customizedRulePath.Count | Should be 3 } + + It "Using IncludeDefaultRules Switch with CustomRulePath" { + $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomRulePath $directory\samplerule\samplerule.psm1 -IncludeDefaultRules + $customizedRulePath.Count | Should Be 2 + } + + It "Using IncludeDefaultRules Switch without CustomRulePath" { + $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -IncludeDefaultRules + $customizedRulePath.Count | Should Be 1 + } + + It "Not Using IncludeDefaultRules Switch and without CustomRulePath" { + $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 + $customizedRulePath.Count | Should Be 1 + } + } } diff --git a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 index fdc945f37..d992f5ca4 100644 --- a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 +++ b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 @@ -53,6 +53,11 @@ Describe "Test Name parameters" { ($rules | Where-Object {$_.RuleName -eq $singularNouns}).Count | Should Be 1 ($rules | Where-Object {$_.RuleName -eq $approvedVerbs}).Count | Should Be 1 } + + It "Get Rules with no parameters supplied" { + $defaultRules = Get-ScriptAnalyzerRule + $defaultRules.Count | Should be 38 + } } Context "When used incorrectly" { From aa40394257517b1495baa9210ad6b259c806f3ce Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Tue, 15 Dec 2015 11:36:31 -0800 Subject: [PATCH 47/53] Fix failing tests --- Engine/ScriptAnalyzer.cs | 8 +-- Tests/Engine/CustomizedRule.tests.ps1 | 73 ++++++++++++++------------- Tests/Engine/LibraryUsage.tests.ps1 | 19 ++++--- 3 files changed, 53 insertions(+), 47 deletions(-) diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index 71a00f27c..e9241770d 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -391,11 +391,11 @@ private void Initialize( { this.outputWriter.ThrowTerminatingError( new ErrorRecord( - new Exception(), + new Exception(), string.Format( - CultureInfo.CurrentCulture, - Strings.RulesNotFound), - ErrorCategory.ResourceExists, + CultureInfo.CurrentCulture, + Strings.RulesNotFound), + ErrorCategory.ResourceExists, this)); } diff --git a/Tests/Engine/CustomizedRule.tests.ps1 b/Tests/Engine/CustomizedRule.tests.ps1 index 6c22076cd..6c3cb10e9 100644 --- a/Tests/Engine/CustomizedRule.tests.ps1 +++ b/Tests/Engine/CustomizedRule.tests.ps1 @@ -123,44 +123,47 @@ Describe "Test importing correct customized rules" { $customizedRulePath.Count | Should Be 1 } - It "will show the custom rule in the results when given a rule folder path with trailing backslash" { - $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomizedRulePath $directory\samplerule\ | Where-Object {$_.Message -eq $message} - $customizedRulePath.Count | Should Be 1 - } - - It "will show the custom rules when given a glob" { - $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomizedRulePath $directory\samplerule\samplerule* | Where-Object {$_.Message -eq $message} - $customizedRulePath.Count | Should be 3 - } - - It "will show the custom rules when given recurse switch" { - $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -RecurseCustomRulePath -CustomizedRulePath $directory\samplerule | Where-Object {$_.Message -eq $message} - $customizedRulePath.Count | Should be 3 - } + if (!$testingLibraryUsage) + { + It "will show the custom rule in the results when given a rule folder path with trailing backslash" { + $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomizedRulePath $directory\samplerule\ | Where-Object {$_.Message -eq $message} + $customizedRulePath.Count | Should Be 1 + } + + It "will show the custom rules when given a glob" { + $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomizedRulePath $directory\samplerule\samplerule* | Where-Object {$_.Message -eq $message} + $customizedRulePath.Count | Should be 3 + } + + It "will show the custom rules when given recurse switch" { + $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -RecurseCustomRulePath -CustomizedRulePath $directory\samplerule | Where-Object {$_.Message -eq $message} + $customizedRulePath.Count | Should be 3 + } - It "will show the custom rules when given glob with recurse switch" { - $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -RecurseCustomRulePath -CustomizedRulePath $directory\samplerule\samplerule* | Where-Object {$_.Message -eq $message} - $customizedRulePath.Count | Should be 4 - } - - It "will show the custom rules when given glob with recurse switch" { - $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -RecurseCustomRulePath -CustomizedRulePath $directory\samplerule* | Where-Object {$_.Message -eq $message} - $customizedRulePath.Count | Should be 3 - } - - It "Using IncludeDefaultRules Switch with CustomRulePath" { - $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomRulePath $directory\samplerule\samplerule.psm1 -IncludeDefaultRules - $customizedRulePath.Count | Should Be 2 - } + It "will show the custom rules when given glob with recurse switch" { + $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -RecurseCustomRulePath -CustomizedRulePath $directory\samplerule\samplerule* | Where-Object {$_.Message -eq $message} + $customizedRulePath.Count | Should be 4 + } + + It "will show the custom rules when given glob with recurse switch" { + $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -RecurseCustomRulePath -CustomizedRulePath $directory\samplerule* | Where-Object {$_.Message -eq $message} + $customizedRulePath.Count | Should be 3 + } + + It "Using IncludeDefaultRules Switch with CustomRulePath" { + $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomRulePath $directory\samplerule\samplerule.psm1 -IncludeDefaultRules + $customizedRulePath.Count | Should Be 2 + } - It "Using IncludeDefaultRules Switch without CustomRulePath" { - $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -IncludeDefaultRules - $customizedRulePath.Count | Should Be 1 - } + It "Using IncludeDefaultRules Switch without CustomRulePath" { + $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -IncludeDefaultRules + $customizedRulePath.Count | Should Be 1 + } - It "Not Using IncludeDefaultRules Switch and without CustomRulePath" { - $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 - $customizedRulePath.Count | Should Be 1 + It "Not Using IncludeDefaultRules Switch and without CustomRulePath" { + $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 + $customizedRulePath.Count | Should Be 1 + } } } diff --git a/Tests/Engine/LibraryUsage.tests.ps1 b/Tests/Engine/LibraryUsage.tests.ps1 index 41d2de3d5..432ac8cee 100644 --- a/Tests/Engine/LibraryUsage.tests.ps1 +++ b/Tests/Engine/LibraryUsage.tests.ps1 @@ -31,22 +31,24 @@ function Invoke-ScriptAnalyzer { [ValidateSet("Warning", "Error", "Information", IgnoreCase = $true)] [Parameter(Mandatory = $false)] [string[]] $Severity = $null, - + [Parameter(Mandatory = $false)] [switch] $Recurse, + [Parameter(Mandatory = $false)] + [switch] $IncludeDefaultRules, + [Parameter(Mandatory = $false)] [switch] $SuppressedOnly, [Parameter(Mandatory = $false)] [string] $Profile = $null - ) - # 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)" + ) + + if ($null -eq $CustomRulePath) + { + $IncludeDefaultRules = $true + } $scriptAnalyzer = New-Object "Microsoft.Windows.PowerShell.ScriptAnalyzer.ScriptAnalyzer"; $scriptAnalyzer.Initialize( @@ -56,6 +58,7 @@ function Invoke-ScriptAnalyzer { $IncludeRule, $ExcludeRule, $Severity, + $IncludeDefaultRules.IsPresent, $SuppressedOnly.IsPresent, $Profile ); From c39b0418563c5ff8f063bedbd19d412e4f8102e0 Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Tue, 15 Dec 2015 15:40:36 -0800 Subject: [PATCH 48/53] Update PSScriptAnalyzer.psd1 --- Engine/PSScriptAnalyzer.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine/PSScriptAnalyzer.psd1 b/Engine/PSScriptAnalyzer.psd1 index 00f513f22..955ff53b5 100644 --- a/Engine/PSScriptAnalyzer.psd1 +++ b/Engine/PSScriptAnalyzer.psd1 @@ -11,7 +11,7 @@ Author = 'Microsoft Corporation' RootModule = 'Microsoft.Windows.PowerShell.ScriptAnalyzer.dll' # Version number of this module. -ModuleVersion = '1.1.1' +ModuleVersion = '1.2.0' # ID used to uniquely identify this module GUID = '324fc715-36bf-4aee-8e58-72e9b4a08ad9' From 84079a990adc77b53ea8431c1af3e2d38f7f5ce6 Mon Sep 17 00:00:00 2001 From: Quoc Truong Date: Tue, 15 Dec 2015 15:42:14 -0800 Subject: [PATCH 49/53] Allow scriptanalyzer to accept configuration in the form of a hashtable --- .../Commands/InvokeScriptAnalyzerCommand.cs | 22 +- Engine/ScriptAnalyzer.cs | 369 ++++++++++++------ Engine/Strings.Designer.cs | 33 +- Engine/Strings.resx | 15 +- Tests/Engine/GlobalSuppression.test.ps1 | 34 +- Tests/Engine/LibraryUsage.tests.ps1 | 15 + 6 files changed, 357 insertions(+), 131 deletions(-) diff --git a/Engine/Commands/InvokeScriptAnalyzerCommand.cs b/Engine/Commands/InvokeScriptAnalyzerCommand.cs index 936d38925..34c99c014 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 Configuration { get { return configuration; } set { configuration = value; } } - private string configuration; + + private object configuration; private bool stopProcessing; @@ -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..3f17f6b4c 100644 --- a/Engine/Strings.Designer.cs +++ b/Engine/Strings.Designer.cs @@ -177,6 +177,15 @@ internal static string InvalidProfile { } } + /// + /// Looks up a localized string similar to Key {0} in the configuration is not a string.. + /// + internal static string KeyNotString { + get { + return ResourceManager.GetString("KeyNotString", resourceCulture); + } + } + /// /// Looks up a localized string similar to No loggers found.. /// @@ -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 configuration 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 configuration 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 configuration 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 configuration 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 configuration 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..9eeca69b8 100644 --- a/Engine/Strings.resx +++ b/Engine/Strings.resx @@ -193,13 +193,13 @@ 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 configuration 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 configuration 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 configuration 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. @@ -234,4 +234,13 @@ WrongConfigurationKey + + Key {0} in the configuration is not a string. + + + {0} is not a valid key in the configuration hashtable. Valid keys are ExcludeRules, IncludeRules and Severity. + + + Value {0} for key {1} has the wrong data type. Value in the configuration 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..dc67bd410 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" -Configuration @{"includerules" = @("PSAvoidUsingCmdletAliases", "PSUseOutputTypeCorrectly")} | + Where-Object { $_.RuleName -eq "PSAvoidUsingComputerNameHardcoded"} + $hashtableConfiguration.Count | Should Be 0 + } } Context "Severity" { @@ -59,6 +75,12 @@ 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" -Configuration @{"severity" = "warning"} | + Where-Object {$_.RuleName -eq "PSUseOutputTypeCorrectly"} + $hashtableConfiguration.Count | should be 0 + } } Context "Error Case" { diff --git a/Tests/Engine/LibraryUsage.tests.ps1 b/Tests/Engine/LibraryUsage.tests.ps1 index 432ac8cee..8b78e9ae9 100644 --- a/Tests/Engine/LibraryUsage.tests.ps1 +++ b/Tests/Engine/LibraryUsage.tests.ps1 @@ -36,6 +36,7 @@ function Invoke-ScriptAnalyzer { [switch] $Recurse, [Parameter(Mandatory = $false)] +<<<<<<< Updated upstream [switch] $IncludeDefaultRules, [Parameter(Mandatory = $false)] @@ -49,6 +50,16 @@ function Invoke-ScriptAnalyzer { { $IncludeDefaultRules = $true } +======= + [switch] $SuppressedOnly + ) + # 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)" +>>>>>>> Stashed changes $scriptAnalyzer = New-Object "Microsoft.Windows.PowerShell.ScriptAnalyzer.ScriptAnalyzer"; $scriptAnalyzer.Initialize( @@ -58,9 +69,13 @@ function Invoke-ScriptAnalyzer { $IncludeRule, $ExcludeRule, $Severity, +<<<<<<< Updated upstream $IncludeDefaultRules.IsPresent, $SuppressedOnly.IsPresent, $Profile +======= + $SuppressedOnly.IsPresent +>>>>>>> Stashed changes ); if ($PSCmdlet.ParameterSetName -eq "File") { From 53bc3ff4c42a3f233debe7d6f2ff6b7d78e3d11c Mon Sep 17 00:00:00 2001 From: Quoc Truong Date: Tue, 15 Dec 2015 15:50:46 -0800 Subject: [PATCH 50/53] Fix merged conflict that caused test failures --- Tests/Engine/LibraryUsage.tests.ps1 | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/Tests/Engine/LibraryUsage.tests.ps1 b/Tests/Engine/LibraryUsage.tests.ps1 index 8b78e9ae9..1ae8eae60 100644 --- a/Tests/Engine/LibraryUsage.tests.ps1 +++ b/Tests/Engine/LibraryUsage.tests.ps1 @@ -36,30 +36,22 @@ function Invoke-ScriptAnalyzer { [switch] $Recurse, [Parameter(Mandatory = $false)] -<<<<<<< Updated upstream [switch] $IncludeDefaultRules, [Parameter(Mandatory = $false)] - [switch] $SuppressedOnly, - - [Parameter(Mandatory = $false)] - [string] $Profile = $null + [switch] $SuppressedOnly ) if ($null -eq $CustomRulePath) { $IncludeDefaultRules = $true } -======= - [switch] $SuppressedOnly - ) # 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)" ->>>>>>> Stashed changes $scriptAnalyzer = New-Object "Microsoft.Windows.PowerShell.ScriptAnalyzer.ScriptAnalyzer"; $scriptAnalyzer.Initialize( @@ -69,13 +61,8 @@ function Invoke-ScriptAnalyzer { $IncludeRule, $ExcludeRule, $Severity, -<<<<<<< Updated upstream $IncludeDefaultRules.IsPresent, - $SuppressedOnly.IsPresent, - $Profile -======= $SuppressedOnly.IsPresent ->>>>>>> Stashed changes ); if ($PSCmdlet.ParameterSetName -eq "File") { From 0cf22a1a7f58d94d8e5e56396e41ca882ac628f6 Mon Sep 17 00:00:00 2001 From: Quoc Truong Date: Tue, 15 Dec 2015 16:15:15 -0800 Subject: [PATCH 51/53] Change configuration to settings --- .../Commands/InvokeScriptAnalyzerCommand.cs | 10 +++---- Engine/Strings.Designer.cs | 26 +++++++++---------- Engine/Strings.resx | 26 +++++++++---------- Tests/Engine/GlobalSuppression.test.ps1 | 16 ++++++------ 4 files changed, 39 insertions(+), 39 deletions(-) diff --git a/Engine/Commands/InvokeScriptAnalyzerCommand.cs b/Engine/Commands/InvokeScriptAnalyzerCommand.cs index 34c99c014..7e552fc9e 100644 --- a/Engine/Commands/InvokeScriptAnalyzerCommand.cs +++ b/Engine/Commands/InvokeScriptAnalyzerCommand.cs @@ -174,13 +174,13 @@ public SwitchParameter SuppressedOnly [Alias("Profile")] [Parameter(Mandatory = false)] [ValidateNotNull] - public object Configuration + public object Settings { - get { return configuration; } - set { configuration = value; } + get { return settings; } + set { settings = value; } } - private object configuration; + private object settings; private bool stopProcessing; @@ -196,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; diff --git a/Engine/Strings.Designer.cs b/Engine/Strings.Designer.cs index 3f17f6b4c..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 { @@ -178,7 +178,7 @@ internal static string InvalidProfile { } /// - /// Looks up a localized string similar to Key {0} in the configuration is not a string.. + /// Looks up a localized string similar to Key {0} in the settings is not a string.. /// internal static string KeyNotString { get { @@ -376,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 { @@ -385,7 +385,7 @@ internal static string WrongConfigurationKey { } /// - /// Looks up a localized string similar to {0} is not a valid key in the configuration 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 { @@ -394,7 +394,7 @@ internal static string WrongKey { } /// - /// Looks up a localized string similar to Key in the configuration 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 { @@ -403,7 +403,7 @@ internal static string WrongKeyFormat { } /// - /// Looks up a localized string similar to {0} is not a valid key in the configuration hashtable. Valid keys are ExcludeRules, IncludeRules and Severity.. + /// 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 { @@ -421,7 +421,7 @@ internal static string WrongScopeArgumentSuppressionAttributeError { } /// - /// Looks up a localized string similar to Value in the configuration 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 { @@ -430,7 +430,7 @@ internal static string WrongValueFormat { } /// - /// Looks up a localized string similar to Value {0} for key {1} has the wrong data type. Value in the configuration hashtable should be a string or an array of strings.. + /// 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 { diff --git a/Engine/Strings.resx b/Engine/Strings.resx index 9eeca69b8..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 configuration 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 configuration 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 configuration 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,30 +217,30 @@ Analyzing Script Definition. - ConfigurationFileHasNoHashTable + SettingsFileHasNoHashTable - ConfigurationFileNotFound + SettingsFileNotFound - ConfigurationKeyNotAString + SettingsKeyNotAString - ConfigurationValueNotAString + SettingsValueNotAString - ConfigurationValueWrongFormat + SettingsValueWrongFormat - WrongConfigurationKey + WrongSettingsKey - Key {0} in the configuration is not a string. + Key {0} in the settings is not a string. - {0} is not a valid key in the configuration hashtable. Valid keys are ExcludeRules, IncludeRules and Severity. + {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 configuration hashtable should be a string or an array of strings. + 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 dc67bd410..285663ec4 100644 --- a/Tests/Engine/GlobalSuppression.test.ps1 +++ b/Tests/Engine/GlobalSuppression.test.ps1 @@ -55,7 +55,7 @@ Describe "GlobalSuppression" { } It "Does not raise any violation for computername hard-coded using configuration hashtable" { - $hashtableConfiguration = Invoke-ScriptAnalyzer "$directory\GlobalSuppression.ps1" -Configuration @{"includerules" = @("PSAvoidUsingCmdletAliases", "PSUseOutputTypeCorrectly")} | + $hashtableConfiguration = Invoke-ScriptAnalyzer "$directory\GlobalSuppression.ps1" -Settings @{"includerules" = @("PSAvoidUsingCmdletAliases", "PSUseOutputTypeCorrectly")} | Where-Object { $_.RuleName -eq "PSAvoidUsingComputerNameHardcoded"} $hashtableConfiguration.Count | Should Be 0 } @@ -77,7 +77,7 @@ Describe "GlobalSuppression" { } It "Does not raise any violation for use output type correctly with configuration hashtable" { - $hashtableConfiguration = Invoke-ScriptAnalyzer "$directory\GlobalSuppression.ps1" -Configuration @{"severity" = "warning"} | + $hashtableConfiguration = Invoke-ScriptAnalyzer "$directory\GlobalSuppression.ps1" -Settings @{"severity" = "warning"} | Where-Object {$_.RuleName -eq "PSUseOutputTypeCorrectly"} $hashtableConfiguration.Count | should be 0 } @@ -85,21 +85,21 @@ Describe "GlobalSuppression" { 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 From 40538cb4a98ac4372b994c15e5d9e560369005c7 Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Wed, 16 Dec 2015 11:51:17 -0800 Subject: [PATCH 52/53] Update CHANGELOG.MD --- CHANGELOG.MD | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 9920b368b..f8fefa7fb 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -1,3 +1,40 @@ +## Released v1.2.0 (Dec.17, 2015) +###Features: +- Support for consuming PowerShell content as streams (-ScriptDefinition) +- ScriptAnalyzer accepts configuration (settings) in the form of a hashtable (-Settings), added sample Settings +- Ability to run default ruleset along with custom ones in the same invocation (-IncludeDefaultRules) +- Recurse Custom Rule Paths (-RecurseCustomRulePath) +- Consistent Engine error handling when working with Settings, Default and Custom Rules + +###Rules: +- Rule to detect the presence of default value for Mandatory parameters (AvoidDefaultValueForMandatoryParameter) + +###Fixes: +####Engine: +- Engine update to prevent script based injection attacks +- CustomizedRulePath is now called CustomRulePath – Fixes to handle folder paths +- Fixes for RecurseCustomRulePath functionality +- Fix to binplace cmdlet help file as part of build process +- ScriptAnalyzer Profile is now called Settings +- Fix to emit filename in the diagnosticrecord when using Script based custom rules +- Fix to prevent Engine from calling Update-Help for script based custom rules +- Added additional pester tests to take care of test holes in Custom Rule feature +- Post-build error handling improvements, fixed typos in the project + +####Rules: +- Fixed bug in Positional parameter rule to trigger only when used with > 3 positional parameters +- Updated keywords that trigger PSAvoidUsingPlainTextForPassword rule +- Updated ProvideDefaultParameterValue rule to AvoidDefaultValueForMandatoryParameter rule +- Deprecate Internal Url rule based on community feedback, identified additional rules to handle hardcoded paths etc +- Added localhost exceptions for HardCodedComputerName Rule +- Update to Credential based rules to validate the presence of CredentialAttribute and PSCredential type + +###Documentation: +- Rule & Cmdlet documentation updates – Cmdlet help file addition + + +## + ## Released v1.1.1 (Nov.3, 2015) ###Features: - Support for PSDrives when using Invoke-ScriptAnalyzer From 51f9d93943dec56cf3d1bb6c5dfa333ca0493b1f Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Wed, 16 Dec 2015 11:52:38 -0800 Subject: [PATCH 53/53] Update appveyor.yml --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 9a037b232..50865ac4c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,7 +6,7 @@ clone_folder: c:\projects\psscriptanalyzer # Install Pester install: - - cinst -y pester + - cinst -y pester --version 3.3.13 # Build PSScriptAnalyzer using msbuild build: true