diff --git a/CHANGELOG.md b/CHANGELOG.md index 46106ab70..970df874b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # PowerShell Editor Services Release History +## 0.7.2 +### Friday, September 2, 2016 + +- Fixed #284: PowerShellContext.AbortException crashes when called more than once +- Fixed #285: PSScriptAnalyzer settings are not being passed to Invoke-ScriptAnalyzer +- Fixed #287: Language service crashes when invalid path chars are used in dot-sourced script reference + ## 0.7.1 ### Tuesday, August 23, 2016 diff --git a/appveyor.yml b/appveyor.yml index 1660201d8..74b046bfb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,7 +5,7 @@ clone_depth: 10 skip_tags: true environment: - core_version: '0.7.1' + core_version: '0.7.2' prerelease_name: '-beta' branches: diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 index 060c3a52f..7d54617cc 100644 --- a/module/PowerShellEditorServices/PowerShellEditorServices.psd1 +++ b/module/PowerShellEditorServices/PowerShellEditorServices.psd1 @@ -12,7 +12,7 @@ RootModule = 'PowerShellEditorServices.psm1' # Version number of this module. -ModuleVersion = '0.7.1' +ModuleVersion = '0.7.2' # ID used to uniquely identify this module GUID = '9ca15887-53a2-479a-9cda-48d26bcb6c47' diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageDispatcher.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageDispatcher.cs index d68123297..5b2be66b4 100644 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageDispatcher.cs +++ b/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageDispatcher.cs @@ -331,6 +331,12 @@ private void OnListenTaskCompleted(Task listenTask) { if (listenTask.IsFaulted) { + Logger.Write( + LogLevel.Error, + string.Format( + "MessageDispatcher loop terminated due to unhandled exception:\r\n\r\n{0}", + listenTask.Exception.ToString())); + this.OnUnhandledException(listenTask.Exception); } else if (listenTask.IsCompleted || listenTask.IsCanceled) diff --git a/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs b/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs index 8cc3a56b1..70f5c8999 100644 --- a/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs +++ b/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs @@ -201,15 +201,20 @@ protected Task HandleDisconnectRequest( if (e.NewSessionState == PowerShellContextState.Ready) { await requestContext.SendResult(null); - editorSession.PowerShellContext.SessionStateChanged -= handler; + this.editorSession.PowerShellContext.SessionStateChanged -= handler; // Stop the server await this.Stop(); } }; - editorSession.PowerShellContext.SessionStateChanged += handler; - editorSession.PowerShellContext.AbortExecution(); + // In some rare cases, the EditorSession will already be disposed + // so we shouldn't try to abort because PowerShellContext will be null + if (this.editorSession != null && this.editorSession.PowerShellContext != null) + { + this.editorSession.PowerShellContext.SessionStateChanged += handler; + this.editorSession.PowerShellContext.AbortExecution(); + } return Task.FromResult(true); } diff --git a/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs b/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs index 0aaf479f9..38a12a5d7 100644 --- a/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs +++ b/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs @@ -366,7 +366,7 @@ protected async Task HandleDidChangeConfigurationNotification( string newSettingsPath = this.currentSettings.ScriptAnalysis.SettingsPath; if (!string.Equals(oldScriptAnalysisSettingsPath, newSettingsPath, StringComparison.OrdinalIgnoreCase)) { - this.editorSession.RestartAnalysisService(newSettingsPath); + this.editorSession.AnalysisService.SettingsPath = newSettingsPath; settingsPathChanged = true; } diff --git a/src/PowerShellEditorServices/Analysis/AnalysisService.cs b/src/PowerShellEditorServices/Analysis/AnalysisService.cs index 7ceceef1e..375af91e9 100644 --- a/src/PowerShellEditorServices/Analysis/AnalysisService.cs +++ b/src/PowerShellEditorServices/Analysis/AnalysisService.cs @@ -5,7 +5,6 @@ using Microsoft.PowerShell.EditorServices.Utility; using System; -using System.IO; using System.Linq; using System.Management.Automation.Runspaces; using System.Threading; @@ -44,6 +43,21 @@ public class AnalysisService : IDisposable #endregion // Private Fields + + #region Properties + + /// + /// Gets or sets the path to a settings file (.psd1) + /// containing PSScriptAnalyzer settings. + /// + public string SettingsPath + { + get; + set; + } + + #endregion + #region Constructors /// @@ -56,6 +70,7 @@ public AnalysisService(IConsoleHost consoleHost, string settingsPath = null) { try { + this.SettingsPath = settingsPath; this.analysisRunspace = RunspaceFactory.CreateRunspace(InitialSessionState.CreateDefault2()); this.analysisRunspace.ThreadOptions = PSThreadOptions.ReuseThread; this.analysisRunspace.Open(); @@ -219,17 +234,28 @@ private IEnumerable GetDiagnosticRecords(ScriptFile file) if (this.scriptAnalyzerModuleInfo != null) { - using (var ps = System.Management.Automation.PowerShell.Create()) + using (var powerShell = System.Management.Automation.PowerShell.Create()) { - ps.Runspace = this.analysisRunspace; + powerShell.Runspace = this.analysisRunspace; Logger.Write( LogLevel.Verbose, String.Format("Running PSScriptAnalyzer against {0}", file.FilePath)); - diagnosticRecords = ps.AddCommand("Invoke-ScriptAnalyzer") - .AddParameter("ScriptDefinition", file.Contents) - .AddParameter("IncludeRule", IncludedRules) - .Invoke(); + powerShell + .AddCommand("Invoke-ScriptAnalyzer") + .AddParameter("ScriptDefinition", file.Contents); + + // Use a settings file if one is provided, otherwise use the default rule list. + if (!string.IsNullOrWhiteSpace(this.SettingsPath)) + { + powerShell.AddParameter("Settings", this.SettingsPath); + } + else + { + powerShell.AddParameter("IncludeRule", IncludedRules); + } + + diagnosticRecords = powerShell.Invoke(); } } diff --git a/src/PowerShellEditorServices/Language/FindDotSourcedVisitor.cs b/src/PowerShellEditorServices/Language/FindDotSourcedVisitor.cs index bbd0832e7..7ffda9c2a 100644 --- a/src/PowerShellEditorServices/Language/FindDotSourcedVisitor.cs +++ b/src/PowerShellEditorServices/Language/FindDotSourcedVisitor.cs @@ -34,9 +34,11 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) { if (commandAst.InvocationOperator.Equals(TokenKind.Dot)) { - string fileName = commandAst.CommandElements[0].Extent.Text; + // Strip any quote characters off of the string + string fileName = commandAst.CommandElements[0].Extent.Text.Trim('\'', '"'); DotSourcedFiles.Add(fileName); } + return base.VisitCommand(commandAst); } } diff --git a/src/PowerShellEditorServices/Session/EditorSession.cs b/src/PowerShellEditorServices/Session/EditorSession.cs index 6352c046c..4bc539959 100644 --- a/src/PowerShellEditorServices/Session/EditorSession.cs +++ b/src/PowerShellEditorServices/Session/EditorSession.cs @@ -104,16 +104,6 @@ public void StartDebugSession(HostDetails hostDetails, ProfilePaths profilePaths this.Workspace = new Workspace(this.PowerShellContext.PowerShellVersion); } - /// - /// Restarts the AnalysisService so it can be configured with a new settings file. - /// - /// Path to the settings file. - public void RestartAnalysisService(string settingsPath) - { - this.AnalysisService?.Dispose(); - InstantiateAnalysisService(settingsPath); - } - internal void InstantiateAnalysisService(string settingsPath = null) { // Only enable the AnalysisService if the machine has PowerShell diff --git a/src/PowerShellEditorServices/Session/PowerShellContext.cs b/src/PowerShellEditorServices/Session/PowerShellContext.cs index 093d8c2c6..78c13798c 100644 --- a/src/PowerShellEditorServices/Session/PowerShellContext.cs +++ b/src/PowerShellEditorServices/Session/PowerShellContext.cs @@ -571,7 +571,8 @@ public async Task LoadHostProfiles() /// public void AbortExecution() { - if (this.SessionState != PowerShellContextState.Aborting) + if (this.SessionState != PowerShellContextState.Aborting && + this.SessionState != PowerShellContextState.Disposed) { Logger.Write(LogLevel.Verbose, "Execution abort requested..."); @@ -587,7 +588,8 @@ public void AbortExecution() { Logger.Write( LogLevel.Verbose, - "Execution abort requested while already aborting"); + string.Format( + $"Execution abort requested when already aborted (SessionState = {this.SessionState})")); } } diff --git a/src/PowerShellEditorServices/Workspace/Workspace.cs b/src/PowerShellEditorServices/Workspace/Workspace.cs index ebe35d45b..24ca0932e 100644 --- a/src/PowerShellEditorServices/Workspace/Workspace.cs +++ b/src/PowerShellEditorServices/Workspace/Workspace.cs @@ -203,6 +203,12 @@ private void RecursivelyFindReferences( baseFilePath, referencedFileName); + // If there was an error resolving the string, skip this reference + if (resolvedScriptPath == null) + { + continue; + } + Logger.Write( LogLevel.Verbose, string.Format( @@ -287,18 +293,49 @@ private string GetBaseFilePath(string filePath) private string ResolveRelativeScriptPath(string baseFilePath, string relativePath) { - if (Path.IsPathRooted(relativePath)) + string combinedPath = null; + Exception resolveException = null; + + try { - return relativePath; + // If the path is already absolute there's no need to resolve it relatively + // to the baseFilePath. + if (Path.IsPathRooted(relativePath)) + { + return relativePath; + } + + // Get the directory of the original script file, combine it + // with the given path and then resolve the absolute file path. + combinedPath = + Path.GetFullPath( + Path.Combine( + baseFilePath, + relativePath)); + } + catch (NotSupportedException e) + { + // Occurs if the path is incorrectly formatted for any reason. One + // instance where this occurred is when a user had curly double-quote + // characters in their source instead of normal double-quotes. + resolveException = e; + } + catch (ArgumentException e) + { + // Occurs if the path contains invalid characters, specifically those + // listed in System.IO.Path.InvalidPathChars. + resolveException = e; } - // Get the directory of the original script file, combine it - // with the given path and then resolve the absolute file path. - string combinedPath = - Path.GetFullPath( - Path.Combine( - baseFilePath, - relativePath)); + if (resolveException != null) + { + Logger.Write( + LogLevel.Error, + $"Could not resolve relative script path\r\n" + + $" baseFilePath = {baseFilePath}\r\n " + + $" relativePath = {relativePath}\r\n\r\n" + + $"{resolveException.ToString()}"); + } return combinedPath; } diff --git a/test/PowerShellEditorServices.Test.Host/ServerTestsBase.cs b/test/PowerShellEditorServices.Test.Host/ServerTestsBase.cs index 46baf3924..55eabdd65 100644 --- a/test/PowerShellEditorServices.Test.Host/ServerTestsBase.cs +++ b/test/PowerShellEditorServices.Test.Host/ServerTestsBase.cs @@ -34,7 +34,7 @@ protected async Task> LaunchService( string scriptPath = Path.Combine(modulePath, "Start-EditorServices.ps1"); // TODO: Need to determine the right module version programmatically! - string editorServicesModuleVersion = "0.7.1"; + string editorServicesModuleVersion = "0.7.2"; string scriptArgs = string.Format(