Skip to content

Commit 29a9043

Browse files
authored
Merge pull request Azure#3089 from cormacpayne/verbs-check
Add StaticAnalysis rule to fail cmdlets not using approved verbs
2 parents fe09e68 + da3f2d9 commit 29a9043

File tree

7 files changed

+136
-14
lines changed

7 files changed

+136
-14
lines changed

tools/StaticAnalysis/Exceptions/SignatureIssues.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -856,6 +856,7 @@
856856
"Microsoft.Azure.Commands.Websites.dll","Microsoft.Azure.Commands.WebApps.Cmdlets.DeploymentSlots.StartAzureWebAppSlotCmdlet","Start-AzureRmWebAppSlot","1","8100","Start-AzureRmWebAppSlot Does not support ShouldProcess but the cmdlet verb Start indicates that it should.","Determine if the cmdlet should implement ShouldProcess and if so determine if it should implement Force / ShouldContinue"
857857
"Microsoft.Azure.Commands.Websites.dll","Microsoft.Azure.Commands.WebApps.Cmdlets.DeploymentSlots.StopAzureWebAppSlotCmdlet","Stop-AzureRmWebAppSlot","1","8100","Stop-AzureRmWebAppSlot Does not support ShouldProcess but the cmdlet verb Stop indicates that it should.","Determine if the cmdlet should implement ShouldProcess and if so determine if it should implement Force / ShouldContinue"
858858
"Microsoft.Azure.Commands.Websites.dll","Microsoft.Azure.Commands.WebApps.Cmdlets.DeploymentSlots.StopAzureWebAppSlotCmdlet","Stop-AzureRmWebAppSlot","2","8210","Stop-AzureRmWebAppSlot does not have a Force parameter but the cmdlet verb 'Stop' indicates that it may perform destructive actions under certain circumstances. Consider whether the cmdlet should have a Force parameter anduse ShouldContinue under some circumstances. ","Consider wether the cmdlet should have a Force parameter and use ShouldContinue under some circumstances. "
859+
"Microsoft.Azure.Commands.Websites.dll","Microsoft.Azure.Commands.WebApps.Cmdlets.DeploymentSlots.SwapAzureWebAppSlot","Swap-AzureRmWebAppSlot","1","8300","Swap-AzureRmWebAppSlot uses the verb 'Swap', which is not on the list of approved verbs for PowerShell commands. Use the cmdlet 'Get-Verb' to see the full list of approved verbs and consider renaming the cmdlet.","Consider renaming the cmdlet to use an approved verb for PowerShell."
859860
"Microsoft.Azure.Commands.Websites.dll","Microsoft.Azure.Commands.WebApps.Cmdlets.BackupRestore.NewAzureRmWebAppDatabaseBackupSetting","New-AzureRmWebAppDatabaseBackupSetting","1","8100","New-AzureRmWebAppDatabaseBackupSetting Does not support ShouldProcess but the cmdlet verb New indicates that it should.","Determine if the cmdlet should implement ShouldProcess and if so determine if it should implement Force / ShouldContinue"
860861
"Microsoft.Azure.Commands.Websites.dll","Microsoft.Azure.Commands.WebApps.Cmdlets.BackupRestore.RestoreAzureWebAppBackup","Restore-AzureRmWebAppBackup","1","8100","Restore-AzureRmWebAppBackup Does not support ShouldProcess but the cmdlet verb Restore indicates that it should.","Determine if the cmdlet should implement ShouldProcess and if so determine if it should implement Force / ShouldContinue"
861862
"Microsoft.Azure.Commands.Websites.dll","Microsoft.Azure.Commands.WebApps.Cmdlets.BackupRestore.RestoreAzureWebAppBackup","Restore-AzureRmWebAppBackup","2","8210","Restore-AzureRmWebAppBackup does not have a Force parameter but the cmdlet verb 'Restore' indicates that it may perform destructive actions under certain circumstances. Consider whether the cmdlet should have a Force parameter anduse ShouldContinue under some circumstances. ","Consider wether the cmdlet should have a Force parameter and use ShouldContinue under some circumstances. "

tools/StaticAnalysis/ProblemIDs.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ public static class SignatureProblemId
77
public const int ActionIndicatesShouldProcess = 8100;
88
public const int ConfirmLevelChange = 8200;
99
public const int CmdletWithDestructiveVerbNoForce = 8210;
10+
public const int CmdletWithUnapprovedVerb = 8300;
1011
public const int CmdletWithDestructiveVerb = 98300;
1112
public const int CmdletWithForceParameter = 98310;
1213
}

tools/StaticAnalysis/SignatureVerifier/CmdletSignatureMetadata.cs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,31 @@ public class CmdletSignatureMetadata
116116
VerbsSecurity.Unblock
117117
};
118118
#endregion
119-
119+
120+
#region ApprovedVerbs
121+
private List<string> ApprovedVerbs;
122+
123+
private List<string> GetApprovedVerbs()
124+
{
125+
if (ApprovedVerbs == null)
126+
{
127+
ApprovedVerbs = new List<string>();
128+
129+
PowerShell powershell = PowerShell.Create();
130+
powershell.AddCommand("Get-Verb");
131+
132+
var cmdletResult = powershell.Invoke();
133+
134+
foreach (PSObject result in cmdletResult)
135+
{
136+
ApprovedVerbs.Add(result.Members["Verb"].Value.ToString());
137+
}
138+
}
139+
140+
return ApprovedVerbs;
141+
}
142+
#endregion
143+
120144
/// <summary>
121145
/// The name of the assembly containing cmdlet
122146
/// </summary>
@@ -153,6 +177,14 @@ public bool IsShouldContinueVerb
153177
get { return VerbName != null && ShouldContinueVerbs.Contains(VerbName); }
154178
}
155179

180+
/// <summary>
181+
/// True if the cmdlet has an approved verb
182+
/// </summary>
183+
public bool IsApprovedVerb
184+
{
185+
get { return VerbName != null && GetApprovedVerbs().Contains(VerbName); }
186+
}
187+
156188
/// <summary>
157189
/// The name of the class that implements the cmdlet
158190
/// </summary>

tools/StaticAnalysis/SignatureVerifier/SignatureVerifier.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,21 @@ public void Analyze(IEnumerable<string> cmdletProbingDirs,
155155
remediation: "Consider wether the cmdlet should have a Force " +
156156
"parameter and use ShouldContinue under some circumstances. ");
157157
}
158+
159+
if (!cmdlet.IsApprovedVerb)
160+
{
161+
issueLogger.LogSignatureIssue(
162+
cmdlet: cmdlet,
163+
severity: 1,
164+
problemId: SignatureProblemId.CmdletWithUnapprovedVerb,
165+
description:
166+
string.Format(
167+
"{0} uses the verb '{1}', which is not on the list of approved " +
168+
"verbs for PowerShell commands. Use the cmdlet 'Get-Verb' to see " +
169+
"the full list of approved verbs and consider renaming the cmdlet.",
170+
cmdlet.Name, cmdlet.VerbName),
171+
remediation: "Consider renaming the cmdlet to use an approved verb for PowerShell.");
172+
}
158173
}
159174

160175
AppDomain.Unload(_appDomain);

tools/StaticAnalysis/StaticAnalysis.Test/SignatureVerifierTests.cs

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public void AddVerbWithoutSupportsShouldProcessParameter()
5050

5151
AnalysisReport testReport = cmdletSignatureVerifier.GetAnalysisReport();
5252

53-
Assert.True(testReport.ProblemIdList.Count == 1);
53+
Assert.Equal(1, testReport.ProblemIdList.Count);
5454
Assert.True(testReport.ProblemIdList.Where<int>((problemId) => problemId.Equals(SignatureProblemId.ActionIndicatesShouldProcess)).SingleOrDefault<int>().Equals(SignatureProblemId.ActionIndicatesShouldProcess));
5555
}
5656

@@ -64,7 +64,7 @@ public void AddVerbWithSupportsShouldProcessParameter()
6464
(cmdletName) => cmdletName.Equals("Add-AddVerbWithSupportsShouldProcessParameter", StringComparison.OrdinalIgnoreCase));
6565

6666
AnalysisReport testReport = cmdletSignatureVerifier.GetAnalysisReport();
67-
Assert.True(testReport.ProblemIdList.Count == 0);
67+
Assert.Equal(0, testReport.ProblemIdList.Count);
6868
//Assert.True(testReport.ProblemIdList.Where<int>((problemId) => problemId.Equals(SignatureProblemId.ActionIndicatesShouldProcess)).SingleOrDefault<int>().Equals(SignatureProblemId.ActionIndicatesShouldProcess));
6969
}
7070
#endregion
@@ -84,7 +84,7 @@ public void ForceParameterWithoutSupportsShouldProcess()
8484

8585

8686
AnalysisReport testReport = cmdletSignatureVerifier.GetAnalysisReport();
87-
Assert.True(testReport.ProblemIdList.Count == 1);
87+
Assert.Equal(1, testReport.ProblemIdList.Count);
8888
Assert.True(testReport.ProblemIdList.Where<int>((problemId) => problemId.Equals(SignatureProblemId.ForceWithoutShouldProcessAttribute)).SingleOrDefault<int>().Equals(SignatureProblemId.ForceWithoutShouldProcessAttribute));
8989
}
9090

@@ -98,7 +98,7 @@ public void ForceParameterWithSupportsShouldProcess()
9898
(cmdletName) => cmdletName.Equals("Test-ForceParameterWithSupportsShouldProcess", StringComparison.OrdinalIgnoreCase));
9999

100100
AnalysisReport testReport = cmdletSignatureVerifier.GetAnalysisReport();
101-
Assert.True(testReport.ProblemIdList.Count == 0);
101+
Assert.Equal(0, testReport.ProblemIdList.Count);
102102
}
103103

104104
#endregion
@@ -114,7 +114,7 @@ public void ConfirmImpactWithSupportsShouldProcess()
114114
(cmdletName) => cmdletName.Equals("Test-ConfirmImpactWithSupportsShouldProcess", StringComparison.OrdinalIgnoreCase));
115115

116116
AnalysisReport testReport = cmdletSignatureVerifier.GetAnalysisReport();
117-
Assert.True(testReport.ProblemIdList.Count == 0);
117+
Assert.Equal(0, testReport.ProblemIdList.Count);
118118
}
119119

120120
[Fact]
@@ -127,7 +127,7 @@ public void ConfirmImpactWithoutSupportsShouldProcess()
127127
(cmdletName) => cmdletName.Equals("Test-ConfirmImpactWithoutSupportsShouldProcess", StringComparison.OrdinalIgnoreCase));
128128

129129
AnalysisReport testReport = cmdletSignatureVerifier.GetAnalysisReport();
130-
Assert.True(testReport.ProblemIdList.Count == 2);
130+
Assert.Equal(2, testReport.ProblemIdList.Count);
131131
Assert.True(testReport.ProblemIdList.Where<int>((problemId) => problemId.Equals(SignatureProblemId.ConfirmLeveleWithNoShouldProcess)).SingleOrDefault<int>().Equals(SignatureProblemId.ConfirmLeveleWithNoShouldProcess));
132132
Assert.True(testReport.ProblemIdList.Where<int>((problemId) => problemId.Equals(SignatureProblemId.ConfirmLevelChange)).SingleOrDefault<int>().Equals(SignatureProblemId.ConfirmLevelChange));
133133
}
@@ -144,7 +144,7 @@ public void ShouldContinueVerbWithForceSwitch()
144144
(cmdletName) => cmdletName.Equals("Copy-ShouldContinueVerbWithForceSwitch", StringComparison.OrdinalIgnoreCase));
145145

146146
AnalysisReport testReport = cmdletSignatureVerifier.GetAnalysisReport();
147-
Assert.True(testReport.ProblemIdList.Count == 0);
147+
Assert.Equal(0, testReport.ProblemIdList.Count);
148148
}
149149

150150
[Fact]
@@ -157,10 +157,39 @@ public void ShouldContinueVerbWithoutForceSwitch()
157157
(cmdletName) => cmdletName.Equals("Export-ShouldContinueVerbWithoutForceSwitch", StringComparison.OrdinalIgnoreCase));
158158

159159
AnalysisReport testReport = cmdletSignatureVerifier.GetAnalysisReport();
160-
Assert.True(testReport.ProblemIdList.Count == 2);
160+
Assert.Equal(2, testReport.ProblemIdList.Count);
161161
Assert.True(testReport.ProblemIdList.Where<int>((problemId) => problemId.Equals(SignatureProblemId.CmdletWithDestructiveVerbNoForce)).SingleOrDefault<int>().Equals(SignatureProblemId.CmdletWithDestructiveVerbNoForce));
162162
Assert.True(testReport.ProblemIdList.Where<int>((problemId) => problemId.Equals(SignatureProblemId.ActionIndicatesShouldProcess)).SingleOrDefault<int>().Equals(SignatureProblemId.ActionIndicatesShouldProcess));
163163
}
164164
#endregion
165+
166+
#region CmdletWithUnapprovedVerb
167+
[Fact]
168+
[Trait(Category.AcceptanceType, Category.CheckIn)]
169+
public void CmdletWithApprovedVerb()
170+
{
171+
cmdletSignatureVerifier.Analyze(
172+
new List<string> { _testCmdletDirPath },
173+
((dirList) => { return new List<string> { _testCmdletDirPath }; }),
174+
(cmdletName) => cmdletName.Equals("Get-SampleCmdlet", StringComparison.OrdinalIgnoreCase));
175+
176+
AnalysisReport testReport = cmdletSignatureVerifier.GetAnalysisReport();
177+
Assert.Equal(0, testReport.ProblemIdList.Count);
178+
}
179+
180+
[Fact]
181+
[Trait(Category.AcceptanceType, Category.CheckIn)]
182+
public void CmdletWithUnapprovedVerb()
183+
{
184+
cmdletSignatureVerifier.Analyze(
185+
new List<string> { _testCmdletDirPath },
186+
((dirList) => { return new List<string> { _testCmdletDirPath }; }),
187+
(cmdletName) => cmdletName.Equals("Prepare-SampleCmdlet", StringComparison.OrdinalIgnoreCase));
188+
189+
AnalysisReport testReport = cmdletSignatureVerifier.GetAnalysisReport();
190+
Assert.Equal(1, testReport.ProblemIdList.Count);
191+
Assert.True(testReport.ProblemIdList.Where<int>((problemId) => problemId.Equals(SignatureProblemId.CmdletWithUnapprovedVerb)).SingleOrDefault<int>().Equals(SignatureProblemId.CmdletWithUnapprovedVerb));
192+
}
193+
#endregion
165194
}
166195
}

tools/StaticAnalysis/StaticAnalysis.Test/StaticAnalysis.Test.csproj

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44
<Import Project="..\..\..\src\packages\xunit.core.2.1.0\build\portable-net45+win8+wp8+wpa81\xunit.core.props" Condition="Exists('..\..\..\src\packages\xunit.core.2.1.0\build\portable-net45+win8+wp8+wpa81\xunit.core.props')" />
55
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
66
<PropertyGroup>
7-
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
8-
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
7+
<!--<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
8+
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>-->
99
<ProjectGuid>{A034BE7F-0CD8-4A03-85B3-44CC2E58B86F}</ProjectGuid>
1010
<OutputType>Library</OutputType>
1111
<AppDesignerFolder>Properties</AppDesignerFolder>
1212
<RootNamespace>StaticAnalysis.Test</RootNamespace>
1313
<AssemblyName>StaticAnalysis.Test</AssemblyName>
1414
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
15-
<RestorePackages>true</RestorePackages>
15+
<RestorePackages>true</RestorePackages>
1616
<FileAlignment>512</FileAlignment>
1717
<NuGetPackageImportStamp>ba98ab34</NuGetPackageImportStamp>
1818
<TestCmdletModuleAssemblyName>TestCmdletsModule</TestCmdletModuleAssemblyName>
@@ -21,15 +21,15 @@
2121
<DebugSymbols>true</DebugSymbols>
2222
<DebugType>full</DebugType>
2323
<Optimize>false</Optimize>
24-
<OutputPath>bin\Debug\</OutputPath>
24+
<OutputPath>bin\Debug</OutputPath>
2525
<DefineConstants>DEBUG;TRACE</DefineConstants>
2626
<ErrorReport>prompt</ErrorReport>
2727
<WarningLevel>4</WarningLevel>
2828
</PropertyGroup>
2929
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
3030
<DebugType>pdbonly</DebugType>
3131
<Optimize>true</Optimize>
32-
<OutputPath>bin\Release\</OutputPath>
32+
<OutputPath>bin\Debug</OutputPath>
3333
<DefineConstants>TRACE</DefineConstants>
3434
<ErrorReport>prompt</ErrorReport>
3535
<WarningLevel>4</WarningLevel>

tools/StaticAnalysis/StaticAnalysis.Test/TestCmdlets/SignatureVerifier_Cmdlet.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,4 +226,48 @@ protected override void BeginProcessing()
226226
}
227227
}
228228
}
229+
#endregion
230+
231+
#region CmdletWithUnapprovedVerb
232+
namespace StaticAnalysis.Test.CmdletTest.Signature.CmdletWithApprovedVerb
233+
{
234+
using System.Management.Automation;
235+
236+
/// <summary>
237+
/// Verify if a cmdlet has an approved verb in its name.
238+
/// </summary>
239+
[Cmdlet(VerbsCommon.Get, "SampleCmdlet")]
240+
public class CmdletWithApprovedVerb : Cmdlet
241+
{
242+
/// <summary>
243+
/// Begin processing the cmdlet.
244+
/// </summary>
245+
protected override void BeginProcessing()
246+
{
247+
WriteObject("Get-SampleCmdlet BeginProcessing()");
248+
WriteInformation("Info", null);
249+
}
250+
}
251+
}
252+
253+
namespace StaticAnalysis.Test.CmdletTest.Signature.CmdletWithUnapprovedVerb
254+
{
255+
using System.Management.Automation;
256+
257+
/// <summary>
258+
/// Verify if a cmdlet has an approved verb in its name.
259+
/// </summary>
260+
[Cmdlet("Prepare", "SampleCmdlet")]
261+
public class CmdletWithUnapprovedVerb : Cmdlet
262+
{
263+
/// <summary>
264+
/// Begin processing the cmdlet.
265+
/// </summary>
266+
protected override void BeginProcessing()
267+
{
268+
WriteObject("Prepare-SampleCmdlet BeginProcessing()");
269+
WriteInformation("Info", null);
270+
}
271+
}
272+
}
229273
#endregion

0 commit comments

Comments
 (0)