@@ -24,6 +24,48 @@ namespace Microsoft.PowerShell.EditorServices
24
24
/// </summary>
25
25
public class AnalysisService : IDisposable
26
26
{
27
+ #region Static fields
28
+
29
+ /// <summary>
30
+ /// Defines the list of Script Analyzer rules to include by default if
31
+ /// no settings file is specified.
32
+ /// </summary>
33
+ private static readonly string [ ] s_includedRules = {
34
+ "PSUseToExportFieldsInManifest" ,
35
+ "PSMisleadingBacktick" ,
36
+ "PSAvoidUsingCmdletAliases" ,
37
+ "PSUseApprovedVerbs" ,
38
+ "PSAvoidUsingPlainTextForPassword" ,
39
+ "PSReservedCmdletChar" ,
40
+ "PSReservedParams" ,
41
+ "PSShouldProcess" ,
42
+ "PSMissingModuleManifestField" ,
43
+ "PSAvoidDefaultValueSwitchParameter" ,
44
+ "PSUseDeclaredVarsMoreThanAssignments" ,
45
+ "PSPossibleIncorrectComparisonWithNull" ,
46
+ "PSAvoidDefaultValueForMandatoryParameter" ,
47
+ "PSPossibleIncorrectUsageOfRedirectionOperator"
48
+ } ;
49
+
50
+ /// <summary>
51
+ /// An empty diagnostic result to return when a script fails analysis.
52
+ /// </summary>
53
+ private static readonly PSObject [ ] s_emptyDiagnosticResult = new PSObject [ 0 ] ;
54
+
55
+ /// <summary>
56
+ /// An empty script marker result to return when no script markers can be returned.
57
+ /// </summary>
58
+ private static readonly ScriptFileMarker [ ] s_emptyScriptMarkerResult = new ScriptFileMarker [ 0 ] ;
59
+
60
+ private static readonly string [ ] s_emptyGetRuleResult = new string [ 0 ] ;
61
+
62
+ /// <summary>
63
+ /// The indentation to add when the logger lists errors.
64
+ /// </summary>
65
+ private static readonly string s_indentJoin = Environment . NewLine + " " ;
66
+
67
+ #endregion // Static fields
68
+
27
69
#region Private Fields
28
70
29
71
/// <summary>
@@ -53,31 +95,8 @@ public class AnalysisService : IDisposable
53
95
/// </summary>
54
96
private PSModuleInfo _pssaModuleInfo ;
55
97
56
- /// <summary>
57
- /// Defines the list of Script Analyzer rules to include by default if
58
- /// no settings file is specified.
59
- /// </summary>
60
- private static readonly string [ ] s_includedRules = new string [ ]
61
- {
62
- "PSUseToExportFieldsInManifest" ,
63
- "PSMisleadingBacktick" ,
64
- "PSAvoidUsingCmdletAliases" ,
65
- "PSUseApprovedVerbs" ,
66
- "PSAvoidUsingPlainTextForPassword" ,
67
- "PSReservedCmdletChar" ,
68
- "PSReservedParams" ,
69
- "PSShouldProcess" ,
70
- "PSMissingModuleManifestField" ,
71
- "PSAvoidDefaultValueSwitchParameter" ,
72
- "PSUseDeclaredVarsMoreThanAssignments" ,
73
- "PSPossibleIncorrectComparisonWithNull" ,
74
- "PSAvoidDefaultValueForMandatoryParameter" ,
75
- "PSPossibleIncorrectUsageOfRedirectionOperator"
76
- } ;
77
-
78
98
#endregion // Private Fields
79
99
80
-
81
100
#region Properties
82
101
83
102
/// <summary>
@@ -281,9 +300,15 @@ public async Task<ScriptFileMarker[]> GetSemanticMarkersAsync(
281
300
/// </summary>
282
301
public IEnumerable < string > GetPSScriptAnalyzerRules ( )
283
302
{
284
- List < string > ruleNames = new List < string > ( ) ;
285
- var ruleObjects = InvokePowerShell ( "Get-ScriptAnalyzerRule" , new Dictionary < string , object > ( ) ) ;
286
- foreach ( var rule in ruleObjects )
303
+ PowerShellResult getRuleResult = InvokePowerShell ( "Get-ScriptAnalyzerRule" ) ;
304
+ if ( getRuleResult == null )
305
+ {
306
+ _logger . Write ( LogLevel . Warning , "Get-ScriptAnalyzerRule returned null result" ) ;
307
+ return s_emptyGetRuleResult ;
308
+ }
309
+
310
+ var ruleNames = new List < string > ( ) ;
311
+ foreach ( var rule in getRuleResult . Output )
287
312
{
288
313
ruleNames . Add ( ( string ) rule . Members [ "RuleName" ] . Value ) ;
289
314
}
@@ -319,8 +344,35 @@ public async Task<string> Format(
319
344
argsDict . Add ( "Range" , rangeList ) ;
320
345
}
321
346
322
- var result = await InvokePowerShellAsync ( "Invoke-Formatter" , argsDict ) ;
323
- return result ? . Select ( r => r ? . ImmediateBaseObject as string ) . FirstOrDefault ( ) ;
347
+ PowerShellResult result = await InvokePowerShellAsync ( "Invoke-Formatter" , argsDict ) ;
348
+
349
+ if ( result == null )
350
+ {
351
+ _logger . Write ( LogLevel . Error , "Formatter returned null result" ) ;
352
+ return null ;
353
+ }
354
+
355
+ if ( result . HasErrors )
356
+ {
357
+ var errorBuilder = new StringBuilder ( ) . Append ( s_indentJoin ) ;
358
+ foreach ( ErrorRecord err in result . Errors )
359
+ {
360
+ errorBuilder . Append ( err ) . Append ( s_indentJoin ) ;
361
+ }
362
+ _logger . Write ( LogLevel . Warning , $ "Errors found while formatting file: { errorBuilder } ") ;
363
+ return null ;
364
+ }
365
+
366
+ foreach ( PSObject resultObj in result . Output )
367
+ {
368
+ string formatResult = resultObj ? . BaseObject as string ;
369
+ if ( formatResult != null )
370
+ {
371
+ return formatResult ;
372
+ }
373
+ }
374
+
375
+ return null ;
324
376
}
325
377
326
378
#endregion // public methods
@@ -342,7 +394,7 @@ private async Task<ScriptFileMarker[]> GetSemanticMarkersAsync<TSettings>(
342
394
else
343
395
{
344
396
// Return an empty marker list
345
- return new ScriptFileMarker [ 0 ] ;
397
+ return s_emptyScriptMarkerResult ;
346
398
}
347
399
}
348
400
@@ -360,7 +412,7 @@ private async Task<ScriptFileMarker[]> GetSemanticMarkersAsync<TSettings>(
360
412
else
361
413
{
362
414
// Return an empty marker list
363
- return new ScriptFileMarker [ 0 ] ;
415
+ return s_emptyScriptMarkerResult ;
364
416
}
365
417
}
366
418
@@ -380,11 +432,16 @@ private void LogAvailablePssaFeatures()
380
432
// If we already know the module that was imported, save some work
381
433
if ( _pssaModuleInfo == null )
382
434
{
383
- PSObject [ ] modules = InvokePowerShell (
435
+ PowerShellResult getModuleResult = InvokePowerShell (
384
436
"Get-Module" ,
385
437
new Dictionary < string , object > { { "Name" , PSSA_MODULE_NAME } } ) ;
386
438
387
- _pssaModuleInfo = modules
439
+ if ( getModuleResult == null )
440
+ {
441
+ throw new AnalysisServiceLoadException ( "Get-Module call to find PSScriptAnalyzer module failed" ) ;
442
+ }
443
+
444
+ _pssaModuleInfo = getModuleResult . Output
388
445
. Select ( m => m . BaseObject )
389
446
. OfType < PSModuleInfo > ( )
390
447
. FirstOrDefault ( ) ;
@@ -426,7 +483,7 @@ private async Task<PSObject[]> GetDiagnosticRecordsAsync<TSettings>(
426
483
string [ ] rules ,
427
484
TSettings settings ) where TSettings : class
428
485
{
429
- var diagnosticRecords = new PSObject [ 0 ] ;
486
+ var diagnosticRecords = s_emptyDiagnosticResult ;
430
487
431
488
// When a new, empty file is created there are by definition no issues.
432
489
// Furthermore, if you call Invoke-ScriptAnalyzer with an empty ScriptDefinition
@@ -452,13 +509,15 @@ private async Task<PSObject[]> GetDiagnosticRecordsAsync<TSettings>(
452
509
settingArgument = rules ;
453
510
}
454
511
455
- diagnosticRecords = await InvokePowerShellAsync (
512
+ PowerShellResult result = await InvokePowerShellAsync (
456
513
"Invoke-ScriptAnalyzer" ,
457
514
new Dictionary < string , object >
458
515
{
459
516
{ "ScriptDefinition" , scriptContent } ,
460
517
{ settingParameter , settingArgument }
461
518
} ) ;
519
+
520
+ diagnosticRecords = result ? . Output ;
462
521
}
463
522
464
523
_logger . Write (
@@ -468,21 +527,26 @@ private async Task<PSObject[]> GetDiagnosticRecordsAsync<TSettings>(
468
527
return diagnosticRecords ;
469
528
}
470
529
471
- private PSObject [ ] InvokePowerShell ( string command , IDictionary < string , object > paramArgMap )
530
+ private PowerShellResult InvokePowerShell ( string command , IDictionary < string , object > paramArgMap = null )
472
531
{
473
532
using ( var powerShell = System . Management . Automation . PowerShell . Create ( ) )
474
533
{
475
534
powerShell . RunspacePool = _analysisRunspacePool ;
476
535
powerShell . AddCommand ( command ) ;
477
- foreach ( var kvp in paramArgMap )
536
+ if ( paramArgMap != null )
478
537
{
479
- powerShell . AddParameter ( kvp . Key , kvp . Value ) ;
538
+ foreach ( KeyValuePair < string , object > kvp in paramArgMap )
539
+ {
540
+ powerShell . AddParameter ( kvp . Key , kvp . Value ) ;
541
+ }
480
542
}
481
543
482
- var result = new PSObject [ 0 ] ;
544
+ PowerShellResult result = null ;
483
545
try
484
546
{
485
- result = powerShell . Invoke ( ) ? . ToArray ( ) ;
547
+ PSObject [ ] output = powerShell . Invoke ( ) . ToArray ( ) ;
548
+ ErrorRecord [ ] errors = powerShell . Streams . Error . ToArray ( ) ;
549
+ result = new PowerShellResult ( output , errors , powerShell . HadErrors ) ;
486
550
}
487
551
catch ( CommandNotFoundException ex )
488
552
{
@@ -503,7 +567,7 @@ private PSObject[] InvokePowerShell(string command, IDictionary<string, object>
503
567
}
504
568
}
505
569
506
- private async Task < PSObject [ ] > InvokePowerShellAsync ( string command , IDictionary < string , object > paramArgMap )
570
+ private async Task < PowerShellResult > InvokePowerShellAsync ( string command , IDictionary < string , object > paramArgMap = null )
507
571
{
508
572
var task = Task . Run ( ( ) =>
509
573
{
@@ -590,6 +654,29 @@ public void Dispose()
590
654
}
591
655
592
656
#endregion
657
+
658
+ /// <summary>
659
+ /// Wraps the result of an execution of PowerShell to send back through
660
+ /// asynchronous calls.
661
+ /// </summary>
662
+ private class PowerShellResult
663
+ {
664
+ public PowerShellResult (
665
+ PSObject [ ] output ,
666
+ ErrorRecord [ ] errors ,
667
+ bool hasErrors )
668
+ {
669
+ Output = output ;
670
+ Errors = errors ;
671
+ HasErrors = hasErrors ;
672
+ }
673
+
674
+ public PSObject [ ] Output { get ; }
675
+
676
+ public ErrorRecord [ ] Errors { get ; }
677
+
678
+ public bool HasErrors { get ; }
679
+ }
593
680
}
594
681
595
682
/// <summary>
0 commit comments