Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
6060fd0
Add ability for searcher to search for commands using either wildcard…
TravisEz13 Mar 18, 2019
2343357
make get-command tell command searcher to use wildcards when searchin…
TravisEz13 Mar 18, 2019
5e0523f
Add tests for the various cases
TravisEz13 Mar 18, 2019
9b8f2f8
Address Paul's comments
TravisEz13 Mar 18, 2019
38f0111
make test names static
TravisEz13 Mar 18, 2019
f143e17
Address Paul's comments (some offline)
TravisEz13 Mar 18, 2019
ffc4ce1
start using `HasFlag`
TravisEz13 Mar 22, 2019
539a0d0
start using `HasFlag` in second location
TravisEz13 Mar 22, 2019
f79768b
Fix CodeFactor issues
TravisEz13 Mar 22, 2019
cdbf1e9
Fix check in fullPath codePath
TravisEz13 Mar 22, 2019
4688ae8
Don't recreate resolvedPaths
TravisEz13 Mar 22, 2019
bc83308
Add -wait to start-pspester
TravisEz13 Mar 29, 2019
74ee28b
make changes that committee asked for
TravisEz13 Mar 29, 2019
e153182
fix CodeFactor issues
TravisEz13 Mar 29, 2019
3b004d1
Add comments to new functions
TravisEz13 Mar 29, 2019
8ac7068
fix function summaries
TravisEz13 Mar 29, 2019
eebc092
fix syntax issue
TravisEz13 Mar 29, 2019
7f14662
Address PR feedback
TravisEz13 Mar 29, 2019
4569916
Address PR feedback
TravisEz13 Mar 29, 2019
9e7edcf
prevent null ref exception
TravisEz13 Mar 29, 2019
eaa81a6
must return directories too
TravisEz13 Mar 29, 2019
5071a54
Update src/System.Management.Automation/engine/CommandSearcher.cs
TravisEz13 Mar 29, 2019
6618eb3
Apply suggestions from code review
TravisEz13 Mar 29, 2019
d9615c5
remove unneeded condition
TravisEz13 Mar 29, 2019
2e43c54
Fix CodeFactor issue
TravisEz13 Mar 29, 2019
3a4e6f3
Apply suggestions from code review
TravisEz13 Mar 29, 2019
925bc41
used named parameters
TravisEz13 Mar 29, 2019
30134f4
Update instance where filesystem provider name is not compared using …
TravisEz13 Mar 29, 2019
93a4ee9
update parameter spacing
TravisEz13 Mar 30, 2019
ddf6c6f
don't get commandinfo if path is null
TravisEz13 Mar 30, 2019
5ddcb4f
fix tests to cover additional cases
TravisEz13 Mar 30, 2019
2d0a2f1
fix codefactor issues
TravisEz13 Mar 30, 2019
866aaf6
fix expected result
TravisEz13 Mar 30, 2019
9670f88
Add additional negative cases
TravisEz13 Mar 30, 2019
32ee808
add missing param block
TravisEz13 Mar 30, 2019
c086e74
expected results aren't as negative as I thought
TravisEz13 Mar 30, 2019
a49ad49
Add additional subFolder execution cases
TravisEz13 Mar 30, 2019
a59bc79
make returns consistent
TravisEz13 Apr 1, 2019
b18aacc
make some pending tests negative tests
TravisEz13 Apr 1, 2019
85a8c85
delete pending tests which were made negative tests
TravisEz13 Apr 1, 2019
0e4c566
fix PR feedback
TravisEz13 Apr 1, 2019
66fc031
Update test/powershell/engine/Basic/CommandDiscovery.Tests.ps1
TravisEz13 Apr 1, 2019
5d7bc15
Update test/powershell/engine/Basic/CommandDiscovery.Tests.ps1
TravisEz13 Apr 2, 2019
c965097
Update test/powershell/engine/Basic/CommandDiscovery.Tests.ps1
TravisEz13 Apr 2, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion build.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -907,7 +907,10 @@ function Start-PSPester {
[switch]$IncludeCommonTests,
[string]$ExperimentalFeatureName,
[Parameter(HelpMessage='Title to publish the results as.')]
[string]$Title = 'PowerShell Core Tests'
[string]$Title = 'PowerShell Core Tests',
[Parameter(ParameterSetName='Wait', Mandatory=$true,
HelpMessage='Wait for the debugger to attach to PowerShell before Pester starts. Debug builds only!')]
[switch]$Wait
)

if (-not (Get-Module -ListAvailable -Name $Pester -ErrorAction SilentlyContinue | Where-Object { $_.Version -ge "4.2" } ))
Expand Down Expand Up @@ -1101,6 +1104,13 @@ function Start-PSPester {
$PSFlags = @("-settings", $configFile, "-noprofile")
}

# -Wait is only available on Debug builds
# It is used to allow the debugger to attach before PowerShell
# runs pester in this case
if($Wait.IsPresent){
$PSFlags += '-wait'
}

# To ensure proper testing, the module path must not be inherited by the spawned process
try {
$originalModulePath = $env:PSModulePath
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -735,7 +735,7 @@ internal CommandInfo LookupCommandInfo(string commandName, CommandOrigin command

internal static CommandInfo LookupCommandInfo(string commandName, CommandOrigin commandOrigin, ExecutionContext context)
{
return LookupCommandInfo(commandName, CommandTypes.All, SearchResolutionOptions.None, commandOrigin, context);
return LookupCommandInfo(commandName, CommandTypes.All, SearchResolutionOptions.ResolveLiteralThenPathPatterns, commandOrigin, context);
}

internal static CommandInfo LookupCommandInfo(
Expand Down
181 changes: 129 additions & 52 deletions src/System.Management.Automation/engine/CommandSearcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -456,52 +456,33 @@ private CommandInfo GetNextFromPath()
"Trying to resolve the path as an PSPath");

// Find the match if it is.

Collection<string> resolvedPaths = new Collection<string>();

try
{
Provider.CmdletProvider providerInstance;
ProviderInfo provider;
resolvedPaths =
_context.LocationGlobber.GetGlobbedProviderPathsFromMonadPath(_commandName, false, out provider, out providerInstance);
}
catch (ItemNotFoundException)
// Try literal path resolution if it is set to run first
if (_commandResolutionOptions.HasFlag(SearchResolutionOptions.ResolveLiteralThenPathPatterns))
{
CommandDiscovery.discoveryTracer.TraceError(
"The path could not be found: {0}",
_commandName);
}
catch (DriveNotFoundException)
{
CommandDiscovery.discoveryTracer.TraceError(
"A drive could not be found for the path: {0}",
_commandName);
}
catch (ProviderNotFoundException)
{
CommandDiscovery.discoveryTracer.TraceError(
"A provider could not be found for the path: {0}",
_commandName);
}
catch (InvalidOperationException)
{
CommandDiscovery.discoveryTracer.TraceError(
"The path specified a home directory, but the provider home directory was not set. {0}",
_commandName);
var path = GetNextLiteralPathThatExists(_commandName, out _);

if (path != null)
{
return GetInfoFromPath(path);
}
}
catch (ProviderInvocationException providerException)

Collection<string> resolvedPaths = new Collection<string>();
if (WildcardPattern.ContainsWildcardCharacters(_commandName))
{
CommandDiscovery.discoveryTracer.TraceError(
"The provider associated with the path '{0}' encountered an error: {1}",
_commandName,
providerException.Message);
resolvedPaths = GetNextFromPathUsingWildcards(_commandName, out _);
}
catch (PSNotSupportedException)

// Try literal path resolution if wildcards are enable first and wildcard search failed
if (!_commandResolutionOptions.HasFlag(SearchResolutionOptions.ResolveLiteralThenPathPatterns) &&
resolvedPaths.Count == 0)
{
CommandDiscovery.discoveryTracer.TraceError(
"The provider associated with the path '{0}' does not implement ContainerCmdletProvider",
_commandName);
string path = GetNextLiteralPathThatExists(_commandName, out _);

if (path != null)
{
return GetInfoFromPath(path);
}
}

if (resolvedPaths.Count > 1)
Expand All @@ -528,6 +509,64 @@ private CommandInfo GetNextFromPath()
return result;
}

/// <summary>
/// Gets the next path using WildCards.
/// </summary>
/// <param name="command">
/// The command to search for.
/// </param>
/// <param name="provider">The provider that the command was found in.</param>
/// <returns>
/// A collection of full paths to the commands which were found.
/// </returns>
private Collection<string> GetNextFromPathUsingWildcards(string command, out ProviderInfo provider)
{
try
{
return _context.LocationGlobber.GetGlobbedProviderPathsFromMonadPath(path: command, allowNonexistingPaths: false, provider: out provider, providerInstance: out _);
}
catch (ItemNotFoundException)
{
CommandDiscovery.discoveryTracer.TraceError(
"The path could not be found: {0}",
command);
}
catch (DriveNotFoundException)
{
CommandDiscovery.discoveryTracer.TraceError(
"A drive could not be found for the path: {0}",
command);
}
catch (ProviderNotFoundException)
{
CommandDiscovery.discoveryTracer.TraceError(
"A provider could not be found for the path: {0}",
command);
}
catch (InvalidOperationException)
{
CommandDiscovery.discoveryTracer.TraceError(
"The path specified a home directory, but the provider home directory was not set. {0}",
command);
}
catch (ProviderInvocationException providerException)
{
CommandDiscovery.discoveryTracer.TraceError(
"The provider associated with the path '{0}' encountered an error: {1}",
command,
providerException.Message);
}
catch (PSNotSupportedException)
{
CommandDiscovery.discoveryTracer.TraceError(
"The provider associated with the path '{0}' does not implement ContainerCmdletProvider",
command);
}

provider = null;
return null;
}

private static bool checkPath(string path, string commandName)
{
return path.StartsWith(commandName, StringComparison.OrdinalIgnoreCase);
Expand Down Expand Up @@ -1092,15 +1131,21 @@ private string ResolvePSPath(string path)
{
ProviderInfo provider = null;
string resolvedPath = null;
if (WildcardPattern.ContainsWildcardCharacters(path))

// Try literal path resolution if it is set to run first
if (_commandResolutionOptions.HasFlag(SearchResolutionOptions.ResolveLiteralThenPathPatterns))
{
// Cannot return early as this code path only expects
// The file system provider and the final check for that
// must verify this before we return.
resolvedPath = GetNextLiteralPathThatExists(path, out provider);
}

if (WildcardPattern.ContainsWildcardCharacters(path) &&
((resolvedPath == null) || (provider == null)))
{
// Let PowerShell resolve relative path with wildcards.
Provider.CmdletProvider providerInstance;
Collection<string> resolvedPaths = _context.LocationGlobber.GetGlobbedProviderPathsFromMonadPath(
path,
false,
out provider,
out providerInstance);
Collection<string> resolvedPaths = GetNextFromPathUsingWildcards(path, out provider);

if (resolvedPaths.Count == 0)
{
Expand All @@ -1124,14 +1169,15 @@ private string ResolvePSPath(string path)
}
}

// Revert to previous path resolver if wildcards produces no results.
if ((resolvedPath == null) || (provider == null))
// Try literal path resolution if wildcards are enabled first and wildcard search failed
if (!_commandResolutionOptions.HasFlag(SearchResolutionOptions.ResolveLiteralThenPathPatterns) &&
((resolvedPath == null) || (provider == null)))
{
resolvedPath = _context.LocationGlobber.GetProviderPath(path, out provider);
resolvedPath = GetNextLiteralPathThatExists(path, out provider);
}

// Verify the path was resolved to a file system path
if (provider.NameEquals(_context.ProviderNames.FileSystem))
if (provider != null && provider.NameEquals(_context.ProviderNames.FileSystem))
{
result = resolvedPath;

Expand Down Expand Up @@ -1176,6 +1222,32 @@ private string ResolvePSPath(string path)
return result;
}

/// <summary>
/// Gets the next literal path.
/// Filtering to ones that exist for the filesystem.
/// </summary>
/// <param name="command">
/// The command to search for.
/// </param>
/// <param name="provider">The provider that the command was found in.</param>
/// <returns>
/// Full path to the command.
/// </returns>
private string GetNextLiteralPathThatExists(string command, out ProviderInfo provider)
{
string resolvedPath = _context.LocationGlobber.GetProviderPath(command, out provider);

if (provider.NameEquals(_context.ProviderNames.FileSystem)
&& !File.Exists(resolvedPath)
&& !Directory.Exists(resolvedPath))
{
provider = null;
return null;
}

return resolvedPath;
}

/// <summary>
/// Creates a collection of patterns used to find the command.
/// </summary>
Expand Down Expand Up @@ -1609,5 +1681,10 @@ internal enum SearchResolutionOptions
/// Enable searching for cmdlets/functions by abbreviation expansion.
/// </summary>
UseAbbreviationExpansion = 0x20,

/// <summary>
/// Enable resolving wildcard in paths.
/// </summary>
ResolveLiteralThenPathPatterns = 0x40
}
}
98 changes: 95 additions & 3 deletions test/powershell/engine/Basic/CommandDiscovery.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -85,18 +85,107 @@ Describe "Command Discovery tests" -Tags "CI" {
(& 'location').Path | Should -Be (get-location).Path
}

Context "Get-Command should use globbing for scripts" {
Context "Use literal path first when executing scripts" {
BeforeAll {
$firstFileName = '[test1].ps1'
$secondFileName = '1.ps1'
$thirdFileName = '2.ps1'
$firstResult = "executing $firstFileName in root"
$secondResult = "executing $secondFileName in root"
$thirdResult = "executing $thirdFileName in root"
setup -f $firstFileName -content "'$firstResult'"
setup -f $secondFileName -content "'$secondResult'"
setup -f $thirdFileName -content "'$thirdResult'"

$subFolder = 'subFolder'
$firstFileInSubFolder = Join-Path $subFolder -ChildPath $firstFileName
$secondFileInSubFolder = Join-Path $subFolder -ChildPath $secondFileName
$thirdFileInSubFolder = Join-Path $subFolder -ChildPath $thirdFileName
setup -f $firstFileInSubFolder -content "'$firstResult'"
setup -f $secondFileInSubFolder -content "'$secondResult'"
setup -f $thirdFileInSubFolder -content "'$thirdResult'"

$secondFileSearchInSubfolder = (Join-Path -Path $subFolder -ChildPath '[t1].ps1')

$executionWithWildcardCases = @(
#Region relative paths with './'
@{command = '.\[test1].ps1' ; expectedResult = $firstResult; name = '.\[test1].ps1'}
@{command = '.\[t1].ps1' ; expectedResult = $secondResult; name = '.\[t1].ps1'}
#endregion

#Region relative Subfolder paths without './'
@{command = $secondFileInSubFolder ; expectedResult = $secondResult; name = $secondFileInSubFolder}

# Wildcard search is not being performed in this scenario before this change.
# I noted the issue in the pending message
@{command = $firstFileInSubFolder ; expectedResult = $firstResult; name = $firstFileInSubFolder; Pending="See note about wildcard in https://github.com/PowerShell/PowerShell/issues/9256"}
@{command = $secondFileSearchInSubfolder ; expectedResult = $secondResult; name = $secondFileSearchInSubfolder; Pending="See note about wildcard in https://github.com/PowerShell/PowerShell/issues/9256"}
#endregion
#Region relative Subfolder paths with '.\'
@{command = '.\' + $secondFileInSubFolder ; expectedResult = $secondResult; name = $secondFileInSubFolder}
@{command = '.\subFolder\[test1].ps1' ; expectedResult = $firstResult; name = '.\subFolder\[test1].ps1'}
@{command = '.\subFolder\[t1].ps1' ; expectedResult = $secondResult; name = '.\' + $secondFileSearchInSubfolder}
@{command = '.\' + $firstFileInSubFolder ; expectedResult = $firstResult; name = '.\' + $firstFileInSubFolder}
@{command = '.\' + $secondFileSearchInSubfolder ; expectedResult = $secondResult; name = '.\' + $secondFileSearchInSubfolder}
#endregion

#region rooted paths
@{command = (Join-Path ${TestDrive} -ChildPath '[test1].ps1') ; expectedResult = $firstResult; name = '.\[test1].ps1 by fully qualified path'}
@{command = (Join-Path ${TestDrive} -ChildPath '[t1].ps1') ; expectedResult = $secondResult; name = '.\1.ps1 by fully qualified path with wildcard'}
#endregion
)

$shouldNotExecuteCases = @(
@{command = 'subFolder\[test1].ps1' ; testName = 'Relative path that where module qualified syntax overlaps'; ExpectedErrorId = 'CouldNotAutoLoadModule'}
@{command = '.\[12].ps1' ; testName = 'relative path with bracket wildcard matctching multiple files'}
@{command = (Join-Path ${TestDrive} -ChildPath '[12].ps1') ; testName = 'fully qualified path with bracket wildcard matching multiple files'}
)

Push-Location ${TestDrive}\
}

AfterAll {
Pop-Location
}

It "Invoking <name> should return '<expectedResult>'" -TestCases $executionWithWildcardCases {
param($command, $expectedResult, [String]$Pending)

if($Pending)
{
Set-TestInconclusive -Message $Pending
}

& $command | Should -BeExactly $expectedResult
}

It "'<testName>' should not execute" -TestCases $shouldNotExecuteCases {
param(
[string]
$command,
[string]
$ExpectedErrorId = 'CommandNotFoundException'
)
{ & $command } | Should -Throw -ErrorId $ExpectedErrorId
}
}

Context "Get-Command should use globbing first for scripts" {
BeforeAll {
$firstResult = '[first script]'
$secondResult = 'alt script'
$thirdResult = 'bad script'
setup -f '[test1].ps1' -content "'$firstResult'"
setup -f '1.ps1' -content "'$secondResult'"
setup -f '2.ps1' -content "'$thirdResult'"

$gcmWithWildcardCases = @(
@{command = '.\?[tb]est1?.ps1'; expectedCommand = '[test1].ps1'; expectedCommandCount =1; name = '''.\?[tb]est1?.ps1'''}
@{command = (Join-Path ${TestDrive} -ChildPath '?[tb]est1?.ps1'); expectedCommand = '[test1].ps1'; expectedCommandCount =1 ; name = '''.\?[tb]est1?.ps1'' by fully qualified path'}
@{command = '.\[test1].ps1'; expectedCommand = '1.ps1'; expectedCommandCount =1; name = '''.\[test1].ps1'''}
@{command = (Join-Path ${TestDrive} -ChildPath '[test1].ps1'); expectedCommand = '1.ps1'; expectedCommandCount =1 ; name = '''.\[test1].ps1'' by fully qualified path'}
@{command = '.\[12].ps1'; expectedCommand = '1.ps1'; expectedCommandCount =0; name = 'relative path with bracket wildcard matctching multiple files'}
@{command = (Join-Path ${TestDrive} -ChildPath '[12].ps1'); expectedCommand = '1.ps1'; expectedCommandCount =0 ; name = 'fully qualified path with bracket wildcard matctching multiple files'}
)

Push-Location ${TestDrive}\
Expand All @@ -108,9 +197,12 @@ Describe "Command Discovery tests" -Tags "CI" {

It "Get-Command <name> should return <expectedCommandCount> command named '<expectedCommand>'" -TestCases $gcmWithWildcardCases {
param($command, $expectedCommand, $expectedCommandCount)
$commands = Get-Command -Name $command
$commands = @(Get-Command -Name $command)
$commands.Count | Should -Be $expectedCommandCount
$commands.Name | Should -BeExactly $expectedCommand
if($expectedCommandCount -gt 0)
{
$commands.Name | Should -BeExactly $expectedCommand
}
}
}
}