Skip to content

Commit b7c875b

Browse files
authored
Remove PSNativePSPathResolution experimental feature (#17670)
1 parent 1437d85 commit b7c875b

5 files changed

Lines changed: 78 additions & 265 deletions

File tree

experimental-feature-linux.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
"PSLoadAssemblyFromNativeCode",
88
"PSNativeCommandArgumentPassing",
99
"PSNativeCommandErrorActionPreference",
10-
"PSNativePSPathResolution",
1110
"PSRemotingSSHTransportErrorHandling",
1211
"PSStrictModeAssignment",
1312
"PSSubsystemPluginModel"

experimental-feature-windows.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
"PSLoadAssemblyFromNativeCode",
88
"PSNativeCommandArgumentPassing",
99
"PSNativeCommandErrorActionPreference",
10-
"PSNativePSPathResolution",
1110
"PSRemotingSSHTransportErrorHandling",
1211
"PSStrictModeAssignment",
1312
"PSSubsystemPluginModel"

src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,6 @@ static ExperimentalFeature()
113113
new ExperimentalFeature(
114114
name: "PSCommandNotFoundSuggestion",
115115
description: "Recommend potential commands based on fuzzy search on a CommandNotFoundException"),
116-
new ExperimentalFeature(
117-
name: "PSNativePSPathResolution",
118-
description: "Convert PSPath to filesystem path, if possible, for native commands"),
119116
new ExperimentalFeature(
120117
name: "PSSubsystemPluginModel",
121118
description: "A plugin model for registering and un-registering PowerShell subsystems"),

src/System.Management.Automation/engine/NativeCommandParameterBinder.cs

Lines changed: 74 additions & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ internal void BindParameters(Collection<CommandParameterInternal> parameters)
8383
if (parameter.ParameterNameSpecified)
8484
{
8585
Diagnostics.Assert(!parameter.ParameterText.Contains(' '), "Parameters cannot have whitespace");
86-
PossiblyGlobArg(parameter.ParameterText, parameter, StringConstantType.BareWord);
86+
PossiblyGlobArg(parameter.ParameterText, parameter, usedQuotes: false);
8787

8888
if (parameter.SpaceAfterParameter)
8989
{
@@ -108,30 +108,22 @@ internal void BindParameters(Collection<CommandParameterInternal> parameters)
108108
// windbg -k com:port=\\devbox\pipe\debug,pipe,resets=0,reconnect
109109
// The parser produced an array of strings but marked the parameter so we
110110
// can properly reconstruct the correct command line.
111-
StringConstantType stringConstantType = StringConstantType.BareWord;
111+
bool usedQuotes = false;
112112
ArrayLiteralAst arrayLiteralAst = null;
113113
switch (parameter?.ArgumentAst)
114114
{
115115
case StringConstantExpressionAst sce:
116-
stringConstantType = sce.StringConstantType;
116+
usedQuotes = sce.StringConstantType != StringConstantType.BareWord;
117117
break;
118118
case ExpandableStringExpressionAst ese:
119-
stringConstantType = ese.StringConstantType;
119+
usedQuotes = ese.StringConstantType != StringConstantType.BareWord;
120120
break;
121121
case ArrayLiteralAst ala:
122122
arrayLiteralAst = ala;
123123
break;
124124
}
125125

126-
// Prior to PSNativePSPathResolution experimental feature, a single quote worked the same as a double quote
127-
// so if the feature is not enabled, we treat any quotes as double quotes. When this feature is no longer
128-
// experimental, this code here needs to be removed.
129-
if (!ExperimentalFeature.IsEnabled("PSNativePSPathResolution") && stringConstantType == StringConstantType.SingleQuoted)
130-
{
131-
stringConstantType = StringConstantType.DoubleQuoted;
132-
}
133-
134-
AppendOneNativeArgument(Context, parameter, argValue, arrayLiteralAst, sawVerbatimArgumentMarker, stringConstantType);
126+
AppendOneNativeArgument(Context, parameter, argValue, arrayLiteralAst, sawVerbatimArgumentMarker, usedQuotes);
135127
}
136128
}
137129
}
@@ -225,8 +217,8 @@ internal NativeArgumentPassingStyle ArgumentPassingStyle
225217
/// <param name="obj">The object to append.</param>
226218
/// <param name="argArrayAst">If the argument was an array literal, the Ast, otherwise null.</param>
227219
/// <param name="sawVerbatimArgumentMarker">True if the argument occurs after --%.</param>
228-
/// <param name="stringConstantType">Bare, SingleQuoted, or DoubleQuoted.</param>
229-
private void AppendOneNativeArgument(ExecutionContext context, CommandParameterInternal parameter, object obj, ArrayLiteralAst argArrayAst, bool sawVerbatimArgumentMarker, StringConstantType stringConstantType)
220+
/// <param name="usedQuotes">True if the argument was a quoted string (single or double).</param>
221+
private void AppendOneNativeArgument(ExecutionContext context, CommandParameterInternal parameter, object obj, ArrayLiteralAst argArrayAst, bool sawVerbatimArgumentMarker, bool usedQuotes)
230222
{
231223
IEnumerator list = LanguagePrimitives.GetEnumerator(obj);
232224

@@ -291,20 +283,11 @@ private void AppendOneNativeArgument(ExecutionContext context, CommandParameterI
291283
if (NeedQuotes(arg))
292284
{
293285
_arguments.Append('"');
294-
295-
if (stringConstantType == StringConstantType.DoubleQuoted)
296-
{
297-
_arguments.Append(ResolvePath(arg, Context));
298-
AddToArgumentList(parameter, ResolvePath(arg, Context));
299-
}
300-
else
301-
{
302-
_arguments.Append(arg);
303-
AddToArgumentList(parameter, arg);
304-
}
286+
AddToArgumentList(parameter, arg);
305287

306288
// need to escape all trailing backslashes so the native command receives it correctly
307289
// according to http://www.daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULESDOC
290+
_arguments.Append(arg);
308291
for (int i = arg.Length - 1; i >= 0 && arg[i] == '\\'; i--)
309292
{
310293
_arguments.Append('\\');
@@ -319,14 +302,14 @@ private void AppendOneNativeArgument(ExecutionContext context, CommandParameterI
319302
// We have a literal array, so take the extent, break it on spaces and add them to the argument list.
320303
foreach (string element in argArrayAst.Extent.Text.Split(' ', StringSplitOptions.RemoveEmptyEntries))
321304
{
322-
PossiblyGlobArg(element, parameter, stringConstantType);
305+
PossiblyGlobArg(element, parameter, usedQuotes);
323306
}
324307

325308
break;
326309
}
327310
else
328311
{
329-
PossiblyGlobArg(arg, parameter, stringConstantType);
312+
PossiblyGlobArg(arg, parameter, usedQuotes);
330313
}
331314
}
332315
}
@@ -346,173 +329,98 @@ private void AppendOneNativeArgument(ExecutionContext context, CommandParameterI
346329
/// </summary>
347330
/// <param name="arg">The argument that possibly needs expansion.</param>
348331
/// <param name="parameter">The parameter associated with the operation.</param>
349-
/// <param name="stringConstantType">Bare, SingleQuoted, or DoubleQuoted.</param>
350-
private void PossiblyGlobArg(string arg, CommandParameterInternal parameter, StringConstantType stringConstantType)
332+
/// <param name="usedQuotes">True if the argument was a quoted string (single or double).</param>
333+
private void PossiblyGlobArg(string arg, CommandParameterInternal parameter, bool usedQuotes)
351334
{
352335
var argExpanded = false;
353336

354337
#if UNIX
355338
// On UNIX systems, we expand arguments containing wildcard expressions against
356339
// the file system just like bash, etc.
357-
358-
if (stringConstantType == StringConstantType.BareWord)
340+
if (!usedQuotes && WildcardPattern.ContainsWildcardCharacters(arg))
359341
{
360-
if (WildcardPattern.ContainsWildcardCharacters(arg))
342+
// See if the current working directory is a filesystem provider location
343+
// We won't do the expansion if it isn't since native commands can only access the file system.
344+
var cwdinfo = Context.EngineSessionState.CurrentLocation;
345+
346+
// If it's a filesystem location then expand the wildcards
347+
if (cwdinfo.Provider.Name.Equals(FileSystemProvider.ProviderName, StringComparison.OrdinalIgnoreCase))
361348
{
362-
// See if the current working directory is a filesystem provider location
363-
// We won't do the expansion if it isn't since native commands can only access the file system.
364-
var cwdinfo = Context.EngineSessionState.CurrentLocation;
349+
// On UNIX, paths starting with ~ or absolute paths are not normalized
350+
bool normalizePath = arg.Length == 0 || !(arg[0] == '~' || arg[0] == '/');
365351

366-
// If it's a filesystem location then expand the wildcards
367-
if (cwdinfo.Provider.Name.Equals(FileSystemProvider.ProviderName, StringComparison.OrdinalIgnoreCase))
352+
// See if there are any matching paths otherwise just add the pattern as the argument
353+
Collection<PSObject> paths = null;
354+
try
368355
{
369-
// On UNIX, paths starting with ~ or absolute paths are not normalized
370-
bool normalizePath = arg.Length == 0 || !(arg[0] == '~' || arg[0] == '/');
371-
372-
// See if there are any matching paths otherwise just add the pattern as the argument
373-
Collection<PSObject> paths = null;
374-
try
375-
{
376-
paths = Context.EngineSessionState.InvokeProvider.ChildItem.Get(arg, false);
377-
}
378-
catch
379-
{
380-
// Fallthrough will append the pattern unchanged.
381-
}
356+
paths = Context.EngineSessionState.InvokeProvider.ChildItem.Get(arg, false);
357+
}
358+
catch
359+
{
360+
// Fallthrough will append the pattern unchanged.
361+
}
382362

383-
// Expand paths, but only from the file system.
384-
if (paths?.Count > 0 && paths.All(static p => p.BaseObject is FileSystemInfo))
363+
// Expand paths, but only from the file system.
364+
if (paths?.Count > 0 && paths.All(p => p.BaseObject is FileSystemInfo))
365+
{
366+
var sep = string.Empty;
367+
foreach (var path in paths)
385368
{
386-
var sep = string.Empty;
387-
foreach (var path in paths)
369+
_arguments.Append(sep);
370+
sep = " ";
371+
var expandedPath = (path.BaseObject as FileSystemInfo).FullName;
372+
if (normalizePath)
388373
{
389-
_arguments.Append(sep);
390-
sep = " ";
391-
var expandedPath = (path.BaseObject as FileSystemInfo).FullName;
392-
if (normalizePath)
393-
{
394-
expandedPath =
395-
Context.SessionState.Path.NormalizeRelativePath(expandedPath, cwdinfo.ProviderPath);
396-
}
397-
// If the path contains spaces, then add quotes around it.
398-
if (NeedQuotes(expandedPath))
399-
{
400-
_arguments.Append('"');
401-
_arguments.Append(expandedPath);
402-
_arguments.Append('"');
403-
AddToArgumentList(parameter, expandedPath);
404-
}
405-
else
406-
{
407-
_arguments.Append(expandedPath);
408-
AddToArgumentList(parameter, expandedPath);
409-
}
410-
411-
argExpanded = true;
374+
expandedPath =
375+
Context.SessionState.Path.NormalizeRelativePath(expandedPath, cwdinfo.ProviderPath);
376+
}
377+
// If the path contains spaces, then add quotes around it.
378+
if (NeedQuotes(expandedPath))
379+
{
380+
_arguments.Append('"');
381+
_arguments.Append(expandedPath);
382+
_arguments.Append('"');
412383
}
384+
else
385+
{
386+
_arguments.Append(expandedPath);
387+
}
388+
389+
AddToArgumentList(parameter, expandedPath);
390+
argExpanded = true;
413391
}
414392
}
415393
}
416-
else
394+
}
395+
else if (!usedQuotes)
396+
{
397+
// Even if there are no wildcards, we still need to possibly
398+
// expand ~ into the filesystem provider home directory path
399+
ProviderInfo fileSystemProvider = Context.EngineSessionState.GetSingleProvider(FileSystemProvider.ProviderName);
400+
string home = fileSystemProvider.Home;
401+
if (string.Equals(arg, "~"))
417402
{
418-
// Even if there are no wildcards, we still need to possibly
419-
// expand ~ into the filesystem provider home directory path
420-
ProviderInfo fileSystemProvider = Context.EngineSessionState.GetSingleProvider(FileSystemProvider.ProviderName);
421-
string home = fileSystemProvider.Home;
422-
if (string.Equals(arg, "~"))
423-
{
424-
_arguments.Append(home);
425-
AddToArgumentList(parameter, home);
426-
argExpanded = true;
427-
}
428-
else if (arg.StartsWith("~/", StringComparison.OrdinalIgnoreCase))
429-
{
430-
string replacementString = string.Concat(home, arg.AsSpan(1));
431-
_arguments.Append(replacementString);
432-
AddToArgumentList(parameter, replacementString);
433-
argExpanded = true;
434-
}
403+
_arguments.Append(home);
404+
AddToArgumentList(parameter, home);
405+
argExpanded = true;
406+
}
407+
else if (arg.StartsWith("~/", StringComparison.OrdinalIgnoreCase))
408+
{
409+
var replacementString = string.Concat(home, arg.AsSpan(1));
410+
_arguments.Append(replacementString);
411+
AddToArgumentList(parameter, replacementString);
412+
argExpanded = true;
435413
}
436414
}
437415
#endif // UNIX
438416

439-
if (stringConstantType != StringConstantType.SingleQuoted)
440-
{
441-
arg = ResolvePath(arg, Context);
442-
}
443-
444417
if (!argExpanded)
445418
{
446419
_arguments.Append(arg);
447420
AddToArgumentList(parameter, arg);
448421
}
449422
}
450423

451-
/// <summary>
452-
/// Check if string is prefixed by psdrive, if so, expand it if filesystem path.
453-
/// </summary>
454-
/// <param name="path">The potential PSPath to resolve.</param>
455-
/// <param name="context">The current ExecutionContext.</param>
456-
/// <returns>Resolved PSPath if applicable otherwise the original path</returns>
457-
internal static string ResolvePath(string path, ExecutionContext context)
458-
{
459-
if (ExperimentalFeature.IsEnabled("PSNativePSPathResolution"))
460-
{
461-
#if !UNIX
462-
// on Windows, we need to expand ~ to point to user's home path
463-
if (string.Equals(path, "~", StringComparison.Ordinal) || path.StartsWith(TildeDirectorySeparator, StringComparison.Ordinal) || path.StartsWith(TildeAltDirectorySeparator, StringComparison.Ordinal))
464-
{
465-
try
466-
{
467-
ProviderInfo fileSystemProvider = context.EngineSessionState.GetSingleProvider(FileSystemProvider.ProviderName);
468-
return new StringBuilder(fileSystemProvider.Home)
469-
.Append(path.AsSpan(1))
470-
.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar)
471-
.ToString();
472-
}
473-
catch
474-
{
475-
return path;
476-
}
477-
}
478-
479-
// check if the driveName is an actual disk drive on Windows, if so, no expansion
480-
if (path.Length >= 2 && path[1] == ':')
481-
{
482-
foreach (var drive in DriveInfo.GetDrives())
483-
{
484-
if (drive.Name.StartsWith(new string(path[0], 1), StringComparison.OrdinalIgnoreCase))
485-
{
486-
return path;
487-
}
488-
}
489-
}
490-
#endif
491-
492-
if (path.Contains(':'))
493-
{
494-
LocationGlobber globber = new LocationGlobber(context.SessionState);
495-
try
496-
{
497-
ProviderInfo providerInfo;
498-
499-
// replace the argument with resolved path if it's a filesystem path
500-
string pspath = globber.GetProviderPath(path, out providerInfo);
501-
if (string.Equals(providerInfo.Name, FileSystemProvider.ProviderName, StringComparison.OrdinalIgnoreCase))
502-
{
503-
path = pspath;
504-
}
505-
}
506-
catch
507-
{
508-
// if it's not a provider path, do nothing
509-
}
510-
}
511-
}
512-
513-
return path;
514-
}
515-
516424
/// <summary>
517425
/// Check to see if the string contains spaces and therefore must be quoted.
518426
/// </summary>
@@ -567,8 +475,6 @@ private static string GetEnumerableArgSeparator(ArrayLiteralAst arrayLiteralAst,
567475
/// The native command to bind to.
568476
/// </summary>
569477
private readonly NativeCommand _nativeCommand;
570-
private static readonly string TildeDirectorySeparator = $"~{Path.DirectorySeparatorChar}";
571-
private static readonly string TildeAltDirectorySeparator = $"~{Path.AltDirectorySeparatorChar}";
572478

573479
#endregion private members
574480
}

0 commit comments

Comments
 (0)