From 6cb8120eaefff825623314f25db7c91ab68c5377 Mon Sep 17 00:00:00 2001 From: Raghu Shantha Date: Tue, 5 May 2015 15:22:10 -0700 Subject: [PATCH 1/3] 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 2/3] 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 dcb5d6e6bd13aa46c1c5034670931c2a8e546649 Mon Sep 17 00:00:00 2001 From: Raghu Shantha Date: Tue, 5 May 2015 16:38:21 -0700 Subject: [PATCH 3/3] 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