diff --git a/Engine/Commands/GetScriptAnalyzerRuleCommand.cs b/Engine/Commands/GetScriptAnalyzerRuleCommand.cs
index a914e1f59..f9c1934bc 100644
--- a/Engine/Commands/GetScriptAnalyzerRuleCommand.cs
+++ b/Engine/Commands/GetScriptAnalyzerRuleCommand.cs
@@ -33,12 +33,25 @@ public class GetScriptAnalyzerRuleCommand : PSCmdlet, IOutputWriter
[Parameter(Mandatory = false)]
[ValidateNotNullOrEmpty]
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
- public string[] CustomizedRulePath
+ [Alias("CustomizedRulePath")]
+ public string CustomRulePath
{
- get { return customizedRulePath; }
- set { customizedRulePath = value; }
+ get { return customRulePath; }
+ set { customRulePath = value; }
}
- private string[] customizedRulePath;
+ private string customRulePath;
+
+ ///
+ /// RecurseCustomRulePath: Find rules within subfolders under the path
+ ///
+ [Parameter(Mandatory = false)]
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
+ public SwitchParameter RecurseCustomRulePath
+ {
+ get { return recurseCustomRulePath; }
+ set { recurseCustomRulePath = value; }
+ }
+ private bool recurseCustomRulePath;
///
/// Name: The name of a specific rule to list.
@@ -76,7 +89,9 @@ public string[] Severity
///
protected override void BeginProcessing()
{
- ScriptAnalyzer.Instance.Initialize(this, customizedRulePath);
+ string[] rulePaths = Helper.ProcessCustomRulePaths(customRulePath,
+ this.SessionState, recurseCustomRulePath);
+ ScriptAnalyzer.Instance.Initialize(this, rulePaths);
}
///
diff --git a/Engine/Commands/InvokeScriptAnalyzerCommand.cs b/Engine/Commands/InvokeScriptAnalyzerCommand.cs
index ccf5d5992..afbfc46b7 100644
--- a/Engine/Commands/InvokeScriptAnalyzerCommand.cs
+++ b/Engine/Commands/InvokeScriptAnalyzerCommand.cs
@@ -73,12 +73,25 @@ public string ScriptDefinition
[Parameter(Mandatory = false)]
[ValidateNotNull]
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
- public string[] CustomizedRulePath
+ [Alias("CustomizedRulePath")]
+ public string CustomRulePath
{
- get { return customizedRulePath; }
- set { customizedRulePath = value; }
+ get { return customRulePath; }
+ set { customRulePath = value; }
}
- private string[] customizedRulePath;
+ private string customRulePath;
+
+ ///
+ /// RecurseCustomRulePath: Find rules within subfolders under the path
+ ///
+ [Parameter(Mandatory = false)]
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
+ public SwitchParameter RecurseCustomRulePath
+ {
+ get { return recurseCustomRulePath; }
+ set { recurseCustomRulePath = value; }
+ }
+ private bool recurseCustomRulePath;
///
/// ExcludeRule: Array of names of rules to be disabled.
@@ -164,9 +177,12 @@ public string Profile
///
protected override void BeginProcessing()
{
+ string[] rulePaths = Helper.ProcessCustomRulePaths(customRulePath,
+ this.SessionState, recurseCustomRulePath);
+
ScriptAnalyzer.Instance.Initialize(
this,
- customizedRulePath,
+ rulePaths,
this.includeRule,
this.excludeRule,
this.severity,
diff --git a/Engine/Helper.cs b/Engine/Helper.cs
index bc2fef74c..ef10cefc5 100644
--- a/Engine/Helper.cs
+++ b/Engine/Helper.cs
@@ -12,6 +12,7 @@
using System;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Management.Automation;
@@ -983,9 +984,45 @@ public Tuple, List> SuppressRule(string
return result;
}
+ public static string[] ProcessCustomRulePaths(string rulePath, SessionState sessionState, bool recurse = false)
+ {
+ //if directory is given, list all the psd1 files
+ List outPaths = new List();
+ if (rulePath == null)
+ {
+ return null;
+ }
+ try
+ {
+ Collection pathInfo = sessionState.Path.GetResolvedPSPathFromPSPath(rulePath);
+ foreach (PathInfo pinfo in pathInfo)
+ {
+ string path = pinfo.Path;
+ if (Directory.Exists(path))
+ {
+ path = path.TrimEnd('\\');
+ if (recurse)
+ {
+ outPaths.AddRange(Directory.GetDirectories(pinfo.Path, "*", SearchOption.AllDirectories));
+ }
+ }
+ outPaths.Add(path);
+ }
+ return outPaths.ToArray();
+ }
+ catch (Exception ex)
+ {
+ // need to do this as the path validation takes place later in the hierarchy.
+ outPaths.Add(rulePath);
+ return outPaths.ToArray();
+ }
+ }
+
+
#endregion
}
+
internal class TupleComparer : IComparer>
{
public int Compare(Tuple t1, Tuple t2)
diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs
index 054db6758..b385fe4ae 100644
--- a/Engine/ScriptAnalyzer.cs
+++ b/Engine/ScriptAnalyzer.cs
@@ -108,7 +108,7 @@ internal void Initialize(
{
throw new ArgumentNullException("cmdlet");
}
-
+
this.Initialize(
cmdlet,
cmdlet.SessionState.Path,
@@ -188,7 +188,7 @@ private void Initialize(
if (!String.IsNullOrWhiteSpace(profile))
{
try
- {
+ {
profile = path.GetResolvedPSPathFromPSPath(profile).First().Path;
}
catch
@@ -784,7 +784,7 @@ public Dictionary> CheckRuleExtension(string[] path, PathIn
// We have to identify the childPath is really a directory or just a module name.
// You can also consider following two commands.
// Get-ScriptAnalyzerRule -RuleExtension "ContosoAnalyzerRules"
- // Get-ScriptAnalyzerRule -RuleExtension "%USERPROFILE%\WindowsPowerShell\Modules\ContosoAnalyzerRules"
+ // Get-ScriptAnalyzerRule -RuleExtension "%USERPROFILE%\WindowsPowerShell\Modules\ContosoAnalyzerRules"
if (Path.GetDirectoryName(childPath) == string.Empty)
{
resolvedPath = childPath;
@@ -797,14 +797,14 @@ public Dictionary> CheckRuleExtension(string[] path, PathIn
using (System.Management.Automation.PowerShell posh =
System.Management.Automation.PowerShell.Create())
- {
+ {
posh.AddCommand("Get-Module").AddParameter("Name", resolvedPath).AddParameter("ListAvailable");
PSModuleInfo moduleInfo = posh.Invoke().First();
// Adds original path, otherwise path.Except(validModPaths) will fail.
// It's possible that user can provide something like this:
// "..\..\..\ScriptAnalyzer.UnitTest\modules\CommunityAnalyzerRules\CommunityAnalyzerRules.psd1"
- if (moduleInfo.ExportedFunctions.Count > 0) validModPaths.Add(childPath);
+ if (moduleInfo.ExportedFunctions.Count > 0) validModPaths.Add(resolvedPath);
}
}
catch
diff --git a/Tests/Engine/CustomizedRule.tests.ps1 b/Tests/Engine/CustomizedRule.tests.ps1
index cf87ed818..2ef337c88 100644
--- a/Tests/Engine/CustomizedRule.tests.ps1
+++ b/Tests/Engine/CustomizedRule.tests.ps1
@@ -51,18 +51,83 @@ Describe "Test importing correct customized rules" {
}
Context "Test Get-ScriptAnalyzer with customized rules" {
- It "will show the customized rule" {
+ It "will show the custom rule" {
$customizedRulePath = Get-ScriptAnalyzerRule -CustomizedRulePath $directory\samplerule\samplerule.psm1 | Where-Object {$_.RuleName -eq $measure}
$customizedRulePath.Count | Should Be 1
}
-
+
+ It "will show the custom rule when given a rule folder path" {
+ $customizedRulePath = Get-ScriptAnalyzerRule -CustomizedRulePath $directory\samplerule | Where-Object {$_.RuleName -eq $measure}
+ $customizedRulePath.Count | Should Be 1
+ }
+
+ if (!$testingLibraryUsage)
+ {
+ It "will show the custom rule when given a rule folder path with trailing backslash" {
+ $customizedRulePath = Get-ScriptAnalyzerRule -CustomizedRulePath $directory\samplerule\ | Where-Object {$_.RuleName -eq $measure}
+ $customizedRulePath.Count | Should Be 1
+ }
+
+ It "will show the custom rules when given a glob" {
+ $customizedRulePath = Get-ScriptAnalyzerRule -CustomizedRulePath $directory\samplerule\samplerule* | Where-Object {$_.RuleName -match $measure}
+ $customizedRulePath.Count | Should be 4
+ }
+
+ It "will show the custom rules when given recurse switch" {
+ $customizedRulePath = Get-ScriptAnalyzerRule -RecurseCustomRulePath -CustomizedRulePath $directory\samplerule | Where-Object {$_.RuleName -eq $measure}
+ $customizedRulePath.Count | Should be 3
+ }
+
+ it "will show the custom rules when given glob with recurse switch" {
+ $customizedRulePath = Get-ScriptAnalyzerRule -RecurseCustomRulePath -CustomizedRulePath $directory\samplerule\samplerule* | Where-Object {$_.RuleName -eq $measure}
+ $customizedRulePath.Count | Should be 5
+ }
+
+ it "will show the custom rules when given glob with recurse switch" {
+ $customizedRulePath = Get-ScriptAnalyzerRule -RecurseCustomRulePath -CustomizedRulePath $directory\samplerule* | Where-Object {$_.RuleName -eq $measure}
+ $customizedRulePath.Count | Should be 3
+ }
+ }
}
Context "Test Invoke-ScriptAnalyzer with customized rules" {
- It "will show the customized rule in the results" {
+ It "will show the custom rule in the results" {
$customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomizedRulePath $directory\samplerule\samplerule.psm1 | Where-Object {$_.Message -eq $message}
$customizedRulePath.Count | Should Be 1
}
+
+ It "will show the custom rule in the results when given a rule folder path" {
+ $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomizedRulePath $directory\samplerule | Where-Object {$_.Message -eq $message}
+ $customizedRulePath.Count | Should Be 1
+ }
+
+ if (!$testingLibraryUsage)
+ {
+ It "will show the custom rule in the results when given a rule folder path with trailing backslash" {
+ $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomizedRulePath $directory\samplerule\ | Where-Object {$_.Message -eq $message}
+ $customizedRulePath.Count | Should Be 1
+ }
+
+ It "will show the custom rules when given a glob" {
+ $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomizedRulePath $directory\samplerule\samplerule* | Where-Object {$_.Message -eq $message}
+ $customizedRulePath.Count | Should be 3
+ }
+
+ It "will show the custom rules when given recurse switch" {
+ $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -RecurseCustomRulePath -CustomizedRulePath $directory\samplerule | Where-Object {$_.Message -eq $message}
+ $customizedRulePath.Count | Should be 3
+ }
+
+ it "will show the custom rules when given glob with recurse switch" {
+ $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -RecurseCustomRulePath -CustomizedRulePath $directory\samplerule\samplerule* | Where-Object {$_.Message -eq $message}
+ $customizedRulePath.Count | Should be 4
+ }
+
+ it "will show the custom rules when given glob with recurse switch" {
+ $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -RecurseCustomRulePath -CustomizedRulePath $directory\samplerule* | Where-Object {$_.Message -eq $message}
+ $customizedRulePath.Count | Should be 3
+ }
+ }
}
+}
-}
\ No newline at end of file
diff --git a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1
index 63895955c..32624e216 100644
--- a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1
+++ b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1
@@ -19,12 +19,16 @@ Describe "Test available parameters" {
Context "RuleExtension parameters" {
It "has a RuleExtension parameter" {
- $params.ContainsKey("CustomizedRulePath") | Should Be $true
+ $params.ContainsKey("CustomRulePath") | Should Be $true
}
It "accepts string array" {
- $params["CustomizedRulePath"].ParameterType.FullName | Should Be "System.String[]"
+ $params["CustomRulePath"].ParameterType.FullName | Should Be "System.String"
}
+
+ It "takes CustomizedRulePath parameter as an alias of CustomRulePath paramter" {
+ $params.CustomRulePath.Aliases.Contains("CustomizedRulePath") | Should be $true
+ }
}
}
diff --git a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1
index c85152b70..ab1d5dc7b 100644
--- a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1
+++ b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1
@@ -30,19 +30,30 @@ Describe "Test available parameters" {
$params.ContainsKey("ScriptDefinition") | Should Be $true
}
- It "accepts string" {
+ It "accepts string" {
$params["ScriptDefinition"].ParameterType.FullName | Should Be "System.String"
}
}
- Context "CustomizedRulePath parameters" {
- It "has a CustomizedRulePath parameter" {
- $params.ContainsKey("CustomizedRulePath") | Should Be $true
+ Context "CustomRulePath parameters" {
+ It "has a CustomRulePath parameter" {
+ $params.ContainsKey("CustomRulePath") | Should Be $true
}
- It "accepts string array" {
- $params["CustomizedRulePath"].ParameterType.FullName | Should Be "System.String[]"
+ It "accepts a string" {
+ if ($testingLibraryUsage)
+ {
+ $params["CustomRulePath"].ParameterType.FullName | Should Be "System.String[]"
+ }
+ else
+ {
+ $params["CustomRulePath"].ParameterType.FullName | Should Be "System.String"
+ }
}
+
+ It "has a CustomizedRulePath alias"{
+ $params.CustomRulePath.Aliases.Contains("CustomizedRulePath") | Should be $true
+ }
}
Context "IncludeRule parameters" {
diff --git a/Tests/Engine/LibraryUsage.tests.ps1 b/Tests/Engine/LibraryUsage.tests.ps1
index 5e3c9694b..49a1c6bdc 100644
--- a/Tests/Engine/LibraryUsage.tests.ps1
+++ b/Tests/Engine/LibraryUsage.tests.ps1
@@ -16,7 +16,11 @@ function Invoke-ScriptAnalyzer {
[string] $ScriptDefinition,
[Parameter(Mandatory = $false)]
- [string[]] $CustomizedRulePath = $null,
+ [Alias("CustomizedRulePath")]
+ [string[]] $CustomRulePath = $null,
+
+ [Parameter(Mandatory = $false)]
+ [switch] $RecurseCustomRulePath,
[Parameter(Mandatory=$false)]
[string[]] $ExcludeRule = $null,
@@ -32,18 +36,28 @@ function Invoke-ScriptAnalyzer {
[switch] $Recurse,
[Parameter(Mandatory = $false)]
- [switch] $SuppressedOnly
- )
+ [switch] $SuppressedOnly,
- $scriptAnalyzer = New-Object "Microsoft.Windows.PowerShell.ScriptAnalyzer.ScriptAnalyzer"
+ [Parameter(Mandatory = $false)]
+ [string] $Profile = $null
+ )
+ # There is an inconsistency between this implementation and c# implementation of the cmdlet.
+ # The CustomRulePath parameter here is of "string[]" type whereas in the c# implementation it is of "string" type.
+ # If we set the CustomRulePath parameter here to "string[]", then the library usage test fails when run as an administrator.
+ # We want to note that the library usage test doesn't fail when run as a non-admin user.
+ # The following is the error statement when the test runs as an administrator.
+ # Assert failed on "Initialize" with "7" argument(s): "Test failed due to terminating error: The module was expected to contain an assembly manifest. (Exception from HRESULT: 0x80131018)"
+
+ $scriptAnalyzer = New-Object "Microsoft.Windows.PowerShell.ScriptAnalyzer.ScriptAnalyzer";
$scriptAnalyzer.Initialize(
$runspace,
$testOutputWriter,
- $CustomizedRulePath,
+ $CustomRulePath,
$IncludeRule,
$ExcludeRule,
$Severity,
- $SuppressedOnly.IsPresent
+ $SuppressedOnly.IsPresent,
+ $Profile
);
if ($PSCmdlet.ParameterSetName -eq "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