diff --git a/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs b/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs
index d6cfdb7065..3794f41c43 100644
--- a/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs
+++ b/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs
@@ -192,25 +192,8 @@ namespace System.Management.Automation
//
// We need to check quotes that the win32 argument parser checks which is currently
// just the normal double quotes, no other special quotes. Also note that mismatched
- // quotes are supported.
-
- bool needQuotes = false, followingBackslash = false;
- int quoteCount = 0;
- for (int i = 0; i < arg.Length; i++)
- {
- if (arg[i] == '"' && !followingBackslash)
- {
- quoteCount += 1;
- }
- else if (char.IsWhiteSpace(arg[i]) && (quoteCount % 2 == 0))
- {
- needQuotes = true;
- }
-
- followingBackslash = arg[i] == '\\';
- }
-
- if (needQuotes)
+ // quotes are supported
+ if (NeedQuotes(arg))
{
_arguments.Append('"');
_arguments.Append(arg);
@@ -218,13 +201,131 @@ namespace System.Management.Automation
}
else
{
+#if UNIX
+ // On UNIX systems, we expand arguments containing wildcard expressions against
+ // the file system just like bash, etc.
+ if (System.Management.Automation.WildcardPattern.ContainsWildcardCharacters(arg))
+ {
+ // See if the current working directory is a filesystem provider location
+ // We won't do the expansion if it isn't since native commands can only access the file system.
+ var cwdinfo = Context.EngineSessionState.CurrentLocation;
+
+ // If it's a filesystem location then expand the wildcards
+ if (string.Equals(cwdinfo.Provider.Name, Microsoft.PowerShell.Commands.FileSystemProvider.ProviderName,
+ StringComparison.OrdinalIgnoreCase))
+ {
+ bool normalizePath = true;
+ // On UNIX, paths starting with ~ are not normalized
+ if (arg.Length > 0 && arg[0] == '~')
+ {
+ normalizePath = false;
+ }
+
+ // See if there are any matching paths otherwise just add the pattern as the argument
+ var paths = Context.EngineSessionState.InvokeProvider.ChildItem.Get(arg, false);
+ if (paths.Count > 0)
+ {
+ bool first = true;
+ foreach (var path in paths)
+ {
+ object pbo = path.BaseObject;
+ if (! first)
+ {
+ _arguments.Append(" ");
+ }
+ else
+ {
+ if (! (pbo is System.IO.FileSystemInfo))
+ {
+ // If the object is not a filesystem object, then just append
+ // the pattern unchanged
+ _arguments.Append(arg);
+ break;
+ }
+ first = false;
+ }
+ var expandedPath = (pbo as System.IO.FileSystemInfo).FullName;
+ if (normalizePath)
+ {
+ expandedPath = Context.SessionState.Path.NormalizeRelativePath(expandedPath, cwdinfo.ProviderPath);
+ }
+ // If the path contains spaces, then add quotes around it.
+ if (NeedQuotes(expandedPath))
+ {
+ _arguments.Append("\"");
+ _arguments.Append(expandedPath);
+ _arguments.Append("\"");
+ }
+ else
+ {
+ _arguments.Append(expandedPath);
+ }
+ }
+ }
+ else
+ {
+ _arguments.Append(arg);
+ }
+ }
+ else
+ {
+ _arguments.Append(arg);
+ }
+ }
+ else
+ {
+ // Even if there are no wildcards, we still need to possibly
+ // expand ~ into the filesystem provider home directory path
+ ProviderInfo fileSystemProvider = Context.EngineSessionState.GetSingleProvider(
+ Microsoft.PowerShell.Commands.FileSystemProvider.ProviderName);
+ string home = fileSystemProvider.Home;
+ if (string.Equals(arg, "~"))
+ {
+ _arguments.Append(home);
+ }
+ else if (arg.StartsWith("~/", StringComparison.OrdinalIgnoreCase))
+ {
+ var replacementString = home + arg.Substring(1);
+ _arguments.Append(replacementString);
+ }
+ else
+ {
+ _arguments.Append(arg);
+ }
+ }
+#else
_arguments.Append(arg);
+#endif
}
}
}
} while (list != null);
}
+ ///
+ /// Check to see if the string contains spaces and therefore must be quoted.
+ ///
+ /// The string to check for spaces
+ private bool NeedQuotes(string stringToCheck)
+ {
+ bool needQuotes = false, followingBackslash = false;
+ int quoteCount = 0;
+ for (int i = 0; i < stringToCheck.Length; i++)
+ {
+ if (stringToCheck[i] == '"' && !followingBackslash)
+ {
+ quoteCount += 1;
+ }
+ else if (char.IsWhiteSpace(stringToCheck[i]) && (quoteCount % 2 == 0))
+ {
+ needQuotes = true;
+ }
+ followingBackslash = stringToCheck[i] == '\\';
+ }
+ return needQuotes;
+ }
+
+
///
/// The native command to bind to
///
diff --git a/test/powershell/Language/Scripting/NativeExecution/NativeUnixGlobbing.Tests.ps1 b/test/powershell/Language/Scripting/NativeExecution/NativeUnixGlobbing.Tests.ps1
new file mode 100644
index 0000000000..c18d75c464
--- /dev/null
+++ b/test/powershell/Language/Scripting/NativeExecution/NativeUnixGlobbing.Tests.ps1
@@ -0,0 +1,59 @@
+
+Describe 'Native UNIX globbing tests' -tags "CI" {
+
+ BeforeAll {
+ if (-not $IsWindows )
+ {
+ "" > "$TESTDRIVE/abc.txt"
+ "" > "$TESTDRIVE/bbb.txt"
+ "" > "$TESTDRIVE/cbb.txt"
+ }
+
+ $defaultParamValues = $PSDefaultParameterValues.Clone()
+ $PSDefaultParameterValues["it:skip"] = $IsWindows
+ }
+
+ AfterAll {
+ $global:PSDefaultParameterValues = $defaultParamValues
+ }
+
+ # Test * expansion
+ It 'The globbing pattern *.txt should match 3 files' {
+ (/bin/ls $TESTDRIVE/*.txt).Length | Should Be 3
+ }
+ It 'The globbing pattern *b.txt should match 2 files whose basenames end in "b"' {
+ (/bin/ls $TESTDRIVE/*b.txt).Length | Should Be 2
+ }
+ # Test character classes
+ It 'The globbing pattern should match 2 files whose names start with either "a" or "b"' {
+ (/bin/ls $TESTDRIVE/[ab]*.txt).Length | Should Be 2
+ }
+ It 'Globbing abc.* should return one file name "abc.txt"' {
+ /bin/ls $TESTDRIVE/abc.* | Should Match "abc.txt"
+ }
+ # Test that ? matches any single character
+ It 'Globbing [cde]b?.* should return one file name "cbb.txt"' {
+ /bin/ls $TESTDRIVE/[cde]b?.* | Should Match "cbb.txt"
+ }
+ It 'Should return the original pattern if there are no matches' {
+ /bin/echo $TESTDRIVE/*.nosuchfile | Should Match "\*\.nosuchfile$"
+ }
+ # Test the behavior in non-filesystem drives
+ It 'Should not expand patterns on non-filesystem drives' {
+ /bin/echo env:ps* | Should BeExactly "env:ps*"
+ }
+ # Test the behavior for files with spaces in the names
+ It 'Globbing filenames with spaces should match 2 files' {
+ "" > "$TESTDRIVE/foo bar.txt"
+ "" > "$TESTDRIVE/foo baz.txt"
+ (/bin/ls $TESTDRIVE/foo*.txt).Length | Should Be 2
+ }
+ # Test ~ expansion
+ It 'Tilde should be replaced by the filesystem provider home directory' {
+ /bin/echo ~ | Should BeExactly ($executioncontext.SessionState.Provider.Get("FileSystem").Home)
+ }
+ # Test ~ expansion with a path fragment (e.g. ~/foo)
+ It '~/foo should be replaced by the /foo' {
+ /bin/echo ~/foo | Should BeExactly "$($executioncontext.SessionState.Provider.Get("FileSystem").Home)/foo"
+ }
+}