Skip to content

Commit 7a1b4fa

Browse files
committed
Merge pull request #150 from rkeithhill/rkeithhill/is149-scriptpath-spaces
Fixes #149 script paths with spaces
2 parents 5a34236 + f639b3c commit 7a1b4fa

File tree

7 files changed

+55
-14
lines changed

7 files changed

+55
-14
lines changed

src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ protected async Task HandleLaunchRequest(
8585
// In case that is null, use the the folder of the script to be executed. If the resulting
8686
// working dir path is a file path then extract the directory and use that.
8787
string workingDir = launchParams.Cwd ?? launchParams.Program;
88+
workingDir = PowerShellContext.UnescapePath(workingDir);
8889
try
8990
{
9091
if ((File.GetAttributes(workingDir) & FileAttributes.Directory) != FileAttributes.Directory)
@@ -94,16 +95,11 @@ protected async Task HandleLaunchRequest(
9495
}
9596
catch (Exception ex)
9697
{
97-
Logger.Write(LogLevel.Error, "cwd path is bad: " + ex.Message);
98+
Logger.Write(LogLevel.Error, "cwd path is invalid: " + ex.Message);
9899
workingDir = Environment.CurrentDirectory;
99100
}
100101

101-
var setWorkingDirCommand = new PSCommand();
102-
setWorkingDirCommand.AddCommand(@"Microsoft.PowerShell.Management\Set-Location")
103-
.AddParameter("LiteralPath", workingDir);
104-
105-
await editorSession.PowerShellContext.ExecuteCommand(setWorkingDirCommand);
106-
102+
editorSession.PowerShellContext.SetWorkingDirectory(workingDir);
107103
Logger.Write(LogLevel.Verbose, "Working dir set to: " + workingDir);
108104

109105
// Prepare arguments to the script - if specified

src/PowerShellEditorServices/Debugging/DebugService.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ public async Task<BreakpointDetails[]> SetBreakpoints(
7979
{
8080
// Fix for issue #123 - file paths that contain wildcard chars [ and ] need to
8181
// quoted and have those wildcard chars escaped.
82-
string escapedScriptPath = PowerShellContext.EscapeWildcardsInPath(scriptFile.FilePath);
82+
string escapedScriptPath =
83+
PowerShellContext.EscapePath(scriptFile.FilePath, escapeSpaces: false);
8384

8485
PSCommand psCommand = new PSCommand();
8586
psCommand.AddCommand("Set-PSBreakpoint");

src/PowerShellEditorServices/Session/PowerShellContext.cs

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using System.Collections.ObjectModel;
1313
using System.Linq;
1414
using System.Text;
15+
using System.Text.RegularExpressions;
1516
using System.Threading;
1617
using System.Threading.Tasks;
1718

@@ -457,7 +458,7 @@ public async Task ExecuteScriptAtPath(string scriptPath, string arguments = null
457458
// If we don't escape wildcard characters in the script path, the script can
458459
// fail to execute if say the script name was foo][.ps1.
459460
// Related to issue #123.
460-
string escapedScriptPath = EscapeWildcardsInPath(scriptPath);
461+
string escapedScriptPath = EscapePath(scriptPath, escapeSpaces: true);
461462

462463
if (arguments != null)
463464
{
@@ -577,13 +578,48 @@ internal void ReleaseRunspaceHandle(RunspaceHandle runspaceHandle)
577578
}
578579

579580
/// <summary>
580-
/// Returns the passed in path with the [ and ] wildcard characters escaped.
581+
/// Sets the current working directory of the powershell context. The path should be
582+
/// unescaped before calling this method.
583+
/// </summary>
584+
/// <param name="path"></param>
585+
public void SetWorkingDirectory(string path)
586+
{
587+
this.currentRunspace.SessionStateProxy.Path.SetLocation(path);
588+
}
589+
590+
/// <summary>
591+
/// Returns the passed in path with the [ and ] characters escaped. Escaping spaces is optional.
581592
/// </summary>
582593
/// <param name="path">The path to process.</param>
594+
/// <param name="escapeSpaces">Specify True to escape spaces in the path, otherwise False.</param>
583595
/// <returns>The path with [ and ] escaped.</returns>
584-
internal static string EscapeWildcardsInPath(string path)
596+
public static string EscapePath(string path, bool escapeSpaces)
585597
{
586-
return path.Replace("[", "`[").Replace("]", "`]");
598+
string escapedPath = Regex.Replace(path, @"(?<!`)\[", "`[");
599+
escapedPath = Regex.Replace(escapedPath, @"(?<!`)\]", "`]");
600+
601+
if (escapeSpaces)
602+
{
603+
escapedPath = Regex.Replace(escapedPath, @"(?<!`) ", "` ");
604+
}
605+
606+
return escapedPath;
607+
}
608+
609+
/// <summary>
610+
/// Unescapes any escaped [, ] or space characters. Typically use this before calling a
611+
/// .NET API that doesn't understand PowerShell escaped chars.
612+
/// </summary>
613+
/// <param name="path">The path to unescape.</param>
614+
/// <returns>The path with the ` character before [, ] and spaces removed.</returns>
615+
public static string UnescapePath(string path)
616+
{
617+
if (!path.Contains("`"))
618+
{
619+
return path;
620+
}
621+
622+
return Regex.Replace(path, @"`(?=[ \[\]])", "");
587623
}
588624

589625
#endregion

src/PowerShellEditorServices/Workspace/Workspace.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,11 @@ private string ResolveFilePath(string filePath)
210210
// Some clients send paths with UNIX-style slashes, replace those if necessary
211211
filePath = filePath.Replace('/', '\\');
212212

213+
// Clients could specify paths with escaped space, [ and ] characters which .NET APIs
214+
// will not handle. These paths will get appropriately escaped just before being passed
215+
// into the PowerShell engine.
216+
filePath = PowerShellContext.UnescapePath(filePath);
217+
213218
// Get the absolute file path
214219
filePath = Path.GetFullPath(filePath);
215220
}

test/PowerShellEditorServices.Test.Shared/PowerShellEditorServices.Test.Shared.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
<None Include="Completion\CompletionExamples.psm1">
6565
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
6666
</None>
67-
<None Include="Debugging\DebugWithParamsTest.ps1" />
67+
<None Include="Debugging\Debug With Params [Test].ps1" />
6868
<None Include="Debugging\VariableTest.ps1" />
6969
<None Include="SymbolDetails\SymbolDetails.ps1" />
7070
<None Include="Symbols\MultipleSymbols.ps1" />

test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,12 @@ public static IEnumerable<object[]> DebuggerAcceptsScriptArgsTestData
8787
[MemberData("DebuggerAcceptsScriptArgsTestData")]
8888
public async Task DebuggerAcceptsScriptArgs(string[] args)
8989
{
90+
// The path is intentionally odd (some escaped chars but not all) because we are testing
91+
// the internal path escaping mechanism - it should escape certains chars ([, ] and space) but
92+
// it should not escape already escaped chars.
9093
ScriptFile debugWithParamsFile =
9194
this.workspace.GetFile(
92-
@"..\..\..\PowerShellEditorServices.Test.Shared\Debugging\DebugWithParamsTest.ps1");
95+
@"..\..\..\PowerShellEditorServices.Test.Shared\Debugging\Debug` With Params `[Test].ps1");
9396

9497
await this.debugService.SetBreakpoints(
9598
debugWithParamsFile,

0 commit comments

Comments
 (0)