From b4338b08d3f1cf476f8a4c86e303041f6e53e4f7 Mon Sep 17 00:00:00 2001 From: "Yuting Chen[MSFT]" Date: Thu, 23 Apr 2015 10:49:37 -0700 Subject: [PATCH 01/57] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a45d0f7e4..1626f4726 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,9 @@ Invoke-ScriptAnalyzer [-Path] [-CustomizedRulePath ] [-Exclud Requirements ============ -WS2012R2 / Windows 8.1 / Windows OS running PowerShell v5.0 which can be obtained using [Windows Management Framework 5.0 Preview February 2015](http://go.microsoft.com/fwlink/?LinkId=398175). +WS2012R2 / Windows 8.1 / Windows OS running **PowerShell v5.0** and **Windows Management Framework 5.0 Preview** + +Download the latest WMF package from [Windows Management Framework 5.0 Preview February 2015](http://go.microsoft.com/fwlink/?LinkId=398175). Installation ============ From 4cd6e49b193ea6b595f3481fa824a6423aa02a36 Mon Sep 17 00:00:00 2001 From: "Yuting Chen[MSFT]" Date: Thu, 23 Apr 2015 10:52:10 -0700 Subject: [PATCH 02/57] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 1626f4726..ad67c0041 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,8 @@ Building the Code Use Visual Studio to build "ScriptAnalyzer.sln". Use ~/PSScriptAnalyzer/ folder to load PSScriptAnalyzer.psd1 +**Note: If there are any build errors, please refer to Requirements section and make sure all dependencies are properly installed** + Running Tests ============= From a01b048ed77c844f3fdd97dd2b98844e3cbd9949 Mon Sep 17 00:00:00 2001 From: Yuting Chen Date: Thu, 23 Apr 2015 15:15:23 -0700 Subject: [PATCH 03/57] Change property display name to be the same as property name --- Engine/Commands/GetScriptAnalyzerRuleCommand.cs | 2 +- Engine/Generic/RuleInfo.cs | 4 ++-- Engine/ScriptAnalyzer.format.ps1xml | 8 ++++---- Engine/ScriptAnalyzer.types.ps1xml | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Engine/Commands/GetScriptAnalyzerRuleCommand.cs b/Engine/Commands/GetScriptAnalyzerRuleCommand.cs index 33ec3f1a6..6ba2003ea 100644 --- a/Engine/Commands/GetScriptAnalyzerRuleCommand.cs +++ b/Engine/Commands/GetScriptAnalyzerRuleCommand.cs @@ -47,7 +47,7 @@ public string[] CustomizedRulePath [Parameter(Mandatory = false)] [ValidateNotNullOrEmpty] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public string[] Name + public string[] RuleName { get { return name; } set { name = value; } diff --git a/Engine/Generic/RuleInfo.cs b/Engine/Generic/RuleInfo.cs index b4c6e4369..666c84afa 100644 --- a/Engine/Generic/RuleInfo.cs +++ b/Engine/Generic/RuleInfo.cs @@ -35,7 +35,7 @@ public class RuleInfo /// Name: The name of the rule. /// [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - public string Name + public string RuleName { get { return name; } private set { name = value; } @@ -102,7 +102,7 @@ public RuleSeverity Severity /// Source name of the rule. public RuleInfo(string name, string commonName, string description, SourceType sourceType, string sourceName, RuleSeverity severity) { - Name = name; + RuleName = name; CommonName = commonName; Description = description; SourceType = sourceType; diff --git a/Engine/ScriptAnalyzer.format.ps1xml b/Engine/ScriptAnalyzer.format.ps1xml index 6e9448d18..188b73ecc 100644 --- a/Engine/ScriptAnalyzer.format.ps1xml +++ b/Engine/ScriptAnalyzer.format.ps1xml @@ -10,7 +10,7 @@ 35 - + 12 @@ -18,7 +18,7 @@ 10 - + 5 @@ -63,7 +63,7 @@ 35 - + 15 @@ -83,7 +83,7 @@ - Name + RuleName Severity diff --git a/Engine/ScriptAnalyzer.types.ps1xml b/Engine/ScriptAnalyzer.types.ps1xml index c6f12268d..ded4e0922 100644 --- a/Engine/ScriptAnalyzer.types.ps1xml +++ b/Engine/ScriptAnalyzer.types.ps1xml @@ -41,7 +41,7 @@ DefaultDisplayPropertySet - Name + RuleName Severity Description SourceName From 0245e93249157dad61ed4a11f4a0fbc77b0c1e9b Mon Sep 17 00:00:00 2001 From: Quoc Truong Date: Fri, 24 Apr 2015 11:22:35 -0700 Subject: [PATCH 04/57] Change severity column in scriptanalyzer.format --- Engine/ScriptAnalyzer.format.ps1xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine/ScriptAnalyzer.format.ps1xml b/Engine/ScriptAnalyzer.format.ps1xml index 269c00e96..9839d5eac 100644 --- a/Engine/ScriptAnalyzer.format.ps1xml +++ b/Engine/ScriptAnalyzer.format.ps1xml @@ -66,7 +66,7 @@ - 10 + 12 From 25a974bd1cda0b6cf18b2395d75d94f1e02f9459 Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Fri, 24 Apr 2015 15:10:10 -0700 Subject: [PATCH 05/57] Added Author field in the Manifest. This is required for publishing to PSGallery. --- Engine/PSScriptAnalyzer.psd1 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Engine/PSScriptAnalyzer.psd1 b/Engine/PSScriptAnalyzer.psd1 index 4e82b6f12..c95a0194f 100644 --- a/Engine/PSScriptAnalyzer.psd1 +++ b/Engine/PSScriptAnalyzer.psd1 @@ -3,6 +3,9 @@ # @{ + +# Author of this module +Author = 'PowerShell ScriptAnalyzer Team' # Script module or binary module file associated with this manifest. RootModule = 'Microsoft.Windows.Powershell.ScriptAnalyzer.dll' From 90a3b5a2441cd38b5c14211d0009e5b489a580d6 Mon Sep 17 00:00:00 2001 From: Yuting Chen Date: Fri, 24 Apr 2015 17:48:31 -0700 Subject: [PATCH 06/57] Add wild card support to Get-ScriptAnalyzerRule --- Engine/ScriptAnalyzer.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index 5f995abce..79046ac10 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -10,6 +10,7 @@ // THE SOFTWARE. // +using System.Text.RegularExpressions; using Microsoft.Windows.Powershell.ScriptAnalyzer.Commands; using Microsoft.Windows.Powershell.ScriptAnalyzer.Generic; using System; @@ -192,8 +193,18 @@ public IEnumerable GetRule(string[] moduleNames, string[] ruleNames) if (ruleNames != null) { - results = rules.Where(item => - ruleNames.Contains(item.GetName(), StringComparer.OrdinalIgnoreCase)); + //Check wild card input for -Name parameter and create regex match patterns + List regexList = new List(); + foreach (string ruleName in ruleNames) + { + Regex includeRegex = new Regex(String.Format("^{0}$", Regex.Escape(ruleName).Replace(@"\*", ".*")), RegexOptions.IgnoreCase); + regexList.Add(includeRegex); + } + + results = from rule in rules + from regex in regexList + where regex.IsMatch(rule.GetName()) + select rule; } else { From b43039eda4f97cef9f03a5a693c77ee4119a3d68 Mon Sep 17 00:00:00 2001 From: Yuting Chen Date: Fri, 24 Apr 2015 18:02:55 -0700 Subject: [PATCH 07/57] Updated test as well --- Tests/Engine/GetScriptAnalyzerRule.tests.ps1 | 52 ++++++++++++-------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 index 8c32e9b4b..798cb53c6 100644 --- a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 +++ b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 @@ -8,12 +8,12 @@ $dscIdentical = "PSDSCUseIdenticalParametersForDSC" Describe "Test available parameters" { $params = $sa.Parameters Context "Name parameter" { - It "has a Name parameter" { - $params.ContainsKey("Name") | Should Be $true + It "has a RuleName parameter" { + $params.ContainsKey("RuleName") | Should Be $true } It "accepts string" { - $params["Name"].ParameterType.FullName | Should Be "System.String[]" + $params["RuleName"].ParameterType.FullName | Should Be "System.String[]" } } @@ -32,35 +32,35 @@ Describe "Test available parameters" { Describe "Test Name parameters" { Context "When used correctly" { It "works with 1 name" { - $rule = Get-ScriptAnalyzerRule -Name $singularNouns + $rule = Get-ScriptAnalyzerRule -RuleName $singularNouns $rule.Count | Should Be 1 - $rule[0].Name | Should Be $singularNouns + $rule[0].RuleName | Should Be $singularNouns } It "works for DSC Rule" { - $rule = Get-ScriptAnalyzerRule -Name $dscIdentical + $rule = Get-ScriptAnalyzerRule -RuleName $dscIdentical $rule.Count | Should Be 1 - $rule[0].Name | Should Be $dscIdentical + $rule[0].RuleName | Should Be $dscIdentical } It "works with 3 names" { - $rules = Get-ScriptAnalyzerRule -Name $approvedVerbs, $singularNouns + $rules = Get-ScriptAnalyzerRule -RuleName $approvedVerbs, $singularNouns $rules.Count | Should Be 2 - ($rules | Where-Object {$_.Name -eq $singularNouns}).Count | Should Be 1 - ($rules | Where-Object {$_.Name -eq $approvedVerbs}).Count | Should Be 1 + ($rules | Where-Object {$_.RuleName -eq $singularNouns}).Count | Should Be 1 + ($rules | Where-Object {$_.RuleName -eq $approvedVerbs}).Count | Should Be 1 } } Context "When used incorrectly" { It "1 incorrect name" { - $rule = Get-ScriptAnalyzerRule -Name "This is a wrong name" + $rule = Get-ScriptAnalyzerRule -RuleName "This is a wrong name" $rule.Count | Should Be 0 } It "1 incorrect and 1 correct" { - $rule = Get-ScriptAnalyzerRule -Name $singularNouns, "This is a wrong name" + $rule = Get-ScriptAnalyzerRule -RuleName $singularNouns, "This is a wrong name" $rule.Count | Should Be 1 - $rule[0].Name | Should Be $singularNouns + $rule[0].RuleName | Should Be $singularNouns } } } @@ -86,17 +86,17 @@ Describe "Test RuleExtension" { } It "with Name of a built-in rules" { - $ruleExtension = Get-ScriptAnalyzerRule -CustomizedRulePath $directory\CommunityAnalyzerRules\CommunityAnalyzerRules.psm1 -Name $singularNouns + $ruleExtension = Get-ScriptAnalyzerRule -CustomizedRulePath $directory\CommunityAnalyzerRules\CommunityAnalyzerRules.psm1 -RuleName $singularNouns $ruleExtension.Count | Should Be 1 - $ruleExtension[0].Name | Should Be $singularNouns + $ruleExtension[0].RuleName | Should Be $singularNouns } 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 = Get-ScriptAnalyzerRule -CustomizedRulePath $directory\CommunityAnalyzerRules\CommunityAnalyzerRules.psm1 -RuleName $singularNouns, $measureRequired, $dscIdentical $ruleExtension.Count | Should be 3 - ($ruleExtension | Where-Object {$_.Name -eq $measureRequired}).Count | Should Be 1 - ($ruleExtension | Where-Object {$_.Name -eq $singularNouns}).Count | Should Be 1 - ($ruleExtension | Where-Object {$_.Name -eq $dscIdentical}).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 } } @@ -104,7 +104,7 @@ Describe "Test RuleExtension" { 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 {$_.Name -eq $singularNouns}).Count | Should Be 1 + ($wrongFile | Where-Object {$_.RuleName -eq $singularNouns}).Count | Should Be 1 } } @@ -120,4 +120,16 @@ Describe "TestSeverity" { $rules = Get-ScriptAnalyzerRule -Severity Error,Information $rules.Count | Should be 8 } +} + +Describe "TestWildCard" { + It "filters rules based on the -RuleName wild card input" { + $rules = Get-ScriptAnalyzerRule -RuleName PSDSC* + $rules.Count | Should be 4 + } + + It "filters rules based on wild card input and severity"{ + $rules = Get-ScriptAnalyzerRule -RuleName PSDSC* -Severity Information + $rules.Count | Should be 2 + } } \ No newline at end of file From 395dc2aa621cfbbc8983591046a7c334eee0da93 Mon Sep 17 00:00:00 2001 From: Raghu Shantha Date: Mon, 27 Apr 2015 11:18:47 -0700 Subject: [PATCH 08/57] Fix typo in file name for AvoidUninitializedVariable.cs --- ...voidUnitializedVariable.cs => AvoidUninitializedVariable.cs} | 0 Rules/ScriptAnalyzerBuiltinRules.csproj | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename Rules/{AvoidUnitializedVariable.cs => AvoidUninitializedVariable.cs} (100%) diff --git a/Rules/AvoidUnitializedVariable.cs b/Rules/AvoidUninitializedVariable.cs similarity index 100% rename from Rules/AvoidUnitializedVariable.cs rename to Rules/AvoidUninitializedVariable.cs diff --git a/Rules/ScriptAnalyzerBuiltinRules.csproj b/Rules/ScriptAnalyzerBuiltinRules.csproj index e85c892d0..f84a2072d 100644 --- a/Rules/ScriptAnalyzerBuiltinRules.csproj +++ b/Rules/ScriptAnalyzerBuiltinRules.csproj @@ -59,7 +59,7 @@ - + From 9bfb65969abd63526f818aaae58d097b55c8c942 Mon Sep 17 00:00:00 2001 From: "Yuting Chen[MSFT]" Date: Mon, 27 Apr 2015 11:23:05 -0700 Subject: [PATCH 09/57] Update AvoidUninitializedVariable.md --- RuleDocumentation/AvoidUninitializedVariable.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/RuleDocumentation/AvoidUninitializedVariable.md b/RuleDocumentation/AvoidUninitializedVariable.md index 85807bff9..68ee59164 100644 --- a/RuleDocumentation/AvoidUninitializedVariable.md +++ b/RuleDocumentation/AvoidUninitializedVariable.md @@ -17,8 +17,8 @@ Wrong: function NotGlobal { $localVars = "Localization?" - $unitialized - Write-Output $unitialized + $uninitialized + Write-Output $uninitialized } @@ -27,4 +27,4 @@ Correct: function NotGlobal { $localVars = "Localization?" Write-Output $localVars - } \ No newline at end of file + } From f5b80316d76142175c44096fb33318c3d1aee45d Mon Sep 17 00:00:00 2001 From: Raghu Shantha Date: Mon, 27 Apr 2015 15:18:37 -0700 Subject: [PATCH 10/57] AvoidUninitialized Variable Rule must recognize built-in variable System.Management.Automation.EngineIntrinsics --- Engine/SpecialVars.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Engine/SpecialVars.cs b/Engine/SpecialVars.cs index 298c0ba16..2c3e506b5 100644 --- a/Engine/SpecialVars.cs +++ b/Engine/SpecialVars.cs @@ -38,6 +38,7 @@ internal class SpecialVars internal const string MyInvocation = "MyInvocation"; internal const string PSScriptRoot = "PSScriptRoot"; internal const string PSCommandPath = "PSCommandPath"; + internal const string ExecutionContext = "ExecutionContext"; internal static readonly string[] InitializedVariables; @@ -66,7 +67,8 @@ static SpecialVars() PSBoundParameters, MyInvocation, PSScriptRoot, - PSCommandPath, + PSCommandPath, + ExecutionContext }; internal static readonly Type[] AutomaticVariableTypes = new Type[] { @@ -79,6 +81,7 @@ static SpecialVars() /* MyInvocation */ typeof(InvocationInfo), /* PSScriptRoot */ typeof(string), /* PSCommandPath */ typeof(string), + /* ExecutionContext */ typeof(EngineIntrinsics), }; From 2d95ca6d6893b6fe0f29b9ee43901eab335c064b Mon Sep 17 00:00:00 2001 From: Yuting Chen Date: Mon, 27 Apr 2015 15:23:05 -0700 Subject: [PATCH 11/57] Change parameter -RuleName to -Name According to cmdlet design guideline: we recommend omitting the cmdlet noun from parameter names since it's redundant (i.e. Get-ScriptAnalyzerRule -RuleName). --- Engine/Commands/GetScriptAnalyzerRuleCommand.cs | 2 +- Engine/ScriptAnalyzer.types.ps1xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Engine/Commands/GetScriptAnalyzerRuleCommand.cs b/Engine/Commands/GetScriptAnalyzerRuleCommand.cs index 6ba2003ea..33ec3f1a6 100644 --- a/Engine/Commands/GetScriptAnalyzerRuleCommand.cs +++ b/Engine/Commands/GetScriptAnalyzerRuleCommand.cs @@ -47,7 +47,7 @@ public string[] CustomizedRulePath [Parameter(Mandatory = false)] [ValidateNotNullOrEmpty] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public string[] RuleName + public string[] Name { get { return name; } set { name = value; } diff --git a/Engine/ScriptAnalyzer.types.ps1xml b/Engine/ScriptAnalyzer.types.ps1xml index ded4e0922..c6f12268d 100644 --- a/Engine/ScriptAnalyzer.types.ps1xml +++ b/Engine/ScriptAnalyzer.types.ps1xml @@ -41,7 +41,7 @@ DefaultDisplayPropertySet - RuleName + Name Severity Description SourceName From 42b677d7a06e389644b37f274c9e40bc600050aa Mon Sep 17 00:00:00 2001 From: Yuting Chen Date: Mon, 27 Apr 2015 15:24:27 -0700 Subject: [PATCH 12/57] Update the parameter in the tests as well --- Tests/Engine/GetScriptAnalyzerRule.tests.ps1 | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 index 798cb53c6..46f4ad5e9 100644 --- a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 +++ b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 @@ -9,11 +9,11 @@ Describe "Test available parameters" { $params = $sa.Parameters Context "Name parameter" { It "has a RuleName parameter" { - $params.ContainsKey("RuleName") | Should Be $true + $params.ContainsKey("Name") | Should Be $true } It "accepts string" { - $params["RuleName"].ParameterType.FullName | Should Be "System.String[]" + $params["Name"].ParameterType.FullName | Should Be "System.String[]" } } @@ -32,19 +32,19 @@ Describe "Test available parameters" { Describe "Test Name parameters" { Context "When used correctly" { It "works with 1 name" { - $rule = Get-ScriptAnalyzerRule -RuleName $singularNouns + $rule = Get-ScriptAnalyzerRule -Name $singularNouns $rule.Count | Should Be 1 $rule[0].RuleName | Should Be $singularNouns } It "works for DSC Rule" { - $rule = Get-ScriptAnalyzerRule -RuleName $dscIdentical + $rule = Get-ScriptAnalyzerRule -Name $dscIdentical $rule.Count | Should Be 1 $rule[0].RuleName | Should Be $dscIdentical } It "works with 3 names" { - $rules = Get-ScriptAnalyzerRule -RuleName $approvedVerbs, $singularNouns + $rules = Get-ScriptAnalyzerRule -Name $approvedVerbs, $singularNouns $rules.Count | Should Be 2 ($rules | Where-Object {$_.RuleName -eq $singularNouns}).Count | Should Be 1 ($rules | Where-Object {$_.RuleName -eq $approvedVerbs}).Count | Should Be 1 @@ -53,12 +53,12 @@ Describe "Test Name parameters" { Context "When used incorrectly" { It "1 incorrect name" { - $rule = Get-ScriptAnalyzerRule -RuleName "This is a wrong name" + $rule = Get-ScriptAnalyzerRule -Name "This is a wrong name" $rule.Count | Should Be 0 } It "1 incorrect and 1 correct" { - $rule = Get-ScriptAnalyzerRule -RuleName $singularNouns, "This is a wrong name" + $rule = Get-ScriptAnalyzerRule -Name $singularNouns, "This is a wrong name" $rule.Count | Should Be 1 $rule[0].RuleName | Should Be $singularNouns } @@ -86,13 +86,13 @@ Describe "Test RuleExtension" { } It "with Name of a built-in rules" { - $ruleExtension = Get-ScriptAnalyzerRule -CustomizedRulePath $directory\CommunityAnalyzerRules\CommunityAnalyzerRules.psm1 -RuleName $singularNouns + $ruleExtension = Get-ScriptAnalyzerRule -CustomizedRulePath $directory\CommunityAnalyzerRules\CommunityAnalyzerRules.psm1 -Name $singularNouns $ruleExtension.Count | Should Be 1 $ruleExtension[0].RuleName | Should Be $singularNouns } It "with Names of built-in, DSC and non-built-in rules" { - $ruleExtension = Get-ScriptAnalyzerRule -CustomizedRulePath $directory\CommunityAnalyzerRules\CommunityAnalyzerRules.psm1 -RuleName $singularNouns, $measureRequired, $dscIdentical + $ruleExtension = Get-ScriptAnalyzerRule -CustomizedRulePath $directory\CommunityAnalyzerRules\CommunityAnalyzerRules.psm1 -Name $singularNouns, $measureRequired, $dscIdentical $ruleExtension.Count | Should be 3 ($ruleExtension | Where-Object {$_.RuleName -eq $measureRequired}).Count | Should Be 1 ($ruleExtension | Where-Object {$_.RuleName -eq $singularNouns}).Count | Should Be 1 @@ -123,13 +123,13 @@ Describe "TestSeverity" { } Describe "TestWildCard" { - It "filters rules based on the -RuleName wild card input" { - $rules = Get-ScriptAnalyzerRule -RuleName PSDSC* + It "filters rules based on the -Name wild card input" { + $rules = Get-ScriptAnalyzerRule -Name PSDSC* $rules.Count | Should be 4 } It "filters rules based on wild card input and severity"{ - $rules = Get-ScriptAnalyzerRule -RuleName PSDSC* -Severity Information + $rules = Get-ScriptAnalyzerRule -Name PSDSC* -Severity Information $rules.Count | Should be 2 } } \ No newline at end of file From 58f5fbe706be8ef29303acc2f58bf6a56288cca7 Mon Sep 17 00:00:00 2001 From: Karol Kaczmarek Date: Mon, 27 Apr 2015 17:27:48 -0700 Subject: [PATCH 13/57] Updating installation instructions --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b6b3a4cd9..2afc6761a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Introduction +Introduction ============ PSScriptAnalyzer is a static code checker for Windows PowerShell modules and scripts. PSScriptAnalyzer checks the quality of Windows PowerShell code by running a set of rules. The rules are based on PowerShell best practices identified by PowerShell Team and the community. It generates DiagnosticResults (errors and warnings) to inform users about potential code defects and suggests possible solutions for improvements. @@ -23,12 +23,13 @@ Download the latest WMF package from [Windows Management Framework 5.0 Preview F Installation ============ -1. Build the Code using Visual Studio [solution part of the repo] and navigate to the binplace location [``~/ProjectRoot/PSScriptAnalyzer``] +1. Build the Code using Visual Studio [solution part of the repo] and navigate to the binplace location [``~/ProjectRoot/ScriptAnalyzer``] 2. In PowerShell Console: ```powershell Import-Module PSScriptAnalyzer ``` +If you have previous version of PSScriptAnalyzer installed on your machine, you may need to override old binaries by copying content of [``~/ProjectRoot/ScriptAnalyzer``] to PSModulePath. To confirm installation: run ```Get-ScriptAnalyzerRule``` in the PowerShell console to obtain the built-in rules From 1d2bfcc147a32b498f9b0fc730345ffec4562902 Mon Sep 17 00:00:00 2001 From: "Yuting Chen[MSFT]" Date: Mon, 27 Apr 2015 17:53:09 -0700 Subject: [PATCH 14/57] Rename PSScriptAnalyzer.psd1 to ScriptAnalyzer.psd1 --- Engine/{PSScriptAnalyzer.psd1 => ScriptAnalyzer.psd1} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Engine/{PSScriptAnalyzer.psd1 => ScriptAnalyzer.psd1} (100%) diff --git a/Engine/PSScriptAnalyzer.psd1 b/Engine/ScriptAnalyzer.psd1 similarity index 100% rename from Engine/PSScriptAnalyzer.psd1 rename to Engine/ScriptAnalyzer.psd1 From b9d27928a372de876c94a8234a2ed13998cd0a58 Mon Sep 17 00:00:00 2001 From: "Yuting Chen[MSFT]" Date: Mon, 27 Apr 2015 18:00:05 -0700 Subject: [PATCH 15/57] Update README.md Change the import module name and .psd1 file to ScriptAnalyzer to match the VS solution name. That way we can solve the confusion of binplace and import-module. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2afc6761a..6d6ffbb21 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Installation 2. In PowerShell Console: ```powershell -Import-Module PSScriptAnalyzer +Import-Module ScriptAnalyzer ``` If you have previous version of PSScriptAnalyzer installed on your machine, you may need to override old binaries by copying content of [``~/ProjectRoot/ScriptAnalyzer``] to PSModulePath. From 8ebe5ecf610e82e96e3389f95e1704e4f36f37c8 Mon Sep 17 00:00:00 2001 From: Yuting Chen Date: Tue, 28 Apr 2015 10:24:51 -0700 Subject: [PATCH 16/57] Rename solution file --- ScriptAnalyzer.sln => PSScriptAnalyzer.sln | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ScriptAnalyzer.sln => PSScriptAnalyzer.sln (100%) diff --git a/ScriptAnalyzer.sln b/PSScriptAnalyzer.sln similarity index 100% rename from ScriptAnalyzer.sln rename to PSScriptAnalyzer.sln From 06544eccd15998c0d1283dde31d7b95aa0192981 Mon Sep 17 00:00:00 2001 From: "Yuting Chen[MSFT]" Date: Tue, 28 Apr 2015 10:29:20 -0700 Subject: [PATCH 17/57] Update README.md Update the module name back to PSScriptAnalyzer name. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6d6ffbb21..501d81ff1 100644 --- a/README.md +++ b/README.md @@ -23,13 +23,13 @@ Download the latest WMF package from [Windows Management Framework 5.0 Preview F Installation ============ -1. Build the Code using Visual Studio [solution part of the repo] and navigate to the binplace location [``~/ProjectRoot/ScriptAnalyzer``] +1. Build the Code using Visual Studio [solution part of the repo] and navigate to the binplace location [``~/ProjectRoot/PSScriptAnalyzer``] 2. In PowerShell Console: ```powershell -Import-Module ScriptAnalyzer +Import-Module PSScriptAnalyzer ``` -If you have previous version of PSScriptAnalyzer installed on your machine, you may need to override old binaries by copying content of [``~/ProjectRoot/ScriptAnalyzer``] to PSModulePath. +If you have previous version of PSScriptAnalyzer installed on your machine, you may need to override old binaries by copying content of [``~/ProjectRoot/PSScriptAnalyzer``] to PSModulePath. To confirm installation: run ```Get-ScriptAnalyzerRule``` in the PowerShell console to obtain the built-in rules From 8c7b4d5c1c5f2452a93b60f6873f6bf4d204ca88 Mon Sep 17 00:00:00 2001 From: "Yuting Chen[MSFT]" Date: Tue, 28 Apr 2015 10:33:51 -0700 Subject: [PATCH 18/57] Rename ScriptAnalyzer.psd1 to PSScriptAnalyzer.psd1 --- Engine/{ScriptAnalyzer.psd1 => PSScriptAnalyzer.psd1} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Engine/{ScriptAnalyzer.psd1 => PSScriptAnalyzer.psd1} (100%) diff --git a/Engine/ScriptAnalyzer.psd1 b/Engine/PSScriptAnalyzer.psd1 similarity index 100% rename from Engine/ScriptAnalyzer.psd1 rename to Engine/PSScriptAnalyzer.psd1 From c03f47c6eb93d044d127ced3022d358333d28dd7 Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Mon, 4 May 2015 10:30:37 -0700 Subject: [PATCH 19/57] Updated Author, CompanyName and Copyright properties We need to have Microsoft Corp in these fields. --- Engine/PSScriptAnalyzer.psd1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Engine/PSScriptAnalyzer.psd1 b/Engine/PSScriptAnalyzer.psd1 index c95a0194f..2c95981f9 100644 --- a/Engine/PSScriptAnalyzer.psd1 +++ b/Engine/PSScriptAnalyzer.psd1 @@ -5,7 +5,7 @@ @{ # Author of this module -Author = 'PowerShell ScriptAnalyzer Team' +Author = 'Microsoft Corporation' # Script module or binary module file associated with this manifest. RootModule = 'Microsoft.Windows.Powershell.ScriptAnalyzer.dll' @@ -17,10 +17,10 @@ ModuleVersion = '1.0' GUID = '324fc715-36bf-4aee-8e58-72e9b4a08ad9' # Company or vendor of this module -CompanyName = 'Microsoft' +CompanyName = 'Microsoft Corporation' # Copyright statement for this module -Copyright = '(c) 2015. All rights reserved.' +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.' From 8919af706da291a16a0c645b977d863c554d82a9 Mon Sep 17 00:00:00 2001 From: Yuting Chen Date: Mon, 4 May 2015 13:29:17 -0700 Subject: [PATCH 20/57] Fix exceptions in importing CustomizedRule --- .../Commands/InvokeScriptAnalyzerCommand.cs | 3 - Engine/Generic/SourceType.cs | 6 -- Engine/ScriptAnalyzer.cs | 66 +++++++++++-------- Engine/ScriptAnalyzerEngine.csproj | 4 +- Engine/Strings.Designer.cs | 27 +++++--- Engine/Strings.resx | 3 + Rules/Strings.Designer.cs | 2 +- Rules/Strings.resx | 3 + Rules/UseCmdletCorrectly.cs | 3 - Tests/Disabled Rules/AvoidOneChar.tests.ps1 | 2 +- .../Disabled Rules/CommandNotFound.tests.ps1 | 2 +- 11 files changed, 66 insertions(+), 55 deletions(-) diff --git a/Engine/Commands/InvokeScriptAnalyzerCommand.cs b/Engine/Commands/InvokeScriptAnalyzerCommand.cs index cdfd7bd4e..c66c53622 100644 --- a/Engine/Commands/InvokeScriptAnalyzerCommand.cs +++ b/Engine/Commands/InvokeScriptAnalyzerCommand.cs @@ -14,14 +14,11 @@ using Microsoft.Windows.Powershell.ScriptAnalyzer.Generic; using System; using System.Collections.Generic; -using System.ComponentModel.Composition; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Management.Automation; using System.Management.Automation.Language; -using System.Resources; -using System.Threading; using System.Reflection; using System.IO; using System.Text; diff --git a/Engine/Generic/SourceType.cs b/Engine/Generic/SourceType.cs index bab7cd0e3..9863a913f 100644 --- a/Engine/Generic/SourceType.cs +++ b/Engine/Generic/SourceType.cs @@ -10,12 +10,6 @@ // THE SOFTWARE. // -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Microsoft.Windows.Powershell.ScriptAnalyzer.Generic { /// diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index 036007ce4..f86b9b8c0 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -142,7 +142,7 @@ public void Initilaize(Dictionary> result) paths = result.ContainsKey("ValidDllPaths") ? result["ValidDllPaths"] : result["ValidPaths"]; foreach (string path in paths) { - if (Path.GetExtension(path).ToLower(CultureInfo.CurrentCulture) == ".dll") + if (String.Equals(Path.GetExtension(path),".dll", StringComparison.OrdinalIgnoreCase)) { catalog.Catalogs.Add(new AssemblyCatalog(path)); } @@ -241,8 +241,8 @@ public List GetExternalRule(string[] moduleNames) FunctionInfo funcInfo = (FunctionInfo)psobject.ImmediateBaseObject; ParameterMetadata param = funcInfo.Parameters.Values - .First(item => item.Name.ToLower(CultureInfo.CurrentCulture).EndsWith("ast", StringComparison.CurrentCulture) || - item.Name.ToLower(CultureInfo.CurrentCulture).EndsWith("token", StringComparison.CurrentCulture)); + .First(item => item.Name.EndsWith("ast", StringComparison.OrdinalIgnoreCase) || + item.Name.EndsWith("token", StringComparison.OrdinalIgnoreCase)); //Only add functions that are defined as rules. if (param != null) @@ -251,7 +251,7 @@ public List GetExternalRule(string[] moduleNames) string desc =posh.AddScript(script).Invoke()[0].ImmediateBaseObject.ToString() .Replace("\r\n", " ").Trim(); - rules.Add(new ExternalRule(funcInfo.Name, funcInfo.Name, desc, param.Name, + rules.Add(new ExternalRule(funcInfo.Name, funcInfo.Name, desc, param.ParameterType.Name, funcInfo.ModuleName, funcInfo.Module.Path)); } } @@ -337,7 +337,7 @@ public IEnumerable GetExternalRecord(Ast ast, Token[] token, E { // Find all AstTypes that appeared in rule groups. IEnumerable childAsts = ast.FindAll(new Func((testAst) => - (testAst.GetType().Name.ToLower(CultureInfo.CurrentCulture) == astRuleGroup.Key.ToLower(CultureInfo.CurrentCulture))), false); + Strings.Equals(testAst.GetType().Name, astRuleGroup.Key)), false); foreach (Ast childAst in childAsts) { @@ -381,30 +381,34 @@ public IEnumerable GetExternalRecord(Ast ast, Token[] token, E string message = string.Empty; string ruleName = string.Empty; - // Because error stream is merged to output stream, - // we need to handle the error records. - if (psobject.ImmediateBaseObject is ErrorRecord) + //Make sure returned DiagnosticRecord is not null + if (psobject!= null && psobject.ImmediateBaseObject != null) { - ErrorRecord record = (ErrorRecord)psobject.ImmediateBaseObject; - command.WriteError(record); - continue; - } - - // DiagnosticRecord may not be correctly returned from external rule. - try - { - Enum.TryParse(psobject.Properties["Severity"].Value.ToString().ToUpper(), out severity); - message = psobject.Properties["Message"].Value.ToString(); - extent = (IScriptExtent)psobject.Properties["Extent"].Value; - ruleName = psobject.Properties["RuleName"].Value.ToString(); - } - catch (Exception ex) - { - command.WriteError(new ErrorRecord(ex, ex.HResult.ToString("X"), ErrorCategory.NotSpecified, this)); - continue; + // Because error stream is merged to output stream, + // we need to handle the error records. + if (psobject.ImmediateBaseObject is ErrorRecord) + { + ErrorRecord record = (ErrorRecord)psobject.ImmediateBaseObject; + command.WriteError(record); + continue; + } + + // DiagnosticRecord may not be correctly returned from external rule. + try + { + Enum.TryParse(psobject.Properties["Severity"].Value.ToString().ToUpper(), out severity); + message = psobject.Properties["Message"].Value.ToString(); + extent = (IScriptExtent)psobject.Properties["Extent"].Value; + ruleName = psobject.Properties["RuleName"].Value.ToString(); + } + catch (Exception ex) + { + command.WriteError(new ErrorRecord(ex, ex.HResult.ToString("X"), ErrorCategory.NotSpecified, this)); + continue; + } + + if (!string.IsNullOrEmpty(message)) yield return new DiagnosticRecord(message, extent, ruleName, severity, null); } - - if (!string.IsNullOrEmpty(message)) yield return new DiagnosticRecord(message, extent, ruleName, severity, null); } } @@ -453,7 +457,11 @@ public Dictionary> CheckRuleExtension(string[] path, PSCmdl // 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(childPath); + cmdlet.WriteVerbose(string.Format(CultureInfo.CurrentCulture, Strings.ImportCustomizedRuleSuccess, childPath)); + } } } catch @@ -478,7 +486,7 @@ public Dictionary> CheckRuleExtension(string[] path, PSCmdl cmdlet.WriteDebug(string.Format(CultureInfo.CurrentCulture, Strings.CheckAssemblyFile, resolvedPath)); - if (Path.GetExtension(resolvedPath).ToLower(CultureInfo.CurrentCulture) == ".dll") + if (String.Equals(Path.GetExtension(resolvedPath),".dll",StringComparison.OrdinalIgnoreCase)) { if (!File.Exists(resolvedPath)) { diff --git a/Engine/ScriptAnalyzerEngine.csproj b/Engine/ScriptAnalyzerEngine.csproj index c259d0ea9..c3a285fd2 100644 --- a/Engine/ScriptAnalyzerEngine.csproj +++ b/Engine/ScriptAnalyzerEngine.csproj @@ -82,7 +82,9 @@ - + + Designer + diff --git a/Engine/Strings.Designer.cs b/Engine/Strings.Designer.cs index 41b4079d7..d262eafd7 100644 --- a/Engine/Strings.Designer.cs +++ b/Engine/Strings.Designer.cs @@ -1,14 +1,12 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.35317 // -// Copyright (c) Microsoft Corporation. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ namespace Microsoft.Windows.Powershell.ScriptAnalyzer { using System; @@ -116,6 +114,15 @@ internal static string FileNotFound { } } + /// + /// Looks up a localized string similar to Customized rules found. Imported to PSScriptAnalyzer rule collections successfully.... + /// + internal static string ImportCustomizedRuleSuccess { + get { + return ResourceManager.GetString("ImportCustomizedRuleSuccess", resourceCulture); + } + } + /// /// Looks up a localized string similar to Cannot find the path '{0}'.. /// diff --git a/Engine/Strings.resx b/Engine/Strings.resx index 369025dca..e4cd9d723 100644 --- a/Engine/Strings.resx +++ b/Engine/Strings.resx @@ -135,6 +135,9 @@ Cannot find file '{0}'. + + Customized rules found. Imported to PSScriptAnalyzer rule collections successfully... + Cannot find the path '{0}'. diff --git a/Rules/Strings.Designer.cs b/Rules/Strings.Designer.cs index 1ec732308..d5ba6269c 100644 --- a/Rules/Strings.Designer.cs +++ b/Rules/Strings.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.34014 +// Runtime Version:4.0.30319.35317 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. diff --git a/Rules/Strings.resx b/Rules/Strings.resx index f7501a45c..7a9b73465 100644 --- a/Rules/Strings.resx +++ b/Rules/Strings.resx @@ -678,4 +678,7 @@ AvoidUsingWMIObjectCmdlet + + Customized rules found. Imported to PSScriptAnalyzer engine successfully... + \ No newline at end of file diff --git a/Rules/UseCmdletCorrectly.cs b/Rules/UseCmdletCorrectly.cs index a5e854b0b..e0e19d728 100644 --- a/Rules/UseCmdletCorrectly.cs +++ b/Rules/UseCmdletCorrectly.cs @@ -45,9 +45,6 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) { CommandAst cmdAst = (CommandAst)foundAst; - // Handles the exception caused by commands like, {& $PLINK $args 2> $TempErrorFile}. - // You can also review the remark section in following document, - // MSDN: CommandAst.GetCommandName Method if (cmdAst.GetCommandName() == null) continue; // Checks mandatory parameters. diff --git a/Tests/Disabled Rules/AvoidOneChar.tests.ps1 b/Tests/Disabled Rules/AvoidOneChar.tests.ps1 index 6c9820fe1..f29d3aacd 100644 --- a/Tests/Disabled Rules/AvoidOneChar.tests.ps1 +++ b/Tests/Disabled Rules/AvoidOneChar.tests.ps1 @@ -3,7 +3,7 @@ $oneCharMessage = "The cmdlet name O only has one character." $oneCharName = "PSOneChar" $directory = Split-Path -Parent $MyInvocation.MyCommand.Path $invoke = Invoke-ScriptAnalyzer $directory\AvoidUsingReservedCharOneCharNames.ps1 | Where-Object {$_.RuleName -eq $oneCharName} -$noViolations = Invoke-ScriptAnalyzer $directory\GoodCmdlet.ps1 | Where-Object {$_.RuleName -eq $oneCharName} +$noViolations = Invoke-ScriptAnalyzer $directory\..\Rules\GoodCmdlet.ps1 | Where-Object {$_.RuleName -eq $oneCharName} Describe "Avoid Using One Char" { Context "When there are violations" { diff --git a/Tests/Disabled Rules/CommandNotFound.tests.ps1 b/Tests/Disabled Rules/CommandNotFound.tests.ps1 index 968c31cef..714ca07ec 100644 --- a/Tests/Disabled Rules/CommandNotFound.tests.ps1 +++ b/Tests/Disabled Rules/CommandNotFound.tests.ps1 @@ -1,4 +1,4 @@ -Import-Module -Verbose ScriptAnalyzer +Import-Module -Verbose PSScriptAnalyzer $violationMessage = "Command Get-WrongCommand Is Not Found" $violationName = "PSCommandNotFound" $directory = Split-Path -Parent $MyInvocation.MyCommand.Path From ff75584eadd3eed6e071149655edb5b75f1ceb0a Mon Sep 17 00:00:00 2001 From: Yuting Chen Date: Mon, 4 May 2015 13:39:39 -0700 Subject: [PATCH 21/57] Revert "Fix exceptions in importing CustomizedRule" This reverts commit 8919af706da291a16a0c645b977d863c554d82a9. --- .../Commands/InvokeScriptAnalyzerCommand.cs | 3 + Engine/Generic/SourceType.cs | 6 ++ Engine/ScriptAnalyzer.cs | 66 ++++++++----------- Engine/ScriptAnalyzerEngine.csproj | 4 +- Engine/Strings.Designer.cs | 27 +++----- Engine/Strings.resx | 3 - Rules/Strings.Designer.cs | 2 +- Rules/Strings.resx | 3 - Rules/UseCmdletCorrectly.cs | 3 + Tests/Disabled Rules/AvoidOneChar.tests.ps1 | 2 +- .../Disabled Rules/CommandNotFound.tests.ps1 | 2 +- 11 files changed, 55 insertions(+), 66 deletions(-) diff --git a/Engine/Commands/InvokeScriptAnalyzerCommand.cs b/Engine/Commands/InvokeScriptAnalyzerCommand.cs index c66c53622..cdfd7bd4e 100644 --- a/Engine/Commands/InvokeScriptAnalyzerCommand.cs +++ b/Engine/Commands/InvokeScriptAnalyzerCommand.cs @@ -14,11 +14,14 @@ using Microsoft.Windows.Powershell.ScriptAnalyzer.Generic; using System; using System.Collections.Generic; +using System.ComponentModel.Composition; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Management.Automation; using System.Management.Automation.Language; +using System.Resources; +using System.Threading; using System.Reflection; using System.IO; using System.Text; diff --git a/Engine/Generic/SourceType.cs b/Engine/Generic/SourceType.cs index 9863a913f..bab7cd0e3 100644 --- a/Engine/Generic/SourceType.cs +++ b/Engine/Generic/SourceType.cs @@ -10,6 +10,12 @@ // THE SOFTWARE. // +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + namespace Microsoft.Windows.Powershell.ScriptAnalyzer.Generic { /// diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index f86b9b8c0..036007ce4 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -142,7 +142,7 @@ public void Initilaize(Dictionary> result) paths = result.ContainsKey("ValidDllPaths") ? result["ValidDllPaths"] : result["ValidPaths"]; foreach (string path in paths) { - if (String.Equals(Path.GetExtension(path),".dll", StringComparison.OrdinalIgnoreCase)) + if (Path.GetExtension(path).ToLower(CultureInfo.CurrentCulture) == ".dll") { catalog.Catalogs.Add(new AssemblyCatalog(path)); } @@ -241,8 +241,8 @@ public List GetExternalRule(string[] moduleNames) FunctionInfo funcInfo = (FunctionInfo)psobject.ImmediateBaseObject; ParameterMetadata param = funcInfo.Parameters.Values - .First(item => item.Name.EndsWith("ast", StringComparison.OrdinalIgnoreCase) || - item.Name.EndsWith("token", StringComparison.OrdinalIgnoreCase)); + .First(item => item.Name.ToLower(CultureInfo.CurrentCulture).EndsWith("ast", StringComparison.CurrentCulture) || + item.Name.ToLower(CultureInfo.CurrentCulture).EndsWith("token", StringComparison.CurrentCulture)); //Only add functions that are defined as rules. if (param != null) @@ -251,7 +251,7 @@ public List GetExternalRule(string[] moduleNames) string desc =posh.AddScript(script).Invoke()[0].ImmediateBaseObject.ToString() .Replace("\r\n", " ").Trim(); - rules.Add(new ExternalRule(funcInfo.Name, funcInfo.Name, desc, param.ParameterType.Name, + rules.Add(new ExternalRule(funcInfo.Name, funcInfo.Name, desc, param.Name, funcInfo.ModuleName, funcInfo.Module.Path)); } } @@ -337,7 +337,7 @@ public IEnumerable GetExternalRecord(Ast ast, Token[] token, E { // Find all AstTypes that appeared in rule groups. IEnumerable childAsts = ast.FindAll(new Func((testAst) => - Strings.Equals(testAst.GetType().Name, astRuleGroup.Key)), false); + (testAst.GetType().Name.ToLower(CultureInfo.CurrentCulture) == astRuleGroup.Key.ToLower(CultureInfo.CurrentCulture))), false); foreach (Ast childAst in childAsts) { @@ -381,34 +381,30 @@ public IEnumerable GetExternalRecord(Ast ast, Token[] token, E string message = string.Empty; string ruleName = string.Empty; - //Make sure returned DiagnosticRecord is not null - if (psobject!= null && psobject.ImmediateBaseObject != null) + // Because error stream is merged to output stream, + // we need to handle the error records. + if (psobject.ImmediateBaseObject is ErrorRecord) { - // Because error stream is merged to output stream, - // we need to handle the error records. - if (psobject.ImmediateBaseObject is ErrorRecord) - { - ErrorRecord record = (ErrorRecord)psobject.ImmediateBaseObject; - command.WriteError(record); - continue; - } - - // DiagnosticRecord may not be correctly returned from external rule. - try - { - Enum.TryParse(psobject.Properties["Severity"].Value.ToString().ToUpper(), out severity); - message = psobject.Properties["Message"].Value.ToString(); - extent = (IScriptExtent)psobject.Properties["Extent"].Value; - ruleName = psobject.Properties["RuleName"].Value.ToString(); - } - catch (Exception ex) - { - command.WriteError(new ErrorRecord(ex, ex.HResult.ToString("X"), ErrorCategory.NotSpecified, this)); - continue; - } - - if (!string.IsNullOrEmpty(message)) yield return new DiagnosticRecord(message, extent, ruleName, severity, null); + ErrorRecord record = (ErrorRecord)psobject.ImmediateBaseObject; + command.WriteError(record); + continue; + } + + // DiagnosticRecord may not be correctly returned from external rule. + try + { + Enum.TryParse(psobject.Properties["Severity"].Value.ToString().ToUpper(), out severity); + message = psobject.Properties["Message"].Value.ToString(); + extent = (IScriptExtent)psobject.Properties["Extent"].Value; + ruleName = psobject.Properties["RuleName"].Value.ToString(); + } + catch (Exception ex) + { + command.WriteError(new ErrorRecord(ex, ex.HResult.ToString("X"), ErrorCategory.NotSpecified, this)); + continue; } + + if (!string.IsNullOrEmpty(message)) yield return new DiagnosticRecord(message, extent, ruleName, severity, null); } } @@ -457,11 +453,7 @@ public Dictionary> CheckRuleExtension(string[] path, PSCmdl // 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); - cmdlet.WriteVerbose(string.Format(CultureInfo.CurrentCulture, Strings.ImportCustomizedRuleSuccess, childPath)); - } + if (moduleInfo.ExportedFunctions.Count > 0) validModPaths.Add(childPath); } } catch @@ -486,7 +478,7 @@ public Dictionary> CheckRuleExtension(string[] path, PSCmdl cmdlet.WriteDebug(string.Format(CultureInfo.CurrentCulture, Strings.CheckAssemblyFile, resolvedPath)); - if (String.Equals(Path.GetExtension(resolvedPath),".dll",StringComparison.OrdinalIgnoreCase)) + if (Path.GetExtension(resolvedPath).ToLower(CultureInfo.CurrentCulture) == ".dll") { if (!File.Exists(resolvedPath)) { diff --git a/Engine/ScriptAnalyzerEngine.csproj b/Engine/ScriptAnalyzerEngine.csproj index c3a285fd2..c259d0ea9 100644 --- a/Engine/ScriptAnalyzerEngine.csproj +++ b/Engine/ScriptAnalyzerEngine.csproj @@ -82,9 +82,7 @@ - - Designer - + diff --git a/Engine/Strings.Designer.cs b/Engine/Strings.Designer.cs index d262eafd7..41b4079d7 100644 --- a/Engine/Strings.Designer.cs +++ b/Engine/Strings.Designer.cs @@ -1,12 +1,14 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.35317 // -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// namespace Microsoft.Windows.Powershell.ScriptAnalyzer { using System; @@ -114,15 +116,6 @@ internal static string FileNotFound { } } - /// - /// Looks up a localized string similar to Customized rules found. Imported to PSScriptAnalyzer rule collections successfully.... - /// - internal static string ImportCustomizedRuleSuccess { - get { - return ResourceManager.GetString("ImportCustomizedRuleSuccess", resourceCulture); - } - } - /// /// Looks up a localized string similar to Cannot find the path '{0}'.. /// diff --git a/Engine/Strings.resx b/Engine/Strings.resx index e4cd9d723..369025dca 100644 --- a/Engine/Strings.resx +++ b/Engine/Strings.resx @@ -135,9 +135,6 @@ Cannot find file '{0}'. - - Customized rules found. Imported to PSScriptAnalyzer rule collections successfully... - Cannot find the path '{0}'. diff --git a/Rules/Strings.Designer.cs b/Rules/Strings.Designer.cs index d5ba6269c..1ec732308 100644 --- a/Rules/Strings.Designer.cs +++ b/Rules/Strings.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.35317 +// Runtime Version:4.0.30319.34014 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. diff --git a/Rules/Strings.resx b/Rules/Strings.resx index 7a9b73465..f7501a45c 100644 --- a/Rules/Strings.resx +++ b/Rules/Strings.resx @@ -678,7 +678,4 @@ AvoidUsingWMIObjectCmdlet - - Customized rules found. Imported to PSScriptAnalyzer engine successfully... - \ No newline at end of file diff --git a/Rules/UseCmdletCorrectly.cs b/Rules/UseCmdletCorrectly.cs index e0e19d728..a5e854b0b 100644 --- a/Rules/UseCmdletCorrectly.cs +++ b/Rules/UseCmdletCorrectly.cs @@ -45,6 +45,9 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) { CommandAst cmdAst = (CommandAst)foundAst; + // Handles the exception caused by commands like, {& $PLINK $args 2> $TempErrorFile}. + // You can also review the remark section in following document, + // MSDN: CommandAst.GetCommandName Method if (cmdAst.GetCommandName() == null) continue; // Checks mandatory parameters. diff --git a/Tests/Disabled Rules/AvoidOneChar.tests.ps1 b/Tests/Disabled Rules/AvoidOneChar.tests.ps1 index f29d3aacd..6c9820fe1 100644 --- a/Tests/Disabled Rules/AvoidOneChar.tests.ps1 +++ b/Tests/Disabled Rules/AvoidOneChar.tests.ps1 @@ -3,7 +3,7 @@ $oneCharMessage = "The cmdlet name O only has one character." $oneCharName = "PSOneChar" $directory = Split-Path -Parent $MyInvocation.MyCommand.Path $invoke = Invoke-ScriptAnalyzer $directory\AvoidUsingReservedCharOneCharNames.ps1 | Where-Object {$_.RuleName -eq $oneCharName} -$noViolations = Invoke-ScriptAnalyzer $directory\..\Rules\GoodCmdlet.ps1 | Where-Object {$_.RuleName -eq $oneCharName} +$noViolations = Invoke-ScriptAnalyzer $directory\GoodCmdlet.ps1 | Where-Object {$_.RuleName -eq $oneCharName} Describe "Avoid Using One Char" { Context "When there are violations" { diff --git a/Tests/Disabled Rules/CommandNotFound.tests.ps1 b/Tests/Disabled Rules/CommandNotFound.tests.ps1 index 714ca07ec..968c31cef 100644 --- a/Tests/Disabled Rules/CommandNotFound.tests.ps1 +++ b/Tests/Disabled Rules/CommandNotFound.tests.ps1 @@ -1,4 +1,4 @@ -Import-Module -Verbose PSScriptAnalyzer +Import-Module -Verbose ScriptAnalyzer $violationMessage = "Command Get-WrongCommand Is Not Found" $violationName = "PSCommandNotFound" $directory = Split-Path -Parent $MyInvocation.MyCommand.Path From 53ca175a46868d36471cfcce0feb54bc916ba053 Mon Sep 17 00:00:00 2001 From: Yuting Chen Date: Mon, 4 May 2015 14:03:39 -0700 Subject: [PATCH 22/57] Fix bugs in importing external rule When importing customized rules, we take Ast type instead of name to apply rules. Modify string comparsion Add check for null diagnosticRecord returned. --- Engine/ScriptAnalyzer.cs | 57 +++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index 036007ce4..0626f02ed 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -142,7 +142,7 @@ public void Initilaize(Dictionary> result) paths = result.ContainsKey("ValidDllPaths") ? result["ValidDllPaths"] : result["ValidPaths"]; foreach (string path in paths) { - if (Path.GetExtension(path).ToLower(CultureInfo.CurrentCulture) == ".dll") + if (String.Equals(Path.GetExtension(path),".dll",StringComparison.OrdinalIgnoreCase)) { catalog.Catalogs.Add(new AssemblyCatalog(path)); } @@ -241,8 +241,8 @@ public List GetExternalRule(string[] moduleNames) FunctionInfo funcInfo = (FunctionInfo)psobject.ImmediateBaseObject; ParameterMetadata param = funcInfo.Parameters.Values - .First(item => item.Name.ToLower(CultureInfo.CurrentCulture).EndsWith("ast", StringComparison.CurrentCulture) || - item.Name.ToLower(CultureInfo.CurrentCulture).EndsWith("token", StringComparison.CurrentCulture)); + .First(item => item.Name.EndsWith("ast", StringComparison.OrdinalIgnoreCase) || + item.Name.EndsWith("token", StringComparison.CurrentCulture)); //Only add functions that are defined as rules. if (param != null) @@ -251,7 +251,7 @@ public List GetExternalRule(string[] moduleNames) string desc =posh.AddScript(script).Invoke()[0].ImmediateBaseObject.ToString() .Replace("\r\n", " ").Trim(); - rules.Add(new ExternalRule(funcInfo.Name, funcInfo.Name, desc, param.Name, + rules.Add(new ExternalRule(funcInfo.Name, funcInfo.Name, desc, param.ParameterType.Name, funcInfo.ModuleName, funcInfo.Module.Path)); } } @@ -381,30 +381,33 @@ public IEnumerable GetExternalRecord(Ast ast, Token[] token, E string message = string.Empty; string ruleName = string.Empty; - // Because error stream is merged to output stream, - // we need to handle the error records. - if (psobject.ImmediateBaseObject is ErrorRecord) + if (psobject != null && psobject.ImmediateBaseObject != null) { - ErrorRecord record = (ErrorRecord)psobject.ImmediateBaseObject; - command.WriteError(record); - continue; - } - - // DiagnosticRecord may not be correctly returned from external rule. - try - { - Enum.TryParse(psobject.Properties["Severity"].Value.ToString().ToUpper(), out severity); - message = psobject.Properties["Message"].Value.ToString(); - extent = (IScriptExtent)psobject.Properties["Extent"].Value; - ruleName = psobject.Properties["RuleName"].Value.ToString(); + // Because error stream is merged to output stream, + // we need to handle the error records. + if (psobject.ImmediateBaseObject is ErrorRecord) + { + ErrorRecord record = (ErrorRecord)psobject.ImmediateBaseObject; + command.WriteError(record); + continue; + } + + // DiagnosticRecord may not be correctly returned from external rule. + try + { + Enum.TryParse(psobject.Properties["Severity"].Value.ToString().ToUpper(), out severity); + message = psobject.Properties["Message"].Value.ToString(); + extent = (IScriptExtent)psobject.Properties["Extent"].Value; + ruleName = psobject.Properties["RuleName"].Value.ToString(); + } + catch (Exception ex) + { + command.WriteError(new ErrorRecord(ex, ex.HResult.ToString("X"), ErrorCategory.NotSpecified, this)); + continue; + } + + if (!string.IsNullOrEmpty(message)) yield return new DiagnosticRecord(message, extent, ruleName, severity, null); } - catch (Exception ex) - { - command.WriteError(new ErrorRecord(ex, ex.HResult.ToString("X"), ErrorCategory.NotSpecified, this)); - continue; - } - - if (!string.IsNullOrEmpty(message)) yield return new DiagnosticRecord(message, extent, ruleName, severity, null); } } @@ -478,7 +481,7 @@ public Dictionary> CheckRuleExtension(string[] path, PSCmdl cmdlet.WriteDebug(string.Format(CultureInfo.CurrentCulture, Strings.CheckAssemblyFile, resolvedPath)); - if (Path.GetExtension(resolvedPath).ToLower(CultureInfo.CurrentCulture) == ".dll") + if (String.Equals(Path.GetExtension(resolvedPath),".dll")) { if (!File.Exists(resolvedPath)) { From c5f1ecce11760356e5be194075a6067fd09dcaf1 Mon Sep 17 00:00:00 2001 From: Yuting Chen Date: Mon, 4 May 2015 14:11:45 -0700 Subject: [PATCH 23/57] Updated string comparison --- Engine/ScriptAnalyzer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index 0626f02ed..f099b318b 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -242,7 +242,7 @@ public List GetExternalRule(string[] moduleNames) FunctionInfo funcInfo = (FunctionInfo)psobject.ImmediateBaseObject; ParameterMetadata param = funcInfo.Parameters.Values .First(item => item.Name.EndsWith("ast", StringComparison.OrdinalIgnoreCase) || - item.Name.EndsWith("token", StringComparison.CurrentCulture)); + item.Name.EndsWith("token", StringComparison.OrdinalIgnoreCase)); //Only add functions that are defined as rules. if (param != null) From 89b4e2393dcd76d83c8eef94477fb73db5245a56 Mon Sep 17 00:00:00 2001 From: Yuting Chen Date: Mon, 4 May 2015 14:16:13 -0700 Subject: [PATCH 24/57] Modified string comparison options --- Engine/ScriptAnalyzer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index f099b318b..708181a34 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -251,7 +251,7 @@ public List GetExternalRule(string[] moduleNames) string desc =posh.AddScript(script).Invoke()[0].ImmediateBaseObject.ToString() .Replace("\r\n", " ").Trim(); - rules.Add(new ExternalRule(funcInfo.Name, funcInfo.Name, desc, param.ParameterType.Name, + rules.Add(new ExternalRule(funcInfo.Name, funcInfo.Name, desc, param.ParameterType.FullName, funcInfo.ModuleName, funcInfo.Module.Path)); } } @@ -481,7 +481,7 @@ public Dictionary> CheckRuleExtension(string[] path, PSCmdl cmdlet.WriteDebug(string.Format(CultureInfo.CurrentCulture, Strings.CheckAssemblyFile, resolvedPath)); - if (String.Equals(Path.GetExtension(resolvedPath),".dll")) + if (String.Equals(Path.GetExtension(resolvedPath),".dll", StringComparison.OrdinalIgnoreCase)) { if (!File.Exists(resolvedPath)) { From 763d990df309a2fd897379000b1f05c7d7429495 Mon Sep 17 00:00:00 2001 From: "Making GitHub Delicious." Date: Mon, 4 May 2015 15:48:35 -0600 Subject: [PATCH 25/57] add waffle.io badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 501d81ff1..7cf2c65aa 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![Stories in Ready](https://badge.waffle.io/PowerShell/PSScriptAnalyzer.png?label=ready&title=Ready)](https://waffle.io/PowerShell/PSScriptAnalyzer) Introduction ============ From 129127b09dc1f999f5058e0bdbd32f46359687a5 Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Mon, 4 May 2015 15:07:28 -0700 Subject: [PATCH 26/57] Added new section about Project Management --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7cf2c65aa..02603f2fa 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Stories in Ready](https://badge.waffle.io/PowerShell/PSScriptAnalyzer.png?label=ready&title=Ready)](https://waffle.io/PowerShell/PSScriptAnalyzer) + Introduction ============ @@ -54,6 +54,13 @@ Pester-based ScriptAnalyzer Tests are located in ```/PSScriptAnalyzer/Te .\*.ps1 (Example - .\ AvoidConvertToSecureStringWithPlainText.ps1) *You can also run all tests under \Engine or \Rules by calling Invoke-Pester in the Engine/Rules directory. +Project Management Dashboard +============================== + +You can track issues, pull requests, backlog items here: + +[![Stories in Ready](https://badge.waffle.io/PowerShell/PSScriptAnalyzer.png?label=ready&title=Ready)](https://waffle.io/PowerShell/PSScriptAnalyzer) + Contributing to ScriptAnalyzer ============================== From be781aa19a30cc5eadc2e95d46623f57e3f8252f Mon Sep 17 00:00:00 2001 From: Yuting Chen Date: Mon, 4 May 2015 16:18:06 -0700 Subject: [PATCH 27/57] Added unit test Also added try/catch to capture errors in customized rule modules and throw non-terminating errors --- Engine/ScriptAnalyzer.cs | 87 ++++++++++++++----------- Tests/Engine/samplerule/samplerule.psm1 | 45 +++++++++++++ 2 files changed, 94 insertions(+), 38 deletions(-) create mode 100644 Tests/Engine/samplerule/samplerule.psm1 diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index 708181a34..be3069fb0 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -251,7 +251,7 @@ public List GetExternalRule(string[] moduleNames) string desc =posh.AddScript(script).Invoke()[0].ImmediateBaseObject.ToString() .Replace("\r\n", " ").Trim(); - rules.Add(new ExternalRule(funcInfo.Name, funcInfo.Name, desc, param.ParameterType.FullName, + rules.Add(new ExternalRule(funcInfo.Name, funcInfo.Name, desc, param.ParameterType.Name, funcInfo.ModuleName, funcInfo.Module.Path)); } } @@ -289,12 +289,12 @@ public IEnumerable GetExternalRecord(Ast ast, Token[] token, E // Groups rules by AstType and Tokens. Dictionary> astRuleGroups = rules - .Where(item => item.GetParameter().EndsWith("ast", true, CultureInfo.CurrentCulture)) + .Where(item => item.GetParameter().EndsWith("ast", StringComparison.OrdinalIgnoreCase)) .GroupBy(item => item.GetParameter()) .ToDictionary(item => item.Key, item => item.ToList()); Dictionary> tokenRuleGroups = rules - .Where(item => item.GetParameter().EndsWith("token", true, CultureInfo.CurrentCulture)) + .Where(item => item.GetParameter().EndsWith("token", StringComparison.OrdinalIgnoreCase)) .GroupBy(item => item.GetParameter()) .ToDictionary(item => item.Key, item => item.ToList()); @@ -337,7 +337,7 @@ public IEnumerable GetExternalRecord(Ast ast, Token[] token, E { // Find all AstTypes that appeared in rule groups. IEnumerable childAsts = ast.FindAll(new Func((testAst) => - (testAst.GetType().Name.ToLower(CultureInfo.CurrentCulture) == astRuleGroup.Key.ToLower(CultureInfo.CurrentCulture))), false); + (String.Equals(testAst.GetType().Name, astRuleGroup.Key, StringComparison.OrdinalIgnoreCase))), false); foreach (Ast childAst in childAsts) { @@ -365,52 +365,63 @@ public IEnumerable GetExternalRecord(Ast ast, Token[] token, E } #endregion - #region Collects the results from commands. - - for (int i = 0; i < powerShellCommands.Count; i++) + List diagnostics = new List(); + try { - // EndInvoke will wait for each command to finish, so we will be getting the commands - // in the same order that they have been invoked withy BeginInvoke. - PSDataCollection psobjects = powerShellCommands[i].EndInvoke(powerShellCommandResults[i]); - - foreach (var psobject in psobjects) + for (int i = 0; i < powerShellCommands.Count; i++) { - DiagnosticSeverity severity; - IScriptExtent extent; - string message = string.Empty; - string ruleName = string.Empty; + // EndInvoke will wait for each command to finish, so we will be getting the commands + // in the same order that they have been invoked withy BeginInvoke. + PSDataCollection psobjects = powerShellCommands[i].EndInvoke(powerShellCommandResults[i]); - if (psobject != null && psobject.ImmediateBaseObject != null) + foreach (var psobject in psobjects) { - // Because error stream is merged to output stream, - // we need to handle the error records. - if (psobject.ImmediateBaseObject is ErrorRecord) - { - ErrorRecord record = (ErrorRecord)psobject.ImmediateBaseObject; - command.WriteError(record); - continue; - } + DiagnosticSeverity severity; + IScriptExtent extent; + string message = string.Empty; + string ruleName = string.Empty; - // DiagnosticRecord may not be correctly returned from external rule. - try + if (psobject != null && psobject.ImmediateBaseObject != null) { - Enum.TryParse(psobject.Properties["Severity"].Value.ToString().ToUpper(), out severity); - message = psobject.Properties["Message"].Value.ToString(); - extent = (IScriptExtent)psobject.Properties["Extent"].Value; - ruleName = psobject.Properties["RuleName"].Value.ToString(); + // Because error stream is merged to output stream, + // we need to handle the error records. + if (psobject.ImmediateBaseObject is ErrorRecord) + { + ErrorRecord record = (ErrorRecord)psobject.ImmediateBaseObject; + command.WriteError(record); + continue; + } + + // DiagnosticRecord may not be correctly returned from external rule. + try + { + Enum.TryParse(psobject.Properties["Severity"].Value.ToString().ToUpper(), out severity); + message = psobject.Properties["Message"].Value.ToString(); + extent = (IScriptExtent)psobject.Properties["Extent"].Value; + ruleName = psobject.Properties["RuleName"].Value.ToString(); + } + catch (Exception ex) + { + command.WriteError(new ErrorRecord(ex, ex.HResult.ToString("X"), ErrorCategory.NotSpecified, this)); + continue; + } + + if (!string.IsNullOrEmpty(message)) + { + diagnostics.Add(new DiagnosticRecord(message, extent, ruleName, severity, null)); + } } - catch (Exception ex) - { - command.WriteError(new ErrorRecord(ex, ex.HResult.ToString("X"), ErrorCategory.NotSpecified, this)); - continue; - } - - if (!string.IsNullOrEmpty(message)) yield return new DiagnosticRecord(message, extent, ruleName, severity, null); } } } + //Catch exception where customized defined rules have exceptins when doing invoke + catch(Exception ex) + { + command.WriteError(new ErrorRecord(ex, ex.HResult.ToString("X"), ErrorCategory.NotSpecified, this)); + } + return diagnostics; #endregion } } diff --git a/Tests/Engine/samplerule/samplerule.psm1 b/Tests/Engine/samplerule/samplerule.psm1 new file mode 100644 index 000000000..18f34d3c7 --- /dev/null +++ b/Tests/Engine/samplerule/samplerule.psm1 @@ -0,0 +1,45 @@ +#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] + $Ast + ) + + + $results = @() + + $result = [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]@{"Message" = "this is help"; + "Extent" = $FunctionDefinitionAst.Extent; + "RuleName" = $PSCmdlet.MyInvocation.InvocationName; + "Severity" = "Warning"} + + $results += $result + return $results + + +} +Export-ModuleMember -Function Measure* \ No newline at end of file From b73aa4d20fbc197b2506ef77cb61e34f9c0a1171 Mon Sep 17 00:00:00 2001 From: Yuting Chen Date: Mon, 4 May 2015 17:55:10 -0700 Subject: [PATCH 28/57] Added unit tests Also added parameterType to external rule to separate parameter type and parameter name --- Engine/ScriptAnalyzer.cs | 8 ++-- Tests/Engine/CustomizedRule.tests.ps1 | 40 +++++++++++++++++++ .../samplerule/SampleRulesWithErrors.psm1 | 40 +++++++++++++++++++ Tests/Engine/samplerule/samplerule.psm1 | 10 +++-- 4 files changed, 90 insertions(+), 8 deletions(-) create mode 100644 Tests/Engine/CustomizedRule.tests.ps1 create mode 100644 Tests/Engine/samplerule/SampleRulesWithErrors.psm1 diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index be3069fb0..de3e0c23e 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -251,7 +251,7 @@ public List GetExternalRule(string[] moduleNames) string desc =posh.AddScript(script).Invoke()[0].ImmediateBaseObject.ToString() .Replace("\r\n", " ").Trim(); - rules.Add(new ExternalRule(funcInfo.Name, funcInfo.Name, desc, param.ParameterType.Name, + rules.Add(new ExternalRule(funcInfo.Name, funcInfo.Name, desc, param.Name,param.ParameterType.FullName, funcInfo.ModuleName, funcInfo.Module.Path)); } } @@ -290,12 +290,12 @@ public IEnumerable GetExternalRecord(Ast ast, Token[] token, E // Groups rules by AstType and Tokens. Dictionary> astRuleGroups = rules .Where(item => item.GetParameter().EndsWith("ast", StringComparison.OrdinalIgnoreCase)) - .GroupBy(item => item.GetParameter()) + .GroupBy(item => item.GetParameterType()) .ToDictionary(item => item.Key, item => item.ToList()); Dictionary> tokenRuleGroups = rules .Where(item => item.GetParameter().EndsWith("token", StringComparison.OrdinalIgnoreCase)) - .GroupBy(item => item.GetParameter()) + .GroupBy(item => item.GetParameterType()) .ToDictionary(item => item.Key, item => item.ToList()); using (rsp) @@ -337,7 +337,7 @@ public IEnumerable GetExternalRecord(Ast ast, Token[] token, E { // Find all AstTypes that appeared in rule groups. IEnumerable childAsts = ast.FindAll(new Func((testAst) => - (String.Equals(testAst.GetType().Name, astRuleGroup.Key, StringComparison.OrdinalIgnoreCase))), false); + (astRuleGroup.Key.IndexOf(testAst.GetType().FullName,StringComparison.OrdinalIgnoreCase) != -1)), false); foreach (Ast childAst in childAsts) { diff --git a/Tests/Engine/CustomizedRule.tests.ps1 b/Tests/Engine/CustomizedRule.tests.ps1 new file mode 100644 index 000000000..2c5777bfd --- /dev/null +++ b/Tests/Engine/CustomizedRule.tests.ps1 @@ -0,0 +1,40 @@ +Import-Module PSScriptAnalyzer +$directory = Split-Path -Parent $MyInvocation.MyCommand.Path +$message = "this is help" +$measure = "Measure-RequiresRunAsAdministrator" + +Describe "Test importing customized rules with null return results" { + Context "Test Get-ScriptAnalyzer with customized rules" { + It "will not terminate the engine" { + $customizedRulePath = Get-ScriptAnalyzerRule -CustomizedRulePath $directory\samplerule\SampleRulesWithErrors.psm1 | Where-Object {$_.RuleName -eq $measure} + $customizedRulePath.Count | Should Be 1 + } + + } + + Context "Test Invoke-ScriptAnalyzer with customized rules" { + It "will not terminate the engine" { + $customizedRulePath = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomizedRulePath $directory\samplerule\SampleRulesWithErrors.psm1 | Where-Object {$_.RuleName -eq $measure} + $customizedRulePath.Count | Should Be 0 + } + } + +} + +Describe "Test importing correct customized rules" { + Context "Test Get-ScriptAnalyzer with customized rules" { + It "will show the customized rule" { + $customizedRulePath = Get-ScriptAnalyzerRule -CustomizedRulePath $directory\samplerule\samplerule.psm1 | Where-Object {$_.RuleName -eq $measure} + $customizedRulePath.Count | Should Be 1 + } + + } + + Context "Test Invoke-ScriptAnalyzer with customized rules" { + It "will show the customized 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 + } + } + +} \ No newline at end of file diff --git a/Tests/Engine/samplerule/SampleRulesWithErrors.psm1 b/Tests/Engine/samplerule/SampleRulesWithErrors.psm1 new file mode 100644 index 000000000..3fe13d9b8 --- /dev/null +++ b/Tests/Engine/samplerule/SampleRulesWithErrors.psm1 @@ -0,0 +1,40 @@ +#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] + $ScriptBlockAst + ) + + + $results = @() + + $results += $null + return $results + + +} +Export-ModuleMember -Function Measure* \ No newline at end of file diff --git a/Tests/Engine/samplerule/samplerule.psm1 b/Tests/Engine/samplerule/samplerule.psm1 index 18f34d3c7..cc94c6b1b 100644 --- a/Tests/Engine/samplerule/samplerule.psm1 +++ b/Tests/Engine/samplerule/samplerule.psm1 @@ -26,18 +26,20 @@ function Measure-RequiresRunAsAdministrator [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.Management.Automation.Language.ScriptBlockAst] - $Ast + $testAst ) $results = @() - + $result = [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]@{"Message" = "this is help"; - "Extent" = $FunctionDefinitionAst.Extent; + "Extent" = $ast.Extent; "RuleName" = $PSCmdlet.MyInvocation.InvocationName; "Severity" = "Warning"} - $results += $result + $results += $result + + return $results From cdcc4e54368896535816100986debfa5f294928d Mon Sep 17 00:00:00 2001 From: Yuting Chen Date: Mon, 4 May 2015 17:56:41 -0700 Subject: [PATCH 29/57] Added changes in ExternalRule --- Engine/Generic/ExternalRule.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Engine/Generic/ExternalRule.cs b/Engine/Generic/ExternalRule.cs index 934bbc831..c73792e28 100644 --- a/Engine/Generic/ExternalRule.cs +++ b/Engine/Generic/ExternalRule.cs @@ -28,7 +28,7 @@ internal class ExternalRule : IExternalRule string param = string.Empty; string srcName = string.Empty; string modPath = string.Empty; - + string paramType = string.Empty; public string GetName() { @@ -55,6 +55,11 @@ public SourceType GetSourceType() return SourceType.Module; } + public string GetParameterType() + { + return this.paramType; + } + //Set the community rule level as warning as the current implementation does not require user to specify rule severity when defining their functions in PS scripts public RuleSeverity GetSeverity() { @@ -80,7 +85,7 @@ public ExternalRule() } - public ExternalRule(string name, string commonName, string desc, string param, string srcName, string modPath) + public ExternalRule(string name, string commonName, string desc, string param, string paramType, string srcName, string modPath) { this.name = name; this.commonName = commonName; @@ -88,6 +93,7 @@ public ExternalRule(string name, string commonName, string desc, string param, s this.param = param; this.srcName = srcName; this.modPath = modPath; + this.paramType = paramType; } #endregion From 49c0e1e2441faba24efe05860386890d0a3491b7 Mon Sep 17 00:00:00 2001 From: Quoc Truong Date: Tue, 5 May 2015 11:01:51 -0700 Subject: [PATCH 30/57] Suppress write host for function that starts with show --- Rules/AvoidUsingWriteHost.cs | 91 ++++++++++++++----- Tests/Rules/AvoidUsingWriteHost.ps1 | 7 +- Tests/Rules/AvoidUsingWriteHost.tests.ps1 | 4 +- .../Rules/AvoidUsingWriteHostNoViolations.ps1 | 8 +- 4 files changed, 84 insertions(+), 26 deletions(-) diff --git a/Rules/AvoidUsingWriteHost.cs b/Rules/AvoidUsingWriteHost.cs index 597d7cf4a..fcc06a38b 100644 --- a/Rules/AvoidUsingWriteHost.cs +++ b/Rules/AvoidUsingWriteHost.cs @@ -23,8 +23,11 @@ namespace Microsoft.Windows.Powershell.ScriptAnalyzer.BuiltinRules /// AvoidUsingWriteHost: Check that Write-Host or Console.Write are not used /// [Export(typeof(IScriptRule))] - public class AvoidUsingWriteHost : IScriptRule + public class AvoidUsingWriteHost : AstVisitor, IScriptRule { + List records; + string fileName; + /// /// AnalyzeScript: check that Write-Host or Console.Write are not used. /// @@ -32,34 +35,78 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) { if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage); - // Finds all CommandAsts. - IEnumerable commandAsts = ast.FindAll(testAst => testAst is CommandAst, true); + records = new List(); + this.fileName = fileName; + + ast.Visit(this); + + return records; + } + + + /// + /// Visit function and skips any function that starts with show + /// + /// + /// + public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst funcAst) + { + if (funcAst == null || funcAst.Name == null) + { + return AstVisitAction.SkipChildren; + } + + if (funcAst.Name.StartsWith("show", StringComparison.OrdinalIgnoreCase)) + { + return AstVisitAction.SkipChildren; + } + + return AstVisitAction.Continue; + } + + /// + /// Checks that write-host command is not used + /// + /// + /// + public override AstVisitAction VisitCommand(CommandAst cmdAst) + { + if (cmdAst == null) + { + return AstVisitAction.SkipChildren; + } - // Iterrates all CommandAsts and check the command name. - foreach (CommandAst cmdAst in commandAsts) + if (cmdAst.GetCommandName() != null && String.Equals(cmdAst.GetCommandName(), "write-host", StringComparison.OrdinalIgnoreCase)) { - if (cmdAst.GetCommandName() != null && String.Equals(cmdAst.GetCommandName(), "write-host", StringComparison.OrdinalIgnoreCase)) - { - yield return new DiagnosticRecord(String.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingWriteHostError, System.IO.Path.GetFileName(fileName)), - cmdAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName); - } + records.Add(new DiagnosticRecord(String.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingWriteHostError, System.IO.Path.GetFileName(fileName)), + cmdAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName)); } - // Finds all InvokeMemberExpressionAst - IEnumerable invokeAsts = ast.FindAll(testAst => testAst is InvokeMemberExpressionAst, true); + return AstVisitAction.Continue; + } - foreach (InvokeMemberExpressionAst invokeAst in invokeAsts) + public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressionAst imeAst) + { + if (imeAst == null) { - TypeExpressionAst typeAst = invokeAst.Expression as TypeExpressionAst; - if (typeAst == null || typeAst.TypeName == null || typeAst.TypeName.FullName == null) continue; - - if (typeAst.TypeName.FullName.EndsWith("console", StringComparison.OrdinalIgnoreCase) - && !String.IsNullOrWhiteSpace(invokeAst.Member.Extent.Text) && invokeAst.Member.Extent.Text.StartsWith("Write", StringComparison.OrdinalIgnoreCase)) - { - yield return new DiagnosticRecord(String.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingConsoleWriteError, System.IO.Path.GetFileName(fileName), invokeAst.Member.Extent.Text), - invokeAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName); - } + return AstVisitAction.SkipChildren; } + + TypeExpressionAst typeAst = imeAst.Expression as TypeExpressionAst; + + if (typeAst == null || typeAst.TypeName == null || typeAst.TypeName.FullName == null) + { + return AstVisitAction.SkipChildren; + } + + 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), + imeAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName)); + } + + return AstVisitAction.Continue; } /// diff --git a/Tests/Rules/AvoidUsingWriteHost.ps1 b/Tests/Rules/AvoidUsingWriteHost.ps1 index b9ea56344..d8f73259c 100644 --- a/Tests/Rules/AvoidUsingWriteHost.ps1 +++ b/Tests/Rules/AvoidUsingWriteHost.ps1 @@ -3,4 +3,9 @@ cls Write-Host "aaa" clear [System.Console]::Write("abcdefg"); -[System.Console]::WriteLine("No console.writeline plz!"); \ No newline at end of file +[System.Console]::WriteLine("No console.writeline plz!"); + +function Test +{ + Write-Host "aaaa" +} \ No newline at end of file diff --git a/Tests/Rules/AvoidUsingWriteHost.tests.ps1 b/Tests/Rules/AvoidUsingWriteHost.tests.ps1 index f9cd926a2..2e28f5c37 100644 --- a/Tests/Rules/AvoidUsingWriteHost.tests.ps1 +++ b/Tests/Rules/AvoidUsingWriteHost.tests.ps1 @@ -8,8 +8,8 @@ $noViolations = Invoke-ScriptAnalyzer $directory\AvoidUsingWriteHostNoViolations Describe "AvoidUsingWriteHost" { Context "When there are violations" { - It "has 3 Write-Host violations" { - $violations.Count | Should Be 3 + It "has 4 Write-Host violations" { + $violations.Count | Should Be 4 } It "has the correct description message for Write-Host" { diff --git a/Tests/Rules/AvoidUsingWriteHostNoViolations.ps1 b/Tests/Rules/AvoidUsingWriteHostNoViolations.ps1 index 4dadd1faa..28b18eeae 100644 --- a/Tests/Rules/AvoidUsingWriteHostNoViolations.ps1 +++ b/Tests/Rules/AvoidUsingWriteHostNoViolations.ps1 @@ -1 +1,7 @@ -Write-Output "This is the correct way to write output" \ No newline at end of file +Write-Output "This is the correct way to write output" + +# Even if write-host is used, error should not be raised in this function +function Show-Something +{ + Write-Host "show something on screen"; +} \ No newline at end of file From 0c82a4aff953a036954e6b10932b6c0d7058574b Mon Sep 17 00:00:00 2001 From: Yuting Chen Date: Tue, 5 May 2015 11:54:32 -0700 Subject: [PATCH 31/57] Add documentation for customized rules --- CustomizedRuleDocumentation.md | 148 +++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 CustomizedRuleDocumentation.md diff --git a/CustomizedRuleDocumentation.md b/CustomizedRuleDocumentation.md new file mode 100644 index 000000000..9aca00fac --- /dev/null +++ b/CustomizedRuleDocumentation.md @@ -0,0 +1,148 @@ +##Documentation for Customized Rules in PowerShell Scripts +PSScriptAnalyzer uses MEF(Managed Extensibility Framework) to import all rules defined in the assembly. It can also consume rules written in PowerShell scripts. When calling Invoke-ScriptAnalyzer, users can pass customized rules using parameter -CustomizedRule to apply rule checkings on the scripts. + +This documentation serves as a basic guideline on how to define customized rules. + +###Basics +1. Function should have comment-based help. Make sure .DESCRIPTION field is there, as it will be consumed as rule description for the customized rule. +<# +.SYNOPSIS + Name of your rule. +.DESCRIPTION + This would be the description of your rule. Please refer to Rule Documentation for consistent rule messages. +.EXAMPLE +.INPUTS +.OUTPUTS +.NOTES +#> + +2. Output type should be DiagnosticRecord: +[OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])] + +3. Make sure the function takes either a Token or an Ast as a parameter +Param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.Language.ScriptBlockAst] + $testAst + ) + +5. DiagnosticRecord should have four properties: Message, Extent, RuleName and Severity +$result = [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]@{"Message" = "this is help"; + "Extent" = $ast.Extent; + "RuleName" = $PSCmdlet.MyInvocation.InvocationName; + "Severity" = "Warning"} + +6. Make sure you export the function(s) at the end of the script using Export-ModuleMember +Export-ModuleMember -Function (FunctionName) + +###Example + +<# +.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 + [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]] +.NOTES + None +#> +function Measure-RequiresRunAsAdministrator +{ + [CmdletBinding()] + [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])] + Param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.Language.ScriptBlockAst] + $ScriptBlockAst + ) + + Process + { + $results = @() + try + { + #region Define predicates to find ASTs. + # Finds specific method, IsInRole. + [ScriptBlock]$predicate1 = { + param ([System.Management.Automation.Language.Ast]$Ast) + [bool]$returnValue = $false + if ($Ast -is [System.Management.Automation.Language.MemberExpressionAst]) + { + [System.Management.Automation.Language.MemberExpressionAst]$meAst = $ast; + if ($meAst.Member -is [System.Management.Automation.Language.StringConstantExpressionAst]) + { + [System.Management.Automation.Language.StringConstantExpressionAst]$sceAst = $meAst.Member; + if ($sceAst.Value -eq "isinrole") + { + $returnValue = $true; + } + } + } + return $returnValue + } + + # Finds specific value, [system.security.principal.windowsbuiltinrole]::administrator. + [ScriptBlock]$predicate2 = { + param ([System.Management.Automation.Language.Ast]$Ast) + [bool]$returnValue = $false + if ($ast -is [System.Management.Automation.Language.AssignmentStatementAst]) + { + [System.Management.Automation.Language.AssignmentStatementAst]$asAst = $Ast; + if ($asAst.Right.ToString().ToLower() -eq "[system.security.principal.windowsbuiltinrole]::administrator") + { + $returnValue = $true + } + } + return $returnValue + } + #endregion + #region Finds ASTs that match the predicates. + + [System.Management.Automation.Language.Ast[]]$methodAst = $ScriptBlockAst.FindAll($predicate1, $true) + [System.Management.Automation.Language.Ast[]]$assignmentAst = $ScriptBlockAst.FindAll($predicate2, $true) + if ($null -ne $ScriptBlockAst.ScriptRequirements) + { + if ((!$ScriptBlockAst.ScriptRequirements.IsElevationRequired) -and + ($methodAst.Count -ne 0) -and ($assignmentAst.Count -ne 0)) + { + $result = [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord]@{"Message" = $Messages.MeasureRequiresRunAsAdministrator; + "Extent" = $assignmentAst.Extent; + "RuleName" = $PSCmdlet.MyInvocation.InvocationName; + "Severity" = "Information"} + $results += $result + } + } + else + { + if (($methodAst.Count -ne 0) -and ($assignmentAst.Count -ne 0)) + { + $result = [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord]@{"Message" = $Messages.MeasureRequiresRunAsAdministrator; + "Extent" = $assignmentAst.Extent; + "RuleName" = $PSCmdlet.MyInvocation.InvocationName; + "Severity" = "Information"} + $results += $result + } + } + return $results + #endregion + } + catch + { + $PSCmdlet.ThrowTerminatingError($PSItem) + } + } +} + +More examples can be found in *Tests\Engine\CommunityRules* \ No newline at end of file From 296f09114d90b3230f45fd9a1eaff7dd78e7e024 Mon Sep 17 00:00:00 2001 From: Yuting Chen Date: Tue, 5 May 2015 11:58:18 -0700 Subject: [PATCH 32/57] Change code formatting --- CustomizedRuleDocumentation.md | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/CustomizedRuleDocumentation.md b/CustomizedRuleDocumentation.md index 9aca00fac..11708ba2a 100644 --- a/CustomizedRuleDocumentation.md +++ b/CustomizedRuleDocumentation.md @@ -5,6 +5,7 @@ This documentation serves as a basic guideline on how to define customized rules ###Basics 1. Function should have comment-based help. Make sure .DESCRIPTION field is there, as it will be consumed as rule description for the customized rule. +``` <# .SYNOPSIS Name of your rule. @@ -15,11 +16,13 @@ This documentation serves as a basic guideline on how to define customized rules .OUTPUTS .NOTES #> +``` 2. Output type should be DiagnosticRecord: [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])] 3. Make sure the function takes either a Token or an Ast as a parameter +``` Param ( [Parameter(Mandatory = $true)] @@ -27,18 +30,21 @@ Param [System.Management.Automation.Language.ScriptBlockAst] $testAst ) - +``` 5. DiagnosticRecord should have four properties: Message, Extent, RuleName and Severity -$result = [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]@{"Message" = "this is help"; - "Extent" = $ast.Extent; - "RuleName" = $PSCmdlet.MyInvocation.InvocationName; - "Severity" = "Warning"} - +``` +$result = [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]@{"Message" = "This is a sample rule"; + "Extent" = $ast.Extent; + "RuleName" = $PSCmdlet.MyInvocation.InvocationName; + "Severity" = "Warning"} +``` 6. Make sure you export the function(s) at the end of the script using Export-ModuleMember +``` Export-ModuleMember -Function (FunctionName) +``` ###Example - +``` <# .SYNOPSIS Uses #Requires -RunAsAdministrator instead of your own methods. @@ -144,5 +150,5 @@ function Measure-RequiresRunAsAdministrator } } } - +``` More examples can be found in *Tests\Engine\CommunityRules* \ No newline at end of file From d2057f185ec3d8628851add1d1348057b77207b1 Mon Sep 17 00:00:00 2001 From: Yuting Chen Date: Tue, 5 May 2015 12:05:09 -0700 Subject: [PATCH 33/57] Modified some wording --- CustomizedRuleDocumentation.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CustomizedRuleDocumentation.md b/CustomizedRuleDocumentation.md index 11708ba2a..c21420b90 100644 --- a/CustomizedRuleDocumentation.md +++ b/CustomizedRuleDocumentation.md @@ -1,10 +1,10 @@ ##Documentation for Customized Rules in PowerShell Scripts -PSScriptAnalyzer uses MEF(Managed Extensibility Framework) to import all rules defined in the assembly. It can also consume rules written in PowerShell scripts. When calling Invoke-ScriptAnalyzer, users can pass customized rules using parameter -CustomizedRule to apply rule checkings on the scripts. +PSScriptAnalyzer uses MEF(Managed Extensibility Framework) to import all rules defined in the assembly. It can also consume rules written in PowerShell scripts. When calling Invoke-ScriptAnalyzer, users can pass customized rules using parameter -CustomizedRulePath to apply rule checkings on the scripts. This documentation serves as a basic guideline on how to define customized rules. ###Basics -1. Function should have comment-based help. Make sure .DESCRIPTION field is there, as it will be consumed as rule description for the customized rule. +1. Functions should have comment-based help. Make sure .DESCRIPTION field is there, as it will be consumed as rule description for the customized rule. ``` <# .SYNOPSIS @@ -21,7 +21,7 @@ This documentation serves as a basic guideline on how to define customized rules 2. Output type should be DiagnosticRecord: [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])] -3. Make sure the function takes either a Token or an Ast as a parameter +3. Make sure each function takes either a Token or an Ast as a parameter ``` Param ( From 0d37d1fd277adee4361bb168ecd49b0a7eda0dcb Mon Sep 17 00:00:00 2001 From: Yuting Chen Date: Tue, 5 May 2015 12:06:31 -0700 Subject: [PATCH 34/57] Update the list order --- CustomizedRuleDocumentation.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CustomizedRuleDocumentation.md b/CustomizedRuleDocumentation.md index c21420b90..52534e77f 100644 --- a/CustomizedRuleDocumentation.md +++ b/CustomizedRuleDocumentation.md @@ -31,14 +31,15 @@ Param $testAst ) ``` -5. DiagnosticRecord should have four properties: Message, Extent, RuleName and Severity + +4. DiagnosticRecord should have four properties: Message, Extent, RuleName and Severity ``` $result = [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]@{"Message" = "This is a sample rule"; "Extent" = $ast.Extent; "RuleName" = $PSCmdlet.MyInvocation.InvocationName; "Severity" = "Warning"} ``` -6. Make sure you export the function(s) at the end of the script using Export-ModuleMember +5. Make sure you export the function(s) at the end of the script using Export-ModuleMember ``` Export-ModuleMember -Function (FunctionName) ``` From 4267caeaea6a0c8601789d473460ff4cbca25239 Mon Sep 17 00:00:00 2001 From: Yuting Chen Date: Tue, 5 May 2015 12:07:58 -0700 Subject: [PATCH 35/57] Ordered list --- CustomizedRuleDocumentation.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CustomizedRuleDocumentation.md b/CustomizedRuleDocumentation.md index 52534e77f..153610cbd 100644 --- a/CustomizedRuleDocumentation.md +++ b/CustomizedRuleDocumentation.md @@ -17,10 +17,8 @@ This documentation serves as a basic guideline on how to define customized rules .NOTES #> ``` - 2. Output type should be DiagnosticRecord: [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])] - 3. Make sure each function takes either a Token or an Ast as a parameter ``` Param @@ -31,7 +29,6 @@ Param $testAst ) ``` - 4. DiagnosticRecord should have four properties: Message, Extent, RuleName and Severity ``` $result = [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]@{"Message" = "This is a sample rule"; From b6c17b3115e93614a7b6101bc9d03568e8220f48 Mon Sep 17 00:00:00 2001 From: "Yuting Chen[MSFT]" Date: Tue, 5 May 2015 12:10:16 -0700 Subject: [PATCH 36/57] Update CustomizedRuleDocumentation.md --- CustomizedRuleDocumentation.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/CustomizedRuleDocumentation.md b/CustomizedRuleDocumentation.md index 153610cbd..d98eb5698 100644 --- a/CustomizedRuleDocumentation.md +++ b/CustomizedRuleDocumentation.md @@ -4,7 +4,7 @@ PSScriptAnalyzer uses MEF(Managed Extensibility Framework) to import all rules d This documentation serves as a basic guideline on how to define customized rules. ###Basics -1. Functions should have comment-based help. Make sure .DESCRIPTION field is there, as it will be consumed as rule description for the customized rule. +- Functions should have comment-based help. Make sure .DESCRIPTION field is there, as it will be consumed as rule description for the customized rule. ``` <# .SYNOPSIS @@ -17,9 +17,10 @@ This documentation serves as a basic guideline on how to define customized rules .NOTES #> ``` -2. Output type should be DiagnosticRecord: +- Output type should be DiagnosticRecord: [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])] -3. Make sure each function takes either a Token or an Ast as a parameter + +- Make sure each function takes either a Token or an Ast as a parameter ``` Param ( @@ -29,14 +30,14 @@ Param $testAst ) ``` -4. DiagnosticRecord should have four properties: Message, Extent, RuleName and Severity +- DiagnosticRecord should have four properties: Message, Extent, RuleName and Severity ``` $result = [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]@{"Message" = "This is a sample rule"; "Extent" = $ast.Extent; "RuleName" = $PSCmdlet.MyInvocation.InvocationName; "Severity" = "Warning"} ``` -5. Make sure you export the function(s) at the end of the script using Export-ModuleMember +- Make sure you export the function(s) at the end of the script using Export-ModuleMember ``` Export-ModuleMember -Function (FunctionName) ``` @@ -149,4 +150,4 @@ function Measure-RequiresRunAsAdministrator } } ``` -More examples can be found in *Tests\Engine\CommunityRules* \ No newline at end of file +More examples can be found in *Tests\Engine\CommunityRules* From 11ced68f586068e6449428ca7a68dc1dc91a34ec Mon Sep 17 00:00:00 2001 From: "Yuting Chen[MSFT]" Date: Tue, 5 May 2015 12:10:51 -0700 Subject: [PATCH 37/57] Update CustomizedRuleDocumentation.md --- CustomizedRuleDocumentation.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CustomizedRuleDocumentation.md b/CustomizedRuleDocumentation.md index d98eb5698..51e58f5a1 100644 --- a/CustomizedRuleDocumentation.md +++ b/CustomizedRuleDocumentation.md @@ -17,8 +17,11 @@ This documentation serves as a basic guideline on how to define customized rules .NOTES #> ``` + - Output type should be DiagnosticRecord: +``` [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])] +``` - Make sure each function takes either a Token or an Ast as a parameter ``` @@ -30,6 +33,7 @@ Param $testAst ) ``` + - DiagnosticRecord should have four properties: Message, Extent, RuleName and Severity ``` $result = [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]@{"Message" = "This is a sample rule"; @@ -37,6 +41,7 @@ $result = [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[ "RuleName" = $PSCmdlet.MyInvocation.InvocationName; "Severity" = "Warning"} ``` + - Make sure you export the function(s) at the end of the script using Export-ModuleMember ``` Export-ModuleMember -Function (FunctionName) From b98774c19fcb652de518bd0b2e77d72899fc68d2 Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Tue, 5 May 2015 12:17:07 -0700 Subject: [PATCH 38/57] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 02603f2fa..b2a3b6996 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Requirements WS2012R2 / Windows 8.1 / Windows OS running **PowerShell v5.0** and **Windows Management Framework 5.0 Preview** -Download the latest WMF package from [Windows Management Framework 5.0 Preview February 2015](http://go.microsoft.com/fwlink/?LinkId=398175). +Download the latest WMF package from [Windows Management Framework 5.0 Preview](http://go.microsoft.com/fwlink/?LinkId=398175). Installation ============ From 6cb8120eaefff825623314f25db7c91ab68c5377 Mon Sep 17 00:00:00 2001 From: Raghu Shantha Date: Tue, 5 May 2015 15:22:10 -0700 Subject: [PATCH 39/57] Updated existing rule for verifiying the presence of WMI cmdlets in a script - new WMI cmdlets covered --- Rules/AvoidUsingWMICmdlet.cs | 144 ++++++++++++++++++++++++ Rules/ScriptAnalyzerBuiltinRules.csproj | 2 +- Rules/Strings.resx | 16 +-- 3 files changed, 153 insertions(+), 9 deletions(-) create mode 100644 Rules/AvoidUsingWMICmdlet.cs diff --git a/Rules/AvoidUsingWMICmdlet.cs b/Rules/AvoidUsingWMICmdlet.cs new file mode 100644 index 000000000..079f0ec14 --- /dev/null +++ b/Rules/AvoidUsingWMICmdlet.cs @@ -0,0 +1,144 @@ +// +// Copyright (c) Microsoft Corporation. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using System; +using System.Collections.ObjectModel; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Management.Automation; +using System.Management.Automation.Language; +using Microsoft.Windows.Powershell.ScriptAnalyzer.Generic; +using System.ComponentModel.Composition; +using System.Resources; +using System.Globalization; +using System.Threading; +using System.Reflection; + +namespace Microsoft.Windows.Powershell.ScriptAnalyzer.BuiltinRules +{ + /// + /// AvoidUsingWMICmdlet: Avoid Using Get-WMIObject, Remove-WMIObject, Invoke-WmiMethod, Register-WmiEvent, Set-WmiInstance + /// + [Export(typeof(IScriptRule))] + public class AvoidUsingWMICmdlet : IScriptRule + { + /// + /// AnalyzeScript: Avoid Using Get-WMIObject, Remove-WMIObject, Invoke-WmiMethod, Register-WmiEvent, Set-WmiInstance + /// + public IEnumerable AnalyzeScript(Ast ast, string fileName) + { + if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage); + + // Rule is applicable only when PowerShell Version is < 3.0, since CIM cmdlet was introduced in 3.0 + int majorPSVersion = GetPSMajorVersion(ast); + if (!(3 > majorPSVersion && 0 < majorPSVersion)) + { + // Finds all CommandAsts. + IEnumerable commandAsts = ast.FindAll(testAst => testAst is CommandAst, true); + + // Iterate all CommandAsts and check the command name + foreach (CommandAst cmdAst in commandAsts) + { + if (cmdAst.GetCommandName() != null && + (String.Equals(cmdAst.GetCommandName(), "get-wmiobject", StringComparison.OrdinalIgnoreCase) + || String.Equals(cmdAst.GetCommandName(), "remove-wmiobject", StringComparison.OrdinalIgnoreCase) + || String.Equals(cmdAst.GetCommandName(), "invoke-wmimethod", StringComparison.OrdinalIgnoreCase) + || String.Equals(cmdAst.GetCommandName(), "register-wmievent", StringComparison.OrdinalIgnoreCase) + || 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); + } + } + } + } + + /// + /// GetPSMajorVersion: Retrieves Major PowerShell Version when supplied using #requires keyword in the script + /// + /// The name of this rule + private int GetPSMajorVersion(Ast ast) + { + if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage); + + IEnumerable scriptBlockAsts = ast.FindAll(testAst => testAst is ScriptBlockAst, true); + + foreach (ScriptBlockAst scriptBlockAst in scriptBlockAsts) + { + if (null != scriptBlockAst.ScriptRequirements && null != scriptBlockAst.ScriptRequirements.RequiredPSVersion) + { + return scriptBlockAst.ScriptRequirements.RequiredPSVersion.Major; + } + } + + // return a non valid Major version if #requires -Version is not supplied in the Script + return -1; + } + + /// + /// GetName: Retrieves the name of this rule. + /// + /// The name of this rule + public string GetName() + { + return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.AvoidUsingWMICmdletName); + } + + /// + /// GetCommonName: Retrieves the common name of this rule. + /// + /// The common name of this rule + public string GetCommonName() + { + return string.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingWMICmdletCommonName); + } + + /// + /// GetDescription: Retrieves the description of this rule. + /// + /// The description of this rule + public string GetDescription() + { + return string.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingWMICmdletDescription); + } + + /// + /// GetSourceType: Retrieves the type of the rule: builtin, managed or module. + /// + public SourceType GetSourceType() + { + return SourceType.Builtin; + } + + + /// + /// GetSeverity:Retrieves the severity of the rule: error, warning of information. + /// + /// + public RuleSeverity GetSeverity() + { + return RuleSeverity.Warning; + } + + + /// + /// GetSourceName: Retrieves the module/assembly name the rule is from. + /// + public string GetSourceName() + { + return string.Format(CultureInfo.CurrentCulture, Strings.SourceName); + } + } +} diff --git a/Rules/ScriptAnalyzerBuiltinRules.csproj b/Rules/ScriptAnalyzerBuiltinRules.csproj index f84a2072d..7cc1c0446 100644 --- a/Rules/ScriptAnalyzerBuiltinRules.csproj +++ b/Rules/ScriptAnalyzerBuiltinRules.csproj @@ -66,7 +66,7 @@ - + diff --git a/Rules/Strings.resx b/Rules/Strings.resx index f7501a45c..607777527 100644 --- a/Rules/Strings.resx +++ b/Rules/Strings.resx @@ -666,16 +666,16 @@ UseShouldProcessForStateChangingFunctions - - Avoid Using Get-WMIObject, Remove-WMIObject + + Avoid Using Get-WMIObject, Remove-WMIObject, Invoke-WmiMethod, Register-WmiEvent, Set-WmiInstance - - Depricated. Starting in Windows PowerShell 3.0, these cmdlets have been superseded by CimInstance cmdlets. + + Depricated. Starting in Windows PowerShell 3.0, these cmdlets have been superseded by CIM cmdlets. - - File '{0}' uses WMIObject cmdlet. For PowerShell 3.0 and above, this is not recommended because the cmdlet is based on a non-standard DCOM protocol. Use CIMInstance cmdlet instead. This is CIM and WS-Man standards compliant and works in a heterogeneous environment. + + File '{0}' 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. - - AvoidUsingWMIObjectCmdlet + + AvoidUsingWMICmdlet \ No newline at end of file From 5e2a145363efcd451b135a21f2887fb873f8b2eb Mon Sep 17 00:00:00 2001 From: Raghu Shantha Date: Tue, 5 May 2015 15:23:57 -0700 Subject: [PATCH 40/57] Updated existing rule for verifiying the presence of WMI cmdlets in a script - new WMI cmdlets covered --- Rules/AvoidUsingWMIObjectCmdlet.cs | 138 ----------------------------- 1 file changed, 138 deletions(-) delete mode 100644 Rules/AvoidUsingWMIObjectCmdlet.cs diff --git a/Rules/AvoidUsingWMIObjectCmdlet.cs b/Rules/AvoidUsingWMIObjectCmdlet.cs deleted file mode 100644 index 6c71f1646..000000000 --- a/Rules/AvoidUsingWMIObjectCmdlet.cs +++ /dev/null @@ -1,138 +0,0 @@ -// -// Copyright (c) Microsoft Corporation. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -using System; -using System.Collections.ObjectModel; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Management.Automation; -using System.Management.Automation.Language; -using Microsoft.Windows.Powershell.ScriptAnalyzer.Generic; -using System.ComponentModel.Composition; -using System.Resources; -using System.Globalization; -using System.Threading; -using System.Reflection; - -namespace Microsoft.Windows.Powershell.ScriptAnalyzer.BuiltinRules -{ - /// - /// AvoidUsingWMIObjectCmdlet: Verify that Get-WMIObject, Remove-WMIObject are not used - /// - [Export(typeof(IScriptRule))] - public class AvoidUsingWMIObjectCmdlet : IScriptRule - { - /// - /// AnalyzeScript: Verify that Get-WMIObject, Remove-WMIObject are not used - /// - public IEnumerable AnalyzeScript(Ast ast, string fileName) - { - if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage); - - // Rule is applicable only when PowerShell Version is < 3.0, since Get-CIMInstance was introduced in 3.0 - int majorPSVersion = GetPSMajorVersion(ast); - if (!(3 > majorPSVersion && 0 < majorPSVersion)) - { - // Finds all CommandAsts. - IEnumerable commandAsts = ast.FindAll(testAst => testAst is CommandAst, true); - - // Iterate all CommandAsts and check the command name - foreach (CommandAst cmdAst in commandAsts) - { - if (cmdAst.GetCommandName() != null && (String.Equals(cmdAst.GetCommandName(), "get-wmiobject", StringComparison.OrdinalIgnoreCase) || String.Equals(cmdAst.GetCommandName(), "remove-wmiobject", StringComparison.OrdinalIgnoreCase))) - { - yield return new DiagnosticRecord(String.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingWMIObjectCmdletError, System.IO.Path.GetFileName(fileName)), - cmdAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName); - } - } - } - } - - /// - /// GetPSMajorVersion: Retrieves Major PowerShell Version when supplied using #requires keyword in the script - /// - /// The name of this rule - private int GetPSMajorVersion(Ast ast) - { - if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage); - - IEnumerable scriptBlockAsts = ast.FindAll(testAst => testAst is ScriptBlockAst, true); - - foreach (ScriptBlockAst scriptBlockAst in scriptBlockAsts) - { - if (null != scriptBlockAst.ScriptRequirements && null != scriptBlockAst.ScriptRequirements.RequiredPSVersion) - { - return scriptBlockAst.ScriptRequirements.RequiredPSVersion.Major; - } - } - - // return a non valid Major version if #requires -Version is not supplied in the Script - return -1; - } - - /// - /// GetName: Retrieves the name of this rule. - /// - /// The name of this rule - public string GetName() - { - return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.AvoidUsingWMIObjectCmdletName); - } - - /// - /// GetCommonName: Retrieves the common name of this rule. - /// - /// The common name of this rule - public string GetCommonName() - { - return string.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingWMIObjectCmdletCommonName); - } - - /// - /// GetDescription: Retrieves the description of this rule. - /// - /// The description of this rule - public string GetDescription() - { - return string.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingWMIObjectCmdletDescription); - } - - /// - /// GetSourceType: Retrieves the type of the rule: builtin, managed or module. - /// - public SourceType GetSourceType() - { - return SourceType.Builtin; - } - - - /// - /// GetSeverity:Retrieves the severity of the rule: error, warning of information. - /// - /// - public RuleSeverity GetSeverity() - { - return RuleSeverity.Warning; - } - - - /// - /// GetSourceName: Retrieves the module/assembly name the rule is from. - /// - public string GetSourceName() - { - return string.Format(CultureInfo.CurrentCulture, Strings.SourceName); - } - } -} From 0d539eef5238f608787178ffc263816424ef5686 Mon Sep 17 00:00:00 2001 From: Karol Kaczmarek Date: Tue, 5 May 2015 16:00:01 -0700 Subject: [PATCH 41/57] Adding scaffolding for DscTestsPresent rule --- Rules/DscTestsPresent.cs | 110 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 Rules/DscTestsPresent.cs diff --git a/Rules/DscTestsPresent.cs b/Rules/DscTestsPresent.cs new file mode 100644 index 000000000..ed6067e33 --- /dev/null +++ b/Rules/DscTestsPresent.cs @@ -0,0 +1,110 @@ +// +// Copyright (c) Microsoft Corporation. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation.Language; +using Microsoft.Windows.Powershell.ScriptAnalyzer.Generic; +using System.ComponentModel.Composition; +using System.Globalization; +using System.IO; +using System.Management.Automation; + +namespace Microsoft.Windows.Powershell.ScriptAnalyzer.BuiltinRules +{ + /// + /// DscTestsPresent: Checks that DSC tests for given resource are present. + /// Rule expects directory Tests to be present: + /// For non-class based resources it should exist at the same folder level as DSCResources folder. + /// For class based resources it should be present at the same folder level as resource psm1 file. + /// Tests folder should contain test script for given resource - file name should contain resource's name. + /// + [Export(typeof(IDSCResourceRule))] + public class DscExamplesPresent : IDSCResourceRule + { + /// + /// AnalyzeDSCResource: Analyzes given DSC Resource + /// + /// The script's ast + /// The name of the script file being analyzed + /// The results of the analysis + public IEnumerable AnalyzeDSCResource(Ast ast, string fileName) + { + + } + + /// + /// AnalyzeDSCClass: Analyzes given DSC class + /// + /// + /// + /// + public IEnumerable AnalyzeDSCClass(Ast ast, string fileName) + { + + } + + /// + /// GetName: Retrieves the name of this rule. + /// + /// The name of this rule + public string GetName() + { + return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.DscExamplesPresent); + } + + /// + /// GetCommonName: Retrieves the Common name of this rule. + /// + /// The common name of this rule + public string GetCommonName() + { + return string.Format(CultureInfo.CurrentCulture, Strings.DscExamplesPresentCommonName); + } + + /// + /// GetDescription: Retrieves the description of this rule. + /// + /// The description of this rule + public string GetDescription() + { + return string.Format(CultureInfo.CurrentCulture, Strings.DscExamplesPresentDescription); + } + + /// + /// GetSourceType: Retrieves the type of the rule: builtin, managed or module. + /// + public SourceType GetSourceType() + { + return SourceType.Builtin; + } + + /// + /// GetSeverity: Retrieves the severity of the rule: error, warning or information. + /// + /// + public RuleSeverity GetSeverity() + { + return RuleSeverity.Information; + } + + /// + /// GetSourceName: Retrieves the module/assembly name the rule is from. + /// + public string GetSourceName() + { + return string.Format(CultureInfo.CurrentCulture, Strings.DSCSourceName); + } + } + +} \ No newline at end of file From 5ca074ba050b19deb9f93ddca03697991b091049 Mon Sep 17 00:00:00 2001 From: Karol Kaczmarek Date: Tue, 5 May 2015 16:09:23 -0700 Subject: [PATCH 42/57] Adding implementation for non-class based resources --- Rules/DscTestsPresent.cs | 32 +++++++++++++++++++++++++++----- Rules/Strings.Designer.cs | 36 ++++++++++++++++++++++++++++++++++++ Rules/Strings.resx | 12 ++++++++++++ 3 files changed, 75 insertions(+), 5 deletions(-) diff --git a/Rules/DscTestsPresent.cs b/Rules/DscTestsPresent.cs index ed6067e33..3de793ec8 100644 --- a/Rules/DscTestsPresent.cs +++ b/Rules/DscTestsPresent.cs @@ -30,7 +30,7 @@ namespace Microsoft.Windows.Powershell.ScriptAnalyzer.BuiltinRules /// Tests folder should contain test script for given resource - file name should contain resource's name. /// [Export(typeof(IDSCResourceRule))] - public class DscExamplesPresent : IDSCResourceRule + public class DscTestsPresent : IDSCResourceRule { /// /// AnalyzeDSCResource: Analyzes given DSC Resource @@ -40,7 +40,29 @@ public class DscExamplesPresent : IDSCResourceRule /// The results of the analysis public IEnumerable AnalyzeDSCResource(Ast ast, string fileName) { - + String fileNameOnly = Path.GetFileName(fileName); + String resourceName = Path.GetFileNameWithoutExtension(fileNameOnly); + String testsQuery = String.Format("*{0}*", resourceName); + Boolean testsPresent = false; + String expectedTestsPath = Path.Combine(new String[] { fileName, "..", "..", "..", "Tests" }); + + // Verify tests are present + if (Directory.Exists(expectedTestsPath)) + { + DirectoryInfo testsFolder = new DirectoryInfo(expectedTestsPath); + FileInfo[] testFiles = testsFolder.GetFiles(testsQuery); + if (testFiles.Length != 0) + { + testsPresent = true; + } + } + + // Return error if no tests present + if (!testsPresent) + { + yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.DscTestsPresentNoTestsError, resourceName), + ast.Extent, GetName(), DiagnosticSeverity.Information, fileName); + } } /// @@ -60,7 +82,7 @@ public IEnumerable AnalyzeDSCClass(Ast ast, string fileName) /// The name of this rule public string GetName() { - return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.DscExamplesPresent); + return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.DscTestsPresent); } /// @@ -69,7 +91,7 @@ public string GetName() /// The common name of this rule public string GetCommonName() { - return string.Format(CultureInfo.CurrentCulture, Strings.DscExamplesPresentCommonName); + return string.Format(CultureInfo.CurrentCulture, Strings.DscTestsPresentCommonName); } /// @@ -78,7 +100,7 @@ public string GetCommonName() /// The description of this rule public string GetDescription() { - return string.Format(CultureInfo.CurrentCulture, Strings.DscExamplesPresentDescription); + return string.Format(CultureInfo.CurrentCulture, Strings.DscTestsPresentDescription); } /// diff --git a/Rules/Strings.Designer.cs b/Rules/Strings.Designer.cs index 3f1eb4469..8064dc86b 100644 --- a/Rules/Strings.Designer.cs +++ b/Rules/Strings.Designer.cs @@ -834,6 +834,42 @@ internal static string DSCSourceName { } } + /// + /// Looks up a localized string similar to DscTestsPresent. + /// + internal static string DscTestsPresent { + get { + return ResourceManager.GetString("DscTestsPresent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Dsc tests are present. + /// + internal static string DscTestsPresentCommonName { + get { + return ResourceManager.GetString("DscTestsPresentCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Every DSC resource module should contain folder "Tests" with tests for every resource. Test scripts should have resource name they are testing in the file name.. + /// + internal static string DscTestsPresentDescription { + get { + return ResourceManager.GetString("DscTestsPresentDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No tests found for resource '{0}'. + /// + internal static string DscTestsPresentNoTestsError { + get { + return ResourceManager.GetString("DscTestsPresentNoTestsError", resourceCulture); + } + } + /// /// Looks up a localized string similar to Module Manifest Fields. /// diff --git a/Rules/Strings.resx b/Rules/Strings.resx index 1d548b83a..7692a7949 100644 --- a/Rules/Strings.resx +++ b/Rules/Strings.resx @@ -690,4 +690,16 @@ UseOutputTypeCorrectly + + DscTestsPresent + + + Dsc tests are present + + + Every DSC resource module should contain folder "Tests" with tests for every resource. Test scripts should have resource name they are testing in the file name. + + + No tests found for resource '{0}' + \ No newline at end of file From d0996875167a487454d932bc51adc4031ffdf184 Mon Sep 17 00:00:00 2001 From: Karol Kaczmarek Date: Tue, 5 May 2015 16:21:33 -0700 Subject: [PATCH 43/57] Adding rule to project file --- Rules/ScriptAnalyzerBuiltinRules.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/Rules/ScriptAnalyzerBuiltinRules.csproj b/Rules/ScriptAnalyzerBuiltinRules.csproj index eef54841b..d0e48eb90 100644 --- a/Rules/ScriptAnalyzerBuiltinRules.csproj +++ b/Rules/ScriptAnalyzerBuiltinRules.csproj @@ -68,6 +68,7 @@ + From ed2f2916194b42d0a24cf7e8c02e3d4ba44a4f13 Mon Sep 17 00:00:00 2001 From: Karol Kaczmarek Date: Tue, 5 May 2015 16:33:05 -0700 Subject: [PATCH 44/57] Adding implementation for class based resources --- Rules/DscTestsPresent.cs | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/Rules/DscTestsPresent.cs b/Rules/DscTestsPresent.cs index 3de793ec8..15fbbda23 100644 --- a/Rules/DscTestsPresent.cs +++ b/Rules/DscTestsPresent.cs @@ -73,7 +73,39 @@ public IEnumerable AnalyzeDSCResource(Ast ast, string fileName /// public IEnumerable AnalyzeDSCClass(Ast ast, string fileName) { - + String resourceName = null; + + IEnumerable dscClasses = ast.FindAll(item => + item is TypeDefinitionAst + && ((item as TypeDefinitionAst).IsClass) + && (item as TypeDefinitionAst).Attributes.Any(attr => String.Equals("DSCResource", attr.TypeName.FullName, StringComparison.OrdinalIgnoreCase)), true); + + foreach (TypeDefinitionAst dscClass in dscClasses) + { + resourceName = dscClass.Name; + + String testsQuery = String.Format("*{0}*", resourceName); + Boolean testsPresent = false; + String expectedTestsPath = Path.Combine(new String[] { fileName, "..", "Tests" }); + + // Verify tests are present + if (Directory.Exists(expectedTestsPath)) + { + DirectoryInfo testsFolder = new DirectoryInfo(expectedTestsPath); + FileInfo[] testFiles = testsFolder.GetFiles(testsQuery); + if (testFiles.Length != 0) + { + testsPresent = true; + } + } + + // Return error if no tests present + if (!testsPresent) + { + yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.DscTestsPresentNoTestsError, resourceName), + dscClass.Extent, GetName(), DiagnosticSeverity.Information, fileName); + } + } } /// From bf204bd1b9e0b0f54f5e7c53c730948aa391e126 Mon Sep 17 00:00:00 2001 From: Karol Kaczmarek Date: Tue, 5 May 2015 16:37:13 -0700 Subject: [PATCH 45/57] Adding tests --- Tests/Rules/DscTestsPresent.tests.ps1 | 70 +++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 Tests/Rules/DscTestsPresent.tests.ps1 diff --git a/Tests/Rules/DscTestsPresent.tests.ps1 b/Tests/Rules/DscTestsPresent.tests.ps1 new file mode 100644 index 000000000..f7a866cf4 --- /dev/null +++ b/Tests/Rules/DscTestsPresent.tests.ps1 @@ -0,0 +1,70 @@ +Import-Module -Verbose PSScriptAnalyzer + +$currentPath = Split-Path -Parent $MyInvocation.MyCommand.Path +$ruleName = "PSDSCDscTestsPresent" + +Describe "DscTestsPresent rule in class based resource" { + + $testsPath = "$currentPath\DSCResources\MyDscResource\Tests" + $classResourcePath = "$currentPath\DSCResources\MyDscResource\MyDscResource.psm1" + + Context "When tests absent" { + + $violations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $classResourcePath | Where-Object {$_.RuleName -eq $ruleName} + $violationMessage = "No tests found for resource 'FileResource'" + + It "has 1 missing test violation" { + $violations.Count | Should Be 1 + } + + It "has the correct description message" { + $violations[0].Message | Should Match $violationMessage + } + } + + Context "When tests present" { + New-Item -Path $testsPath -ItemType Directory + New-Item -Path "$testsPath\FileResource_Test.psm1" -ItemType File + + $noViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $classResourcePath | Where-Object {$_.RuleName -eq $ruleName} + + It "returns no violations" { + $noViolations.Count | Should Be 0 + } + + Remove-Item -Path $testsPath -Recurse -Force + } +} + +Describe "DscTestsPresent rule in regular (non-class) based resource" { + + $testsPath = "$currentPath\Tests" + $resourcePath = "$currentPath\DSCResources\MSFT_WaitForAll\MSFT_WaitForAll.psm1" + + Context "When tests absent" { + + $violations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $resourcePath | Where-Object {$_.RuleName -eq $ruleName} + $violationMessage = "No tests found for resource 'MSFT_WaitForAll'" + + It "has 1 missing tests violation" { + $violations.Count | Should Be 1 + } + + It "has the correct description message" { + $violations[0].Message | Should Match $violationMessage + } + } + + Context "When tests present" { + New-Item -Path $testsPath -ItemType Directory + New-Item -Path "$testsPath\MSFT_WaitForAll_Test.psm1" -ItemType File + + $noViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $resourcePath | Where-Object {$_.RuleName -eq $ruleName} + + It "returns no violations" { + $noViolations.Count | Should Be 0 + } + + Remove-Item -Path $testsPath -Recurse -Force + } +} \ No newline at end of file From dcb5d6e6bd13aa46c1c5034670931c2a8e546649 Mon Sep 17 00:00:00 2001 From: Raghu Shantha Date: Tue, 5 May 2015 16:38:21 -0700 Subject: [PATCH 46/57] Tests for [AvoidUsingWMICmdlet Rule --- Tests/Rules/AvoidUsingWMICmdlet.ps1 | 18 ++++++++++++++ Tests/Rules/AvoidUsingWMICmdlet.tests.ps1 | 24 +++++++++++++++++++ .../Rules/AvoidUsingWMICmdletNoViolations.ps1 | 19 +++++++++++++++ Tests/Rules/AvoidUsingWMIObjectCmdlet.ps1 | 13 ---------- .../Rules/AvoidUsingWMIObjectCmdlet.tests.ps1 | 24 ------------------- .../AvoidUsingWMIObjectCmdletNoViolations.ps1 | 14 ----------- 6 files changed, 61 insertions(+), 51 deletions(-) create mode 100644 Tests/Rules/AvoidUsingWMICmdlet.ps1 create mode 100644 Tests/Rules/AvoidUsingWMICmdlet.tests.ps1 create mode 100644 Tests/Rules/AvoidUsingWMICmdletNoViolations.ps1 delete mode 100644 Tests/Rules/AvoidUsingWMIObjectCmdlet.ps1 delete mode 100644 Tests/Rules/AvoidUsingWMIObjectCmdlet.tests.ps1 delete mode 100644 Tests/Rules/AvoidUsingWMIObjectCmdletNoViolations.ps1 diff --git a/Tests/Rules/AvoidUsingWMICmdlet.ps1 b/Tests/Rules/AvoidUsingWMICmdlet.ps1 new file mode 100644 index 000000000..81c03f64d --- /dev/null +++ b/Tests/Rules/AvoidUsingWMICmdlet.ps1 @@ -0,0 +1,18 @@ +#Script violates the rule because Get-CIMInstance is available on PS 3.0 and needs to use that + +#requires -version 3.0 + +function TestFunction +{ + Get-WmiObject -Class Win32_ComputerSystem + + Invoke-WMIMethod -Path Win32_Process -Name Create -ArgumentList notepad.exe + + Register-WMIEvent -Class Win32_ProcessStartTrace -SourceIdentifier "ProcessStarted" + + Set-WMIInstance -Class Win32_Environment -Argument @{Name='MyEnvVar';VariableValue='VarValue';UserName=''} +} + +TestFunction + +Remove-WmiObject -Class Win32_OperatingSystem -Verbose \ No newline at end of file diff --git a/Tests/Rules/AvoidUsingWMICmdlet.tests.ps1 b/Tests/Rules/AvoidUsingWMICmdlet.tests.ps1 new file mode 100644 index 000000000..b8c7a6e44 --- /dev/null +++ b/Tests/Rules/AvoidUsingWMICmdlet.tests.ps1 @@ -0,0 +1,24 @@ +Import-Module PSScriptAnalyzer +$WMIRuleName = "PSAvoidUsingWMICmdlet" +$violationMessage = "File 'AvoidUsingWMICmdlet.ps1' 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." +$directory = Split-Path -Parent $MyInvocation.MyCommand.Path +$violations = Invoke-ScriptAnalyzer $directory\AvoidUsingWMICmdlet.ps1 -IncludeRule $WMIRuleName +$noViolations = Invoke-ScriptAnalyzer $directory\AvoidUsingWMICmdletNoViolations.ps1 -IncludeRule $WMIRuleName + +Describe "AvoidUsingWMICmdlet" { + Context "Script contains references to WMI cmdlets - Violation" { + It "Have 5 WMI cmdlet Violations" { + $violations.Count | Should Be 5 + } + + It "has the correct description message for WMI rule violation" { + $violations[0].Message | Should Be $violationMessage + } + } + + Context "Script contains no calls to WMI cmdlet - No violation" { + It "results in no rule violations" { + $noViolations.Count | Should Be 0 + } + } +} \ No newline at end of file diff --git a/Tests/Rules/AvoidUsingWMICmdletNoViolations.ps1 b/Tests/Rules/AvoidUsingWMICmdletNoViolations.ps1 new file mode 100644 index 000000000..51809547a --- /dev/null +++ b/Tests/Rules/AvoidUsingWMICmdletNoViolations.ps1 @@ -0,0 +1,19 @@ +# No Rule violations since this script requires PS 2.0 and Get-CIMInstance is not available for this version +# So using Get-WMIObject is OK + +#requires -Version 2.0 + +Invoke-WMIMethod -Path Win32_Process -Name Create -ArgumentList notepad.exe + +function TestFunction +{ + Get-WmiObject -Class Win32_ComputerSystem + + Register-WMIEvent -Class Win32_ProcessStartTrace -SourceIdentifier "ProcessStarted" + + Set-WMIInstance -Class Win32_Environment -Argument @{Name='MyEnvVar';VariableValue='VarValue';UserName=''} +} + +TestFunction + +Remove-WmiObject -Class Win32_OperatingSystem -Verbose \ No newline at end of file diff --git a/Tests/Rules/AvoidUsingWMIObjectCmdlet.ps1 b/Tests/Rules/AvoidUsingWMIObjectCmdlet.ps1 deleted file mode 100644 index 13f0412c9..000000000 --- a/Tests/Rules/AvoidUsingWMIObjectCmdlet.ps1 +++ /dev/null @@ -1,13 +0,0 @@ -#Script violates the rule because Get-CIMInstance is available on PS 3.0 and needs to use that - -#requires -version 3.0 - -function TestFunction -{ - Get-WmiObject -Class Win32_ComputerSystem - -} - -TestFunction - -Remove-WmiObject -Class Win32_OperatingSystem -Verbose \ No newline at end of file diff --git a/Tests/Rules/AvoidUsingWMIObjectCmdlet.tests.ps1 b/Tests/Rules/AvoidUsingWMIObjectCmdlet.tests.ps1 deleted file mode 100644 index 0a96ad16e..000000000 --- a/Tests/Rules/AvoidUsingWMIObjectCmdlet.tests.ps1 +++ /dev/null @@ -1,24 +0,0 @@ -Import-Module PSScriptAnalyzer -$wmiObjectRuleName = "PSAvoidUsingWMIObjectCmdlet" -$violationMessage = "File 'AvoidUsingWMIObjectCmdlet.ps1' uses WMIObject cmdlet. For PowerShell 3.0 and above, this is not recommended because the cmdlet is based on a non-standard DCOM protocol. Use CIMInstance cmdlet instead. This is CIM and WS-Man standards compliant and works in a heterogeneous environment." -$directory = Split-Path -Parent $MyInvocation.MyCommand.Path -$violations = Invoke-ScriptAnalyzer $directory\AvoidUsingWMIObjectCmdlet.ps1 | Where-Object {$_.RuleName -eq $wmiObjectRuleName} -$noViolations = Invoke-ScriptAnalyzer $directory\AvoidUsingWMIObjectCmdletNoViolations.ps1 | Where-Object {$_.RuleName -eq $wmiObjectRuleName} - -Describe "AvoidUsingWMIObjectCmdlet" { - Context "Script contains references to WMIObject cmdlets - Violation" { - It "Have 2 WMIObject cmdlet Violations" { - $violations.Count | Should Be 2 - } - - It "has the correct description message for WMIObject rule violation" { - $violations[0].Message | Should Match $violationMessage - } - } - - Context "Script contains no calls to WMIObject cmdlet - No violation" { - It "results in no rule violations" { - $noViolations.Count | Should Be 0 - } - } -} \ No newline at end of file diff --git a/Tests/Rules/AvoidUsingWMIObjectCmdletNoViolations.ps1 b/Tests/Rules/AvoidUsingWMIObjectCmdletNoViolations.ps1 deleted file mode 100644 index b5d2e6f14..000000000 --- a/Tests/Rules/AvoidUsingWMIObjectCmdletNoViolations.ps1 +++ /dev/null @@ -1,14 +0,0 @@ -# No Rule violations since this script requires PS 2.0 and Get-CIMInstance is not available for this version -# So using Get-WMIObject is OK - -#requires -Version 2.0 - -function TestFunction -{ - Remove-WmiObject -Class Win32_ComputerSystem - -} - -TestFunction - -Get-WmiObject -Class Win32_OperatingSystem -Verbose \ No newline at end of file From 4238f90be93df2798ab4b59d24f09510ae090935 Mon Sep 17 00:00:00 2001 From: GoodOlClint Date: Tue, 5 May 2015 19:34:48 -0500 Subject: [PATCH 47/57] Do not require Write-Verbose in scripts or functions without the CmdletBinding attribute specified. --- Rules/ProvideVerboseMessage.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Rules/ProvideVerboseMessage.cs b/Rules/ProvideVerboseMessage.cs index a69447c0e..af1c6fa10 100644 --- a/Rules/ProvideVerboseMessage.cs +++ b/Rules/ProvideVerboseMessage.cs @@ -12,10 +12,12 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Management.Automation.Language; using Microsoft.Windows.Powershell.ScriptAnalyzer.Generic; using System.ComponentModel.Composition; using System.Globalization; +using System.Management.Automation; namespace Microsoft.Windows.Powershell.ScriptAnalyzer.BuiltinRules { @@ -33,11 +35,11 @@ public class ProvideVerboseMessage : SkipNamedBlock, IScriptRule public IEnumerable AnalyzeScript(Ast ast, string fileName) { if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage); - + ClearList(); this.AddNames(new List() { "Configuration", "Workflow" }); DiagnosticRecords.Clear(); - + this.fileName = fileName; //We only check that advanced functions should have Write-Verbose ast.Visit(this); @@ -57,6 +59,17 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun return AstVisitAction.SkipChildren; } + //Write-Verbose is not required for non-advanced functions + if (funcAst.Body != null && funcAst.Body.ParamBlock != null + && funcAst.Body.ParamBlock.Attributes != null && + funcAst.Body.ParamBlock.Parameters != null) + { + if (!funcAst.Body.ParamBlock.Attributes.Any(attr => attr.TypeName.GetReflectionType() == typeof(CmdletBindingAttribute))) + { + return AstVisitAction.Continue; + } + } + var commandAsts = funcAst.Body.FindAll(testAst => testAst is CommandAst, false); bool hasVerbose = false; From 4f8e7a61865dcef1890f035c7ff18bcf022fd0a0 Mon Sep 17 00:00:00 2001 From: "Yuting Chen[MSFT]" Date: Wed, 6 May 2015 09:47:43 -0700 Subject: [PATCH 48/57] Revert "Only Trigger PSProvideVerboseMessage in Advanced Scripts or Functions" --- Rules/ProvideVerboseMessage.cs | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/Rules/ProvideVerboseMessage.cs b/Rules/ProvideVerboseMessage.cs index af1c6fa10..a69447c0e 100644 --- a/Rules/ProvideVerboseMessage.cs +++ b/Rules/ProvideVerboseMessage.cs @@ -12,12 +12,10 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Management.Automation.Language; using Microsoft.Windows.Powershell.ScriptAnalyzer.Generic; using System.ComponentModel.Composition; using System.Globalization; -using System.Management.Automation; namespace Microsoft.Windows.Powershell.ScriptAnalyzer.BuiltinRules { @@ -35,11 +33,11 @@ public class ProvideVerboseMessage : SkipNamedBlock, IScriptRule public IEnumerable AnalyzeScript(Ast ast, string fileName) { if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage); - + ClearList(); this.AddNames(new List() { "Configuration", "Workflow" }); DiagnosticRecords.Clear(); - + this.fileName = fileName; //We only check that advanced functions should have Write-Verbose ast.Visit(this); @@ -59,17 +57,6 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun return AstVisitAction.SkipChildren; } - //Write-Verbose is not required for non-advanced functions - if (funcAst.Body != null && funcAst.Body.ParamBlock != null - && funcAst.Body.ParamBlock.Attributes != null && - funcAst.Body.ParamBlock.Parameters != null) - { - if (!funcAst.Body.ParamBlock.Attributes.Any(attr => attr.TypeName.GetReflectionType() == typeof(CmdletBindingAttribute))) - { - return AstVisitAction.Continue; - } - } - var commandAsts = funcAst.Body.FindAll(testAst => testAst is CommandAst, false); bool hasVerbose = false; From 3aa8ee84b9dfc80e23a64128050fa29010a75b66 Mon Sep 17 00:00:00 2001 From: "Yuting Chen[MSFT]" Date: Wed, 6 May 2015 09:53:43 -0700 Subject: [PATCH 49/57] =?UTF-8?q?Revert=20"Revert=20"Only=20Trigger=20PSPr?= =?UTF-8?q?ovideVerboseMessage=20in=20Advanced=20Scripts=20or=20F=E2=80=A6?= =?UTF-8?q?"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Rules/ProvideVerboseMessage.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Rules/ProvideVerboseMessage.cs b/Rules/ProvideVerboseMessage.cs index a69447c0e..af1c6fa10 100644 --- a/Rules/ProvideVerboseMessage.cs +++ b/Rules/ProvideVerboseMessage.cs @@ -12,10 +12,12 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Management.Automation.Language; using Microsoft.Windows.Powershell.ScriptAnalyzer.Generic; using System.ComponentModel.Composition; using System.Globalization; +using System.Management.Automation; namespace Microsoft.Windows.Powershell.ScriptAnalyzer.BuiltinRules { @@ -33,11 +35,11 @@ public class ProvideVerboseMessage : SkipNamedBlock, IScriptRule public IEnumerable AnalyzeScript(Ast ast, string fileName) { if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage); - + ClearList(); this.AddNames(new List() { "Configuration", "Workflow" }); DiagnosticRecords.Clear(); - + this.fileName = fileName; //We only check that advanced functions should have Write-Verbose ast.Visit(this); @@ -57,6 +59,17 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun return AstVisitAction.SkipChildren; } + //Write-Verbose is not required for non-advanced functions + if (funcAst.Body != null && funcAst.Body.ParamBlock != null + && funcAst.Body.ParamBlock.Attributes != null && + funcAst.Body.ParamBlock.Parameters != null) + { + if (!funcAst.Body.ParamBlock.Attributes.Any(attr => attr.TypeName.GetReflectionType() == typeof(CmdletBindingAttribute))) + { + return AstVisitAction.Continue; + } + } + var commandAsts = funcAst.Body.FindAll(testAst => testAst is CommandAst, false); bool hasVerbose = false; From f3b55e60c26f6b1144584c94f4ff3a6c13c1f89f Mon Sep 17 00:00:00 2001 From: Raghu Shantha Date: Wed, 6 May 2015 10:22:01 -0700 Subject: [PATCH 50/57] Rule Documentation for WMI Cmdlet --- RuleDocumentation/AvoidUsingWMICmdlet.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 RuleDocumentation/AvoidUsingWMICmdlet.md diff --git a/RuleDocumentation/AvoidUsingWMICmdlet.md b/RuleDocumentation/AvoidUsingWMICmdlet.md new file mode 100644 index 000000000..b5b308076 --- /dev/null +++ b/RuleDocumentation/AvoidUsingWMICmdlet.md @@ -0,0 +1,17 @@ +#AvoidAlias +**Severity Level: Warning** + + +##Description + +An alias is an alternate name or nickname for a cmdlet or for a command element, such as a function, script, file, or executable file. But when writing scripts that will potentially need to be maintained over time, either by the original author or another Windows PowerShell scripter, please consider using full cmdlet name instead of alias. Aliases can introduce these problems, readability, understandability and availability. + +##How to Fix + +Please consider using full cmdlet name instead of alias. + +##Example + +Wrong: gps | Where-Object {$_.WorkingSet -gt 20000000} + +Correct: Get-Process | Where-Object {$_.WorkingSet -gt 20000000} From 72858d2f63a134e95cd3a571489856dddc39b1b8 Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Wed, 6 May 2015 10:32:01 -0700 Subject: [PATCH 51/57] Update AvoidUsingWMICmdlet.md --- RuleDocumentation/AvoidUsingWMICmdlet.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/RuleDocumentation/AvoidUsingWMICmdlet.md b/RuleDocumentation/AvoidUsingWMICmdlet.md index b5b308076..6065d1083 100644 --- a/RuleDocumentation/AvoidUsingWMICmdlet.md +++ b/RuleDocumentation/AvoidUsingWMICmdlet.md @@ -1,17 +1,18 @@ -#AvoidAlias +#AvoidUsingWMICmdlet **Severity Level: Warning** ##Description -An alias is an alternate name or nickname for a cmdlet or for a command element, such as a function, script, file, or executable file. But when writing scripts that will potentially need to be maintained over time, either by the original author or another Windows PowerShell scripter, please consider using full cmdlet name instead of alias. Aliases can introduce these problems, readability, understandability and availability. +Avoid Using Get-WMIObject, Remove-WMIObject, Invoke-WmiMethod, Register-WmiEvent, Set-WmiInstance + +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. ##How to Fix -Please consider using full cmdlet name instead of alias. +Use corresponding CIM cmdlets such as Get-CIMInstance, Remove-CIMInstance, Invoke-CIMMethod, Register-CimIndicationEvent, Set-CimInstance ##Example -Wrong: gps | Where-Object {$_.WorkingSet -gt 20000000} - -Correct: Get-Process | Where-Object {$_.WorkingSet -gt 20000000} +Get-CimInstance -Query 'Select * from Win32_Process where name LIKE "myprocess%"' | Remove-CIMInstance +Invoke-CimMethod –ClassName Win32_Process –MethodName "Create" –Arguments @{ CommandLine = "notepad.exe" } From 6f2a57b7bd46442d52bc2f99e9dcd86aa786b8a2 Mon Sep 17 00:00:00 2001 From: "Raghu Shantha [MSFT]" Date: Wed, 6 May 2015 11:18:18 -0700 Subject: [PATCH 52/57] Update AvoidUsingWMICmdlet.md --- RuleDocumentation/AvoidUsingWMICmdlet.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/RuleDocumentation/AvoidUsingWMICmdlet.md b/RuleDocumentation/AvoidUsingWMICmdlet.md index 6065d1083..2171c4a62 100644 --- a/RuleDocumentation/AvoidUsingWMICmdlet.md +++ b/RuleDocumentation/AvoidUsingWMICmdlet.md @@ -14,5 +14,10 @@ Use corresponding CIM cmdlets such as Get-CIMInstance, Remove-CIMInstance, Invok ##Example +Wrong: +Get-WmiObject -Query 'Select * from Win32_Process where name LIKE "myprocess%"' | Remove-WmiObject +Invoke-WmiMethod –Class Win32_Process –Name "Create" –ArgumentList @{ CommandLine = "notepad.exe" } + +Correct: Get-CimInstance -Query 'Select * from Win32_Process where name LIKE "myprocess%"' | Remove-CIMInstance Invoke-CimMethod –ClassName Win32_Process –MethodName "Create" –Arguments @{ CommandLine = "notepad.exe" } From 5e7d2f61a9294f30775e3a3591d074f026851860 Mon Sep 17 00:00:00 2001 From: "Yuting Chen[MSFT]" Date: Wed, 6 May 2015 11:21:20 -0700 Subject: [PATCH 53/57] Update AvoidUsingWMICmdlet.md Added quotes for code --- RuleDocumentation/AvoidUsingWMICmdlet.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/RuleDocumentation/AvoidUsingWMICmdlet.md b/RuleDocumentation/AvoidUsingWMICmdlet.md index 2171c4a62..01d6cca01 100644 --- a/RuleDocumentation/AvoidUsingWMICmdlet.md +++ b/RuleDocumentation/AvoidUsingWMICmdlet.md @@ -15,9 +15,12 @@ Use corresponding CIM cmdlets such as Get-CIMInstance, Remove-CIMInstance, Invok ##Example Wrong: +``` Get-WmiObject -Query 'Select * from Win32_Process where name LIKE "myprocess%"' | Remove-WmiObject Invoke-WmiMethod –Class Win32_Process –Name "Create" –ArgumentList @{ CommandLine = "notepad.exe" } - +``` Correct: +``` Get-CimInstance -Query 'Select * from Win32_Process where name LIKE "myprocess%"' | Remove-CIMInstance Invoke-CimMethod –ClassName Win32_Process –MethodName "Create" –Arguments @{ CommandLine = "notepad.exe" } +``` From 87301516a8f35e41e694184a18a9f008b6e8fd51 Mon Sep 17 00:00:00 2001 From: KarolKaczmarek Date: Wed, 6 May 2015 11:48:16 -0700 Subject: [PATCH 54/57] Create documentation for DscTestsPresent rule --- RuleDocumentation/DscTestsPresent.md | 54 ++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 RuleDocumentation/DscTestsPresent.md diff --git a/RuleDocumentation/DscTestsPresent.md b/RuleDocumentation/DscTestsPresent.md new file mode 100644 index 000000000..0ed054337 --- /dev/null +++ b/RuleDocumentation/DscTestsPresent.md @@ -0,0 +1,54 @@ +#DscTestsPresent +**Severity Level: Information** + + +##Description + +Checks that DSC tests for given resource are present. + +##How to Fix + +To fix a violation of this rule, please make sure Tests directory is present: +* For non-class based resources it should exist at the same folder level as DSCResources folder. +* For class based resources it should be present at the same folder level as resource psm1 file. + +Tests folder should contain test script for given resource - file name should contain resource's name. + +##Example + +### Non-class based resource + +Let's assume we have non-class based resource with a following file structure: + +* xAzure + * DSCResources + * MSFT_xAzureSubscription + * MSFT_xAzureSubscription.psm1 + * MSFT_xAzureSubscription.schema.mof + +In this case, to fix this warning, we should add tests in a following way: + +* xAzure + * DSCResources + * MSFT_xAzureSubscription + * MSFT_xAzureSubscription.psm1 + * MSFT_xAzureSubscription.schema.mof + * Tests + * MSFT_xAzureSubscription_Tests.ps1 + +### Class based resource + +Let's assume we have class based resource with a following file structure: + +* MyDscResource + * MyDscResource.psm1 + * MyDscresource.psd1 + +In this case, to fix this warning, we should add tests in a following way: + +* MyDscResource + * MyDscResource.psm1 + * MyDscresource.psd1 + * Tests + * MyDscResource_Tests.ps1 + From a29203d5c8e038b534817ef83012b92697c5e90b Mon Sep 17 00:00:00 2001 From: "Yuting Chen[MSFT]" Date: Wed, 6 May 2015 11:51:19 -0700 Subject: [PATCH 55/57] =?UTF-8?q?Revert=20"Revert=20"Revert=20"Only=20Trig?= =?UTF-8?q?ger=20PSProvideVerboseMessage=20in=20Advanced=20Scri=E2=80=A6"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Rules/ProvideVerboseMessage.cs | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/Rules/ProvideVerboseMessage.cs b/Rules/ProvideVerboseMessage.cs index af1c6fa10..a69447c0e 100644 --- a/Rules/ProvideVerboseMessage.cs +++ b/Rules/ProvideVerboseMessage.cs @@ -12,12 +12,10 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Management.Automation.Language; using Microsoft.Windows.Powershell.ScriptAnalyzer.Generic; using System.ComponentModel.Composition; using System.Globalization; -using System.Management.Automation; namespace Microsoft.Windows.Powershell.ScriptAnalyzer.BuiltinRules { @@ -35,11 +33,11 @@ public class ProvideVerboseMessage : SkipNamedBlock, IScriptRule public IEnumerable AnalyzeScript(Ast ast, string fileName) { if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage); - + ClearList(); this.AddNames(new List() { "Configuration", "Workflow" }); DiagnosticRecords.Clear(); - + this.fileName = fileName; //We only check that advanced functions should have Write-Verbose ast.Visit(this); @@ -59,17 +57,6 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun return AstVisitAction.SkipChildren; } - //Write-Verbose is not required for non-advanced functions - if (funcAst.Body != null && funcAst.Body.ParamBlock != null - && funcAst.Body.ParamBlock.Attributes != null && - funcAst.Body.ParamBlock.Parameters != null) - { - if (!funcAst.Body.ParamBlock.Attributes.Any(attr => attr.TypeName.GetReflectionType() == typeof(CmdletBindingAttribute))) - { - return AstVisitAction.Continue; - } - } - var commandAsts = funcAst.Body.FindAll(testAst => testAst is CommandAst, false); bool hasVerbose = false; From 8a6e39499382043e13caf41432846d7f765f0f71 Mon Sep 17 00:00:00 2001 From: Karol Kaczmarek Date: Wed, 6 May 2015 14:14:27 -0700 Subject: [PATCH 56/57] Fixing tests --- Tests/Rules/DscTestsPresent.tests.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Rules/DscTestsPresent.tests.ps1 b/Tests/Rules/DscTestsPresent.tests.ps1 index f7a866cf4..03fbeaa66 100644 --- a/Tests/Rules/DscTestsPresent.tests.ps1 +++ b/Tests/Rules/DscTestsPresent.tests.ps1 @@ -18,7 +18,7 @@ Describe "DscTestsPresent rule in class based resource" { } It "has the correct description message" { - $violations[0].Message | Should Match $violationMessage + $violations[0].Message | Should Be $violationMessage } } @@ -51,7 +51,7 @@ Describe "DscTestsPresent rule in regular (non-class) based resource" { } It "has the correct description message" { - $violations[0].Message | Should Match $violationMessage + $violations[0].Message | Should Be $violationMessage } } From e99e88d33027cf8c0396d336a884219b2885313e Mon Sep 17 00:00:00 2001 From: Raghu Shantha Date: Wed, 6 May 2015 15:22:05 -0700 Subject: [PATCH 57/57] Regenerated strings.designer since the existing rule name (WMI) was changed --- Rules/Strings.Designer.cs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Rules/Strings.Designer.cs b/Rules/Strings.Designer.cs index 1ec732308..6c1285181 100644 --- a/Rules/Strings.Designer.cs +++ b/Rules/Strings.Designer.cs @@ -718,38 +718,38 @@ internal static string AvoidUsingPositionalParametersName { } /// - /// Looks up a localized string similar to Avoid Using Get-WMIObject, Remove-WMIObject. + /// Looks up a localized string similar to Avoid Using Get-WMIObject, Remove-WMIObject, Invoke-WmiMethod, Register-WmiEvent, Set-WmiInstance. /// - internal static string AvoidUsingWMIObjectCmdletCommonName { + internal static string AvoidUsingWMICmdletCommonName { get { - return ResourceManager.GetString("AvoidUsingWMIObjectCmdletCommonName", resourceCulture); + return ResourceManager.GetString("AvoidUsingWMICmdletCommonName", resourceCulture); } } /// - /// Looks up a localized string similar to Depricated. Starting in Windows PowerShell 3.0, these cmdlets have been superseded by CimInstance cmdlets.. + /// Looks up a localized string similar to Depricated. Starting in Windows PowerShell 3.0, these cmdlets have been superseded by CIM cmdlets.. /// - internal static string AvoidUsingWMIObjectCmdletDescription { + internal static string AvoidUsingWMICmdletDescription { get { - return ResourceManager.GetString("AvoidUsingWMIObjectCmdletDescription", resourceCulture); + return ResourceManager.GetString("AvoidUsingWMICmdletDescription", resourceCulture); } } /// - /// Looks up a localized string similar to File '{0}' uses WMIObject cmdlet. For PowerShell 3.0 and above, this is not recommended because the cmdlet is based on a non-standard DCOM protocol. Use CIMInstance cmdlet instead. This is CIM and WS-Man standards compliant and works in a heterogeneous environment.. + /// Looks up a localized string similar to File '{0}' 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 AvoidUsingWMIObjectCmdletError { + internal static string AvoidUsingWMICmdletError { get { - return ResourceManager.GetString("AvoidUsingWMIObjectCmdletError", resourceCulture); + return ResourceManager.GetString("AvoidUsingWMICmdletError", resourceCulture); } } /// - /// Looks up a localized string similar to AvoidUsingWMIObjectCmdlet. + /// Looks up a localized string similar to AvoidUsingWMICmdlet. /// - internal static string AvoidUsingWMIObjectCmdletName { + internal static string AvoidUsingWMICmdletName { get { - return ResourceManager.GetString("AvoidUsingWMIObjectCmdletName", resourceCulture); + return ResourceManager.GetString("AvoidUsingWMICmdletName", resourceCulture); } }