Fix PATH caching in CommandDiscovery code (#27809)

The method `GetLookupDirectoryPaths` always returns the cached instance of path collection. However, the returned collection gets mutated in `CommandPathSearch.ResolveCurrentDirectoryInLookupPaths` to resolve the relative paths such as `.\tools` based on the user's current working directory `$PWD`, so for example, `.\tools` gets replaced with `cwd-1\tools` in the cached instance. Then, when the user changes to a different working directory `cwd-2`, command discovery won't find the executable or ps1 script under `cwd-2/tools` as expected, but those executables under `cwd-1\tools` will always be discoverable no matter what the `$PWD` is. That behavior is incorrect.

This pull request improves the caching logic of `GetLookupDirectoryPaths` and makes it return a copy of the path collection, so the mutation happens to the returned copy, and the cached instance is kept intact. So, for every command search, the `CommandPathSearch` will resolve relative paths against the `$PWD` that the user is located at that time.
This commit is contained in:
Dongbo Wang authored and GitHub committed 2026-08-17 15:31:31 -07:00
1 parent f83ccddf80
commit bac40c877b
2 files changed
+84 -46

No files matched your search

@@ -275,3 +275,54 @@ Describe "Get-Command Tests" -Tags "CI" {
$result | Should -Be $null
}
}
Describe "Test relative path in PATH env var" -Tags "CI" {
BeforeAll {
$originalPath = $env:PATH
$originalLocation = Get-Location
$subDir1 = Join-Path $TestDrive -ChildPath "subdir1"
$subDir2 = Join-Path $TestDrive -ChildPath "subdir2"
$toolsUnderSubDir1 = Join-Path $subDir1 -ChildPath "tools"
$toolsUnderSubDir2 = Join-Path $subDir2 -ChildPath "tools"
$null = New-Item -Path $subDir1 -ItemType Directory -Force
$null = New-Item -Path $subDir2 -ItemType Directory -Force
$null = New-Item -Path $toolsUnderSubDir1 -ItemType Directory -Force
$null = New-Item -Path $toolsUnderSubDir2 -ItemType Directory -Force
$helloScript = Join-Path $toolsUnderSubDir1 -ChildPath "hello.ps1"
$byeScript = Join-Path $toolsUnderSubDir2 -ChildPath "bye.ps1"
$null = New-Item -Path $helloScript -ItemType File -Force
$null = New-Item -Path $byeScript -ItemType File -Force
}
AfterAll {
Set-Location $originalLocation
$env:PATH = $originalPath
}
It "Get-Command should resolve relative path in PATH env var based on user's current working directory" {
$dirSep = [System.IO.Path]::DirectorySeparatorChar
$pathSep = [System.IO.Path]::PathSeparator
Set-Location $subDir1
$env:PATH = ".${dirSep}tools${pathSep}$env:PATH"
$result = Get-Command "hello.ps1" -ErrorAction silentlycontinue
$result | Should -Not -BeNullOrEmpty -Because "CWD is $subDir1, so '.${dirSep}tools' in PATH should be resolved to '$toolsUnderSubDir1', which contains 'hello.ps1'"
$result.Path | Should -BeExactly $helloScript
$result = Get-Command "bye.ps1" -ErrorAction silentlycontinue
$result | Should -BeNullOrEmpty -Because "'bye.ps1' is not in '$toolsUnderSubDir1'"
Set-Location $subDir2
$result = Get-Command "hello.ps1" -ErrorAction silentlycontinue
$result | Should -BeNullOrEmpty -Because "CWD is $subDir2, so '.${dirSep}tools' in PATH should be resolved to '$toolsUnderSubDir2', which contains 'bye.ps1'"
$result = Get-Command "bye.ps1" -ErrorAction silentlycontinue
$result | Should -Not -BeNullOrEmpty
$result.Path | Should -BeExactly $byeScript
}
}