2222using System . Globalization ;
2323using System . Threading ;
2424using System . Reflection ;
25+ using System . Management . Automation ;
2526
2627namespace Microsoft . Windows . Powershell . ScriptAnalyzer . BuiltinRules
2728{
@@ -31,6 +32,8 @@ namespace Microsoft.Windows.Powershell.ScriptAnalyzer.BuiltinRules
3132 [ Export ( typeof ( IScriptRule ) ) ]
3233 public class ProvideCommentHelp : SkipTypeDefinition , IScriptRule
3334 {
35+ private HashSet < string > exportedFunctions ;
36+
3437 /// <summary>
3538 /// AnalyzeScript: Analyzes the ast to check that cmdlets have help.
3639 /// </summary>
@@ -42,6 +45,131 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName) {
4245
4346 DiagnosticRecords . Clear ( ) ;
4447 this . fileName = fileName ;
48+ exportedFunctions = new HashSet < string > ( StringComparer . OrdinalIgnoreCase ) ;
49+ List < string > exportFunctionsCmdlet = Helper . Instance . CmdletNameAndAliases ( "export-modulemember" ) ;
50+
51+ // find functions exported
52+ IEnumerable < Ast > cmdAsts = ast . FindAll ( item => item is CommandAst
53+ && exportFunctionsCmdlet . Contains ( ( item as CommandAst ) . GetCommandName ( ) , StringComparer . OrdinalIgnoreCase ) , true ) ;
54+
55+ CommandInfo exportMM = Helper . Instance . GetCommandInfo ( "export-modulemember" , CommandTypes . Cmdlet ) ;
56+
57+ // switch parameters
58+ IEnumerable < ParameterMetadata > switchParams = ( exportMM != null ) ? exportMM . Parameters . Values . Where < ParameterMetadata > ( pm => pm . SwitchParameter ) : Enumerable . Empty < ParameterMetadata > ( ) ;
59+
60+ if ( exportMM == null )
61+ {
62+ return DiagnosticRecords ;
63+ }
64+
65+ foreach ( CommandAst cmdAst in cmdAsts )
66+ {
67+ if ( cmdAst . CommandElements == null || cmdAst . CommandElements . Count < 2 )
68+ {
69+ continue ;
70+ }
71+
72+ int i = 1 ;
73+
74+ while ( i < cmdAst . CommandElements . Count )
75+ {
76+ CommandElementAst ceAst = cmdAst . CommandElements [ i ] ;
77+ ExpressionAst exprAst = null ;
78+
79+ if ( ceAst is CommandParameterAst )
80+ {
81+ var paramAst = ceAst as CommandParameterAst ;
82+ var param = exportMM . ResolveParameter ( paramAst . ParameterName ) ;
83+
84+ if ( param == null )
85+ {
86+ i += 1 ;
87+ continue ;
88+ }
89+
90+ if ( string . Equals ( param . Name , "function" , StringComparison . OrdinalIgnoreCase ) )
91+ {
92+ // checks for the case of -Function:"verb-nouns"
93+ if ( paramAst . Argument != null )
94+ {
95+ exprAst = paramAst . Argument ;
96+ }
97+ // checks for the case of -Function "verb-nouns"
98+ else if ( i < cmdAst . CommandElements . Count - 1 )
99+ {
100+ i += 1 ;
101+ exprAst = cmdAst . CommandElements [ i ] as ExpressionAst ;
102+ }
103+ }
104+ // some other parameter. we just checks whether the one after this is positional
105+ else if ( i < cmdAst . CommandElements . Count - 1 )
106+ {
107+ // the next element is a parameter like -module so just move to that one
108+ if ( cmdAst . CommandElements [ i + 1 ] is CommandParameterAst )
109+ {
110+ i += 1 ;
111+ continue ;
112+ }
113+
114+ // not a switch parameter so the next element is definitely the argument to this parameter
115+ if ( paramAst . Argument == null && ! switchParams . Contains ( param ) )
116+ {
117+ // skips the next element
118+ i += 1 ;
119+ }
120+
121+ i += 1 ;
122+ continue ;
123+ }
124+ }
125+ else if ( ceAst is ExpressionAst )
126+ {
127+ exprAst = ceAst as ExpressionAst ;
128+ }
129+
130+ if ( exprAst != null )
131+ {
132+ // One string so just add this to the list
133+ if ( exprAst is StringConstantExpressionAst )
134+ {
135+ exportedFunctions . Add ( ( exprAst as StringConstantExpressionAst ) . Value ) ;
136+ }
137+ // Array of the form "v-n", "v-n1"
138+ else if ( exprAst is ArrayLiteralAst )
139+ {
140+ exportedFunctions . UnionWith ( Helper . Instance . GetStringsFromArrayLiteral ( exprAst as ArrayLiteralAst ) ) ;
141+ }
142+ // Array of the form @("v-n", "v-n1")
143+ else if ( exprAst is ArrayExpressionAst )
144+ {
145+ ArrayExpressionAst arrExAst = exprAst as ArrayExpressionAst ;
146+ if ( arrExAst . SubExpression != null && arrExAst . SubExpression . Statements != null )
147+ {
148+ foreach ( StatementAst stAst in arrExAst . SubExpression . Statements )
149+ {
150+ if ( stAst is PipelineAst )
151+ {
152+ PipelineAst pipeAst = stAst as PipelineAst ;
153+ if ( pipeAst . PipelineElements != null )
154+ {
155+ foreach ( CommandBaseAst cmdBaseAst in pipeAst . PipelineElements )
156+ {
157+ if ( cmdBaseAst is CommandExpressionAst )
158+ {
159+ exportedFunctions . UnionWith ( Helper . Instance . GetStringsFromArrayLiteral ( ( cmdBaseAst as CommandExpressionAst ) . Expression as ArrayLiteralAst ) ) ;
160+ }
161+ }
162+ }
163+ }
164+ }
165+ }
166+ }
167+ }
168+
169+ i += 1 ;
170+ }
171+ }
172+
45173 ast . Visit ( this ) ;
46174
47175 return DiagnosticRecords ;
@@ -59,10 +187,8 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun
59187 return AstVisitAction . SkipChildren ;
60188 }
61189
62- if ( ! string . Equals ( funcAst . Name , "Get-TargetResource" , StringComparison . OrdinalIgnoreCase ) && ! string . Equals ( funcAst . Name , "Set-TargetResource" , StringComparison . OrdinalIgnoreCase ) &&
63- ! string . Equals ( funcAst . Name , "Test-TargetResource" , StringComparison . OrdinalIgnoreCase ) )
190+ if ( exportedFunctions . Contains ( funcAst . Name ) )
64191 {
65-
66192 if ( funcAst . GetHelpContent ( ) == null )
67193 {
68194 DiagnosticRecords . Add (
0 commit comments