diff --git a/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs b/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs index 656319470..a357535e9 100644 --- a/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs +++ b/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs @@ -295,7 +295,14 @@ protected Task HandlePauseRequest( object pauseParams, RequestContext requestContext) { - editorSession.DebugService.Break(); + try + { + editorSession.DebugService.Break(); + } + catch (NotSupportedException e) + { + return requestContext.SendError(e.Message); + } // This request is responded to by sending the "stopped" event return Task.FromResult(true); diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj index e813124e4..914047a31 100644 --- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj +++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj @@ -98,8 +98,12 @@ + + + + diff --git a/src/PowerShellEditorServices/Session/EditorSession.cs b/src/PowerShellEditorServices/Session/EditorSession.cs index 7ab2f3b59..a06c10e64 100644 --- a/src/PowerShellEditorServices/Session/EditorSession.cs +++ b/src/PowerShellEditorServices/Session/EditorSession.cs @@ -5,9 +5,11 @@ using Microsoft.PowerShell.EditorServices.Console; using Microsoft.PowerShell.EditorServices.Utility; +using System; using System.IO; using System.Management.Automation; using System.Management.Automation.Runspaces; +using System.Reflection; using System.Threading; namespace Microsoft.PowerShell.EditorServices @@ -66,17 +68,34 @@ public void StartSession() this.DebugService = new DebugService(this.PowerShellContext); this.ConsoleService = new ConsoleService(this.PowerShellContext); - // AnalysisService will throw FileNotFoundException if - // Script Analyzer binaries are not included. - try + // Only enable the AnalysisService if the machine has PowerShell + // v5 installed. Script Analyzer works on earlier PowerShell + // versions but our hard dependency on their binaries complicates + // the deployment and assembly loading since we would have to + // conditionally load the binaries for v3/v4 support. This problem + // will be solved in the future by using Script Analyzer as a + // module rather than an assembly dependency. + if (this.PowerShellContext.PowerShellVersion.Major >= 5) { - this.AnalysisService = new AnalysisService(); + // AnalysisService will throw FileNotFoundException if + // Script Analyzer binaries are not included. + try + { + this.AnalysisService = new AnalysisService(); + } + catch (FileNotFoundException) + { + Logger.Write( + LogLevel.Warning, + "Script Analyzer binaries not found, AnalysisService will be disabled."); + } } - catch (FileNotFoundException) + else { Logger.Write( - LogLevel.Warning, - "Script Analyzer binaries not found, AnalysisService will be disabled."); + LogLevel.Normal, + "Script Analyzer cannot be loaded due to unsupported PowerShell version " + + this.PowerShellContext.PowerShellVersion.ToString()); } // Create a workspace to contain open files diff --git a/src/PowerShellEditorServices/Session/IVersionSpecificOperations.cs b/src/PowerShellEditorServices/Session/IVersionSpecificOperations.cs new file mode 100644 index 000000000..f55ddbcab --- /dev/null +++ b/src/PowerShellEditorServices/Session/IVersionSpecificOperations.cs @@ -0,0 +1,25 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Collections.Generic; +using System.Management.Automation; +using System.Management.Automation.Runspaces; + +namespace Microsoft.PowerShell.EditorServices.Session +{ + internal interface IVersionSpecificOperations + { + void ConfigureDebugger(Runspace runspace); + + void PauseDebugger(Runspace runspace); + + IEnumerable ExecuteCommandInDebugger( + PowerShellContext powerShellContext, + Runspace currentRunspace, + PSCommand psCommand, + bool sendOutputToHost); + } +} + diff --git a/src/PowerShellEditorServices/Session/PowerShell3Operations.cs b/src/PowerShellEditorServices/Session/PowerShell3Operations.cs new file mode 100644 index 000000000..faa4996c9 --- /dev/null +++ b/src/PowerShellEditorServices/Session/PowerShell3Operations.cs @@ -0,0 +1,62 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; +using System.Management.Automation.Runspaces; + +namespace Microsoft.PowerShell.EditorServices.Session +{ + internal class PowerShell3Operations : IVersionSpecificOperations + { + public void ConfigureDebugger(Runspace runspace) + { + // The debugger has no SetDebugMode in PowerShell v3. + } + + public void PauseDebugger(Runspace runspace) + { + // The debugger cannot be paused in PowerShell v3. + throw new NotSupportedException("Debugger cannot be paused in PowerShell v3"); + } + + public IEnumerable ExecuteCommandInDebugger( + PowerShellContext powerShellContext, + Runspace currentRunspace, + PSCommand psCommand, + bool sendOutputToHost) + { + IEnumerable executionResult = null; + + using (var nestedPipeline = currentRunspace.CreateNestedPipeline()) + { + foreach (var command in psCommand.Commands) + { + nestedPipeline.Commands.Add(command); + } + + executionResult = + nestedPipeline + .Invoke() + .Select(pso => pso.BaseObject) + .Cast(); + } + + // Write the output to the host if necessary + if (sendOutputToHost) + { + foreach (var line in executionResult) + { + powerShellContext.WriteOutput(line.ToString(), true); + } + } + + return executionResult; + } + } +} + diff --git a/src/PowerShellEditorServices/Session/PowerShell4Operations.cs b/src/PowerShellEditorServices/Session/PowerShell4Operations.cs new file mode 100644 index 000000000..fd582f4bd --- /dev/null +++ b/src/PowerShellEditorServices/Session/PowerShell4Operations.cs @@ -0,0 +1,65 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; +using System.Management.Automation.Runspaces; + +namespace Microsoft.PowerShell.EditorServices.Session +{ + internal class PowerShell4Operations : IVersionSpecificOperations + { + public void ConfigureDebugger(Runspace runspace) + { +#if !PowerShellv3 + runspace.Debugger.SetDebugMode(DebugModes.LocalScript | DebugModes.RemoteScript); +#endif + } + + public virtual void PauseDebugger(Runspace runspace) + { + // The debugger cannot be paused in PowerShell v4. + throw new NotSupportedException("Debugger cannot be paused in PowerShell v4"); + } + + public IEnumerable ExecuteCommandInDebugger( + PowerShellContext powerShellContext, + Runspace currentRunspace, + PSCommand psCommand, + bool sendOutputToHost) + { + PSDataCollection outputCollection = new PSDataCollection(); + +#if !PowerShellv3 + if (sendOutputToHost) + { + outputCollection.DataAdded += + (obj, e) => + { + for (int i = e.Index; i < outputCollection.Count; i++) + { + powerShellContext.WriteOutput( + outputCollection[i].ToString(), + true); + } + }; + } + + DebuggerCommandResults commandResults = + currentRunspace.Debugger.ProcessCommand( + psCommand, + outputCollection); +#endif + + return + outputCollection + .Select(pso => pso.BaseObject) + .Cast(); + } + } +} + diff --git a/src/PowerShellEditorServices/Session/PowerShell5Operations.cs b/src/PowerShellEditorServices/Session/PowerShell5Operations.cs new file mode 100644 index 000000000..65c7f4527 --- /dev/null +++ b/src/PowerShellEditorServices/Session/PowerShell5Operations.cs @@ -0,0 +1,20 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Management.Automation.Runspaces; + +namespace Microsoft.PowerShell.EditorServices.Session +{ + internal class PowerShell5Operations : PowerShell4Operations + { + public override void PauseDebugger(Runspace runspace) + { +#if !PowerShellv3 && !PowerShellv4 + runspace.Debugger.SetDebuggerStepMode(true); +#endif + } + } +} + diff --git a/src/PowerShellEditorServices/Session/PowerShellContext.cs b/src/PowerShellEditorServices/Session/PowerShellContext.cs index d0aa07666..dd90c951b 100644 --- a/src/PowerShellEditorServices/Session/PowerShellContext.cs +++ b/src/PowerShellEditorServices/Session/PowerShellContext.cs @@ -18,6 +18,7 @@ namespace Microsoft.PowerShell.EditorServices { + using Session; using System.Management.Automation; using System.Management.Automation.Runspaces; using System.Reflection; @@ -38,6 +39,7 @@ public class PowerShellContext : IDisposable private Runspace currentRunspace; private ConsoleServicePSHost psHost; private InitialSessionState initialSessionState; + private IVersionSpecificOperations versionSpecificOperations; private int pipelineThreadId; private TaskCompletionSource debuggerStoppedTask; @@ -174,12 +176,28 @@ private void Initialize(Runspace initialRunspace) "PowerShell runtime version: {0}", this.PowerShellVersion)); -#if !PowerShellv3 - if (PowerShellVersion > new Version(3,0)) + if (PowerShellVersion >= new Version(5,0)) { - this.currentRunspace.Debugger.SetDebugMode(DebugModes.LocalScript | DebugModes.RemoteScript); + this.versionSpecificOperations = new PowerShell5Operations(); } -#endif + else if (PowerShellVersion.Major == 4) + { + this.versionSpecificOperations = new PowerShell4Operations(); + } + else if (PowerShellVersion.Major == 3) + { + this.versionSpecificOperations = new PowerShell3Operations(); + } + else + { + throw new NotSupportedException( + "This computer has an unsupported version of PowerShell installed: " + + PowerShellVersion.ToString()); + } + + // Configure the runspace's debugger + this.versionSpecificOperations.ConfigureDebugger( + this.currentRunspace); this.SessionState = PowerShellContextState.Ready; @@ -510,12 +528,9 @@ internal void BreakExecution() { Logger.Write(LogLevel.Verbose, "Debugger break requested..."); -#if PowerShellv5 - if (PowerShellVersion >= new Version(5, 0)) - { - this.currentRunspace.Debugger.SetDebuggerStepMode(true); - } -#endif + // Pause the debugger + this.versionSpecificOperations.PauseDebugger( + this.currentRunspace); } internal void ResumeDebugger(DebuggerResumeAction resumeAction) @@ -659,72 +674,14 @@ private void OnSessionStateChanged(object sender, SessionStateChangedEventArgs e private IEnumerable ExecuteCommandInDebugger(PSCommand psCommand, bool sendOutputToHost) { - IEnumerable executionResult = null; - - if (PowerShellVersion >= new Version(4, 0)) - { -#if PowerShellv4 || PowerShellv5 - PSDataCollection outputCollection = new PSDataCollection(); - - if (sendOutputToHost) - { - outputCollection.DataAdded += - (obj, e) => - { - for (int i = e.Index; i < outputCollection.Count; i++) - { - this.WriteOutput(outputCollection[i].ToString(), true); - } - }; - } - - DebuggerCommandResults commandResults = - this.currentRunspace.Debugger.ProcessCommand( - psCommand, - outputCollection); - - // If the command was a debugger action, run it - if (commandResults.ResumeAction.HasValue) - { - this.ResumeDebugger(commandResults.ResumeAction.Value); - } - - executionResult = - outputCollection - .Select(pso => pso.BaseObject) - .Cast(); -#endif - } - else - { - using (var nestedPipeline = this.currentRunspace.CreateNestedPipeline()) - { - foreach (var command in psCommand.Commands) - { - nestedPipeline.Commands.Add(command); - } - - executionResult = - nestedPipeline - .Invoke() - .Select(pso => pso.BaseObject) - .Cast(); - } - - // Write the output to the host if necessary - if (sendOutputToHost) - { - foreach (var line in executionResult) - { - this.WriteOutput(line.ToString(), true); - } - } - } - - return executionResult; + return this.versionSpecificOperations.ExecuteCommandInDebugger( + this, + this.currentRunspace, + psCommand, + sendOutputToHost); } - private void WriteOutput(string outputString, bool includeNewLine) + internal void WriteOutput(string outputString, bool includeNewLine) { if (this.ConsoleHost != null) { @@ -1064,7 +1021,8 @@ private void OnDebuggerStop(object sender, DebuggerStopEventArgs e) if (taskIndex == 0) { // Write a new output line before continuing - this.WriteOutput("", true); + // TODO: Re-enable this with fix for #133 + //this.WriteOutput("", true); e.ResumeAction = this.debuggerStoppedTask.Task.Result; Logger.Write(LogLevel.Verbose, "Received debugger resume action " + e.ResumeAction.ToString());