mirror of
https://github.com/aphrody-labs/PowerShell.git
synced 2026-10-14 05:39:11 +00:00
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.
329 lines
16 KiB
PowerShell
329 lines
16 KiB
PowerShell
# Copyright (c) Microsoft Corporation.
|
|
# Licensed under the MIT License.
|
|
Describe "Get-Command Tests" -Tags "CI" {
|
|
BeforeAll {
|
|
function TestGetCommand-DynamicParametersDCR
|
|
{
|
|
[CmdletBinding()]
|
|
param (
|
|
[Parameter(Mandatory = $true, Position = 0)]
|
|
[ValidateSet("ReturnNull", "ReturnThis", "Return1", "Return2","Return3", "ReturnDuplicateParameter", "ReturnAlias", "ReturnDuplicateAlias","ReturnObjectNoParameters", "ReturnGenericParameter", "ThrowException")]
|
|
[string] $TestToRun,
|
|
|
|
[Parameter()]
|
|
[Type]$ParameterType
|
|
)
|
|
|
|
DynamicParam {
|
|
if ( ! $TestToRun ) {
|
|
$TestToRun = "returnnull"
|
|
}
|
|
$dynamicParamDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
|
|
switch ( $TestToRun )
|
|
{
|
|
"returnnull" {
|
|
$dynamicParamDictionary = $null
|
|
break
|
|
}
|
|
"return1" {
|
|
$attr = [System.Management.Automation.ParameterAttribute]::new()
|
|
$attr.Mandatory = $true
|
|
$dynamicParameter = [System.Management.Automation.RuntimeDefinedParameter]::new("OneString",[string],$attr)
|
|
$dynamicParamDictionary.Add("OneString",$dynamicParameter)
|
|
break
|
|
}
|
|
"return2" {
|
|
$attr1 = [System.Management.Automation.ParameterAttribute]::new()
|
|
$attr1.Mandatory = $true
|
|
$attr1.ParameterSetName = "__AllParameterSets"
|
|
$ac1 = [Collections.ObjectModel.Collection[Attribute]]::new()
|
|
$ac1.Add($attr1)
|
|
$p1 = [System.Management.Automation.RuntimeDefinedParameter]::new("OneString",[string],$ac1)
|
|
$dynamicParamDictionary.Add("OneString",$p1)
|
|
|
|
$attr2 = [System.Management.Automation.ParameterAttribute]::new()
|
|
$attr2.Mandatory = $false
|
|
$attr2.ParameterSetName = "__AllParameterSets"
|
|
$VRattr = [System.Management.Automation.ValidateRangeAttribute]::new(5,9)
|
|
|
|
$ac2 = [Collections.ObjectModel.Collection[Attribute]]::new()
|
|
$ac2.Add($attr2)
|
|
$ac2.Add($VRattr)
|
|
$p2 = [System.Management.Automation.RuntimeDefinedParameter]::New("TwoInt",[int],$ac2)
|
|
$dynamicParamDictionary.Add("TwoInt",$p2)
|
|
break
|
|
}
|
|
"return3" {
|
|
$attr1 = [System.Management.Automation.ParameterAttribute]::new()
|
|
$attr1.Mandatory = $true
|
|
$attr1.ParameterSetName = "__AllParameterSets"
|
|
$ac1 = [Collections.ObjectModel.Collection[Attribute]]::new()
|
|
$ac1.Add($attr1)
|
|
$p1 = [System.Management.Automation.RuntimeDefinedParameter]::new("OneString",[string],$ac1)
|
|
$dynamicParamDictionary.Add("OneString",$p1)
|
|
|
|
$attr2 = [System.Management.Automation.ParameterAttribute]::new()
|
|
$attr2.Mandatory = $false
|
|
$attr2.ParameterSetName = "__AllParameterSets"
|
|
$VRattr = [System.Management.Automation.ValidateRangeAttribute]::new(5,9)
|
|
|
|
$ac2 = [Collections.ObjectModel.Collection[Attribute]]::new()
|
|
$ac2.Add($attr2)
|
|
$ac2.Add($VRattr)
|
|
$p2 = [System.Management.Automation.RuntimeDefinedParameter]::New("TwoInt",[int],$ac2)
|
|
$dynamicParamDictionary.Add("TwoInt",$p2)
|
|
|
|
$attr3 = [System.Management.Automation.ParameterAttribute]::new()
|
|
$attr3.Mandatory = $false
|
|
$attr3.ParameterSetName = "__AllParameterSets"
|
|
$ac3 = [Collections.ObjectModel.Collection[Attribute]]::new()
|
|
$ac3.Add($attr3)
|
|
$p3 = [System.Management.Automation.RuntimeDefinedParameter]::new("ThreeBool",[bool],$ac3)
|
|
$dynamicParamDictionary.Add("ThreeBool",$p3)
|
|
break
|
|
}
|
|
"returnduplicateparameter" {
|
|
$attr1 = [System.Management.Automation.ParameterAttribute]::new()
|
|
$attr1.Mandatory = $false
|
|
$attr1.ParameterSetName = "__AllParameterSets"
|
|
$ac1 = [Collections.ObjectModel.Collection[Attribute]]::new()
|
|
$ac1.Add($attr1)
|
|
$p1 = [System.Management.Automation.RuntimeDefinedParameter]::new("TestToRun",[int],$ac1)
|
|
$dynamicParamDictionary.Add("TestToRun",$p1)
|
|
break
|
|
}
|
|
"returngenericparameter" {
|
|
$attr1 = [System.Management.Automation.ParameterAttribute]::new()
|
|
$attr1.ParameterSetName = "__AllParameterSets"
|
|
$ac1 = [Collections.ObjectModel.Collection[Attribute]]::new()
|
|
$ac1.Add($attr1)
|
|
$p1 = [System.Management.Automation.RuntimeDefinedParameter]::new("TypedValue",$ParameterType,$ac1)
|
|
$dynamicParamDictionary.Add("TypedValue",$p1)
|
|
break
|
|
}
|
|
default {
|
|
throw ([invalidoperationexception]::new("unable to determine which dynamic parameters to return!"))
|
|
break
|
|
}
|
|
}
|
|
return $dynamicParamDictionary
|
|
}
|
|
|
|
BEGIN {
|
|
$ReturnNull = "ReturnNull"
|
|
$ReturnThis = "ReturnThis"
|
|
$ReturnAlias = "ReturnAlias"
|
|
$ReturnDuplicateAlias = "ReturnDuplicateAlias"
|
|
$Return1 = "Return1"
|
|
$Return2 = "Return2"
|
|
$Return3 = "Return3"
|
|
$ReturnDuplicateParameter = "ReturnDuplicateParameter"
|
|
$ReturnObjectNoParameters = "ReturnObjectNoParameters"
|
|
$ReturnGenericParameter = "ReturnGenericParameter"
|
|
$ThrowException = "ThrowException"
|
|
return $dynamicParamDictionary
|
|
}
|
|
}
|
|
|
|
function GetDynamicParameter($cmdlet, $parameterName)
|
|
{
|
|
foreach ($paramSet in $cmdlet.ParameterSets)
|
|
{
|
|
foreach ($pinfo in $paramSet.Parameters)
|
|
{
|
|
if ($pinfo.Name -eq $parameterName)
|
|
{
|
|
$foundParam = $pinfo
|
|
break
|
|
}
|
|
}
|
|
if($null -ne $foundParam)
|
|
{
|
|
break
|
|
}
|
|
}
|
|
return $foundParam
|
|
}
|
|
|
|
function VerifyDynamicParametersExist($cmdlet, $parameterNames)
|
|
{
|
|
foreach($paramName in $parameterNames)
|
|
{
|
|
$foundParam = GetDynamicParameter -cmdlet $cmdlet -parameterName $paramName
|
|
$foundParam.Name | Should -BeExactly $paramName
|
|
}
|
|
}
|
|
|
|
function VerifyParameterType($cmdlet, $parameterName, $ParameterType)
|
|
{
|
|
$foundParam = GetDynamicParameter -cmdlet $cmdlet -parameterName $parameterName
|
|
$foundParam.ParameterType | Should -Be $ParameterType
|
|
}
|
|
}
|
|
|
|
It "Verify that Get-Command Get-Content includes the dynamic parameters when the cmdlet is checked against the file system provider implementation" {
|
|
$fullPath = Join-Path $TestDrive -ChildPath "blah"
|
|
New-Item -Path $fullPath -ItemType directory -Force
|
|
$results = Get-Command Get-Content -Path $fullPath
|
|
$dynamicParameter = "Wait", "Encoding", "Delimiter"
|
|
VerifyDynamicParametersExist -cmdlet $results[0] -parameterNames $dynamicParameter
|
|
}
|
|
|
|
It "Verify that Get-Command Get-Content doesn't have any dynamic parameters for Function provider" {
|
|
$results =Get-Command Get-Content -Path function:
|
|
$dynamicParameter = "Wait", "Encoding", "Delimiter"
|
|
foreach ($dynamicPara in $dynamicParameter)
|
|
{
|
|
$results[0].ParameterSets.Parameters.Name -contains $dynamicPara | Should -BeFalse
|
|
}
|
|
}
|
|
|
|
It "Verify that the specified dynamic parameter exists in the CmdletInfo result returned" {
|
|
$results = Get-Command TestGetCommand-DynamicParametersDCR -TestToRun return1
|
|
$dynamicParameter = "OneString"
|
|
VerifyDynamicParametersExist -cmdlet $results[0] -parameterNames $dynamicParameter
|
|
VerifyParameterType -cmdlet $results[0] -parameterName $dynamicParameter -ParameterType string
|
|
}
|
|
|
|
It "Verify three dynamic parameters are created properly" {
|
|
$results = Get-Command TestGetCommand-DynamicParametersDCR -TestToRun return3
|
|
$dynamicParameter = "OneString", "TwoInt", "ThreeBool"
|
|
|
|
VerifyDynamicParametersExist -cmdlet $results[0] -parameterNames $dynamicParameter
|
|
VerifyParameterType -cmdlet $results[0] -parameterName "OneString" -parameterType string
|
|
VerifyParameterType -cmdlet $results[0] -parameterName "TwoInt" -parameterType Int
|
|
VerifyParameterType -cmdlet $results[0] -parameterName "ThreeBool" -parameterType bool
|
|
}
|
|
|
|
It "Verify dynamic parameter type is process" {
|
|
$results = Get-Command TestGetCommand-DynamicParametersDCR -Args '-TestToRun','returngenericparameter','-parametertype','System.Diagnostics.Process'
|
|
VerifyParameterType -cmdlet $results[0] -parameterName "TypedValue" -parameterType System.Diagnostics.Process
|
|
}
|
|
|
|
It "Verify a single cmdlet returned using verb and noun parameter set syntax works properly" {
|
|
$paramName = "OneString"
|
|
$results = Get-Command -Verb TestGetCommand -Noun DynamicParametersDCR -TestToRun Return1
|
|
VerifyDynamicParametersExist -cmdlet $results[0] -parameterNames $paramName
|
|
VerifyParameterType -cmdlet $results[0] -parameterName $paramName -parameterType string
|
|
}
|
|
|
|
It "Verify Single Cmdlet Using Verb&Noun ParameterSet" {
|
|
$paramName = "Encoding"
|
|
$results = Get-Command -Verb get -Noun content -Encoding Unicode
|
|
VerifyDynamicParametersExist -cmdlet $results[0] -parameterNames $paramName
|
|
VerifyParameterType -cmdlet $results[0] -parameterName $paramName -parameterType System.Text.Encoding
|
|
}
|
|
|
|
It "Verify Single Cmdlet Using Verb&Noun ParameterSet With Usage" {
|
|
$results = Get-Command -Verb get -Noun content -Encoding Unicode -Syntax
|
|
$results.ToString() | Should -Match "-Encoding"
|
|
$results.ToString() | Should -Match "-Wait"
|
|
$results.ToString() | Should -Match "-Delimiter"
|
|
}
|
|
|
|
It "Test Script Lookup Positive Script Info" {
|
|
$tempFile = "mytempfile.ps1"
|
|
$fullPath = Join-Path $TestDrive -ChildPath $tempFile
|
|
"$a = dir" > $fullPath
|
|
$results = Get-Command $fullPath
|
|
|
|
$results.Name | Should -BeExactly $tempFile
|
|
$results.Definition | Should -BeExactly $fullPath
|
|
}
|
|
|
|
It "Two dynamic parameters are created properly" {
|
|
$results = Get-Command TestGetCommand-DynamicParametersDCR -TestToRun return2
|
|
$dynamicParameter = "OneString", "TwoInt"
|
|
VerifyDynamicParametersExist -cmdlet $results[0] -parameterNames $dynamicParameter
|
|
VerifyParameterType -cmdlet $results[0] -parameterName "OneString" -ParameterType string
|
|
VerifyParameterType -cmdlet $results[0] -parameterName "TwoInt" -ParameterType int
|
|
}
|
|
|
|
It "Throw an Exception when set TestToRun to 'returnduplicateparameter'" {
|
|
{ Get-Command TestGetCommand-DynamicParametersDCR -TestToRun returnduplicateparameter -ErrorAction Stop } |
|
|
Should -Throw -ErrorId "GetCommandMetadataError,Microsoft.PowerShell.Commands.GetCommandCommand"
|
|
}
|
|
|
|
It "verify if get the proper dynamic parameter type skipped by issue #1430" -Pending {
|
|
$results = Get-Command TestGetCommand-DynamicParametersDCR -TestToRun returngenericparameter -ParameterType System.Diagnostics.Process
|
|
VerifyParameterType -cmdlet $results[0] -parameterName "TypedValue" -parameterType System.Diagnostics.Process
|
|
}
|
|
|
|
It "It works with Single Cmdlet Using Verb&Noun ParameterSet" {
|
|
$paramName = "Encoding"
|
|
$results = Get-Command -Verb get -Noun content -encoding UTF8
|
|
VerifyDynamicParametersExist -cmdlet $results[0] -parameterNames $paramName
|
|
VerifyParameterType -cmdlet $results[0] -parameterName $paramName -ParameterType System.Text.Encoding
|
|
}
|
|
|
|
#unsupported parameter: -synop
|
|
It "[Unsupported]It works with Single Cmdlet Using Verb&Noun ParameterSet With Synopsis" -Pending {
|
|
$paramName = "Encoding"
|
|
$results = Get-Command -Verb get -Noun content -encoding UTF8 -Synop
|
|
VerifyDynamicParametersExist -cmdlet $results[0] -parameterNames $paramName
|
|
VerifyParameterType -cmdlet $results[0] -parameterName $paramName -ParameterType System.Text.Encoding
|
|
}
|
|
|
|
It "Piping more than one CommandInfo works" {
|
|
$result = Get-Command -Name Add-Content, Get-Content | Get-Command
|
|
$result.Count | Should -Be 2
|
|
$result.Name | Should -Be "Add-Content","Get-Content"
|
|
}
|
|
|
|
It "Excluding modules works" {
|
|
$result = Get-Command -Name Get-Command -ExcludeModule Microsoft.PowerShell.Core
|
|
$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
|
|
}
|
|
}
|