diff --git a/src/PowerShellEditorServices/Analysis/AnalysisService.cs b/src/PowerShellEditorServices/Analysis/AnalysisService.cs
index 0d9763b9e..40b6327e5 100644
--- a/src/PowerShellEditorServices/Analysis/AnalysisService.cs
+++ b/src/PowerShellEditorServices/Analysis/AnalysisService.cs
@@ -24,6 +24,48 @@ namespace Microsoft.PowerShell.EditorServices
///
public class AnalysisService : IDisposable
{
+ #region Static fields
+
+ ///
+ /// Defines the list of Script Analyzer rules to include by default if
+ /// no settings file is specified.
+ ///
+ private static readonly string[] s_includedRules = {
+ "PSUseToExportFieldsInManifest",
+ "PSMisleadingBacktick",
+ "PSAvoidUsingCmdletAliases",
+ "PSUseApprovedVerbs",
+ "PSAvoidUsingPlainTextForPassword",
+ "PSReservedCmdletChar",
+ "PSReservedParams",
+ "PSShouldProcess",
+ "PSMissingModuleManifestField",
+ "PSAvoidDefaultValueSwitchParameter",
+ "PSUseDeclaredVarsMoreThanAssignments",
+ "PSPossibleIncorrectComparisonWithNull",
+ "PSAvoidDefaultValueForMandatoryParameter",
+ "PSPossibleIncorrectUsageOfRedirectionOperator"
+ };
+
+ ///
+ /// An empty diagnostic result to return when a script fails analysis.
+ ///
+ private static readonly PSObject[] s_emptyDiagnosticResult = new PSObject[0];
+
+ ///
+ /// An empty script marker result to return when no script markers can be returned.
+ ///
+ private static readonly ScriptFileMarker[] s_emptyScriptMarkerResult = new ScriptFileMarker[0];
+
+ private static readonly string[] s_emptyGetRuleResult = new string[0];
+
+ ///
+ /// The indentation to add when the logger lists errors.
+ ///
+ private static readonly string s_indentJoin = Environment.NewLine + " ";
+
+ #endregion // Static fields
+
#region Private Fields
///
@@ -53,31 +95,8 @@ public class AnalysisService : IDisposable
///
private PSModuleInfo _pssaModuleInfo;
- ///
- /// Defines the list of Script Analyzer rules to include by default if
- /// no settings file is specified.
- ///
- private static readonly string[] s_includedRules = new string[]
- {
- "PSUseToExportFieldsInManifest",
- "PSMisleadingBacktick",
- "PSAvoidUsingCmdletAliases",
- "PSUseApprovedVerbs",
- "PSAvoidUsingPlainTextForPassword",
- "PSReservedCmdletChar",
- "PSReservedParams",
- "PSShouldProcess",
- "PSMissingModuleManifestField",
- "PSAvoidDefaultValueSwitchParameter",
- "PSUseDeclaredVarsMoreThanAssignments",
- "PSPossibleIncorrectComparisonWithNull",
- "PSAvoidDefaultValueForMandatoryParameter",
- "PSPossibleIncorrectUsageOfRedirectionOperator"
- };
-
#endregion // Private Fields
-
#region Properties
///
@@ -281,9 +300,15 @@ public async Task GetSemanticMarkersAsync(
///
public IEnumerable GetPSScriptAnalyzerRules()
{
- List ruleNames = new List();
- var ruleObjects = InvokePowerShell("Get-ScriptAnalyzerRule", new Dictionary());
- foreach (var rule in ruleObjects)
+ PowerShellResult getRuleResult = InvokePowerShell("Get-ScriptAnalyzerRule");
+ if (getRuleResult == null)
+ {
+ _logger.Write(LogLevel.Warning, "Get-ScriptAnalyzerRule returned null result");
+ return s_emptyGetRuleResult;
+ }
+
+ var ruleNames = new List();
+ foreach (var rule in getRuleResult.Output)
{
ruleNames.Add((string)rule.Members["RuleName"].Value);
}
@@ -319,8 +344,35 @@ public async Task Format(
argsDict.Add("Range", rangeList);
}
- var result = await InvokePowerShellAsync("Invoke-Formatter", argsDict);
- return result?.Select(r => r?.ImmediateBaseObject as string).FirstOrDefault();
+ PowerShellResult result = await InvokePowerShellAsync("Invoke-Formatter", argsDict);
+
+ if (result == null)
+ {
+ _logger.Write(LogLevel.Error, "Formatter returned null result");
+ return null;
+ }
+
+ if (result.HasErrors)
+ {
+ var errorBuilder = new StringBuilder().Append(s_indentJoin);
+ foreach (ErrorRecord err in result.Errors)
+ {
+ errorBuilder.Append(err).Append(s_indentJoin);
+ }
+ _logger.Write(LogLevel.Warning, $"Errors found while formatting file: {errorBuilder}");
+ return null;
+ }
+
+ foreach (PSObject resultObj in result.Output)
+ {
+ string formatResult = resultObj?.BaseObject as string;
+ if (formatResult != null)
+ {
+ return formatResult;
+ }
+ }
+
+ return null;
}
#endregion // public methods
@@ -342,7 +394,7 @@ private async Task GetSemanticMarkersAsync(
else
{
// Return an empty marker list
- return new ScriptFileMarker[0];
+ return s_emptyScriptMarkerResult;
}
}
@@ -360,7 +412,7 @@ private async Task GetSemanticMarkersAsync(
else
{
// Return an empty marker list
- return new ScriptFileMarker[0];
+ return s_emptyScriptMarkerResult;
}
}
@@ -380,11 +432,16 @@ private void LogAvailablePssaFeatures()
// If we already know the module that was imported, save some work
if (_pssaModuleInfo == null)
{
- PSObject[] modules = InvokePowerShell(
+ PowerShellResult getModuleResult = InvokePowerShell(
"Get-Module",
new Dictionary{ {"Name", PSSA_MODULE_NAME} });
- _pssaModuleInfo = modules
+ if (getModuleResult == null)
+ {
+ throw new AnalysisServiceLoadException("Get-Module call to find PSScriptAnalyzer module failed");
+ }
+
+ _pssaModuleInfo = getModuleResult.Output
.Select(m => m.BaseObject)
.OfType()
.FirstOrDefault();
@@ -426,7 +483,7 @@ private async Task GetDiagnosticRecordsAsync(
string[] rules,
TSettings settings) where TSettings : class
{
- var diagnosticRecords = new PSObject[0];
+ var diagnosticRecords = s_emptyDiagnosticResult;
// When a new, empty file is created there are by definition no issues.
// Furthermore, if you call Invoke-ScriptAnalyzer with an empty ScriptDefinition
@@ -452,13 +509,15 @@ private async Task GetDiagnosticRecordsAsync(
settingArgument = rules;
}
- diagnosticRecords = await InvokePowerShellAsync(
+ PowerShellResult result = await InvokePowerShellAsync(
"Invoke-ScriptAnalyzer",
new Dictionary
{
{ "ScriptDefinition", scriptContent },
{ settingParameter, settingArgument }
});
+
+ diagnosticRecords = result?.Output;
}
_logger.Write(
@@ -468,21 +527,26 @@ private async Task GetDiagnosticRecordsAsync(
return diagnosticRecords;
}
- private PSObject[] InvokePowerShell(string command, IDictionary paramArgMap)
+ private PowerShellResult InvokePowerShell(string command, IDictionary paramArgMap = null)
{
using (var powerShell = System.Management.Automation.PowerShell.Create())
{
powerShell.RunspacePool = _analysisRunspacePool;
powerShell.AddCommand(command);
- foreach (var kvp in paramArgMap)
+ if (paramArgMap != null)
{
- powerShell.AddParameter(kvp.Key, kvp.Value);
+ foreach (KeyValuePair kvp in paramArgMap)
+ {
+ powerShell.AddParameter(kvp.Key, kvp.Value);
+ }
}
- var result = new PSObject[0];
+ PowerShellResult result = null;
try
{
- result = powerShell.Invoke()?.ToArray();
+ PSObject[] output = powerShell.Invoke().ToArray();
+ ErrorRecord[] errors = powerShell.Streams.Error.ToArray();
+ result = new PowerShellResult(output, errors, powerShell.HadErrors);
}
catch (CommandNotFoundException ex)
{
@@ -503,7 +567,7 @@ private PSObject[] InvokePowerShell(string command, IDictionary
}
}
- private async Task InvokePowerShellAsync(string command, IDictionary paramArgMap)
+ private async Task InvokePowerShellAsync(string command, IDictionary paramArgMap = null)
{
var task = Task.Run(() =>
{
@@ -590,6 +654,29 @@ public void Dispose()
}
#endregion
+
+ ///
+ /// Wraps the result of an execution of PowerShell to send back through
+ /// asynchronous calls.
+ ///
+ private class PowerShellResult
+ {
+ public PowerShellResult(
+ PSObject[] output,
+ ErrorRecord[] errors,
+ bool hasErrors)
+ {
+ Output = output;
+ Errors = errors;
+ HasErrors = hasErrors;
+ }
+
+ public PSObject[] Output { get; }
+
+ public ErrorRecord[] Errors { get; }
+
+ public bool HasErrors { get; }
+ }
}
///