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
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/Engine/Commands/GetScriptAnalyzerRuleCommand.cs b/Engine/Commands/GetScriptAnalyzerRuleCommand.cs
index a914e1f59..95ed669cc 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, null, null, null, null == rulePaths ? true : false);
}
///
diff --git a/Engine/Commands/InvokeScriptAnalyzerCommand.cs b/Engine/Commands/InvokeScriptAnalyzerCommand.cs
index d89413948..7e552fc9e 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,18 +53,57 @@ 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.
///
[Parameter(Mandatory = false)]
[ValidateNotNull]
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
- public string[] CustomizedRulePath
+ [Alias("CustomizedRulePath")]
+ public string[] CustomRulePath
+ {
+ get { return customRulePath; }
+ set { customRulePath = value; }
+ }
+ private string[] customRulePath;
+
+ ///
+ /// RecurseCustomRulePath: Find rules within subfolders under the path
+ ///
+ [Parameter(Mandatory = false)]
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
+ public SwitchParameter RecurseCustomRulePath
{
- get { return customizedRulePath; }
- set { customizedRulePath = value; }
+ get { return recurseCustomRulePath; }
+ set { recurseCustomRulePath = value; }
}
- private string[] customizedRulePath;
+ 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.
@@ -125,16 +169,20 @@ 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 Profile
+ public object Settings
{
- get { return profile; }
- set { profile = value; }
+ get { return settings; }
+ set { settings = value; }
}
- private string profile;
+
+ private object settings;
+
+ private bool stopProcessing;
#endregion Parameters
@@ -145,14 +193,23 @@ public string Profile
///
protected override void BeginProcessing()
{
+ string[] rulePaths = Helper.ProcessCustomRulePaths(customRulePath,
+ this.SessionState, recurseCustomRulePath);
+
+ if (!ScriptAnalyzer.Instance.ParseProfile(this.settings, this.SessionState.Path, this))
+ {
+ stopProcessing = true;
+ return;
+ }
+
ScriptAnalyzer.Instance.Initialize(
this,
- customizedRulePath,
+ rulePaths,
this.includeRule,
this.excludeRule,
this.severity,
- this.suppressedOnly,
- this.profile);
+ null == rulePaths ? true : this.includeDefaultRules,
+ this.suppressedOnly);
}
///
@@ -160,22 +217,55 @@ 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 (stopProcessing)
+ {
+ stopProcessing = false;
+ return;
+ }
+
+ if (String.Equals(this.ParameterSetName, "File", StringComparison.OrdinalIgnoreCase))
+ {
+ // 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))
{
- ProcessPath(this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(p.Path));
+ ProcessPathOrScriptDefinition(scriptDefinition);
}
}
+ protected override void EndProcessing()
+ {
+ ScriptAnalyzer.Instance.CleanUp();
+ base.EndProcessing();
+ }
+
+ protected override void StopProcessing()
+ {
+ ScriptAnalyzer.Instance.CleanUp();
+ base.StopProcessing();
+ }
+
#endregion
#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, this.recurse);
+ }
+ 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/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/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/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/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/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/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/Helper.cs b/Engine/Helper.cs
index 7297bc4b0..78e43dc2a 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;
@@ -956,8 +957,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));
}
}
@@ -973,9 +984,51 @@ public Tuple, List> SuppressRule(string
return result;
}
+ 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 (rulePaths == null)
+ {
+ return null;
+ }
+
+ Collection pathInfo = new Collection();
+ foreach (string rulePath in rulePaths)
+ {
+ Collection pathInfosForRulePath = sessionState.Path.GetResolvedPSPathFromPSPath(rulePath);
+ if (null != pathInfosForRulePath)
+ {
+ foreach (PathInfo pathInfoForRulePath in pathInfosForRulePath)
+ {
+ pathInfo.Add(pathInfoForRulePath);
+ }
+ }
+ }
+
+ 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();
+
+ }
+
+
#endregion
}
+
internal class TupleComparer : IComparer>
{
public int Compare(Tuple t1, Tuple t2)
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..f9548dd6b
--- /dev/null
+++ b/Engine/Microsoft.Windows.PowerShell.ScriptAnalyzer.dll-Help.xml
@@ -0,0 +1,998 @@
+
+
+
+
+
+
+
+
+
+ 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
+
+ CustomRulePath
+
+ 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 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
+ 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 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
+
+
+ Severity
+
+ 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
+
+
+
+
+
+
+ CustomRulePath
+
+ 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 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
+
+ SwitchParameter
+
+
+
+
+
+ Name
+
+ 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[]
+
+ 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.
+
+ String[]
+
+ String[]
+
+
+ All rules
+
+
+
+
+
+
+ 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.
+
+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
+
+
+
+
+
+
+
+ -------------------------- 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*, *Alias*
+
+ 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
+ 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
+
+
+ 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 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
+
+
+
+
+ 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 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
+
+
+
+
+
+ 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
+
+ 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
+
+ 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[]
+
+ 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. To search the CustomRulePath recursively, use the RecurseCustomRulePath 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 of Invoke-ScriptAnalyzer.
+
+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.
+
+
+ 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.
+
+
+
+
+
+
+
+
+
+ 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
+
+
+
+
+
+
+
+ -------------------------- EXAMPLE 1 --------------------------
+
+ PS C:\>
+
+ Invoke-ScriptAnalyzer -Path C:\Scripts\Get-LogData.ps1
+
+ This command runs all Script Analyzer rules on the Get-LogData.ps1 script.
+
+
+
+ -------------------------- EXAMPLE 2 --------------------------
+
+ PS C:\>
+
+ Invoke-ScriptAnalyzer -Path $home\Documents\WindowsPowerShell\Modules -Recurse
+
+ This command runs all Script Analyzer rules on all .ps1 and .psm1 files in the Modules directory and its
+subdirectories.
+
+
+
+ -------------------------- EXAMPLE 3 --------------------------
+
+ PS C:\>
+
+ Invoke-ScriptAnalyzer -Path C:\Windows\System32\WindowsPowerShell\v1.0\Modules\PSDiagnostics -IncludeRule PSAvoidUsingPositionalParameters
+
+ 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.
+
+
+
+ -------------------------- 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, including the scripts in its subdirectories, with all rules except for PSAvoidUsingCmdletAliases and PSAvoidUsingInternalURLs.
+
+
+
+ -------------------------- EXAMPLE 5 --------------------------
+
+ PS C:\>
+
+ Invoke-ScriptAnalyzer -Path D:\test_scripts\Test-Script.ps1 -CustomRulePath C:\CommunityAnalyzerRules
+
+ This command runs Script Analyzer on Test-Script.ps1 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.
+
+
+
+ -------------------------- EXAMPLE 9 --------------------------
+
+ PS C:\>
+
+ Invoke-ScriptAnalyzer -ScriptDefinition "function Get-Widgets {Write-Host 'Hello'}"
+
+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.
+
+When you use the ScriptDefinition parameter, the FileName property of the DiagnosticRecord object is $null.
+
+
+
+
+
+
+ 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
diff --git a/Engine/PSScriptAnalyzer.psd1 b/Engine/PSScriptAnalyzer.psd1
index c68e1d647..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'
@@ -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 c6cbcd528..b37fe099d 100644
--- a/Engine/ScriptAnalyzer.cs
+++ b/Engine/ScriptAnalyzer.cs
@@ -26,6 +26,8 @@
using System.Globalization;
using System.Collections.Concurrent;
using System.Threading.Tasks;
+using System.Collections.ObjectModel;
+using System.Collections;
namespace Microsoft.Windows.PowerShell.ScriptAnalyzer
{
@@ -95,19 +97,19 @@ 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 suppressedOnly = false,
- string profile = null)
+ bool includeDefaultRules = false,
+ bool suppressedOnly = false)
where TCmdlet : PSCmdlet, IOutputWriter
{
if (cmdlet == null)
{
throw new ArgumentNullException("cmdlet");
}
-
+
this.Initialize(
cmdlet,
cmdlet.SessionState.Path,
@@ -116,8 +118,8 @@ internal void Initialize(
includeRuleNames,
excludeRuleNames,
severity,
- suppressedOnly,
- profile);
+ includeDefaultRules,
+ suppressedOnly);
}
///
@@ -126,12 +128,12 @@ 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 suppressedOnly = false,
- string profile = null)
+ bool includeDefaultRules = false,
+ bool suppressedOnly = false)
{
if (runspace == null)
{
@@ -146,189 +148,341 @@ public void Initialize(
includeRuleNames,
excludeRuleNames,
severity,
- suppressedOnly,
- profile);
+ includeDefaultRules,
+ suppressedOnly);
}
- private void Initialize(
- IOutputWriter outputWriter,
- PathIntrinsics path,
- CommandInvocationIntrinsics invokeCommand,
- string[] customizedRulePath,
- string[] includeRuleNames,
- string[] excludeRuleNames,
- string[] severity,
- bool suppressedOnly = false,
- string profile = null)
+ ///
+ /// clean up this instance, resetting all properties
+ ///
+ public void CleanUp()
{
- if (outputWriter == null)
+ 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)
{
- throw new ArgumentNullException("outputWriter");
+ return true;
}
- this.outputWriter = outputWriter;
+ if (!(profileObject is string || profileObject is Hashtable))
+ {
+ return false;
+ }
- #region Verifies rule extensions and loggers path
+ List includeRuleList = new List();
+ List excludeRuleList = new List();
+ List severityList = new List();
- List paths = this.GetValidCustomRulePaths(customizedRulePath, path);
+ bool hasError = false;
- #endregion
+ Hashtable hashTableProfile = profileObject as Hashtable;
- #region Initializes Rules
+ // checks whether we get a hashtable
+ if (hashTableProfile != null)
+ {
+ hasError = ParseProfileHashtable(hashTableProfile, path, writer, severityList, includeRuleList, excludeRuleList);
+ }
+ else
+ {
+ // checks whether we get a string instead
+ string profile = profileObject as string;
- this.severity = severity;
- this.suppressedOnly = suppressedOnly;
- this.includeRule = includeRuleNames;
- this.excludeRule = excludeRuleNames;
- this.includeRegexList = new List();
- this.excludeRegexList = new List();
+ if (!String.IsNullOrWhiteSpace(profile))
+ {
+ hasError = ParseProfileString(profile, path, writer, severityList, includeRuleList, excludeRuleList);
+ }
+ }
+
+ 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;
+ }
- #region ParseProfile
- if (!String.IsNullOrWhiteSpace(profile))
+ 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)
{
- try
+ string key = obj as string;
+
+ // key should be a string
+ if (key == null)
{
- profile = path.GetResolvedPSPathFromPSPath(profile).First().Path;
+ writer.WriteError(new ErrorRecord(new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.KeyNotString, key)),
+ Strings.ConfigurationKeyNotAString, ErrorCategory.InvalidData, profile));
+ hasError = true;
+ continue;
}
- catch
+
+ // checks whether it falls into list of valid keys
+ if (!validKeys.Contains(key))
{
- this.outputWriter.WriteWarning(string.Format(CultureInfo.CurrentCulture, Strings.FileNotFound, profile));
+ writer.WriteError(new ErrorRecord(
+ new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongKeyHashTable, key)),
+ Strings.WrongConfigurationKey, ErrorCategory.InvalidData, profile));
+ hasError = true;
+ continue;
}
- if (File.Exists(profile))
+ 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))
{
- 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.WrongValueHashTable, value, key)),
+ Strings.WrongConfigurationKey, ErrorCategory.InvalidData, profile));
+ hasError = true;
+ continue;
+ }
+
+ // if we get here then everything is good
- // no hashtable, raise warning
- if (hashTableAsts.Count() == 0)
+ 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)
{
- this.outputWriter.WriteWarning(string.Format(CultureInfo.CurrentCulture, Strings.InvalidProfile, profile));
+ 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
- this.outputWriter.WriteWarning(
- string.Format(CultureInfo.CurrentCulture, Strings.WrongKeyFormat, kvp.Item1.Extent.StartLineNumber, kvp.Item1.Extent.StartColumnNumber, profile));
- 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)
+ ArrayLiteralAst arrayLitAst = pureExp as ArrayLiteralAst;
+ if (arrayLitAst == null && pureExp is ArrayExpressionAst)
{
- rhsList.Add((pureExp as StringConstantExpressionAst).Value);
- }
- else
- {
- ArrayLiteralAst arrayLitAst = pureExp as ArrayLiteralAst;
- if (arrayLitAst == null && pureExp is ArrayExpressionAst)
+ ArrayExpressionAst arrayExp = pureExp as ArrayExpressionAst;
+ // Statements property is never null
+ if (arrayExp.SubExpression != null)
{
- 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.First();
- 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.First();
- if (cmdBaseAst != null && cmdBaseAst is CommandExpressionAst)
+ CommandExpressionAst cmdExpAst = cmdBaseAst as CommandExpressionAst;
+ if (cmdExpAst.Expression is StringConstantExpressionAst)
+ {
+ rhsList.Add((cmdExpAst.Expression as StringConstantExpressionAst).Value);
+ }
+ else
{
- CommandExpressionAst cmdExpAst = cmdBaseAst as CommandExpressionAst;
- if (cmdExpAst.Expression is StringConstantExpressionAst)
- {
- rhsList.Add((cmdExpAst.Expression as StringConstantExpressionAst).Value);
- }
- else
- {
- arrayLitAst = cmdExpAst.Expression as ArrayLiteralAst;
- }
+ 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))
- {
- this.outputWriter.WriteWarning(
- string.Format(CultureInfo.CurrentCulture, Strings.WrongValueFormat, element.Extent.StartLineNumber, element.Extent.StartColumnNumber, profile));
- 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)
- {
- this.outputWriter.WriteWarning(
- string.Format(CultureInfo.CurrentCulture, Strings.WrongValueFormat, kvp.Item2.Extent.StartLineNumber, kvp.Item2.Extent.StartColumnNumber, profile));
- break;
- }
+ 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;
+ }
- switch ((kvp.Item1 as StringConstantExpressionAst).Value.ToLower())
- {
- case "severity":
- if (this.severity == null)
- {
- this.severity = rhsList.ToArray();
- }
- else
- {
- this.severity = this.severity.Union(rhsList).ToArray();
- }
- break;
- case "includerules":
- if (this.includeRule == null)
- {
- this.includeRule = rhsList.ToArray();
- }
- else
- {
- this.includeRule = this.includeRule.Union(rhsList).ToArray();
- }
- break;
- case "excluderules":
- if (this.excludeRule == null)
- {
- this.excludeRule = rhsList.ToArray();
- }
- else
- {
- this.excludeRule = this.excludeRule.Union(rhsList).ToArray();
- }
- break;
- default:
- this.outputWriter.WriteWarning(
- string.Format(CultureInfo.CurrentCulture, Strings.WrongKey, kvp.Item1.Extent.StartLineNumber, kvp.Item1.Extent.StartColumnNumber, profile));
- break;
- }
+ string key = (kvp.Item1 as StringConstantExpressionAst).Value.ToLower();
+
+ 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;
}
}
}
}
+ return hasError;
+ }
+
+ private void Initialize(
+ IOutputWriter outputWriter,
+ PathIntrinsics path,
+ CommandInvocationIntrinsics invokeCommand,
+ string[] customizedRulePath,
+ string[] includeRuleNames,
+ string[] excludeRuleNames,
+ string[] severity,
+ bool includeDefaultRules = false,
+ 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)
{
@@ -338,6 +492,7 @@ private void Initialize(
this.includeRegexList.Add(includeRegex);
}
}
+
if (this.excludeRule != null)
{
foreach (string rule in excludeRule)
@@ -349,7 +504,7 @@ private void Initialize(
try
{
- this.LoadRules(this.validationResults, invokeCommand);
+ this.LoadRules(this.validationResults, invokeCommand, includeDefaultRules);
}
catch (Exception ex)
{
@@ -377,11 +532,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));
}
@@ -417,7 +572,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();
@@ -471,9 +626,16 @@ private void LoadRules(Dictionary> result, CommandInvocatio
}
}
+ if (!loadBuiltInRules)
+ {
+ this.ScriptRules = null;
+ }
+
// Gets external rules.
if (result.ContainsKey("ValidModPaths") && result["ValidModPaths"].Count > 0)
+ {
ExternalRules = GetExternalRule(result["ValidModPaths"].ToArray());
+ }
}
internal string[] GetValidModulePaths()
@@ -494,8 +656,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)
@@ -545,35 +711,63 @@ 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;
-
- // 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("Get-Module").AddParameter("Name", moduleName).AddParameter("ListAvailable");
+ shortModuleName = posh.Invoke().First().Name;
+
// 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)
{
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)
{
- 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();
+ // 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
+ 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;
+
+ if ((null != helpContent) && ( 1 == helpContent.Count))
+ {
+ dynamic description = helpContent[0].Properties["Description"];
+
+ if (null != description)
+ {
+ desc = description.Value[0].Text;
+ }
+ }
+
rules.Add(new ExternalRule(funcInfo.Name, funcInfo.Name, desc, param.Name, param.ParameterType.FullName,
funcInfo.ModuleName, funcInfo.Module.Path));
}
@@ -732,7 +926,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));
}
}
}
@@ -770,7 +964,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;
@@ -783,14 +977,14 @@ 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:
// "..\..\..\ScriptAnalyzer.UnitTest\modules\CommunityAnalyzerRules\CommunityAnalyzerRules.psd1"
- if (moduleInfo.ExportedFunctions.Count > 0) validModPaths.Add(childPath);
+ if (moduleInfo.ExportedFunctions.Count > 0) validModPaths.Add(resolvedPath);
}
}
catch
@@ -840,7 +1034,7 @@ public Dictionary> CheckRuleExtension(string[] path, PathIn
}
}
- // Resloves relative paths.
+ // Resolves relative paths.
try
{
for (int i = 0; i < validModPaths.Count; i++)
@@ -869,7 +1063,7 @@ public Dictionary> CheckRuleExtension(string[] path, PathIn
}
#endregion
-
+
///
/// Analyzes a script file or a directory containing script files.
@@ -910,6 +1104,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,
@@ -1024,7 +1261,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,
@@ -1038,8 +1277,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)
{
@@ -1069,7 +1312,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)
{
@@ -1271,7 +1514,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)
@@ -1362,7 +1605,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/ScriptAnalyzerEngine.csproj b/Engine/ScriptAnalyzerEngine.csproj
index 80e91a809..46c607751 100644
--- a/Engine/ScriptAnalyzerEngine.csproj
+++ b/Engine/ScriptAnalyzerEngine.csproj
@@ -1,111 +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)"
-copy /y "$(TargetPath)" "$(SolutionDir)$(SolutionName)"
-mkdir "$(SolutionDir)$(SolutionName)\en-US"
-copy /y "$(ProjectDir)\about_*.help.txt" "$(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/Engine/Strings.Designer.cs b/Engine/Strings.Designer.cs
index cc892c6c8..379b35d13 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 SettingsFileHasNoHashTable.
+ ///
+ internal static string ConfigurationFileHasNoHashTable {
+ get {
+ return ResourceManager.GetString("ConfigurationFileHasNoHashTable", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to SettingsFileNotFound.
+ ///
+ internal static string ConfigurationFileNotFound {
+ get {
+ return ResourceManager.GetString("ConfigurationFileNotFound", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to SettingsKeyNotAString.
+ ///
+ internal static string ConfigurationKeyNotAString {
+ get {
+ return ResourceManager.GetString("ConfigurationKeyNotAString", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to SettingsValueNotAString.
+ ///
+ internal static string ConfigurationValueNotAString {
+ get {
+ return ResourceManager.GetString("ConfigurationValueNotAString", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to SettingsValueWrongFormat.
+ ///
+ internal static string ConfigurationValueWrongFormat {
+ get {
+ return ResourceManager.GetString("ConfigurationValueWrongFormat", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Writes all diagnostics to WriteObject..
///
@@ -124,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 {
@@ -132,6 +177,15 @@ internal static string InvalidProfile {
}
}
+ ///
+ /// Looks up a localized string similar to Key {0} in the settings is not a string..
+ ///
+ internal static string KeyNotString {
+ get {
+ return ResourceManager.GetString("KeyNotString", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to No loggers found..
///
@@ -177,6 +231,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 +258,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.
///
@@ -205,7 +277,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 {
@@ -222,6 +294,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}..
///
@@ -286,7 +367,25 @@ internal static string VerboseRunningMessage {
}
///
- /// 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 Analyzing Script Definition..
+ ///
+ internal static string VerboseScriptDefinitionMessage {
+ get {
+ return ResourceManager.GetString("VerboseScriptDefinitionMessage", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to WrongSettingsKey.
+ ///
+ 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 settings hashtable: line {1} column {2} in file {3}. Valid keys are ExcludeRules, IncludeRules and Severity..
///
internal static string WrongKey {
get {
@@ -295,7 +394,7 @@ internal static string WrongKey {
}
///
- /// Looks up a localized string similar to Key in the profile hashtable should be a string: line {0} column {1} in file {2}.
+ /// Looks up a localized string similar to Key in the settings hashtable should be a string: line {0} column {1} in file {2}.
///
internal static string WrongKeyFormat {
get {
@@ -303,6 +402,15 @@ internal static string WrongKeyFormat {
}
}
+ ///
+ /// Looks up a localized string similar to {0} is not a valid key in the settings hashtable. Valid keys are ExcludeRules, IncludeRules and Severity..
+ ///
+ internal static string WrongKeyHashTable {
+ get {
+ return ResourceManager.GetString("WrongKeyHashTable", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Scope can only be either function or class..
///
@@ -313,12 +421,21 @@ internal static string WrongScopeArgumentSuppressionAttributeError {
}
///
- /// Looks up a localized string similar to Value in the profile hashtable should be a string or an array of strings: line {0} column {1} in file {2}.
+ /// Looks up a localized string similar to Value in the settings hashtable should be a string or an array of strings: line {0} column {1} in file {2}.
///
internal static string WrongValueFormat {
get {
return ResourceManager.GetString("WrongValueFormat", resourceCulture);
}
}
+
+ ///
+ /// Looks up a localized string similar to Value {0} for key {1} has the wrong data type. Value in the settings hashtable should be a string or an array of strings..
+ ///
+ internal static string WrongValueHashTable {
+ get {
+ return ResourceManager.GetString("WrongValueHashTable", resourceCulture);
+ }
+ }
}
}
diff --git a/Engine/Strings.resx b/Engine/Strings.resx
index 87daf264d..aa1a3f779 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}
@@ -193,15 +193,54 @@
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 settings hashtable: line {1} column {2} in file {3}. Valid keys are ExcludeRules, IncludeRules and Severity.
- Key in the profile hashtable should be a string: line {0} column {1} in file {2}
+ Key in the settings hashtable should be a string: line {0} column {1} in file {2}
- Value in the profile hashtable should be a string or an array of strings: line {0} column {1} in file {2}
+ Value in the settings hashtable should be a string or an array of strings: line {0} column {1} in file {2}
- Profile file '{0}' is invalid because it does not contain a hashtable.
+ Settings file '{0}' is invalid because it does not contain a hashtable.
+
+
+ Parse error in script definition: {0} at line {1} column {2}.
+
+
+ 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.
+
+
+ SettingsFileHasNoHashTable
+
+
+ SettingsFileNotFound
+
+
+ SettingsKeyNotAString
+
+
+ SettingsValueNotAString
+
+
+ SettingsValueWrongFormat
+
+
+ WrongSettingsKey
+
+
+ Key {0} in the settings is not a string.
+
+
+ {0} is not a valid key in the settings hashtable. Valid keys are ExcludeRules, IncludeRules and Severity.
+
+
+ Value {0} for key {1} has the wrong data type. Value in the settings hashtable should be a string or an array of strings.
\ No newline at end of file
diff --git a/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/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/README.md b/README.md
index babe53032..d2de4d679 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 - 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)
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 932da65e6..c170308a4 100644
--- a/Rules/AvoidDefaultTrueValueSwitchParameter.cs
+++ b/Rules/AvoidDefaultTrueValueSwitchParameter.cs
@@ -36,15 +36,24 @@ 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))
&& 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/ProvideDefaultParameterValue.cs b/Rules/AvoidDefaultValueForMandatoryParameter.cs
similarity index 60%
rename from Rules/ProvideDefaultParameterValue.cs
rename to Rules/AvoidDefaultValueForMandatoryParameter.cs
index 3d5c24023..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 = 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/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 0222200e9..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);
@@ -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/AvoidUserNameAndPasswordParams.cs b/Rules/AvoidUserNameAndPasswordParams.cs
index 823ca6e3c..a0b4aec87 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;
@@ -49,13 +50,19 @@ 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)
{
- 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 (paramType == typeof(PSCredential) || (paramType.IsArray && paramType.GetElementType() == typeof (PSCredential)))
+ // 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/AvoidUsingComputerNameHardcoded.cs b/Rules/AvoidUsingComputerNameHardcoded.cs
index f0454d412..95ec0a454 100644
--- a/Rules/AvoidUsingComputerNameHardcoded.cs
+++ b/Rules/AvoidUsingComputerNameHardcoded.cs
@@ -15,6 +15,8 @@
using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic;
using System.ComponentModel.Composition;
using System.Globalization;
+using System.Collections.Generic;
+using System.Linq;
namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules
{
@@ -48,7 +50,23 @@ 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;
+ 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;
+ }
}
}
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/AvoidUsingPlainTextForPassword.cs b/Rules/AvoidUsingPlainTextForPassword.cs
index ffec527f5..766aa78ee 100644
--- a/Rules/AvoidUsingPlainTextForPassword.cs
+++ b/Rules/AvoidUsingPlainTextForPassword.cs
@@ -37,9 +37,9 @@ 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.
+ // 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/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/ScriptAnalyzerBuiltinRules.csproj b/Rules/ScriptAnalyzerBuiltinRules.csproj
index 9328f21d1..73ca65257 100644
--- a/Rules/ScriptAnalyzerBuiltinRules.csproj
+++ b/Rules/ScriptAnalyzerBuiltinRules.csproj
@@ -1,118 +1,119 @@
-
-
-
- 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
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Designer
- ResXFileCodeGenerator
- Strings.Designer.cs
-
-
-
-
- {f4bde3d0-3eef-4157-8a3e-722df7adef60}
- ScriptAnalyzerEngine
-
-
-
-
-
-
-
- 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/Strings.Designer.cs b/Rules/Strings.Designer.cs
index b49545e74..55a65fd0a 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.
///
@@ -123,6 +159,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 +285,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.
///
@@ -367,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 with CredentialAttribute where PSCredential comes before CredentialAttribute instead of username and password parameters..
///
internal static string AvoidUsernameAndPasswordParamsDescription {
get {
@@ -376,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 with a CredentialAttribute where PSCredential comes before CredentialAttribute should be used..
///
internal static string AvoidUsernameAndPasswordParamsError {
get {
@@ -501,6 +555,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 +834,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.
///
@@ -790,7 +862,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 +871,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 {
@@ -807,6 +879,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.
///
@@ -1131,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.
///
@@ -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.
///
@@ -1717,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 with CredentialAttribute where PSCredential comes before CredentialAttribute.. This comes from the PowerShell teams best practices..
///
internal static string UsePSCredentialTypeDescription {
get {
@@ -1726,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 with CredentialAttribute where PSCredential comes before CredentialAttribute..
///
internal static string UsePSCredentialTypeError {
get {
@@ -1735,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 with CredentialAttribute where PSCredential comes before CredentialAttribute..
///
internal static string UsePSCredentialTypeErrorSB {
get {
diff --git a/Rules/Strings.resx b/Rules/Strings.resx
index c5b254de5..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. 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.
+ 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.
+ The Credential parameter in a found script block must be of the type PSCredential with CredentialAttribute where PSCredential comes before CredentialAttribute.
PSCredential
@@ -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
@@ -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 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 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
@@ -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
@@ -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/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);
diff --git a/Rules/UsePSCredentialType.cs b/Rules/UsePSCredentialType.cs
index 99ddbd70f..bddc3bf66 100644
--- a/Rules/UsePSCredentialType.cs
+++ b/Rules/UsePSCredentialType.cs
@@ -11,6 +11,8 @@
//
using System;
+using System.Reflection;
+using System.Linq;
using System.Collections.Generic;
using System.Management.Automation;
using System.Management.Automation.Language;
@@ -50,7 +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))
+ if (WrongCredentialUsage(parameter))
{
yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.UsePSCredentialTypeError, funcName), funcDefAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName);
}
@@ -61,7 +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))
+ if (WrongCredentialUsage(parameter))
{
yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.UsePSCredentialTypeError, funcName), funcDefAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName);
}
@@ -71,11 +73,17 @@ 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)
{
- if (parameter.Name.VariablePath.UserPath.Equals("Credential", StringComparison.OrdinalIgnoreCase) && parameter.StaticType != typeof(PSCredential))
+ if (WrongCredentialUsage(parameter))
{
yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.UsePSCredentialTypeErrorSB), scriptBlockAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName);
}
@@ -84,6 +92,27 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName)
}
}
+ private bool WrongCredentialUsage(ParameterAst parameter)
+ {
+ if (parameter.Name.VariablePath.UserPath.Equals("Credential", StringComparison.OrdinalIgnoreCase))
+ {
+ 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));
+
+ // check that both exists and pscredentialtype comes before credential attribute
+ if (psCredentialType != null && credentialAttribute != null && psCredentialType.Extent.EndOffset < credentialAttribute.Extent.StartOffset)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
///
/// GetName: Retrieves the name of this rule.
///
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/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
diff --git a/Tests/Engine/CustomizedRule.tests.ps1 b/Tests/Engine/CustomizedRule.tests.ps1
index cf87ed818..6c3cb10e9 100644
--- a/Tests/Engine/CustomizedRule.tests.ps1
+++ b/Tests/Engine/CustomizedRule.tests.ps1
@@ -6,6 +6,19 @@ 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"
@@ -46,23 +59,113 @@ 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 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" {
- 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
+ }
+
+ 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", "$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
+ }
}
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
+ }
+
+ 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
+ }
+ }
+
}
+}
-}
\ No newline at end of file
diff --git a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1
index 63895955c..d992f5ca4 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 parameter" {
+ $params.CustomRulePath.Aliases.Contains("CustomizedRulePath") | Should be $true
+ }
}
}
@@ -49,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" {
@@ -87,24 +96,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 -CustomRulePath "Invalid CustomRulePath"
+ }
+ catch
+ {
+ $Error[0].FullyQualifiedErrorId | should match "PathNotFound,Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands.GetScriptAnalyzerRuleCommand"
+ }
}
}
@@ -118,7 +131,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" {
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 11b030756..285663ec4 100644
--- a/Tests/Engine/GlobalSuppression.test.ps1
+++ b/Tests/Engine/GlobalSuppression.test.ps1
@@ -7,43 +7,99 @@ 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
+ 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"}
+ $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" }
$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" {
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
+ }
+
+ It "Does not raise any violation for computername hard-coded using configuration hashtable" {
+ $hashtableConfiguration = Invoke-ScriptAnalyzer "$directory\GlobalSuppression.ps1" -Settings @{"includerules" = @("PSAvoidUsingCmdletAliases", "PSUseOutputTypeCorrectly")} |
+ Where-Object { $_.RuleName -eq "PSAvoidUsingComputerNameHardcoded"}
+ $hashtableConfiguration.Count | Should Be 0
}
}
Context "Severity" {
- 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 "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 "PSUseOutputTypeCorrectly" }
+ $withProfile.Count | Should be 0
+ }
+
+ It "Does not raise any violation for use output type correctly with configuration hashtable" {
+ $hashtableConfiguration = Invoke-ScriptAnalyzer "$directory\GlobalSuppression.ps1" -Settings @{"severity" = "warning"} |
+ Where-Object {$_.RuleName -eq "PSUseOutputTypeCorrectly"}
+ $hashtableConfiguration.Count | should be 0
+ }
+ }
+
+ Context "Error Case" {
+ It "Raises Error for file not found" {
+ $invokeWithError = Invoke-ScriptAnalyzer "$directory\GlobalSuppression.ps1" -Settings ".\ThisFileDoesNotExist.ps1" -ErrorAction SilentlyContinue
+ $invokeWithError.Count | should be 0
+ $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" -Settings "$directory\GlobalSuppression.ps1" -ErrorAction SilentlyContinue
+ $invokeWithError.Count | should be 0
+ $Error[0].FullyQualifiedErrorId | should match "SettingsFileHasNoHashTable,Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands.InvokeScriptAnalyzerCommand"
+ }
+
+ It "Raises Error for wrong profile" {
+ $invokeWithError = Invoke-ScriptAnalyzer "$directory\GlobalSuppression.ps1" -Settings "$directory\WrongProfile.ps1" -ErrorAction SilentlyContinue
+ $invokeWithError.Count | should be 0
+ $Error[0].FullyQualifiedErrorId | should match "WrongSettingsKey,Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands.InvokeScriptAnalyzerCommand"
}
}
}
\ No newline at end of file
diff --git a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1
index 467a71723..2b1b04917 100644
--- a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1
+++ b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1
@@ -25,14 +25,28 @@ Describe "Test available parameters" {
}
}
- Context "CustomizedRulePath parameters" {
- It "has a CustomizedRulePath parameter" {
- $params.ContainsKey("CustomizedRulePath") | Should Be $true
+ 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 "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 array" {
+ $params["CustomRulePath"].ParameterType.FullName | Should Be "System.String[]"
}
+
+ It "has a CustomizedRulePath alias"{
+ $params.CustomRulePath.Aliases.Contains("CustomizedRulePath") | Should be $true
+ }
}
Context "IncludeRule parameters" {
@@ -54,6 +68,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" {
@@ -254,22 +308,28 @@ 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
+ }
+
}
- 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'."
+ Context "When used incorrectly" {
+ It "file cannot be found" {
+ try
+ {
+ Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomRulePath "Invalid CustomRulePath"
+ }
+ catch
+ {
+ if (-not $testingLibraryUsage)
+ {
+ $Error[0].FullyQualifiedErrorId | should match "PathNotFound,Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands.InvokeScriptAnalyzerCommand"
+ }
+ }
}
}
}
\ No newline at end of file
diff --git a/Tests/Engine/LibraryUsage.tests.ps1 b/Tests/Engine/LibraryUsage.tests.ps1
index 37137c832..1ae8eae60 100644
--- a/Tests/Engine/LibraryUsage.tests.ps1
+++ b/Tests/Engine/LibraryUsage.tests.ps1
@@ -6,12 +6,21 @@ $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,
+ [Alias("CustomizedRulePath")]
+ [string[]] $CustomRulePath = $null,
+
+ [Parameter(Mandatory = $false)]
+ [switch] $RecurseCustomRulePath,
[Parameter(Mandatory=$false)]
[string[]] $ExcludeRule = $null,
@@ -22,26 +31,46 @@ function Invoke-ScriptAnalyzer {
[ValidateSet("Warning", "Error", "Information", IgnoreCase = $true)]
[Parameter(Mandatory = $false)]
[string[]] $Severity = $null,
-
+
[Parameter(Mandatory = $false)]
[switch] $Recurse,
[Parameter(Mandatory = $false)]
- [switch] $SuppressedOnly
- )
+ [switch] $IncludeDefaultRules,
- $scriptAnalyzer = New-Object "Microsoft.Windows.PowerShell.ScriptAnalyzer.ScriptAnalyzer"
+ [Parameter(Mandatory = $false)]
+ [switch] $SuppressedOnly
+ )
+
+ if ($null -eq $CustomRulePath)
+ {
+ $IncludeDefaultRules = $true
+ }
+ # There is an inconsistency between this implementation and c# implementation of the cmdlet.
+ # The CustomRulePath parameter here is of "string[]" type whereas in the c# implementation it is of "string" type.
+ # If we set the CustomRulePath parameter here to "string[]", then the library usage test fails when run as an administrator.
+ # We want to note that the library usage test doesn't fail when run as a non-admin user.
+ # The following is the error statement when the test runs as an administrator.
+ # Assert failed on "Initialize" with "7" argument(s): "Test failed due to terminating error: The module was expected to contain an assembly manifest. (Exception from HRESULT: 0x80131018)"
+
+ $scriptAnalyzer = New-Object "Microsoft.Windows.PowerShell.ScriptAnalyzer.ScriptAnalyzer";
$scriptAnalyzer.Initialize(
$runspace,
$testOutputWriter,
- $CustomizedRulePath,
+ $CustomRulePath,
$IncludeRule,
$ExcludeRule,
$Severity,
+ $IncludeDefaultRules.IsPresent,
$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
@@ -110,6 +139,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
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 7902648d7..555b770c5 100644
--- a/Tests/Engine/RuleSuppression.tests.ps1
+++ b/Tests/Engine/RuleSuppression.tests.ps1
@@ -7,13 +7,16 @@ 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.Count | Should Be 0
+ $suppression = $violationsUsingScriptDefinition | Where-Object { $_.RuleName -eq "PSProvideCommentHelp" }
+ $suppression.Count | Should Be 0
}
}
@@ -21,6 +24,8 @@ Describe "RuleSuppressionWithoutScope" {
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
}
}
@@ -28,6 +33,8 @@ Describe "RuleSuppressionWithoutScope" {
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
}
}
@@ -35,12 +42,16 @@ Describe "RuleSuppressionWithoutScope" {
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
}
}
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 "PSAvoidDefaultValueForMandatoryParameter" }
$suppression.Count | Should Be 1
}
}
@@ -51,6 +62,8 @@ Describe "RuleSuppressionWithScope" {
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
}
}
@@ -58,6 +71,8 @@ Describe "RuleSuppressionWithScope" {
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
}
}
}
\ 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
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
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/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 14f567160..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 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 f964458cf..2ea0db9cc 100644
--- a/Tests/Rules/AvoidUserNameAndPasswordParamsNoViolations.ps1
+++ b/Tests/Rules/AvoidUserNameAndPasswordParamsNoViolations.ps1
@@ -8,6 +8,24 @@ function MyFunction2 ($param1, $passwords)
}
-function MyFunction3 ([PSCredential]$username, $passwords)
+function MyFunction3
{
+ [CmdletBinding()]
+ [Alias()]
+ [OutputType([int])]
+ Param
+ (
+ # Param1 help description
+ [Parameter(Mandatory=$true,
+ ValueFromPipelineByPropertyName=$true,
+ Position=0)]
+ [pscredential]
+ [System.Management.Automation.CredentialAttribute()]
+ $UserName,
+
+ # Param2 help description
+ [pscredential]
+ [System.Management.Automation.CredentialAttribute()]
+ $Password
+ )
}
diff --git a/Tests/Rules/AvoidUsingComputerNameHardcodedNoViolations.ps1 b/Tests/Rules/AvoidUsingComputerNameHardcodedNoViolations.ps1
index a2bdfbea7..fc179a9e1 100644
--- a/Tests/Rules/AvoidUsingComputerNameHardcodedNoViolations.ps1
+++ b/Tests/Rules/AvoidUsingComputerNameHardcodedNoViolations.ps1
@@ -1,2 +1,10 @@
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
+Invoke-Command -ComputerName:'localhost'
+Invoke-Command -ComputerName:"."
+Invoke-Command -ComputerName:'::1'
+Invoke-Command -ComputerName:"127.0.0.1"
\ No newline at end of file
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" {
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}
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 50a5f083c..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."
+$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 ebe613883..7b8f09bc0 100644
--- a/Tests/Rules/PSCredentialTypeNoViolations.ps1
+++ b/Tests/Rules/PSCredentialTypeNoViolations.ps1
@@ -1,3 +1,16 @@
-function Credential([pscredential]$credential) {
-
+function Credential
+{
+ [CmdletBinding()]
+ [Alias()]
+ [OutputType([int])]
+ Param
+ (
+ # Param1 help description
+ [Parameter(Mandatory=$true,
+ ValueFromPipelineByPropertyName=$true,
+ Position=0)]
+ [pscredential]
+ [System.Management.Automation.CredentialAttribute()]
+ $Credential
+ )
}
\ No newline at end of file
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
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