1212
1313using System . Text . RegularExpressions ;
1414using System ;
15+ using System . ComponentModel ;
1516using System . Collections . Generic ;
1617using System . Diagnostics . CodeAnalysis ;
1718using System . Globalization ;
2021using System . Management . Automation . Language ;
2122using System . IO ;
2223using Microsoft . Windows . PowerShell . ScriptAnalyzer . Generic ;
24+ using System . Threading . Tasks ;
25+ using System . Collections . Concurrent ;
26+ using System . Threading ;
2327
2428namespace Microsoft . Windows . PowerShell . ScriptAnalyzer . Commands
2529{
@@ -267,6 +271,13 @@ private void ProcessPath(string path)
267271
268272 }
269273
274+ ConcurrentBag < DiagnosticRecord > diagnostics ;
275+ ConcurrentBag < SuppressedRecord > suppressed ;
276+ Dictionary < string , List < RuleSuppression > > ruleSuppressions ;
277+ List < Regex > includeRegexList ;
278+ List < Regex > excludeRegexList ;
279+ ConcurrentDictionary < string , List < object > > ruleDictionary ;
280+
270281 /// <summary>
271282 /// Analyzes a single script file.
272283 /// </summary>
@@ -275,15 +286,16 @@ private void AnalyzeFile(string filePath)
275286 {
276287 Token [ ] tokens = null ;
277288 ParseError [ ] errors = null ;
278- List < DiagnosticRecord > diagnostics = new List < DiagnosticRecord > ( ) ;
279- List < SuppressedRecord > suppressed = new List < SuppressedRecord > ( ) ;
289+ ConcurrentBag < DiagnosticRecord > diagnostics = new ConcurrentBag < DiagnosticRecord > ( ) ;
290+ ConcurrentBag < SuppressedRecord > suppressed = new ConcurrentBag < SuppressedRecord > ( ) ;
291+ BlockingCollection < List < object > > verboseOrErrors = new BlockingCollection < List < object > > ( ) ;
280292
281293 // Use a List of KVP rather than dictionary, since for a script containing inline functions with same signature, keys clash
282294 List < KeyValuePair < CommandInfo , IScriptExtent > > cmdInfoTable = new List < KeyValuePair < CommandInfo , IScriptExtent > > ( ) ;
283295
284296 //Check wild card input for the Include/ExcludeRules and create regex match patterns
285- List < Regex > includeRegexList = new List < Regex > ( ) ;
286- List < Regex > excludeRegexList = new List < Regex > ( ) ;
297+ includeRegexList = new List < Regex > ( ) ;
298+ excludeRegexList = new List < Regex > ( ) ;
287299 if ( includeRule != null )
288300 {
289301 foreach ( string rule in includeRule )
@@ -331,7 +343,7 @@ private void AnalyzeFile(string filePath)
331343 return ;
332344 }
333345
334- Dictionary < string , List < RuleSuppression > > ruleSuppressions = Helper . Instance . GetRuleSuppression ( ast ) ;
346+ ruleSuppressions = Helper . Instance . GetRuleSuppression ( ast ) ;
335347
336348 foreach ( List < RuleSuppression > ruleSuppressionsList in ruleSuppressions . Values )
337349 {
@@ -360,43 +372,75 @@ private void AnalyzeFile(string filePath)
360372
361373 if ( ScriptAnalyzer . Instance . ScriptRules != null )
362374 {
363- foreach ( IScriptRule scriptRule in ScriptAnalyzer . Instance . ScriptRules )
364- {
365- bool includeRegexMatch = false ;
366- bool excludeRegexMatch = false ;
367- foreach ( Regex include in includeRegexList )
375+ var tasks = ScriptAnalyzer . Instance . ScriptRules . Select ( scriptRule => Task . Factory . StartNew ( ( ) =>
368376 {
369- if ( include . IsMatch ( scriptRule . GetName ( ) ) )
377+ bool includeRegexMatch = false ;
378+ bool excludeRegexMatch = false ;
379+
380+ foreach ( Regex include in includeRegexList )
370381 {
371- includeRegexMatch = true ;
372- break ;
382+ if ( include . IsMatch ( scriptRule . GetName ( ) ) )
383+ {
384+ includeRegexMatch = true ;
385+ break ;
386+ }
373387 }
374- }
375388
376- foreach ( Regex exclude in excludeRegexList )
377- {
378- if ( exclude . IsMatch ( scriptRule . GetName ( ) ) )
389+ foreach ( Regex exclude in excludeRegexList )
379390 {
380- excludeRegexMatch = true ;
381- break ;
391+ if ( exclude . IsMatch ( scriptRule . GetName ( ) ) )
392+ {
393+ excludeRegexMatch = true ;
394+ break ;
395+ }
382396 }
383- }
384-
385- if ( ( includeRule == null || includeRegexMatch ) && ( excludeRule == null || ! excludeRegexMatch ) )
386- {
387- WriteVerbose ( string . Format ( CultureInfo . CurrentCulture , Strings . VerboseRunningMessage , scriptRule . GetName ( ) ) ) ;
388397
389- // Ensure that any unhandled errors from Rules are converted to non-terminating errors
390- // We want the Engine to continue functioning even if one or more Rules throws an exception
391- try
398+ if ( ( includeRule == null || includeRegexMatch ) && ( excludeRule == null || ! excludeRegexMatch ) )
392399 {
393- var records = Helper . Instance . SuppressRule ( scriptRule . GetName ( ) , ruleSuppressions , scriptRule . AnalyzeScript ( ast , filePath ) . ToList ( ) ) ;
394- diagnostics . AddRange ( records . Item2 ) ;
395- suppressed . AddRange ( records . Item1 ) ;
400+ List < object > result = new List < object > ( ) ;
401+
402+ result . Add ( string . Format ( CultureInfo . CurrentCulture , Strings . VerboseRunningMessage , scriptRule . GetName ( ) ) ) ;
403+
404+ // Ensure that any unhandled errors from Rules are converted to non-terminating errors
405+ // We want the Engine to continue functioning even if one or more Rules throws an exception
406+ try
407+ {
408+ var records = Helper . Instance . SuppressRule ( scriptRule . GetName ( ) , ruleSuppressions , scriptRule . AnalyzeScript ( ast , ast . Extent . File ) . ToList ( ) ) ;
409+ foreach ( var record in records . Item2 )
410+ {
411+ diagnostics . Add ( record ) ;
412+ }
413+ foreach ( var suppressedRec in records . Item1 )
414+ {
415+ suppressed . Add ( suppressedRec ) ;
416+ }
417+ }
418+ catch ( Exception scriptRuleException )
419+ {
420+ result . Add ( new ErrorRecord ( scriptRuleException , Strings . RuleErrorMessage , ErrorCategory . InvalidOperation , ast . Extent . File ) ) ;
421+ }
422+
423+ verboseOrErrors . Add ( result ) ;
396424 }
397- catch ( Exception scriptRuleException )
425+ } ) ) ;
426+
427+ Task . Factory . ContinueWhenAll ( tasks . ToArray ( ) , t => verboseOrErrors . CompleteAdding ( ) ) ;
428+
429+ while ( ! verboseOrErrors . IsCompleted )
430+ {
431+ List < object > data = null ;
432+ try
433+ {
434+ data = verboseOrErrors . Take ( ) ;
435+ }
436+ catch ( InvalidOperationException ) { }
437+
438+ if ( data != null )
439+ {
440+ WriteVerbose ( data [ 0 ] as string ) ;
441+ if ( data . Count == 2 )
398442 {
399- WriteError ( new ErrorRecord ( scriptRuleException , Strings . RuleErrorMessage , ErrorCategory . InvalidOperation , filePath ) ) ;
443+ WriteError ( data [ 1 ] as ErrorRecord ) ;
400444 }
401445 }
402446 }
@@ -437,8 +481,14 @@ private void AnalyzeFile(string filePath)
437481 try
438482 {
439483 var records = Helper . Instance . SuppressRule ( tokenRule . GetName ( ) , ruleSuppressions , tokenRule . AnalyzeTokens ( tokens , filePath ) . ToList ( ) ) ;
440- diagnostics . AddRange ( records . Item2 ) ;
441- suppressed . AddRange ( records . Item1 ) ;
484+ foreach ( var record in records . Item2 )
485+ {
486+ diagnostics . Add ( record ) ;
487+ }
488+ foreach ( var suppressedRec in records . Item1 )
489+ {
490+ suppressed . Add ( suppressedRec ) ;
491+ }
442492 }
443493 catch ( Exception tokenRuleException )
444494 {
@@ -489,8 +539,14 @@ private void AnalyzeFile(string filePath)
489539 try
490540 {
491541 var records = Helper . Instance . SuppressRule ( dscResourceRule . GetName ( ) , ruleSuppressions , dscResourceRule . AnalyzeDSCClass ( ast , filePath ) . ToList ( ) ) ;
492- diagnostics . AddRange ( records . Item2 ) ;
493- suppressed . AddRange ( records . Item1 ) ;
542+ foreach ( var record in records . Item2 )
543+ {
544+ diagnostics . Add ( record ) ;
545+ }
546+ foreach ( var suppressedRec in records . Item1 )
547+ {
548+ suppressed . Add ( suppressedRec ) ;
549+ }
494550 }
495551 catch ( Exception dscResourceRuleException )
496552 {
@@ -532,8 +588,14 @@ private void AnalyzeFile(string filePath)
532588 try
533589 {
534590 var records = Helper . Instance . SuppressRule ( dscResourceRule . GetName ( ) , ruleSuppressions , dscResourceRule . AnalyzeDSCResource ( ast , filePath ) . ToList ( ) ) ;
535- diagnostics . AddRange ( records . Item2 ) ;
536- suppressed . AddRange ( records . Item1 ) ;
591+ foreach ( var record in records . Item2 )
592+ {
593+ diagnostics . Add ( record ) ;
594+ }
595+ foreach ( var suppressedRec in records . Item1 )
596+ {
597+ suppressed . Add ( suppressedRec ) ;
598+ }
537599 }
538600 catch ( Exception dscResourceRuleException )
539601 {
@@ -573,15 +635,20 @@ private void AnalyzeFile(string filePath)
573635 }
574636 }
575637
576- diagnostics . AddRange ( ScriptAnalyzer . Instance . GetExternalRecord ( ast , tokens , exRules . ToArray ( ) , this , fileName ) ) ;
638+ foreach ( var record in ScriptAnalyzer . Instance . GetExternalRecord ( ast , tokens , exRules . ToArray ( ) , this , fileName ) )
639+ {
640+ diagnostics . Add ( record ) ;
641+ }
577642 }
578643
579644 #endregion
580645
646+ IEnumerable < DiagnosticRecord > diagnosticsList = diagnostics ;
647+
581648 if ( severity != null )
582649 {
583650 var diagSeverity = severity . Select ( item => Enum . Parse ( typeof ( DiagnosticSeverity ) , item , true ) ) ;
584- diagnostics = diagnostics . Where ( item => diagSeverity . Contains ( item . Severity ) ) . ToList ( ) ;
651+ diagnosticsList = diagnostics . Where ( item => diagSeverity . Contains ( item . Severity ) ) ;
585652 }
586653
587654 //Output through loggers
@@ -596,7 +663,7 @@ private void AnalyzeFile(string filePath)
596663 }
597664 else
598665 {
599- foreach ( DiagnosticRecord diagnostic in diagnostics )
666+ foreach ( DiagnosticRecord diagnostic in diagnosticsList )
600667 {
601668 logger . LogObject ( diagnostic , this ) ;
602669 }
0 commit comments