Skip to content

Commit d7a833e

Browse files
committed
Merge pull request #238 from PowerShell/VerboseMessageForDSCResourceBranch
Rule to validate the presence of Verbose statements in DSC Resource
2 parents 5908771 + 909db52 commit d7a833e

11 files changed

+124
-117
lines changed

Rules/ScriptAnalyzerBuiltinRules.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
<Compile Include="MissingModuleManifestField.cs" />
7676
<Compile Include="PossibleIncorrectComparisonWithNull.cs" />
7777
<Compile Include="ProvideCommentHelp.cs" />
78-
<Compile Include="ProvideVerboseMessage.cs" />
78+
<Compile Include="UseVerboseMessageInDSCResource.cs" />
7979
<Compile Include="Strings.Designer.cs">
8080
<AutoGen>True</AutoGen>
8181
<DesignTime>True</DesignTime>

Rules/Strings.Designer.cs

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

Rules/Strings.resx

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -276,17 +276,14 @@
276276
<data name="SourceName" xml:space="preserve">
277277
<value>PS</value>
278278
</data>
279-
<data name="ProvideVerboseMessageDescription" xml:space="preserve">
280-
<value>Checks that Write-Verbose is called at least once in every cmdlet or script. This is in line with the PowerShell best practices.</value>
279+
<data name="UseVerboseMessageInDSCResourceDescription" xml:space="preserve">
280+
<value>It is a best practice to emit informative, verbose messages in DSC resource functions. This helps in debugging issues when a DSC configuration is executed.</value>
281281
</data>
282-
<data name="ProvideVerboseMessageErrorFunction" xml:space="preserve">
283-
<value>There is no call to Write-Verbose in the function ‘{0}’.</value>
282+
<data name="UseVerboseMessageInDSCResourceErrorFunction" xml:space="preserve">
283+
<value>There is no call to Write-Verbose in DSC function ‘{0}’. If you are using Write-Verbose in a helper function, suppress this rule application.</value>
284284
</data>
285-
<data name="ProvideVerboseMessageCommonName" xml:space="preserve">
286-
<value>Verbose</value>
287-
</data>
288-
<data name="ProvideVerboseMessageScript" xml:space="preserve">
289-
<value>There is no call to Write-Verbose in the script.</value>
285+
<data name="UseVerboseMessageInDSCResourceCommonName" xml:space="preserve">
286+
<value>Use verbose message in DSC resource</value>
290287
</data>
291288
<data name="MissingModuleManifestFieldDescription" xml:space="preserve">
292289
<value>Some fields of the module manifest (such as ModuleVersion) are required.</value>
@@ -459,8 +456,8 @@
459456
<data name="MissingModuleManifestFieldName" xml:space="preserve">
460457
<value>MissingModuleManifestField</value>
461458
</data>
462-
<data name="ProvideVerboseMessageName" xml:space="preserve">
463-
<value>ProvideVerboseMessage</value>
459+
<data name="UseVerboseMessageInDSCResourceName" xml:space="preserve">
460+
<value>UseVerboseMessageInDSCResource</value>
464461
</data>
465462
<data name="CommandNotFoundCommonName" xml:space="preserve">
466463
<value>Command Not Found</value>

Rules/UseDeclaredVarsMoreThanAssignments.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
3535
{
3636
if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage);
3737

38-
IEnumerable<Ast> assignmentAsts = ast.FindAll(testAst => testAst is AssignmentStatementAst, true);
39-
IEnumerable<Ast> assingmentVarAsts;
38+
IEnumerable<Ast> assignmentAsts = ast.FindAll(testAst => testAst is AssignmentStatementAst, true);
4039
IEnumerable<Ast> varAsts = ast.FindAll(testAst => testAst is VariableExpressionAst, true);
4140
IEnumerable<Ast> varsInAssignment;
4241

Rules/ProvideVerboseMessage.cs renamed to Rules/UseVerboseMessageInDSCResource.cs

Lines changed: 39 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -22,78 +22,64 @@
2222
namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules
2323
{
2424
/// <summary>
25-
/// ProvideVerboseMessage: Analyzes the ast to check that Write-Verbose is called at least once in every cmdlet or script.
25+
/// ProvideVerboseMessage: Analyzes the ast to check that Write-Verbose is called for DSC Resources
2626
/// </summary>
27-
[Export(typeof(IScriptRule))]
28-
public class ProvideVerboseMessage : SkipNamedBlock, IScriptRule
27+
[Export(typeof(IDSCResourceRule))]
28+
public class UseVerboseMessageInDSCResource : SkipNamedBlock, IDSCResourceRule
2929
{
3030
/// <summary>
31-
/// AnalyzeScript: Analyzes the ast to check that Write-Verbose is called at least once in every cmdlet or script.
31+
/// AnalyzeDSCResource: Analyzes the ast to check that Write-Verbose is called for DSC Resources
3232
/// <param name="ast">The script's ast</param>
3333
/// <param name="fileName">The script's file name</param>
3434
/// </summary>
35-
public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
35+
public IEnumerable<DiagnosticRecord> AnalyzeDSCResource(Ast ast, string fileName)
3636
{
37-
if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage);
38-
39-
ClearList();
40-
this.AddNames(new List<string>() { "Configuration", "Workflow" });
41-
DiagnosticRecords.Clear();
42-
43-
this.fileName = fileName;
44-
//We only check that advanced functions should have Write-Verbose
45-
ast.Visit(this);
46-
47-
return DiagnosticRecords;
48-
}
49-
50-
/// <summary>
51-
/// Visit function and checks that it has write verbose
52-
/// </summary>
53-
/// <param name="funcAst"></param>
54-
/// <returns></returns>
55-
public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst funcAst)
56-
{
57-
if (funcAst == null)
37+
if (ast == null)
5838
{
59-
return AstVisitAction.SkipChildren;
60-
}
39+
throw new ArgumentNullException(Strings.NullAstErrorMessage);
40+
}
41+
42+
IEnumerable<Ast> functionDefinitionAsts = Helper.Instance.DscResourceFunctions(ast);
6143

62-
//Write-Verbose is not required for non-advanced functions
63-
if (funcAst.Body == null || funcAst.Body.ParamBlock == null
64-
|| funcAst.Body.ParamBlock.Attributes == null ||
65-
funcAst.Body.ParamBlock.Parameters == null ||
66-
!funcAst.Body.ParamBlock.Attributes.Any(attr => attr.TypeName.GetReflectionType() == typeof(CmdletBindingAttribute)))
44+
foreach (FunctionDefinitionAst functionDefinitionAst in functionDefinitionAsts)
6745
{
68-
return AstVisitAction.Continue;
69-
}
46+
var commandAsts = functionDefinitionAst.Body.FindAll(testAst => testAst is CommandAst, false);
47+
bool hasVerbose = false;
7048

71-
var commandAsts = funcAst.Body.FindAll(testAst => testAst is CommandAst, false);
72-
bool hasVerbose = false;
49+
if (null != commandAsts)
50+
{
51+
foreach (CommandAst commandAst in commandAsts)
52+
{
53+
hasVerbose |= String.Equals(commandAst.GetCommandName(), "Write-Verbose", StringComparison.OrdinalIgnoreCase);
54+
}
55+
}
7356

74-
if (commandAsts != null)
75-
{
76-
foreach (CommandAst commandAst in commandAsts)
57+
if (!hasVerbose)
7758
{
78-
hasVerbose |= String.Equals(commandAst.GetCommandName(), "Write-Verbose", StringComparison.OrdinalIgnoreCase);
59+
yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.UseVerboseMessageInDSCResourceErrorFunction, functionDefinitionAst.Name),
60+
functionDefinitionAst.Extent, GetName(), DiagnosticSeverity.Information, fileName);
7961
}
80-
}
8162

82-
if (!hasVerbose)
83-
{
84-
DiagnosticRecords.Add(new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.ProvideVerboseMessageErrorFunction, funcAst.Name),
85-
funcAst.Extent, GetName(), DiagnosticSeverity.Information, fileName));
8663
}
87-
88-
return AstVisitAction.Continue;
64+
}
65+
66+
/// <summary>
67+
/// AnalyzeDSCClass: This function returns nothing in the case of dsc class.
68+
/// </summary>
69+
/// <param name="ast"></param>
70+
/// <param name="fileName"></param>
71+
/// <returns></returns>
72+
public IEnumerable<DiagnosticRecord> AnalyzeDSCClass(Ast ast, string fileName)
73+
{
74+
return Enumerable.Empty<DiagnosticRecord>();
8975
}
9076

9177
/// <summary>
9278
/// Method: Retrieves the name of this rule.
9379
/// </summary>
9480
public string GetName()
9581
{
96-
return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.ProvideVerboseMessageName);
82+
return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.UseVerboseMessageInDSCResourceName);
9783
}
9884

9985
/// <summary>
@@ -102,7 +88,7 @@ public string GetName()
10288
/// <returns>The common name of this rule</returns>
10389
public string GetCommonName()
10490
{
105-
return string.Format(CultureInfo.CurrentCulture, Strings.ProvideVerboseMessageCommonName);
91+
return string.Format(CultureInfo.CurrentCulture, Strings.UseVerboseMessageInDSCResourceCommonName);
10692
}
10793

10894
/// <summary>
@@ -111,7 +97,7 @@ public string GetCommonName()
11197
/// <returns>The description of this rule</returns>
11298
public string GetDescription()
11399
{
114-
return string.Format(CultureInfo.CurrentCulture, Strings.ProvideVerboseMessageDescription);
100+
return string.Format(CultureInfo.CurrentCulture, Strings.UseVerboseMessageInDSCResourceDescription);
115101
}
116102

117103
/// <summary>
@@ -136,7 +122,7 @@ public RuleSeverity GetSeverity()
136122
/// </summary>
137123
public string GetSourceName()
138124
{
139-
return string.Format(CultureInfo.CurrentCulture, Strings.SourceName);
125+
return string.Format(CultureInfo.CurrentCulture, Strings.DSCSourceName);
140126
}
141127
}
142-
}
128+
}

Tests/Engine/GetScriptAnalyzerRule.tests.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,11 @@ Describe "TestSeverity" {
130130
Describe "TestWildCard" {
131131
It "filters rules based on the -Name wild card input" {
132132
$rules = Get-ScriptAnalyzerRule -Name PSDSC*
133-
$rules.Count | Should be 6
133+
$rules.Count | Should be 7
134134
}
135135

136136
It "filters rules based on wild card input and severity"{
137137
$rules = Get-ScriptAnalyzerRule -Name PSDSC* -Severity Information
138-
$rules.Count | Should be 3
138+
$rules.Count | Should be 4
139139
}
140140
}

Tests/Engine/RuleSuppression.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Param(
55

66
function SuppressMe ()
77
{
8-
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSProvideVerboseMessage")]
8+
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSProvideCommentHelp")]
99
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSProvideDefaultParameterValue", "unused1")]
1010
Param([string]$unUsed1, [int] $unUsed2)
1111
{

Tests/Engine/RuleSuppression.tests.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ $violations = Invoke-ScriptAnalyzer $directory\RuleSuppression.ps1
55
Describe "RuleSuppressionWithoutScope" {
66
Context "Function" {
77
It "Does not raise violations" {
8-
$suppression = $violations | Where-Object { $_.RuleName -eq "PSProvideVerboseMessage" }
8+
$suppression = $violations | Where-Object { $_.RuleName -eq "PSProvideCommentHelp" }
99
$suppression.Count | Should Be 0
1010
}
1111
}

Tests/Rules/DSCResources/MSFT_WaitForAny/MSFT_WaitForAny.psm1

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ function Get-TargetResource
2929
[Uint32] $ThrottleLimit = 32 #Powershell New-CimSession default throttle value
3030
)
3131

32+
Write-Verbose "In Get-TargetResource"
33+
3234
Import-Module $PSScriptRoot\..\..\PSDSCxMachine.psm1
3335

3436
$b = @{"hash" = "table"}
@@ -75,10 +77,14 @@ function Set-TargetResource
7577
[Uint32] $ThrottleLimit = 32 #Powershell New-CimSession default throttle value
7678
)
7779

80+
Write-Verbose "In Set-TargetResource"
81+
7882
Import-Module $PSScriptRoot\..\..\PSDSCxMachine.psm1
7983

8084
if ($PSBoundParameters["Verbose"])
8185
{
86+
Write-Verbose "Calling xMachine with Verbose parameter"
87+
8288
PSDSCxMachine\Set-_InternalPSDscXMachineTR `
8389
-RemoteResourceId $ResourceName `
8490
-RemoteMachine $NodeName `
@@ -130,6 +136,8 @@ function Test-TargetResource
130136
[Uint32] $ThrottleLimit = 32 #Powershell New-CimSession default throttle value
131137
)
132138

139+
Write-Verbose "In Test-TargetResource"
140+
133141
Import-Module $PSScriptRoot\..\..\PSDSCxMachine.psm1
134142

135143
$a = $true
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
Import-Module PSScriptAnalyzer
2+
3+
$violationMessage = "There is no call to Write-Verbose in DSC function ‘Set-TargetResource’. If you are using Write-Verbose in a helper function, suppress this rule application."
4+
$violationName = "PSDSCUseVerboseMessageInDSCResource"
5+
$directory = Split-Path -Parent $MyInvocation.MyCommand.Path
6+
$violations = Invoke-ScriptAnalyzer $directory\DSCResources\MSFT_WaitForAll\MSFT_WaitForAll.psm1 | Where-Object {$_.RuleName -eq $violationName}
7+
$noViolations = Invoke-ScriptAnalyzer $directory\DSCResources\MSFT_WaitForAny\MSFT_WaitForAny.psm1 | Where-Object {$_.RuleName -eq $violationName}
8+
$noClassViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $directory\DSCResources\MyDscResource\MyDscResource.psm1 | Where-Object {$_.RuleName -eq $violationName}
9+
10+
Describe "UseVerboseMessageInDSCResource" {
11+
Context "When there are violations" {
12+
It "has 2 Verbose Message violations" {
13+
$violations.Count | Should Be 2
14+
}
15+
16+
It "has the correct description message" {
17+
$violations[0].Message | Should Match $violationMessage
18+
}
19+
}
20+
21+
Context "When there are no violations" {
22+
It "returns no violations" {
23+
$noViolations.Count | Should Be 0
24+
}
25+
}
26+
}

0 commit comments

Comments
 (0)