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 diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index 036007ce4..de3e0c23e 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.Name,param.ParameterType.FullName, funcInfo.ModuleName, funcInfo.Module.Path)); } } @@ -289,13 +289,13 @@ 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)) - .GroupBy(item => item.GetParameter()) + .Where(item => item.GetParameter().EndsWith("ast", StringComparison.OrdinalIgnoreCase)) + .GroupBy(item => item.GetParameterType()) .ToDictionary(item => item.Key, item => item.ToList()); Dictionary> tokenRuleGroups = rules - .Where(item => item.GetParameter().EndsWith("token", true, CultureInfo.CurrentCulture)) - .GroupBy(item => item.GetParameter()) + .Where(item => item.GetParameter().EndsWith("token", StringComparison.OrdinalIgnoreCase)) + .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) => - (testAst.GetType().Name.ToLower(CultureInfo.CurrentCulture) == astRuleGroup.Key.ToLower(CultureInfo.CurrentCulture))), false); + (astRuleGroup.Key.IndexOf(testAst.GetType().FullName,StringComparison.OrdinalIgnoreCase) != -1)), false); foreach (Ast childAst in childAsts) { @@ -365,49 +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; - - // 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; - } + // 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]); - // DiagnosticRecord may not be correctly returned from external rule. - try + foreach (var psobject in psobjects) { - 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(); + DiagnosticSeverity severity; + IScriptExtent extent; + string message = string.Empty; + string ruleName = string.Empty; + + 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) + { + 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 } } @@ -478,7 +492,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/RuleDocumentation/AvoidUsingWMICmdlet.md b/RuleDocumentation/AvoidUsingWMICmdlet.md new file mode 100644 index 000000000..01d6cca01 --- /dev/null +++ b/RuleDocumentation/AvoidUsingWMICmdlet.md @@ -0,0 +1,26 @@ +#AvoidUsingWMICmdlet +**Severity Level: Warning** + + +##Description + +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 + +Use corresponding CIM cmdlets such as Get-CIMInstance, Remove-CIMInstance, Invoke-CIMMethod, Register-CimIndicationEvent, Set-CimInstance + +##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" } +``` diff --git a/Rules/AvoidUsingWMIObjectCmdlet.cs b/Rules/AvoidUsingWMICmdlet.cs similarity index 78% rename from Rules/AvoidUsingWMIObjectCmdlet.cs rename to Rules/AvoidUsingWMICmdlet.cs index 6c71f1646..079f0ec14 100644 --- a/Rules/AvoidUsingWMIObjectCmdlet.cs +++ b/Rules/AvoidUsingWMICmdlet.cs @@ -28,19 +28,19 @@ namespace Microsoft.Windows.Powershell.ScriptAnalyzer.BuiltinRules { /// - /// AvoidUsingWMIObjectCmdlet: Verify that Get-WMIObject, Remove-WMIObject are not used + /// AvoidUsingWMICmdlet: Avoid Using Get-WMIObject, Remove-WMIObject, Invoke-WmiMethod, Register-WmiEvent, Set-WmiInstance /// [Export(typeof(IScriptRule))] - public class AvoidUsingWMIObjectCmdlet : IScriptRule + public class AvoidUsingWMICmdlet : IScriptRule { /// - /// AnalyzeScript: Verify that Get-WMIObject, Remove-WMIObject are not used + /// 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 Get-CIMInstance was introduced in 3.0 + // 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)) { @@ -50,9 +50,15 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) // 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))) + 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.AvoidUsingWMIObjectCmdletError, System.IO.Path.GetFileName(fileName)), + yield return new DiagnosticRecord(String.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingWMICmdletError, System.IO.Path.GetFileName(fileName)), cmdAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName); } } @@ -87,7 +93,7 @@ private int GetPSMajorVersion(Ast ast) /// The name of this rule public string GetName() { - return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.AvoidUsingWMIObjectCmdletName); + return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.AvoidUsingWMICmdletName); } /// @@ -96,7 +102,7 @@ public string GetName() /// The common name of this rule public string GetCommonName() { - return string.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingWMIObjectCmdletCommonName); + return string.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingWMICmdletCommonName); } /// @@ -105,7 +111,7 @@ public string GetCommonName() /// The description of this rule public string GetDescription() { - return string.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingWMIObjectCmdletDescription); + return string.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingWMICmdletDescription); } /// 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/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 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 new file mode 100644 index 000000000..cc94c6b1b --- /dev/null +++ b/Tests/Engine/samplerule/samplerule.psm1 @@ -0,0 +1,47 @@ +#Requires -Version 3.0 + +<# +.SYNOPSIS + Uses #Requires -RunAsAdministrator instead of your own methods. +.DESCRIPTION + The #Requires statement prevents a script from running unless the Windows PowerShell version, modules, snap-ins, and module and snap-in version prerequisites are met. + From Windows PowerShell 4.0, the #Requires statement let script developers require that sessions be run with elevated user rights (run as Administrator). + Script developers does not need to write their own methods any more. + To fix a violation of this rule, please consider to use #Requires -RunAsAdministrator instead of your own methods. +.EXAMPLE + Measure-RequiresRunAsAdministrator -ScriptBlockAst $ScriptBlockAst +.INPUTS + [System.Management.Automation.Language.ScriptBlockAst] +.OUTPUTS + [OutputType([PSCustomObject[])] +.NOTES + None +#> +function Measure-RequiresRunAsAdministrator +{ + [CmdletBinding()] + [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])] + Param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.Language.ScriptBlockAst] + $testAst + ) + + + $results = @() + + $result = [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]@{"Message" = "this is help"; + "Extent" = $ast.Extent; + "RuleName" = $PSCmdlet.MyInvocation.InvocationName; + "Severity" = "Warning"} + + $results += $result + + + return $results + + +} +Export-ModuleMember -Function Measure* \ No newline at end of file diff --git a/Tests/Rules/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 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