diff --git a/src/System.Management.Automation/engine/Utils.cs b/src/System.Management.Automation/engine/Utils.cs index a6642e81c55..32f63c07d36 100644 --- a/src/System.Management.Automation/engine/Utils.cs +++ b/src/System.Management.Automation/engine/Utils.cs @@ -1241,7 +1241,7 @@ internal static bool PathIsUnc(string path, bool networkOnly = false) } // handle special cases like '\\wsl$\ubuntu', '\\?\', and '\\.\pipe\' which aren't a UNC path, but we can say it is so the filesystemprovider can use it - if (!networkOnly && (path.StartsWith(WslRootPath, StringComparison.OrdinalIgnoreCase) || path.StartsWith("\\\\?\\") || path.StartsWith("\\\\.\\"))) + if (!networkOnly && (path.StartsWith(WslRootPath, StringComparison.OrdinalIgnoreCase) || PathIsDevicePath(path))) { return true; } @@ -1251,6 +1251,15 @@ internal static bool PathIsUnc(string path, bool networkOnly = false) #endif } + internal static bool PathIsDevicePath(string path) + { +#if UNIX + return false; +#else + return path.StartsWith(@"\\.\") || path.StartsWith(@"\\?\"); +#endif + } + internal static readonly string PowerShellAssemblyStrongNameFormat = "{0}, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"; diff --git a/src/System.Management.Automation/namespaces/FileSystemProvider.cs b/src/System.Management.Automation/namespaces/FileSystemProvider.cs index 1db69c2a125..112c66337a7 100644 --- a/src/System.Management.Automation/namespaces/FileSystemProvider.cs +++ b/src/System.Management.Automation/namespaces/FileSystemProvider.cs @@ -4913,7 +4913,17 @@ protected override string GetParentPath(string path, string root) // make sure we return two backslashes so it still results in a UNC path parentPath = "\\\\"; } + + if (!parentPath.EndsWith(StringLiterals.DefaultPathSeparator) + && Utils.PathIsDevicePath(parentPath) + && parentPath.Length - parentPath.Replace(StringLiterals.DefaultPathSeparatorString, string.Empty).Length == 3) + { + // Device paths start with either "\\.\" or "\\?\" + // When referring to the root, like: "\\.\CDROM0\" then it needs the trailing separator to be valid. + parentPath += StringLiterals.DefaultPathSeparator; + } #endif + s_tracer.WriteLine("GetParentPath returning '{0}'", parentPath); return parentPath; } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/FileSystem.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/FileSystem.Tests.ps1 index 0f5222b3db5..a5c487bf9b2 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/FileSystem.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/FileSystem.Tests.ps1 @@ -529,6 +529,43 @@ Describe "Handling of globbing patterns" -Tags "CI" { Test-Path -LiteralPath $testPath2 | Should -BeTrue } } + + Context "Device paths" { + # The globber is overly greedy somewhere so you need to escape the escape backtick to preserve the question mark issue https://github.com/PowerShell/PowerShell/issues/19627 + It "Handle device paths: " -Skip:(!$IsWindows) -TestCases @( + @{ path = "\\.\${env:SystemDrive}\" } + @{ path = "\\.\${env:SystemDrive}\*" } + @{ path = "\\``?\${env:SystemDrive}\" } + @{ path = "\\``?\${env:SystemDrive}\*" } + ) { + param($path) + $expected = Get-ChildItem -Path ${env:SystemDrive}\ + $result = Get-ChildItem -Path $path + $result.Count | Should -Be $expected.Count + } + + It "Handle folders within a device path: " -Skip:(!$IsWindows) -TestCases @( + @{ path = "\\.\${env:SystemRoot}\" } + @{ path = "\\.\${env:SystemRoot}\*" } + @{ path = "\\``?\${env:SystemRoot}\" } + @{ path = "\\``?\${env:SystemRoot}\*" } + ) { + param($path) + $expected = Get-ChildItem -Path ${env:SystemRoot} + $result = Get-ChildItem -Path $path + $result.Count | Should -Be $expected.Count + } + + It "Fails for invalid device path: " -Skip:(!$IsWindows) -TestCases @( + @{ path = "\\.\INVALID0\" } + @{ path = "\\``?\INVALID0\" } + # @{ path = "\\.\INVALID0\*" } // problem in globber where this fails but is ignored issue https://github.com/PowerShell/PowerShell/issues/19626 + # @{ path = "\\``?\INVALID0\*" } + ) { + param($path) + { Get-ChildItem -Path $path -ErrorAction Stop } | Should -Throw -ErrorId 'PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand' + } + } } Describe "Hard link and symbolic link tests" -Tags "CI", "RequireAdminOnWindows" {