@@ -24,6 +24,48 @@ namespace Microsoft.PowerShell.EditorServices
2424 /// </summary>
2525 public class AnalysisService : IDisposable
2626 {
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+
2769 #region Private Fields
2870
2971 /// <summary>
@@ -53,31 +95,8 @@ public class AnalysisService : IDisposable
5395 /// </summary>
5496 private PSModuleInfo _pssaModuleInfo ;
5597
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-
7898 #endregion // Private Fields
7999
80-
81100 #region Properties
82101
83102 /// <summary>
@@ -281,9 +300,15 @@ public async Task<ScriptFileMarker[]> GetSemanticMarkersAsync(
281300 /// </summary>
282301 public IEnumerable < string > GetPSScriptAnalyzerRules ( )
283302 {
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 )
287312 {
288313 ruleNames . Add ( ( string ) rule . Members [ "RuleName" ] . Value ) ;
289314 }
@@ -319,8 +344,35 @@ public async Task<string> Format(
319344 argsDict . Add ( "Range" , rangeList ) ;
320345 }
321346
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 ;
324376 }
325377
326378 #endregion // public methods
@@ -342,7 +394,7 @@ private async Task<ScriptFileMarker[]> GetSemanticMarkersAsync<TSettings>(
342394 else
343395 {
344396 // Return an empty marker list
345- return new ScriptFileMarker [ 0 ] ;
397+ return s_emptyScriptMarkerResult ;
346398 }
347399 }
348400
@@ -360,7 +412,7 @@ private async Task<ScriptFileMarker[]> GetSemanticMarkersAsync<TSettings>(
360412 else
361413 {
362414 // Return an empty marker list
363- return new ScriptFileMarker [ 0 ] ;
415+ return s_emptyScriptMarkerResult ;
364416 }
365417 }
366418
@@ -380,11 +432,16 @@ private void LogAvailablePssaFeatures()
380432 // If we already know the module that was imported, save some work
381433 if ( _pssaModuleInfo == null )
382434 {
383- PSObject [ ] modules = InvokePowerShell (
435+ PowerShellResult getModuleResult = InvokePowerShell (
384436 "Get-Module" ,
385437 new Dictionary < string , object > { { "Name" , PSSA_MODULE_NAME } } ) ;
386438
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
388445 . Select ( m => m . BaseObject )
389446 . OfType < PSModuleInfo > ( )
390447 . FirstOrDefault ( ) ;
@@ -426,7 +483,7 @@ private async Task<PSObject[]> GetDiagnosticRecordsAsync<TSettings>(
426483 string [ ] rules ,
427484 TSettings settings ) where TSettings : class
428485 {
429- var diagnosticRecords = new PSObject [ 0 ] ;
486+ var diagnosticRecords = s_emptyDiagnosticResult ;
430487
431488 // When a new, empty file is created there are by definition no issues.
432489 // Furthermore, if you call Invoke-ScriptAnalyzer with an empty ScriptDefinition
@@ -452,13 +509,15 @@ private async Task<PSObject[]> GetDiagnosticRecordsAsync<TSettings>(
452509 settingArgument = rules ;
453510 }
454511
455- diagnosticRecords = await InvokePowerShellAsync (
512+ PowerShellResult result = await InvokePowerShellAsync (
456513 "Invoke-ScriptAnalyzer" ,
457514 new Dictionary < string , object >
458515 {
459516 { "ScriptDefinition" , scriptContent } ,
460517 { settingParameter , settingArgument }
461518 } ) ;
519+
520+ diagnosticRecords = result ? . Output ;
462521 }
463522
464523 _logger . Write (
@@ -468,21 +527,26 @@ private async Task<PSObject[]> GetDiagnosticRecordsAsync<TSettings>(
468527 return diagnosticRecords ;
469528 }
470529
471- private PSObject [ ] InvokePowerShell ( string command , IDictionary < string , object > paramArgMap )
530+ private PowerShellResult InvokePowerShell ( string command , IDictionary < string , object > paramArgMap = null )
472531 {
473532 using ( var powerShell = System . Management . Automation . PowerShell . Create ( ) )
474533 {
475534 powerShell . RunspacePool = _analysisRunspacePool ;
476535 powerShell . AddCommand ( command ) ;
477- foreach ( var kvp in paramArgMap )
536+ if ( paramArgMap != null )
478537 {
479- powerShell . AddParameter ( kvp . Key , kvp . Value ) ;
538+ foreach ( KeyValuePair < string , object > kvp in paramArgMap )
539+ {
540+ powerShell . AddParameter ( kvp . Key , kvp . Value ) ;
541+ }
480542 }
481543
482- var result = new PSObject [ 0 ] ;
544+ PowerShellResult result = null ;
483545 try
484546 {
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 ) ;
486550 }
487551 catch ( CommandNotFoundException ex )
488552 {
@@ -503,7 +567,7 @@ private PSObject[] InvokePowerShell(string command, IDictionary<string, object>
503567 }
504568 }
505569
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 )
507571 {
508572 var task = Task . Run ( ( ) =>
509573 {
@@ -590,6 +654,29 @@ public void Dispose()
590654 }
591655
592656 #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+ }
593680 }
594681
595682 /// <summary>
0 commit comments