Skip to content

Commit 0ee5278

Browse files
pougetatadityapatwardhan
authored andcommitted
Fix Get-Module -FullyQualifiedName option to work with paths (PowerShell#9101)
1 parent 17f5a5c commit 0ee5278

File tree

3 files changed

+142
-21
lines changed

3 files changed

+142
-21
lines changed

src/System.Management.Automation/engine/Modules/GetModuleCommand.cs

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -375,10 +375,11 @@ protected override void ProcessRecord()
375375
var moduleSpecTable = new Dictionary<string, ModuleSpecification>(StringComparer.OrdinalIgnoreCase);
376376
if (FullyQualifiedName != null)
377377
{
378-
// TODO:
379-
// FullyQualifiedName.Name could be a path, in which case it will not match module.Name.
380-
// This is potentially a bug (since version checks are ignored).
381-
// We should normalize FullyQualifiedName.Name here with ModuleIntrinsics.NormalizeModuleName().
378+
for (int modSpecIndex = 0; modSpecIndex < FullyQualifiedName.Length; modSpecIndex++)
379+
{
380+
FullyQualifiedName[modSpecIndex] = FullyQualifiedName[modSpecIndex].WithNormalizedName(Context, SessionState.Path.CurrentLocation.Path);
381+
}
382+
382383
moduleSpecTable = FullyQualifiedName.ToDictionary(moduleSpecification => moduleSpecification.Name, StringComparer.OrdinalIgnoreCase);
383384
strNames.AddRange(FullyQualifiedName.Select(spec => spec.Name));
384385
}
@@ -545,22 +546,36 @@ private static IEnumerable<PSModuleInfo> FilterModulesForSpecificationMatch(
545546

546547
foreach (PSModuleInfo module in modules)
547548
{
548-
// TODO:
549-
// moduleSpecification.Name may be a path and will not match module.Name when they refer to the same module.
550-
// This actually causes the module to be returned always, so other specification checks are skipped erroneously.
551-
// Instead we need to be able to look up or match modules by path as well (e.g. a new comparer for PSModuleInfo).
549+
IEnumerable<ModuleSpecification> candidateModuleSpecs = GetCandidateModuleSpecs(moduleSpecificationTable, module);
552550

553-
// No table entry means we return the module
554-
if (!moduleSpecificationTable.TryGetValue(module.Name, out ModuleSpecification moduleSpecification))
551+
// Modules with table entries only get returned if they match them
552+
// We skip the name check since modules have already been prefiltered base on the moduleSpec path/name
553+
foreach (ModuleSpecification moduleSpec in candidateModuleSpecs)
555554
{
556-
yield return module;
557-
continue;
555+
if (ModuleIntrinsics.IsModuleMatchingModuleSpec(module, moduleSpec, skipNameCheck: true))
556+
{
557+
yield return module;
558+
}
558559
}
560+
}
561+
}
559562

560-
// Modules with table entries only get returned if they match them
561-
if (ModuleIntrinsics.IsModuleMatchingModuleSpec(module, moduleSpecification))
563+
/// <summary>
564+
/// Take a dictionary of module specifications and return those that potentially match the module
565+
/// passed in as a parameter (checks on names and paths).
566+
/// </summary>
567+
/// <param name="moduleSpecTable">The module specifications to filter candidates from.</param>
568+
/// <param name="module">The module to find candidates for from the module specification table.</param>
569+
/// <returns>The module specifications matching the module based on name, path and subpath.</returns>
570+
private static IEnumerable<ModuleSpecification> GetCandidateModuleSpecs(
571+
IDictionary<string, ModuleSpecification> moduleSpecTable,
572+
PSModuleInfo module)
573+
{
574+
foreach (ModuleSpecification moduleSpec in moduleSpecTable.Values)
575+
{
576+
if (moduleSpec.Name == module.Name || moduleSpec.Name == module.Path || module.Path.Contains(moduleSpec.Name))
562577
{
563-
yield return module;
578+
yield return moduleSpec;
564579
}
565580
}
566581
}

src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -423,10 +423,14 @@ internal List<PSModuleInfo> GetModules(ModuleSpecification[] fullyQualifiedName,
423423
/// </summary>
424424
/// <param name="moduleInfo">The module info object to check.</param>
425425
/// <param name="moduleSpec">The module specification to match the module info object against.</param>
426+
/// <param name="skipNameCheck">True if we should skip the name check on the module specification.</param>
426427
/// <returns>True if the module info object meets all the constraints on the module specification, false otherwise.</returns>
427-
internal static bool IsModuleMatchingModuleSpec(PSModuleInfo moduleInfo, ModuleSpecification moduleSpec)
428+
internal static bool IsModuleMatchingModuleSpec(
429+
PSModuleInfo moduleInfo,
430+
ModuleSpecification moduleSpec,
431+
bool skipNameCheck = false)
428432
{
429-
return IsModuleMatchingModuleSpec(out ModuleMatchFailure matchFailureReason, moduleInfo, moduleSpec);
433+
return IsModuleMatchingModuleSpec(out ModuleMatchFailure matchFailureReason, moduleInfo, moduleSpec, skipNameCheck);
430434
}
431435

432436
/// <summary>
@@ -435,8 +439,13 @@ internal static bool IsModuleMatchingModuleSpec(PSModuleInfo moduleInfo, ModuleS
435439
/// <param name="matchFailureReason">The constraint that caused the match failure, if any.</param>
436440
/// <param name="moduleInfo">The module info object to check.</param>
437441
/// <param name="moduleSpec">The module specification to match the module info object against.</param>
442+
/// <param name="skipNameCheck">True if we should skip the name check on the module specification.</param>
438443
/// <returns>True if the module info object meets all the constraints on the module specification, false otherwise.</returns>
439-
internal static bool IsModuleMatchingModuleSpec(out ModuleMatchFailure matchFailureReason, PSModuleInfo moduleInfo, ModuleSpecification moduleSpec)
444+
internal static bool IsModuleMatchingModuleSpec(
445+
out ModuleMatchFailure matchFailureReason,
446+
PSModuleInfo moduleInfo,
447+
ModuleSpecification moduleSpec,
448+
bool skipNameCheck = false)
440449
{
441450
if (moduleSpec == null)
442451
{
@@ -447,7 +456,7 @@ internal static bool IsModuleMatchingModuleSpec(out ModuleMatchFailure matchFail
447456
return IsModuleMatchingConstraints(
448457
out matchFailureReason,
449458
moduleInfo,
450-
moduleSpec.Name,
459+
skipNameCheck ? null : moduleSpec.Name,
451460
moduleSpec.Guid,
452461
moduleSpec.RequiredVersion,
453462
moduleSpec.Version,

test/powershell/Modules/Microsoft.PowerShell.Core/Get-Module.Tests.ps1

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@ Describe "Get-Module -ListAvailable" -Tags "CI" {
2727
New-Item -ItemType File -Path "$testdrive\Modules\Az\Az.psm1" > $null
2828

2929
$fullyQualifiedPathTestCases = @(
30-
# The current behaviour in PowerShell is that version gets ignored when using Get-Module -FullyQualifiedName with a path
31-
@{ ModPath = "$TestDrive/Modules\Foo"; Name = 'Foo'; Version = '2.0'; Count = 2 }
30+
@{ ModPath = "$TestDrive/Modules\Foo"; Name = 'Foo'; Version = '2.0'; Count = 1 }
3231
@{ ModPath = "$TestDrive\Modules/Foo\1.1/Foo.psd1"; Name = 'Foo'; Version = '1.1'; Count = 1 }
3332
@{ ModPath = "$TestDrive\Modules/Bar.psd1"; Name = 'Bar'; Version = '0.0'; Count = 1 }
3433
@{ ModPath = "$TestDrive\Modules\Zoo\Too\Zoo.psm1"; Name = 'Zoo'; Version = '0.0'; Count = 1 }
@@ -225,3 +224,101 @@ Describe "Get-Module -ListAvailable" -Tags "CI" {
225224
}
226225
}
227226
}
227+
228+
Describe 'Get-Module -ListAvailable with path' -Tags "CI" {
229+
BeforeAll {
230+
$moduleName = 'Banana'
231+
$modulePath = Join-Path $TestDrive $moduleName
232+
$v1 = '1.2.3'
233+
$v2 = '4.8.3'
234+
$v1DirPath = Join-Path $modulePath $v1
235+
$v2DirPath = Join-Path $modulePath $v2
236+
$manifestV1Path = Join-Path $v1DirPath "$moduleName.psd1"
237+
$manifestV2Path = Join-Path $v2DirPath "$moduleName.psd1"
238+
239+
New-Item -ItemType Directory $modulePath
240+
New-Item -ItemType Directory -Path $v1DirPath
241+
New-Item -ItemType Directory -Path $v2DirPath
242+
New-ModuleManifest -Path $manifestV1Path -ModuleVersion $v1
243+
New-ModuleManifest -Path $manifestV2Path -ModuleVersion $v2
244+
}
245+
246+
It "Gets all versions by path" {
247+
$modules = Get-Module -ListAvailable $modulePath | Sort-Object -Property Version
248+
249+
$modules | Should -HaveCount 2
250+
$modules[0].Name | Should -BeExactly $moduleName
251+
$modules[0].Path | Should -BeExactly $manifestV1Path
252+
$modules[0].Version | Should -Be $v1
253+
$modules[1].Name | Should -BeExactly $moduleName
254+
$modules[1].Path | Should -BeExactly $manifestV2Path
255+
$modules[1].Version | Should -Be $v2
256+
}
257+
258+
It "Gets all versions by FullyQualifiedName with path with lower version" {
259+
$modules = Get-Module -ListAvailable -FullyQualifiedName @{ ModuleName = $modulePath; ModuleVersion = '0.0' } | Sort-Object -Property Version
260+
261+
$modules | Should -HaveCount 2
262+
$modules[0].Name | Should -BeExactly $moduleName
263+
$modules[0].Path | Should -BeExactly $manifestV1Path
264+
$modules[0].Version | Should -Be $v1
265+
$modules[1].Name | Should -BeExactly $moduleName
266+
$modules[1].Path | Should -BeExactly $manifestV2Path
267+
$modules[1].Version | Should -Be $v2
268+
}
269+
270+
It "Gets high version by FullyQualifiedName with path with high version" {
271+
$modules = Get-Module -ListAvailable -FullyQualifiedName @{ ModuleName = $modulePath; ModuleVersion = '2.0' } | Sort-Object -Property Version
272+
273+
$modules | Should -HaveCount 1
274+
$modules[0].Name | Should -BeExactly $moduleName
275+
$modules[0].Path | Should -BeExactly $manifestV2Path
276+
$modules[0].Version | Should -Be $v2
277+
}
278+
279+
It "Gets low version by FullyQualifiedName with path with low maximum version" {
280+
$modules = Get-Module -ListAvailable -FullyQualifiedName @{ ModuleName = $modulePath; MaximumVersion = '2.0' } | Sort-Object -Property Version
281+
282+
$modules | Should -HaveCount 1
283+
$modules[0].Name | Should -BeExactly $moduleName
284+
$modules[0].Path | Should -BeExactly $manifestV1Path
285+
$modules[0].Version | Should -Be $v1
286+
}
287+
288+
It "Gets low version by FullyQualifiedName with path with low maximum version and version" {
289+
$modules = Get-Module -ListAvailable -FullyQualifiedName @{ ModuleName = $modulePath; MaximumVersion = '2.0'; ModuleVersion = '1.0' } | Sort-Object -Property Version
290+
291+
$modules | Should -HaveCount 1
292+
$modules[0].Name | Should -BeExactly $moduleName
293+
$modules[0].Path | Should -BeExactly $manifestV1Path
294+
$modules[0].Version | Should -Be $v1
295+
}
296+
297+
It "Gets correct version by FullyQualifiedName with path with required version" -TestCases @(
298+
@{ Version = $v1 }
299+
@{ Version = $v2 }
300+
) {
301+
param([version]$Version)
302+
303+
switch ($Version)
304+
{
305+
$v1
306+
{
307+
$expectedPath = $manifestV1Path
308+
break
309+
}
310+
311+
$v2
312+
{
313+
$expectedPath = $manifestV2Path
314+
}
315+
}
316+
317+
$modules = Get-Module -ListAvailable -FullyQualifiedName @{ ModuleName = $modulePath; RequiredVersion = $Version }
318+
319+
$modules | Should -HaveCount 1
320+
$modules[0].Name | Should -BeExactly $moduleName
321+
$modules[0].Path | Should -BeExactly $expectedPath
322+
$modules[0].Version | Should -Be $Version
323+
}
324+
}

0 commit comments

Comments
 (0)