diff --git a/src/System.Management.Automation/namespaces/FileSystemProvider.cs b/src/System.Management.Automation/namespaces/FileSystemProvider.cs index b62454ac5ce..54c68653ea9 100644 --- a/src/System.Management.Automation/namespaces/FileSystemProvider.cs +++ b/src/System.Management.Automation/namespaces/FileSystemProvider.cs @@ -98,7 +98,75 @@ public FileSystemProvider() /// private static string NormalizePath(string path) { - return path.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator); + return GetCorrectCasedPath(path.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator)); + } + + /// + /// Get the correct casing for a path. This method assumes it's being called by NormalizePath() + /// so that the path is already normalized. + /// + /// + /// The path to retrieve. + /// + /// + /// The path with accurate casing if item exists, otherwise it returns path that was passed in. + /// + private static string GetCorrectCasedPath(string path) + { + // Only apply to directories where there are issues with some tools if the casing + // doesn't match the source like git + if (Directory.Exists(path)) + { + string exactPath = string.Empty; + int itemsToSkip = 0; + if (Utils.PathIsUnc(path)) + { + // With the Split method, a UNC path like \\server\share, we need to skip + // trying to enumerate the server and share, so skip the first two empty + // strings, then server, and finally share name. + itemsToSkip = 4; + } + + foreach (string item in path.Split(StringLiterals.DefaultPathSeparator)) + { + if (itemsToSkip-- > 0) + { + // This handles the UNC server and share and 8.3 short path syntax + exactPath += item + StringLiterals.DefaultPathSeparator; + continue; + } + else if (string.IsNullOrEmpty(exactPath)) + { + // This handles the drive letter or / root path start + exactPath = item + StringLiterals.DefaultPathSeparator; + } + else if (string.IsNullOrEmpty(item)) + { + // This handles the trailing slash case + continue; + } + else if (item.Contains('~')) + { + // This handles short path names + exactPath += StringLiterals.DefaultPathSeparator + item; + } + else + { + exactPath = Directory.GetFileSystemEntries(exactPath, item).First(); + } + } + + if (path.EndsWith(StringLiterals.DefaultPathSeparator)) + { + return exactPath + StringLiterals.DefaultPathSeparator; + } + + return exactPath; + } + else + { + return path; + } } /// diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Location.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Location.Tests.ps1 index 5824419f3b1..10a276b32b6 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Location.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Location.Tests.ps1 @@ -70,6 +70,24 @@ Describe "Set-Location" -Tags "CI" { Should -Throw -ErrorId "PathNotFound,Microsoft.PowerShell.Commands.SetLocationCommand" } + It "Should use actual casing of folder on case-insensitive filesystem" -Skip:($IsLinux) { + $testPath = New-Item -ItemType Directory -Path testdrive:/teST + Set-Location $testPath.FullName.ToUpper() + $(Get-Location).Path | Should -BeExactly $testPath.FullName + } + + It "Should use actual casing of folder on case-sensitive filesystem: " -Skip:(!$IsLinux) + { + $dir = "teST" + $testPathLower = New-Item -ItemType Directory -Path (Join-Path $TestDrive $dir.ToLower()) + $testPathUpper = New-Item -ItemType Directory -Path (Join-Path $TestDrive $dir.ToUpper()) + Set-Location $testPathLower.FullName + $(Get-Location).Path | Should -BeExactly $testPathLower.FullName + Set-Location $testPathUpper.FullName + $(Get-Location).Path | Should -BeExactly $testPathUpper.FullName + { Set-Locaiton (Join-Path $TestDrive $dir) } | Should -Throw -ErrorId "PathNotFound,Microsoft.PowerShell.Commands.SetLocationCommand" + } + Context 'Set-Location with no arguments' { It 'Should go to $env:HOME when Set-Location run with no arguments from FileSystem provider' {