Skip to content

Commit 6e831b1

Browse files
committed
Rule for presence of deprecated WMIObject cmdlets
1 parent 2545bfa commit 6e831b1

7 files changed

+228
-1
lines changed

Rules/AvoidUsingWMIObjectCmdlet.cs

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
//
2+
// Copyright (c) Microsoft Corporation.
3+
//
4+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10+
// THE SOFTWARE.
11+
//
12+
13+
using System;
14+
using System.Collections.ObjectModel;
15+
using System.Collections.Generic;
16+
using System.Linq;
17+
using System.Text;
18+
using System.Threading.Tasks;
19+
using System.Management.Automation;
20+
using System.Management.Automation.Language;
21+
using Microsoft.Windows.Powershell.ScriptAnalyzer.Generic;
22+
using System.ComponentModel.Composition;
23+
using System.Resources;
24+
using System.Globalization;
25+
using System.Threading;
26+
using System.Reflection;
27+
28+
namespace Microsoft.Windows.Powershell.ScriptAnalyzer.BuiltinRules
29+
{
30+
/// <summary>
31+
/// AvoidUsingWMIObjectCmdlet: Verify that Get-WMIObject, Remove-WMIObject are not used
32+
/// </summary>
33+
[Export(typeof(IScriptRule))]
34+
public class AvoidUsingWMIObjectCmdlet : IScriptRule
35+
{
36+
/// <summary>
37+
/// AnalyzeScript: Verify that Get-WMIObject, Remove-WMIObject are not used
38+
/// </summary>
39+
public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
40+
{
41+
if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage);
42+
43+
// Rule is applicable only when PowerShell Version is < 3.0, since Get-CIMInstance was introduced in 3.0
44+
int majorPSVersion = GetPSMajorVersion(ast);
45+
if (!(3 > majorPSVersion && 0 < majorPSVersion))
46+
{
47+
// Finds all CommandAsts.
48+
IEnumerable<Ast> commandAsts = ast.FindAll(testAst => testAst is CommandAst, true);
49+
50+
// Iterate all CommandAsts and check the command name
51+
foreach (CommandAst cmdAst in commandAsts)
52+
{
53+
if (cmdAst.GetCommandName() != null && (String.Equals(cmdAst.GetCommandName(), "get-wmiobject", StringComparison.OrdinalIgnoreCase) || String.Equals(cmdAst.GetCommandName(), "remove-wmiobject", StringComparison.OrdinalIgnoreCase)))
54+
{
55+
yield return new DiagnosticRecord(String.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingWMIObjectCmdletError, System.IO.Path.GetFileName(fileName)),
56+
cmdAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName);
57+
}
58+
}
59+
}
60+
}
61+
62+
/// <summary>
63+
/// GetPSMajorVersion: Retrieves Major PowerShell Version when supplied using #requires keyword in the script
64+
/// </summary>
65+
/// <returns>The name of this rule</returns>
66+
private int GetPSMajorVersion(Ast ast)
67+
{
68+
if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage);
69+
70+
IEnumerable<Ast> scriptBlockAsts = ast.FindAll(testAst => testAst is ScriptBlockAst, true);
71+
72+
foreach (ScriptBlockAst scriptBlockAst in scriptBlockAsts)
73+
{
74+
if (null != scriptBlockAst.ScriptRequirements && null != scriptBlockAst.ScriptRequirements.RequiredPSVersion)
75+
{
76+
return scriptBlockAst.ScriptRequirements.RequiredPSVersion.Major;
77+
}
78+
}
79+
80+
// return a non valid Major version if #requires -Version is not supplied in the Script
81+
return -1;
82+
}
83+
84+
/// <summary>
85+
/// GetName: Retrieves the name of this rule.
86+
/// </summary>
87+
/// <returns>The name of this rule</returns>
88+
public string GetName()
89+
{
90+
return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.AvoidUsingWMIObjectCmdletName);
91+
}
92+
93+
/// <summary>
94+
/// GetCommonName: Retrieves the common name of this rule.
95+
/// </summary>
96+
/// <returns>The common name of this rule</returns>
97+
public string GetCommonName()
98+
{
99+
return string.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingWMIObjectCmdletCommonName);
100+
}
101+
102+
/// <summary>
103+
/// GetDescription: Retrieves the description of this rule.
104+
/// </summary>
105+
/// <returns>The description of this rule</returns>
106+
public string GetDescription()
107+
{
108+
return string.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingWMIObjectCmdletDescription);
109+
}
110+
111+
/// <summary>
112+
/// GetSourceType: Retrieves the type of the rule: builtin, managed or module.
113+
/// </summary>
114+
public SourceType GetSourceType()
115+
{
116+
return SourceType.Builtin;
117+
}
118+
119+
/// <summary>
120+
/// GetSourceName: Retrieves the module/assembly name the rule is from.
121+
/// </summary>
122+
public string GetSourceName()
123+
{
124+
return string.Format(CultureInfo.CurrentCulture, Strings.SourceName);
125+
}
126+
}
127+
}

Rules/ScriptAnalyzerBuiltinRules.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
<Compile Include="AvoidUsingInternalURLs.cs" />
6767
<Compile Include="AvoidUsingInvokeExpression.cs" />
6868
<Compile Include="AvoidUsingPlainTextForPassword.cs" />
69+
<Compile Include="AvoidUsingWMIObjectCmdlet.cs" />
6970
<Compile Include="AvoidUsingWriteHost.cs" />
7071
<Compile Include="MissingModuleManifestField.cs" />
7172
<Compile Include="PossibleIncorrectComparisonWithNull.cs" />

Rules/Strings.Designer.cs

Lines changed: 37 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Rules/Strings.resx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,4 +666,16 @@
666666
<data name="UseShouldProcessForStateChangingFunctionsName" xml:space="preserve">
667667
<value>UseShouldProcessForStateChangingFunctions</value>
668668
</data>
669+
<data name="AvoidUsingWMIObjectCmdletCommonName" xml:space="preserve">
670+
<value>Avoid Using Get-WMIObject, Remove-WMIObject</value>
671+
</data>
672+
<data name="AvoidUsingWMIObjectCmdletDescription" xml:space="preserve">
673+
<value>Depricated. Starting in Windows PowerShell 3.0, these cmdlets have been superseded by CimInstance cmdlets.</value>
674+
</data>
675+
<data name="AvoidUsingWMIObjectCmdletError" xml:space="preserve">
676+
<value>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.</value>
677+
</data>
678+
<data name="AvoidUsingWMIObjectCmdletName" xml:space="preserve">
679+
<value>AvoidUsingWMIObjectCmdlet</value>
680+
</data>
669681
</root>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#Script violates the rule because Get-CIMInstance is available on PS 3.0 and needs to use that
2+
3+
#requires -version 3.0
4+
5+
function TestFunction
6+
{
7+
Get-WmiObject -Class Win32_ComputerSystem
8+
9+
}
10+
11+
TestFunction
12+
13+
Remove-WmiObject -Class Win32_OperatingSystem -Verbose
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
Import-Module PSScriptAnalyzer
2+
$wmiObjectRuleName = "PSAvoidUsingWMIObjectCmdlet"
3+
$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."
4+
$directory = Split-Path -Parent $MyInvocation.MyCommand.Path
5+
$violations = Invoke-ScriptAnalyzer $directory\AvoidUsingWMIObjectCmdlet.ps1 | Where-Object {$_.RuleName -eq $wmiObjectRuleName}
6+
$noViolations = Invoke-ScriptAnalyzer $directory\AvoidUsingWMIObjectCmdletNoViolations.ps1 | Where-Object {$_.RuleName -eq $wmiObjectRuleName}
7+
8+
Describe "AvoidUsingWMIObjectCmdlet" {
9+
Context "Script contains references to WMIObject cmdlets - Violation" {
10+
It "Have 2 WMIObject cmdlet Violations" {
11+
$violations.Count | Should Be 2
12+
}
13+
14+
It "has the correct description message for WMIObject rule violation" {
15+
$violations[0].Message | Should Match $violationMessage
16+
}
17+
}
18+
19+
Context "Script contains no calls to WMIObject cmdlet - No violation" {
20+
It "results in no rule violations" {
21+
$noViolations.Count | Should Be 0
22+
}
23+
}
24+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# No Rule violations since this script requires PS 2.0 and Get-CIMInstance is not available for this version
2+
# So using Get-WMIObject is OK
3+
4+
#requires -Version 2.0
5+
6+
function TestFunction
7+
{
8+
Remove-WmiObject -Class Win32_ComputerSystem
9+
10+
}
11+
12+
TestFunction
13+
14+
Get-WmiObject -Class Win32_OperatingSystem -Verbose

0 commit comments

Comments
 (0)