diff --git a/src/System.Management.Automation/engine/SessionStateContainer.cs b/src/System.Management.Automation/engine/SessionStateContainer.cs
index fa8884b92..8749105a1 100644
--- a/src/System.Management.Automation/engine/SessionStateContainer.cs
+++ b/src/System.Management.Automation/engine/SessionStateContainer.cs
@@ -1323,52 +1323,7 @@ namespace System.Management.Automation
try
{
- // If we're recursing, do some path fixups to match user
- // expectations, but only if the last part is a file and not a directory:
- if (recurse && !path.EndsWith(Path.DirectorySeparatorChar) && !path.EndsWith(Path.AltDirectorySeparatorChar))
- {
- string childName = GetChildName(path, context);
-
- // If -File or -Directory is specified and path is ended with '*', we should include the parent path as search path
-
- bool isFileOrDirectoryPresent = false;
-
- if (context.DynamicParameters is Microsoft.PowerShell.Commands.GetChildDynamicParameters dynParam)
- {
- isFileOrDirectoryPresent = dynParam.File.IsPresent || dynParam.Directory.IsPresent;
- }
-
- if (string.Equals(childName, "*", StringComparison.OrdinalIgnoreCase) && isFileOrDirectoryPresent)
- {
- string parentName = path.Substring(0, path.Length - childName.Length);
- path = parentName;
- }
- // dir c:\tem* -include *.ps1 -rec => No change
- if ((context.Include == null) || (context.Include.Count == 0))
- {
- // dir c:\tem* -rec => dir c:\ -include tem* -rec
- // dir tem* -rec => dir -include tem* -rec
- // dir temp -rec
-
- // Should glob paths and files that match tem*, but then
- // recurse into all subdirectories and do the same for
- // those directories.
- if (!string.IsNullOrEmpty(path) && !IsItemContainer(path))
- {
- if (!string.Equals(childName, "*", StringComparison.OrdinalIgnoreCase))
- {
- if (context.Include != null)
- {
- context.Include.Add(childName);
- modifiedInclude = true;
- }
- }
-
- string parentName = path.Substring(0, path.Length - childName.Length);
- path = parentName;
- }
- }
- }
+ modifiedInclude = NormalizePathForChildItemRecursion(ref path, recurse, context);
// Save the include and exclude filters so that we can ignore
// them when doing recursion
@@ -1529,6 +1484,63 @@ namespace System.Management.Automation
}
}
+ private bool NormalizePathForChildItemRecursion(
+ ref string path,
+ bool recurse,
+ CmdletProviderContext context)
+ {
+ bool modifiedInclude = false;
+
+ // If we're recursing, do some path fixups to match user
+ // expectations, but only if the last part is a file and not a directory:
+ if (recurse && !path.EndsWith(Path.DirectorySeparatorChar) && !path.EndsWith(Path.AltDirectorySeparatorChar))
+ {
+ string childName = GetChildName(path, context);
+
+ // If -File or -Directory is specified and path is ended with '*', we should include the parent path as search path
+
+ bool isFileOrDirectoryPresent = false;
+
+ if (context.DynamicParameters is Microsoft.PowerShell.Commands.GetChildDynamicParameters dynParam)
+ {
+ isFileOrDirectoryPresent = dynParam.File.IsPresent || dynParam.Directory.IsPresent;
+ }
+
+ if (string.Equals(childName, "*", StringComparison.OrdinalIgnoreCase) && isFileOrDirectoryPresent)
+ {
+ string parentName = path.Substring(0, path.Length - childName.Length);
+ path = parentName;
+ }
+ // dir c:\tem* -include *.ps1 -rec => No change
+ if ((context.Include == null) || (context.Include.Count == 0))
+ {
+ // dir c:\tem* -rec => dir c:\ -include tem* -rec
+ // dir tem* -rec => dir -include tem* -rec
+ // dir temp -rec
+
+ // Should glob paths and files that match tem*, but then
+ // recurse into all subdirectories and do the same for
+ // those directories.
+ if (!string.IsNullOrEmpty(path) && !IsItemContainer(path))
+ {
+ if (!string.Equals(childName, "*", StringComparison.OrdinalIgnoreCase))
+ {
+ if (context.Include != null)
+ {
+ context.Include.Add(childName);
+ modifiedInclude = true;
+ }
+ }
+
+ string parentName = path.Substring(0, path.Length - childName.Length);
+ path = parentName;
+ }
+ }
+ }
+
+ return modifiedInclude;
+ }
+
///
/// Gets the child items of the item at the specified path.
///
@@ -2229,6 +2241,10 @@ namespace System.Management.Automation
}
CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext);
+ context.SetFilters(
+ include: new Collection(),
+ exclude: new Collection(),
+ filter: null);
context.Force = force;
context.SuppressWildcardExpansion = literalPath;
@@ -2319,6 +2335,33 @@ namespace System.Management.Automation
throw PSTraceSource.NewArgumentNullException(nameof(path));
}
+ bool modifiedInclude = false;
+
+ try
+ {
+ if ((recurse && !context.SuppressWildcardExpansion) || LocationGlobber.ShouldPerformGlobbing(path, context))
+ {
+ modifiedInclude = NormalizePathForChildItemRecursion(ref path, recurse, context);
+ }
+
+ GetChildNamesCore(path, returnContainers, recurse, depth, context);
+ }
+ finally
+ {
+ if (modifiedInclude)
+ {
+ context.Include.Clear();
+ }
+ }
+ }
+
+ private void GetChildNamesCore(
+ string path,
+ ReturnContainers returnContainers,
+ bool recurse,
+ uint depth,
+ CmdletProviderContext context)
+ {
// Construct the include filter
Collection includeMatcher =
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Get-ChildItem.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Get-ChildItem.Tests.ps1
index 4c7fdfa52..e43ef3b62 100644
--- a/test/powershell/Modules/Microsoft.PowerShell.Management/Get-ChildItem.Tests.ps1
+++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Get-ChildItem.Tests.ps1
@@ -142,6 +142,77 @@ Describe "Get-ChildItem" -Tags "CI" {
(Get-ChildItem -Path $TestDrive -Depth 1 -Exclude $item_a).Count | Should -Be 7
}
+ It "Should return names recursively when the path has a wildcard leaf" {
+ $root = New-Item -Path $TestDrive -Name "NameRecurse" -ItemType Directory
+ try {
+ $child = New-Item -Path $root -Name "Child" -ItemType Directory
+ $null = New-Item -Path $root -Name "root.md" -ItemType File
+ $null = New-Item -Path $child -Name "child.md" -ItemType File
+ $null = New-Item -Path $child -Name "ignored.txt" -ItemType File
+ $path = Join-Path $root "*.md"
+
+ $result = Get-ChildItem -Path $path -Recurse -File -Name
+ $result | Should -HaveCount 2
+ $result | Should -Contain "root.md"
+ $result | Should -Contain "Child$([IO.Path]::DirectorySeparatorChar)child.md"
+
+ Get-ChildItem -Path $path -Depth 0 -File -Name | Should -BeExactly "root.md"
+ }
+ finally {
+ Remove-Item -LiteralPath $root -Recurse -Force
+ }
+ }
+
+ It "Should preserve recursive wildcard filters through the public GetNames API" {
+ $root = New-Item -Path $TestDrive -Name "PublicGetNamesRecurse" -ItemType Directory
+ try {
+ $child = New-Item -Path $root -Name "Child" -ItemType Directory
+ $null = New-Item -Path $root -Name "root.md" -ItemType File
+ $null = New-Item -Path $child -Name "child.md" -ItemType File
+ $null = New-Item -Path $child -Name "ignored.txt" -ItemType File
+ $path = Join-Path $root "*.md"
+
+ $result = $ExecutionContext.SessionState.InvokeProvider.ChildItem.GetNames(
+ $path,
+ [System.Management.Automation.ReturnContainers]::ReturnMatchingContainers,
+ $true)
+
+ $result | Should -HaveCount 2
+ $result | Should -Contain "root.md"
+ $result | Should -Contain "Child$([IO.Path]::DirectorySeparatorChar)child.md"
+ }
+ finally {
+ Remove-Item -LiteralPath $root -Recurse -Force
+ }
+ }
+
+ It "Should resolve recursive leaf paths from the current directory" {
+ $root = New-Item -Path $TestDrive -Name "CurrentDirectoryRecurse" -ItemType Directory
+ try {
+ $child = New-Item -Path $root -Name "Child" -ItemType Directory
+ $rootFile = New-Item -Path $root -Name "root.md" -ItemType File
+ $childFile = New-Item -Path $child -Name "child.md" -ItemType File
+
+ Push-Location $root
+ try {
+ $result = Get-ChildItem *.md -Recurse -File -Name
+ $result | Should -HaveCount 2
+ $result | Should -Contain $rootFile.Name
+ $result | Should -Contain "Child$([IO.Path]::DirectorySeparatorChar)$($childFile.Name)"
+
+ $result = Get-ChildItem child.md -Recurse -File -Name
+ $result | Should -HaveCount 1
+ $result | Should -BeExactly "Child$([IO.Path]::DirectorySeparatorChar)$($childFile.Name)"
+ }
+ finally {
+ Pop-Location
+ }
+ }
+ finally {
+ Remove-Item -LiteralPath $root -Recurse -Force
+ }
+ }
+
It "Should return items recursively when using 'Include' or 'Exclude' parameters with -LiteralPath" {
(Get-ChildItem -LiteralPath $TestDrive -Recurse -Exclude *).Count | Should -Be 0
(Get-ChildItem -LiteralPath $TestDrive -Recurse -Include *.dll).Count | Should -Be (Get-ChildItem $TestDrive -Recurse -Include *.dll).Count