diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ResolvePathCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ResolvePathCommand.cs index d0f5fecf495..0b619e76e83 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ResolvePathCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ResolvePathCommand.cs @@ -129,7 +129,8 @@ protected override void BeginProcessing() { try { - _relativeBasePath = SessionState.Internal.Globber.GetProviderPath(RelativeBasePath, CmdletProviderContext, out _, out _relativeDrive); + // Since there is only one base path, it should be parsed literally. + _relativeBasePath = SessionState.Path.GetUnresolvedProviderPathFromPSPath(RelativeBasePath, CmdletProviderContext, out _, out _relativeDrive); } catch (ProviderNotFoundException providerNotFound) { @@ -190,8 +191,16 @@ protected override void ProcessRecord() try { SessionState.Path.PushCurrentLocation(string.Empty); - _ = SessionState.Path.SetLocation(_relativeBasePath); - result = SessionState.Path.GetResolvedPSPathFromPSPath(path, CmdletProviderContext); + var cmdpvdContext = CmdletProviderContext; + + // Specify that the base path should be treated literally. + cmdpvdContext.SuppressWildcardExpansion = true; + + // A potential bug is that using the `SetLocation` method modifies the history stack, + // which can unexpectedly affect the results of commands that rely on the history stack, such as `Set-Location +`. + _ = SessionState.Path.SetLocation(_relativeBasePath, cmdpvdContext, true); + cmdpvdContext.SuppressWildcardExpansion = SuppressWildcardExpansion; + result = SessionState.Path.GetResolvedPSPathFromPSPath(path, cmdpvdContext); } finally { diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index 73406e449bf..fd3292cdb67 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -2462,6 +2462,11 @@ private static void NativeCommandArgumentCompletion( NativeCompletionSetLocationCommand(context, parameterName, result); break; } + case "Resolve-Path" when parameterName.Equals("RelativeBasePath", StringComparison.OrdinalIgnoreCase): + { + NativeCompletionPathArgument(context, "LiteralPath", result); + break; + } case "Move-Item": case "Copy-Item": { diff --git a/src/System.Management.Automation/engine/InitialSessionState.cs b/src/System.Management.Automation/engine/InitialSessionState.cs index 62308282d17..b6d7092e47d 100644 --- a/src/System.Management.Automation/engine/InitialSessionState.cs +++ b/src/System.Management.Automation/engine/InitialSessionState.cs @@ -4889,7 +4889,7 @@ internal static SessionStateAliasEntry[] BuiltInAliases # .ExternalHelp System.Management.Automation.dll-help.xml "; - internal const string DefaultSetDriveFunctionText = "Set-Location $MyInvocation.MyCommand.Name"; + internal const string DefaultSetDriveFunctionText = "Set-Location -LiteralPath $MyInvocation.MyCommand.Name"; internal static readonly ScriptBlock SetDriveScriptBlock = ScriptBlock.CreateDelayParsedScriptBlock(DefaultSetDriveFunctionText, isProductCode: true); diff --git a/src/System.Management.Automation/engine/SessionStateLocationAPIs.cs b/src/System.Management.Automation/engine/SessionStateLocationAPIs.cs index 56de07b8871..656d35071d8 100644 --- a/src/System.Management.Automation/engine/SessionStateLocationAPIs.cs +++ b/src/System.Management.Automation/engine/SessionStateLocationAPIs.cs @@ -253,6 +253,8 @@ internal PathInfo SetLocation(string path, CmdletProviderContext context, bool l throw new InvalidOperationException(SessionStateStrings.LocationUndoStackIsEmpty); } + // Should process history path as literal + context.SuppressWildcardExpansion = true; path = _setLocationHistory.Undo(this.CurrentLocation).Path; break; case string originalPathSwitch when !literalPath && originalPathSwitch.Equals("+", StringComparison.Ordinal): @@ -261,6 +263,8 @@ internal PathInfo SetLocation(string path, CmdletProviderContext context, bool l throw new InvalidOperationException(SessionStateStrings.LocationRedoStackIsEmpty); } + // Should process history path as literal + context.SuppressWildcardExpansion = true; path = _setLocationHistory.Redo(this.CurrentLocation).Path; break; default: diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Resolve-Path.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Resolve-Path.Tests.ps1 index 9e38d1f8c20..2579e4d7a74 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Resolve-Path.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Resolve-Path.Tests.ps1 @@ -176,3 +176,31 @@ Describe "Resolve-Path returns proper path" -Tag "CI" { (Resolve-Path -Path $Path -RelativeBasePath $BasePath -Force:$Force).Path | Should -BeExactly $ExpectedResult } } + + +Describe "The parameter '-RelativeBasePath' should treat path as literal" -Tags "CI" { + BeforeAll { + $testFolder = 'TestDrive:\[Folder]' + New-Item -ItemType Directory -Path $testFolder + Push-Location -LiteralPath $testFolder + } + + It "Should succeed in resolving a path when the relative base path contains a wildcard character" { + Resolve-Path -LiteralPath . -RelativeBasePath . | Should -BeTrue + Resolve-Path -LiteralPath . -RelativeBasePath . -Relative | Should -BeTrue + Resolve-Path -Path . -RelativeBasePath . | Should -BeTrue + Resolve-Path -Path . -RelativeBasePath . -Relative | Should -BeTrue + } + + It "Should fill path with literal in tab completion of parameter '-RelativeBasePath'" { + $wildcardNameFile = '[WildcardName].txt' + 123 | Out-File $wildcardNameFile + $completionInfo = TabExpansion2 'Resolve-Path -RelativeBasePath .\' + $completionInfo.CompletionMatches[0].ListItemText | Should -Be $wildcardNameFile + } + + AfterAll { + Pop-Location + } +} + 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 73f3250ae89..99df9f472ce 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Location.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Location.Tests.ps1 @@ -180,6 +180,25 @@ Describe "Set-Location" -Tags "CI" { (Get-Location).Path | Should -Be $tempPath { Set-Location + } | Should -Throw -ErrorId 'System.InvalidOperationException,Microsoft.PowerShell.Commands.SetLocationCommand' } + + It 'Should go to last location back, even if any path contains wildcard' { + $initialLocation = (Get-Location).Path + $wildcardPath = 'TestDrive:\[Folder]' + # Use the return path of `New-Item` to avoid incorrect results caused by the different slash directions of path separators on different operating systems. + $wildcardLocation = (New-Item $wildcardPath -Type Directory).FullName + + Set-Location -LiteralPath $wildcardLocation + Set-Location - + (Get-Location).Path | Should -Be $initialLocation + Set-Location + + (Get-Location).Path | Should -Be $wildcardLocation + + Set-Location -LiteralPath $initialLocation + Set-Location - + (Get-Location).Path | Should -Be $wildcardLocation + Set-Location + + (Get-Location).Path | Should -Be $initialLocation + } } It 'Should nativate to literal path ""' -TestCases @( @@ -249,7 +268,7 @@ Describe "Set-Location" -Tags "CI" { #root is / on linux and Mac, so it's not happy with this check. Set-Location 'TestDrive:\' $DriveRoot = (Get-Location).path - New-Item -Path 'TestDrive:\Directory1' -Name 'Directory2' -ItemType Directory + New-Item -Path 'TestDrive:\Directory1' -Name 'Directory2' -ItemType Directory -ErrorAction Ignore Set-Location 'TestDrive:\Directory1\Directory2' cd\ (Get-Location).Path | Should -BeExactly $DriveRoot