diff --git a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs
index 538c4775f0a..71ac49e9701 100644
--- a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs
+++ b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs
@@ -724,56 +724,63 @@ internal static bool MatchesModulePath(string modulePath, string requiredPath)
}
///
- /// Takes the name of a module as used in a module specification
- /// and either returns it as a simple name (if it was a simple name)
- /// or a fully qualified, PowerShell-resolved path.
+ /// Takes a module name from a module specification and returns a
+ /// normalized module name.
///
- /// The name or path of the module from the specification.
- /// The path to base relative paths off.
+ ///
+ ///
+ /// A normalized module name is either:
+ ///
+ /// - A simple module name if was a simple name.
+ /// - A fully qualified, PowerShell-resolved path.
+ /// -
+ /// A fully qualified path by combination of and
+ /// if PowerShell could not resolve the path.
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// The name or path of the module from the specification.
+ /// The path to base relative paths off.
/// The current execution context.
///
- /// The simple module name if the given one was simple,
- /// otherwise a fully resolved, absolute path to the module.
+ /// A simple module name if was a simple module
+ /// name, otherwise a fully qualified path to the module.
///
- ///
- /// 2018-11-09 rjmholt:
- /// There are several, possibly inconsistent, path handling mechanisms
- /// in the module cmdlets. After looking through all of them and seeing
- /// they all make some assumptions about their caller I wrote this method.
- /// Hopefully we can find a standard path resolution API to settle on.
- ///
- internal static string NormalizeModuleName(
- string moduleName,
- string basePath,
- ExecutionContext executionContext)
+ internal static string NormalizeModuleName(string moduleNameOrPath, string relativeTo, ExecutionContext executionContext)
{
- if (moduleName == null)
- {
- return null;
- }
+ ArgumentNullException.ThrowIfNull(moduleNameOrPath);
+ ArgumentNullException.ThrowIfNull(relativeTo);
- // Check whether the module is a path -- if not, it is a simple name and we just return it.
- if (!IsModuleNamePath(moduleName))
+ if (!IsModuleNamePath(moduleNameOrPath))
{
- return moduleName;
+ return moduleNameOrPath;
}
- // Standardize directory separators -- Path.IsPathRooted() will return false for "\path\here" on *nix and for "/path/there" on Windows
- moduleName = moduleName.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator);
+ // Ensure OS default directory separators because
+ // - Path.IsPathRooted("\some\path") returns false on *nix, and
+ // - Path.IsPathRooted("/some/path") return false on Windows.
+ moduleNameOrPath = moduleNameOrPath.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator);
- // Note: Path.IsFullyQualified("\default\root") is false on Windows, but Path.IsPathRooted returns true
- if (!Path.IsPathRooted(moduleName))
+ // On Windows:
+ // - Path.IsFullyQualified("\default\root") returns false, but
+ // - Path.IsPathRooted("\default\root") returns true.
+ if (!Path.IsPathRooted(moduleNameOrPath))
{
- moduleName = Path.Join(basePath, moduleName);
+ moduleNameOrPath = Path.Join(relativeTo, moduleNameOrPath);
}
- // Use the PowerShell filesystem provider to fully resolve the path
- // If there is a problem, null could be returned -- so default back to the pre-normalized path
- string normalizedPath = ModuleCmdletBase.GetResolvedPath(moduleName, executionContext)?.TrimEnd(StringLiterals.DefaultPathSeparator);
+ // Resolving the path using ModuleCmdletBase.GetResolvedPath() may rarely return null.
+ string normalizedPath = ModuleCmdletBase.GetResolvedPath(moduleNameOrPath, executionContext)?.TrimEnd(StringLiterals.DefaultPathSeparator);
- // ModuleCmdletBase.GetResolvePath will return null in the unlikely event that it failed.
- // If it does, we return the fully qualified path generated before.
- return normalizedPath ?? Path.GetFullPath(moduleName);
+ // If the resolved path is null, just return the fully qualified path generated before.
+ return normalizedPath ?? Path.GetFullPath(moduleNameOrPath);
}
///
diff --git a/src/System.Management.Automation/engine/Modules/ModuleSpecification.cs b/src/System.Management.Automation/engine/Modules/ModuleSpecification.cs
index 3c06ee856f3..4a9d6a2cab1 100644
--- a/src/System.Management.Automation/engine/Modules/ModuleSpecification.cs
+++ b/src/System.Management.Automation/engine/Modules/ModuleSpecification.cs
@@ -90,24 +90,25 @@ internal static Exception ModuleSpecificationInitHelper(ModuleSpecification modu
{
string field = entry.Key.ToString();
- if (field.Equals("ModuleName", StringComparison.OrdinalIgnoreCase))
+ if (field.EqualsOrdinalIgnoreCase("ModuleName"))
{
moduleSpecification.Name = LanguagePrimitives.ConvertTo(entry.Value);
}
- else if (field.Equals("ModuleVersion", StringComparison.OrdinalIgnoreCase))
+ else if (field.EqualsOrdinalIgnoreCase("ModuleVersion"))
{
moduleSpecification.Version = LanguagePrimitives.ConvertTo(entry.Value);
}
- else if (field.Equals("RequiredVersion", StringComparison.OrdinalIgnoreCase))
+ else if (field.EqualsOrdinalIgnoreCase("RequiredVersion"))
{
moduleSpecification.RequiredVersion = LanguagePrimitives.ConvertTo(entry.Value);
}
- else if (field.Equals("MaximumVersion", StringComparison.OrdinalIgnoreCase))
+ else if (field.EqualsOrdinalIgnoreCase("MaximumVersion"))
{
moduleSpecification.MaximumVersion = LanguagePrimitives.ConvertTo(entry.Value);
- ModuleCmdletBase.GetMaximumVersion(moduleSpecification.MaximumVersion);
+ // Ensure max version is correctly formatted.
+ _ = ModuleCmdletBase.GetMaximumVersion(moduleSpecification.MaximumVersion);
}
- else if (field.Equals("GUID", StringComparison.OrdinalIgnoreCase))
+ else if (field.EqualsOrdinalIgnoreCase("GUID"))
{
moduleSpecification.Guid = LanguagePrimitives.ConvertTo(entry.Value);
}
@@ -339,7 +340,7 @@ internal ModuleSpecification WithNormalizedName(ExecutionContext context, string
}
///
- /// Compares two ModuleSpecification objects for equality.
+ /// Compares two objects for structural equality.
///
internal class ModuleSpecificationComparer : IEqualityComparer
{
@@ -351,7 +352,7 @@ internal class ModuleSpecificationComparer : IEqualityComparerTrue if the specifications are equal, false otherwise.
public bool Equals(ModuleSpecification x, ModuleSpecification y)
{
- if (x == y)
+ if (ReferenceEquals(x, y))
{
return true;
}
@@ -361,7 +362,7 @@ public bool Equals(ModuleSpecification x, ModuleSpecification y)
&& Guid.Equals(x.Guid, y.Guid)
&& Version.Equals(x.RequiredVersion, y.RequiredVersion)
&& Version.Equals(x.Version, y.Version)
- && string.Equals(x.MaximumVersion, y.MaximumVersion);
+ && string.Equals(x.MaximumVersion, y.MaximumVersion, StringComparison.OrdinalIgnoreCase);
}
///
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Core/Get-Module.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Core/Get-Module.Tests.ps1
index fddd7a12ea6..e4855255df3 100644
--- a/test/powershell/Modules/Microsoft.PowerShell.Core/Get-Module.Tests.ps1
+++ b/test/powershell/Modules/Microsoft.PowerShell.Core/Get-Module.Tests.ps1
@@ -280,6 +280,754 @@ Describe "Get-Module -ListAvailable" -Tags "CI" {
}
}
+Describe 'Get-Module -ListAvailable -(FullyQualifiedName|Name) when argument is module name or filename-like' -Tags "CI" {
+ BeforeAll {
+ $oldPSModulePath = $env:PSModulePath
+ $env:PSModulePath = New-Item -ItemType Directory (Join-Path $TestDrive modules)
+
+ # Manifest modules under $env:PSModulePath
+ #
+ # Versioned, manifest
+ $inPSModulePathWithManifestFileName = 'existing'
+ $inPSModulePatWithManifestDirectory = Join-Path $env:PSModulePath $inPSModulePathWithManifestFileName '0.0.1'
+ $inPSModulePathWithManifestFilePath = Join-Path $inPSModulePatWithManifestDirectory "$inPSModulePathWithManifestFileName.psm1"
+ New-Item -ItemType File -Force $inPSModulePathWithManifestFilePath > $null
+ New-ModuleManifest -Path (Join-Path $inPSModulePatWithManifestDirectory "$inPSModulePathWithManifestFileName.psd1")
+ #
+ # Versioned, manifest
+ $inPSModulePathWithManifestFileName2 = 'existing2'
+ $inPSModulePatWithManifestDirectory2 = Join-Path $env:PSModulePath $inPSModulePathWithManifestFileName2 '0.0.1'
+ $inPSModulePathWithManifestFilePath2 = Join-Path $inPSModulePatWithManifestDirectory2 "$inPSModulePathWithManifestFileName2.psm1"
+ New-Item -ItemType File -Force $inPSModulePathWithManifestFilePath2 > $null
+ New-ModuleManifest -Path (Join-Path $inPSModulePatWithManifestDirectory2 "$inPSModulePathWithManifestFileName2.psd1")
+ #
+ # Versioned, manifest, multiple components in name
+ $inPSModulePathWithManifestWithPathLikeNameFileName = 'existing.xxx'
+ $inPsModulePathWithManifestWithPathLikeNameDirectory = Join-Path $env:PSModulePath $inPSModulePathWithManifestWithPathLikeNameFileName '0.0.1'
+ $inPsModulePathWithManifestWithPathLikeNameFilePath = Join-Path $inPsModulePathWithManifestWithPathLikeNameDirectory "$inPSModulePathWithManifestWithPathLikeNameFileName.psm1"
+ New-Item -ItemType File -Force $inPsModulePathWithManifestWithPathLikeNameFilePath > $null
+ New-ModuleManifest -Path (Join-Path $inPsModulePathWithManifestWithPathLikeNameDirectory "$inPSModulePathWithManifestWithPathLikeNameFileName.psd1")
+ #
+ # Versioned, manifest, multiple components in name, name ending with PS module extension
+ $inPSModulePathWithManifestWithPathLikeNameAsPSExtensionFileName = 'existing.psm1'
+ $inPSModulePathWithManifestWithPathLikeNameAsPSExtensionDirectory = Join-Path $env:PSModulePath $inPSModulePathWithManifestWithPathLikeNameAsPSExtensionFileName '0.0.1'
+ $inPSModulePathWithManifestWithPathLikeNameAsPSExtensionFilePath = Join-Path $inPSModulePathWithManifestWithPathLikeNameAsPSExtensionDirectory "$inPSModulePathWithManifestWithPathLikeNameAsPSExtensionFileName.psm1"
+ New-Item -ItemType File -Force $inPSModulePathWithManifestWithPathLikeNameAsPSExtensionFilePath > $null
+ New-ModuleManifest -Path (Join-Path $inPSModulePathWithManifestWithPathLikeNameAsPSExtensionDirectory "$inPSModulePathWithManifestWithPathLikeNameAsPSExtensionFileName.psd1")
+
+ # Script modules
+ #
+ # Under $env:PSModulePath. TODO: Is this a supported scenario?
+ $inPSModulePathLooseFilePath = Join-Path $env:PSModulePath 'existing-loose.psm1'
+ New-Item -ItemType File -Force $inPSModulePathLooseFilePath > $null
+ #
+ # Under $env:PSModulePath, multiple components in name. TODO: Is this a supported scenario?
+ $inPSModulePathLooseWithPathLikeFilePath = Join-Path $env:PSModulePath "existing-loose.xxx.psm1"
+ New-Item -ItemType File -Force $inPSModulePathLooseWithPathLikeFilePath > $null
+ #
+ # Under $env:PSModulePath, multiple components in name, name ending with PS module extension. TODO: Is this a supported scenario?
+ $inPSModulePathLooseWithPathLikeNameAsPSExtensionFilePath = Join-Path $env:PSModulePath "existing.psm1.psm1"
+ New-Item -ItemType File -Force $inPSModulePathLooseWithPathLikeNameAsPSExtensionFilePath > $null
+ #
+ # In working directory
+ $inWorkingDirectoryLooseFilePath = Join-Path . existing.psm1
+ New-Item -ItemType File -Force $inWorkingDirectoryLooseFilePath > $null
+ #
+ # In parent directory
+ $inParentDirectoryLooseFilePath = Join-Path .. existing.psm1
+ New-Item -ItemType File -Force $inParentDirectoryLooseFilePath > $null
+ #
+ # In working directory, ignored
+ 'function foo { "_" }' > (Join-Path $pwd ignore.psm1)
+ 'function foo { "_" }' > (Join-Path $pwd ignore.psm1.psm1)
+ }
+
+ AfterAll {
+ $env:PSModulePath = $oldPSModulePath
+
+ Remove-Item -ErrorAction Stop -Force -Recurse $inPSModulePatWithManifestDirectory
+ Remove-Item -ErrorAction Stop -Force -Recurse $inPSModulePatWithManifestDirectory2
+ Remove-Item -ErrorAction Stop -Force -Recurse $inPsModulePathWithManifestWithPathLikeNameDirectory
+ Remove-Item -ErrorAction Stop -Force -Recurse $inPSModulePathWithManifestWithPathLikeNameAsPSExtensionDirectory
+
+ Remove-Item -ErrorAction Stop $inPSModulePathLooseFilePath
+ Remove-Item -ErrorAction Stop $inPSModulePathLooseWithPathLikeFilePath
+ Remove-Item -ErrorAction Stop $inPSModulePathLooseWithPathLikeNameAsPSExtensionFilePath
+ Remove-Item -ErrorAction Stop $inWorkingDirectoryLooseFilePath
+ Remove-Item -ErrorAction Stop $inParentDirectoryLooseFilePath
+ Remove-Item -ErrorAction Stop ignore.psm1
+ Remove-Item -ErrorAction Stop ignore.psm1.psm1
+ }
+
+ It 'returns module information for manifest module' {
+ Get-Module -ListAvailable -FullyQualifiedName existing | ForEach-Object Path | Should -BeExactly (Join-Path $env:PSModulePath existing '0.0.1','existing.psd1')
+ Get-Module -ListAvailable -Name existing | ForEach-Object Path | Should -BeExactly (Join-Path $env:PSModulePath existing '0.0.1','existing.psd1')
+ }
+
+ It 'ignores module at current directory' {
+ Get-Module -ListAvailable -FullyQualifiedName ignore | Should -Be $null
+ Get-Module -ListAvailable -Name ignore | Should -Be $null
+ }
+
+ Context 'When path value has PS module extension, such as ''name.psm1''' {
+ # Because non-rooted/non-relative-rooted paths MUST be resolved to modules under $env:PSModulePath.
+ It 'ignores module at current directory' {
+ Get-Module -ListAvailable -FullyQualifiedName ignore.psm1 | Should -Be $null
+ Get-Module -ListAvailable -Name ignore.psm1 | Should -Be $null
+ }
+
+ Context 'Resolves to module under $env:PSModulePath' {
+ # Unclear whether this is correct behavior. Are loose modules allowed under $env:PSModulePath?
+ # Could resolve to:
+ # - 'existing-loose.psm1' loose module
+ It 'wrongly/correctly returns $null instead of module information for loose module' {
+ Get-Module -ListAvailable -FullyQualifiedName existing-loose.psm1 | Should -Be $null
+ Get-Module -ListAvailable -Name existing-loose.psm1 | Should -Be $null
+ }
+
+ # Unclear whether this is correct behavior. Are loose modules allowed under $env:PSModulePath?.
+ # Could resolve to:
+ # - 'existing2' manifest module
+ It 'wrongly/correctly returns $null instead of module information for versioned module' {
+ Get-Module -ListAvailable -FullyQualifiedName existing2.psm1 | Should -Be $null
+ Get-Module -ListAvailable -Name existing2.psm1 | Should -Be $null
+ }
+
+ # Finds manifest module 'existing.psm1.psm1'. TODO: Is that correct?
+ It 'wrongly/correctly returns module information for versioned module ending with PS extension' {
+ Get-Module -ListAvailable -FullyQualifiedName existing.psm1 | Should -BeOfType ([System.Management.Automation.PSModuleInfo])
+ Get-Module -ListAvailable -Name existing.psm1 | Should -BeOfType ([System.Management.Automation.PSModuleInfo])
+ }
+ }
+
+ Context 'Resolves to non-existing loose or versioned module under $env:PSModulePath' {
+ It 'wrongly returns $null instead of error' {
+ $path1 = Join-Path $env:PSModulePath missing.psm1
+ $path2 = Join-Path $env:PSModulePath missing.psm1 *, missing.psm1
+ Test-Path $path1 | Should -BeFalse
+ Test-Path $path2 | Should -BeFalse
+
+ Get-Module -ListAvailable -FullyQualifiedName missing.psm1 | Should -Be $null
+ Get-Module -ListAvailable -Name missing.psm1 | Should -Be $null
+ }
+ }
+ }
+
+ Context 'When path value has unknown extension, such as ''name.xxx''' {
+ Context 'Resolves to non-existing loose or versioned module under $env:PSModulePath' {
+ It 'wrongly returns $null instead of error' {
+ $path1 = Join-Path $env:PSModulePath missing.xxx.psm1
+ $path2 = Join-Path $env:PSModulePath missing *, missing.xxx.psm1
+ Test-Path $path1 | Should -BeFalse
+ Test-Path $path2 | Should -BeFalse
+
+ Get-Module -ListAvailable -FullyQualifiedName missing.xxx | Should -Be $null
+ Get-Module -ListAvailable -Name missing.xxx | Should -Be $null
+ }
+ }
+
+ Context 'Resolves to loose module under $env:PSModule' {
+ # Unclear whether only manifest modules are discovered under $env:PSModulePath.
+ It 'wrongly/correctly returns $null instead of module information' {
+ $path = Join-Path $env:PSModulePath existing-loose.xxx.psm1
+ Test-Path $path | Should -BeTrue
+
+ Get-Module -ListAvailable -FullyQualifiedName existing-loose.xxx | Should -Be $null
+ }
+ }
+
+ Context 'Resolves to manifest module under $env:PSModule' {
+ # Unclear whether only manifest modules are discovered under $env:PSModulePath.
+ It 'returns module information' {
+ $path = Join-Path $env:PSModulePath existing.xxx 0.0.1, existing.xxx.psm1
+ Test-Path $path | Should -BeTrue
+
+ Get-Module -ListAvailable -FullyQualifiedName existing.xxx | Should -BeOfType ([System.Management.Automation.PSModuleInfo])
+ Get-Module -ListAvailable -Name existing.xxx | Should -BeOfType ([System.Management.Automation.PSModuleInfo])
+ }
+ }
+
+ Context 'When argument has wildcards' {
+ It 'ignores existing script modules in working directory' {
+ Join-Path $pwd ignore.psm1 | Should -BeTrue
+
+ $actual = Get-Module -ListAvailable -Name ignore*
+ $actual | Should -Be $null
+
+ $actual = Get-Module -ListAvailable -FullyQualifiedName ignore*
+ $actual | Should -Be $null
+ }
+
+ Context 'Resolves to existing script modules' {
+ It 'wrongly/correctly returns $null' {
+ $actual = Get-Module -ListAvailable -Name existing-loose*
+ $actual | Should -Be $null
+
+ $actual = Get-Module -ListAvailable -FullyQualifiedName existing-loose*
+ $actual | Should -Be $null
+ }
+ }
+
+ Context 'Resolves to existing manifest modules' {
+ It 'returns modules' {
+ $actual = Get-Module -ListAvailable -Name existing*
+ $actual | Should -HaveCount 4
+ $actual[0].Name | Should -BeExactly existing
+ $actual[1].Name | Should -BeExactly existing.psm1
+ $actual[2].Name | Should -BeExactly existing.xxx
+ $actual[3].Name | Should -BeExactly existing2
+
+ $actual = Get-Module -ListAvailable -FullyQualifiedName existing*
+ $actual | Should -HaveCount 4
+ $actual[0].Name | Should -BeExactly existing
+ $actual[1].Name | Should -BeExactly existing.psm1
+ $actual[2].Name | Should -BeExactly existing.xxx
+ $actual[3].Name | Should -BeExactly existing2
+ }
+ }
+
+ Context 'Resolves to non-existing modules' {
+ It 'wrongly/correctly returns $null' {
+ $actual = Get-Module -ListAvailable -Name missing*
+ $actual | Should -Be $null
+
+ $actual = Get-Module -ListAvailable -FullyQualifiedName missing*
+ $actual | Should -Be $null
+ }
+ }
+ }
+ }
+}
+
+Describe 'Get-Module -ListAvailable -(FullyQualifiedName|Name) when argument is home-rooted' -Tags "CI" {
+ It 'wrongly does not expand ''~'' to $HOME' {
+ $path = Join-Path ~ missing.psm1
+ Test-Path $path | Should -BeFalse
+
+ Get-Module -ListAvailable -Name $path | ForEach-Object Path | Should -BeExactly (Join-Path $HOME missing.psm1)
+ # TODO: This is a bug.
+ Get-Module -ListAvailable -FullyQualifiedName $path | ForEach-Object Path | Should -BeExactly (Join-Path $pwd $path)
+ }
+
+ It 'wrongly returns module information instead of $null or error for missing script module' {
+ $path = Join-Path ~ missing.psm1
+ Test-Path $path | Should -BeFalse
+
+ Get-Module -ListAvailable -Name $path | Should -BeOfType ([System.Management.Automation.PSModuleInfo])
+ Get-Module -ListAvailable -FullyQualifiedName $path | Should -BeOfType ([System.Management.Automation.PSModuleInfo])
+ }
+
+ It 'writes error for missing manifest module' {
+ $path = Join-Path ~ missing.psm1
+ Test-Path $path | Should -BeFalse
+
+ $name = Join-Path ~ missing
+
+ $err = { Get-Module -ListAvailable -Name $name -ErrorAction Stop } | Should -Throw -PassThru
+ $err.Exception.Message | Should -BeLike '*Update the Name parameter*'
+
+ $err = { Get-Module -ListAvailable -FullyQualifiedName $name -ErrorAction Stop } | Should -Throw -PassThru
+ $err.Exception.Message | Should -BeLike '*Update the Name parameter*'
+ }
+
+ Context 'Locating existing script module' {
+ BeforeAll {
+ # Script modules
+ #
+ $inHomeLooseFilePath = Join-Path $HOME loose.psm1
+ New-Item -ItemType File -Force $inHomeLooseFilePath > $null
+ }
+
+ AfterAll {
+ Remove-Item $inHomeLooseFilePath
+ }
+
+ # TODO: This looks like a bug.
+ It 'wrongly writes error instead of returning module information for existing script module using basename' {
+ $path = Join-Path ~ loose.psm1
+ Test-Path $path | Should -BeTrue
+
+ $name = Join-Path ~ loose
+
+ $err = { Get-Module -ListAvailable -Name $name -ErrorAction Stop } | Should -Throw -PassThru
+ $err.Exception.Message | Should -BeLike '*Update the Name parameter*'
+
+ $err = { Get-Module -ListAvailable -FullyQualifiedName $name -ErrorAction Stop } | Should -Throw -PassThru
+ $err.Exception.Message | Should -BeLike '*Update the Name parameter*'
+ }
+
+ It 'returns module information for existing script module using file name' {
+ $path = Join-Path ~ loose.psm1
+ Test-Path $path | Should -BeTrue
+
+ $actual = Get-Module -ListAvailable -Name $path
+ $actual | Should -HaveCount 1
+ $actual[0].Path | Should -BeExactly (Join-Path $HOME loose.psm1)
+
+ # TODO: This is a bug.
+ $actual = Get-Module -ListAvailable -FullyQualifiedName $path
+ $actual | Should -HaveCount 1
+ $actual[0].Path | Should -BeExactly (Join-Path $pwd $path)
+ }
+ }
+
+ Context 'When argument contains wildcards' {
+ BeforeAll {
+ # Manifest modules under '~'
+ #
+ # Versioned, manifest
+ $inHomeWithManifestFileName = 'existing'
+ $inHomeWithManifestDirectory = Join-Path '~' $inHomeWithManifestFileName '0.0.1'
+ $inHomeWithManifestFilePath = Join-Path $inHomeWithManifestDirectory "$inHomeWithManifestFileName.psm1"
+ New-Item -ItemType File -Force $inHomeWithManifestFilePath > $null
+ New-ModuleManifest -Path (Join-Path $inHomeWithManifestDirectory "$inHomeWithManifestFileName.psd1")
+ #
+ # Versioned, manifest
+ $inHomeWithManifestFileName2 = 'existing2'
+ $inHomeWithManifestDirectory2 = Join-Path '~' $inHomeWithManifestFileName2 '0.0.1'
+ $inHomeWithManifestFilePath2 = Join-Path $inHomeWithManifestDirectory2 "$inHomeWithManifestFileName2.psm1"
+ New-Item -ItemType File -Force $inHomeWithManifestFilePath2 > $null
+ New-ModuleManifest -Path (Join-Path $inHomeWithManifestDirectory2 "$inHomeWithManifestFileName2.psd1")
+
+ # Script modules under '~'
+ #
+ $inHomeLooseFilePath = Join-Path '~' 'loose.psm1'
+ New-Item -ItemType File -Force $inHomeLooseFilePath > $null
+ }
+
+ AfterAll {
+ Remove-Item -Force -Recurse $inHomeWithManifestDirectory
+ Remove-Item -Force -Recurse $inHomeWithManifestDirectory2
+
+ Remove-Item $inHomeLooseFilePath
+ }
+
+ It 'returns existing manifest modules when using the -Name parameter' {
+ $name = Join-Path ~ existing*
+
+ $actual = Get-Module -ListAvailable -Name $name
+ $actual | Should -HaveCount 2
+ $actual[0].Path | Should -BeExactly (Join-Path $HOME existing 0.0.1, existing.psd1)
+ $actual[1].Path | Should -BeExactly (Join-Path $HOME existing2 0.0.1, existing2.psd1)
+ }
+
+ # TODO: This looks like a bug.
+ It 'wrongly returns $null for existing manifest modules when using the -FullyQualifiedName parameter' {
+ $actual = Get-Module -ListAvailable -FullyQualifiedName (Join-Path ~ existing*)
+ $actual | Should -Be $null
+ }
+
+ It 'returns module information for existing script module' {
+ $actual = Get-Module -ListAvailable -Name (Join-Path ~ loose*)
+ $actual | Should -HaveCount 1
+ $actual[0].Path | Should -BeExactly (Join-Path $HOME loose.psm1)
+ }
+ }
+}
+
+Describe 'Get-Module -ListAvailable -(FullyQualifiedName|Name) when argument is relative-rooted' -Tags "CI" {
+ It 'wrongly returns module information instead of $null or error for missing script module' {
+ $path1 = Join-Path . missing.psm1
+ $path2 = Join-Path .. missing.psm1
+ Test-Path $path1 | Should -BeFalse
+ Test-Path $path2 | Should -BeFalse
+
+ Get-Module -ListAvailable -Name $path1 | Should -BeOfType ([System.Management.Automation.PSModuleInfo])
+ Get-Module -ListAvailable -Name $path2 | Should -BeOfType ([System.Management.Automation.PSModuleInfo])
+ Get-Module -ListAvailable -FullyQualifiedName $path1 | Should -BeOfType ([System.Management.Automation.PSModuleInfo])
+ Get-Module -ListAvailable -FullyQualifiedName $path2 | Should -BeOfType ([System.Management.Automation.PSModuleInfo])
+ }
+
+ It 'writes error for missing manifest module' {
+ $path1 = Join-Path . missing
+ $path2 = Join-Path .. missing
+ Test-Path $path1 | Should -BeFalse
+ Test-Path $path2 | Should -BeFalse
+
+ { Get-Module -ListAvailable -Name $path1 -ErrorAction Stop } | Should -Throw -Because '*Update the Name parameter*'
+ { Get-Module -ListAvailable -Name $path2 -ErrorAction Stop } | Should -Throw -Because '*Update the Name parameter*'
+ { Get-Module -ListAvailable -FullyQualifiedName $path1 -ErrorAction Stop } | Should -Throw -Because '*Update the Name parameter*'
+ { Get-Module -ListAvailable -FullyQualifiedName $path2 -ErrorAction Stop } | Should -Throw -Because '*Update the Name parameter*'
+ }
+
+ Context 'Locating existing script module' {
+ BeforeAll {
+ # Script modules
+ #
+ # Under $env:PSModulePath. TODO: Is this a supported scenario?
+ $inPSModulePathLooseFilePath = Join-Path . loose.psm1
+ New-Item -ItemType File -Force $inPSModulePathLooseFilePath > $null
+ #
+ # Under $pwd
+ $inPSModulePathLooseFilePathParent = Join-Path .. loose.psm1
+ New-Item -ItemType File -Force $inPSModulePathLooseFilePathParent > $null
+ }
+
+ AfterAll {
+ Remove-Item $inPSModulePathLooseFilePath
+ Remove-Item $inPSModulePathLooseFilePathParent
+ }
+
+ # TODO: This looks like a bug.
+ It 'wrongly writes error instead of returning module information for existing script module using basename' {
+ $path1 = Join-Path . loose.psm1
+ $path2 = Join-Path .. loose.psm1
+ Test-Path $path1 | Should -BeTrue
+ Test-Path $path2 | Should -BeTrue
+
+ $name1 = Join-Path . loose
+ $name2 = Join-Path .. loose
+
+ $err = { Get-Module -ListAvailable -Name $name1 -ErrorAction Stop } | Should -Throw -PassThru
+ $err.Exception.Message | Should -BeLike '*Update the Name parameter*'
+
+ $err = { Get-Module -ListAvailable -Name $name2 -ErrorAction Stop } | Should -Throw -PassThru
+ $err.Exception.Message | Should -BeLike '*Update the Name parameter*'
+ }
+
+ It 'returns module information for existing script module using file name' {
+ $name1 = Join-Path . loose.psm1
+ $name2 = Join-Path .. loose.psm1
+ Test-Path $name1 | Should -BeTrue
+ Test-Path $name2 | Should -BeTrue
+
+ $actual = Get-Module -ListAvailable -Name $name1
+ $actual | Should -HaveCount 1
+ $actual[0].Path | Should -BeExactly ([System.IO.Path]::GetFullPath($name1))
+
+ $actual = Get-Module -ListAvailable -Name $name2
+ $actual | Should -HaveCount 1
+ $actual[0].Path | Should -BeExactly ([System.IO.Path]::GetFullPath($name2))
+
+ $actual = Get-Module -ListAvailable -FullyQualifiedName $name1
+ $actual | Should -HaveCount 1
+ $actual[0].Path | Should -BeExactly ([System.IO.Path]::GetFullPath($name1))
+
+ $actual = Get-Module -ListAvailable -FullyQualifiedName $name2
+ $actual | Should -HaveCount 1
+ $actual[0].Path | Should -BeExactly ([System.IO.Path]::GetFullPath($name2))
+ }
+ }
+
+ Context 'When argument contains wildcards' {
+ BeforeAll {
+ # Manifest modules under '.'
+ #
+ # Versioned, manifest
+ $inCwdWithManifestFileName = 'existing'
+ $inCwdWithManifestDirectory = Join-Path '.' $inCwdWithManifestFileName '0.0.1'
+ $inCwdWithManifestFilePath = Join-Path $inCwdWithManifestDirectory "$inCwdWithManifestFileName.psm1"
+ New-Item -ItemType File -Force $inCwdWithManifestFilePath > $null
+ New-ModuleManifest -Path (Join-Path $inCwdWithManifestDirectory "$inCwdWithManifestFileName.psd1")
+ #
+ # Versioned, manifest
+ $inCwdWithManifestFileName2 = 'existing2'
+ $inCwdWithManifestDirectory2 = Join-Path '.' $inCwdWithManifestFileName2 '0.0.1'
+ $inCwdWithManifestFilePath2 = Join-Path $inCwdWithManifestDirectory2 "$inCwdWithManifestFileName2.psm1"
+ New-Item -ItemType File -Force $inCwdWithManifestFilePath2 > $null
+ New-ModuleManifest -Path (Join-Path $inCwdWithManifestDirectory2 "$inCwdWithManifestFileName2.psd1")
+
+ # Manifest modules under '..'
+ #
+ # Versioned, manifest
+ $inParentWithManifestFileName = 'existing'
+ $inParentWithManifestDirectory = Join-Path '..' $inParentWithManifestFileName '0.0.1'
+ $inParentWithManifestFilePath = Join-Path $inParentWithManifestDirectory "$inParentWithManifestFileName.psm1"
+ New-Item -ItemType File -Force $inParentWithManifestFilePath > $null
+ New-ModuleManifest -Path (Join-Path $inParentWithManifestDirectory "$inParentWithManifestFileName.psd1")
+ #
+ # Versioned, manifest
+ $inParentWithManifestFileName2 = 'existing2'
+ $inParentWithManifestDirectory2 = Join-Path '..' $inParentWithManifestFileName2 '0.0.1'
+ $inParentWithManifestFilePath2 = Join-Path $inParentWithManifestDirectory2 "$inParentWithManifestFileName2.psm1"
+ New-Item -ItemType File -Force $inParentWithManifestFilePath2 > $null
+ New-ModuleManifest -Path (Join-Path $inParentWithManifestDirectory2 "$inParentWithManifestFileName2.psd1")
+
+ # Script modules under '.' and '..'
+ #
+ # Under '.'
+ $inCwdLooseFilePath = Join-Path '.' 'loose.psm1'
+ New-Item -ItemType File -Force $inCwdLooseFilePath > $null
+ #
+ # Under '..'
+ $inParentLooseFilePath = Join-Path '..' 'loose.psm1'
+ New-Item -ItemType File -Force $inParentLooseFilePath > $null
+ }
+
+ AfterAll {
+ Remove-Item -Force -Recurse $inCwdWithManifestDirectory
+ Remove-Item -Force -Recurse $inCwdWithManifestDirectory2
+
+ Remove-Item -Force -Recurse $inParentWithManifestDirectory
+ Remove-Item -Force -Recurse $inParentWithManifestDirectory2
+
+ Remove-Item $inCwdLooseFilePath
+ }
+
+ It 'returns existing manifest modules when using the -Name parameter' {
+ $actual = Get-Module -ListAvailable -Name '.\existing*'
+ $actual | Should -HaveCount 2
+ $actual[0].Path | Should -BeExactly ([System.IO.Path]::GetFullPath('.\existing\0.0.1\existing.psd1'))
+ $actual[1].Path | Should -BeExactly ([System.IO.Path]::GetFullPath('.\existing2\0.0.1\existing2.psd1'))
+
+ $actual = Get-Module -ListAvailable -Name '..\existing*'
+ $actual | Should -HaveCount 2
+ $actual[0].Path | Should -BeExactly ([System.IO.Path]::GetFullPath('..\existing\0.0.1\existing.psd1'))
+ $actual[1].Path | Should -BeExactly ([System.IO.Path]::GetFullPath('..\existing2\0.0.1\existing2.psd1'))
+ }
+
+ # TODO: This looks like a bug.
+ It 'wrongly returns $null for existing manifest modules when using the -FullyQualifiedName parameter' {
+ $actual = Get-Module -ListAvailable -FullyQualifiedName '.\existing*'
+ $actual | Should -Be $null
+
+ $actual = Get-Module -ListAvailable -FullyQualifiedName '..\existing*'
+ $actual | Should -Be $null
+ }
+
+ It 'returns module information for existing script module' {
+ $actual = Get-Module -ListAvailable -Name '.\loose*'
+ $actual | Should -HaveCount 1
+ $actual[0].Path | Should -BeExactly ([System.IO.Path]::GetFullPath('.\loose.psm1'))
+
+ $actual = Get-Module -ListAvailable -FullyQualifiedName '..\loose*'
+ $actual | Should -HaveCount 1
+ $actual[0].Path | Should -BeExactly ([System.IO.Path]::GetFullPath('..\loose.psm1'))
+ }
+ }
+}
+
+Describe 'Get-Module -ListAvailable -(FullyQualifiedName|Name) when argument is absolute path' -Tags "CI" {
+ BeforeAll {
+ $oldPSModulePath = $env:PSModulePath
+ $env:PSModulePath = New-Item -ItemType Directory (Join-Path $TestDrive modules)
+ }
+
+ AfterAll {
+ $env:PSModulePath = $oldPSModulePath
+ }
+
+ It 'wrongly returns module information instead of $null or error for missing script module' {
+ $path = [System.IO.Path]::GetFullPath((Join-Path $pwd missing.psm1))
+ Test-Path $path | Should -BeFalse
+
+ Get-Module -ListAvailable -FullyQualifiedName $path | Should -BeOfType ([System.Management.Automation.PSModuleInfo])
+ Get-Module -ListAvailable -Name $path | Should -BeOfType ([System.Management.Automation.PSModuleInfo])
+ }
+
+ It 'wrongly returns module information instead of $null or error for missing script module under $env:PSModulePath' {
+ $path = [System.IO.Path]::GetFullPath((Join-Path $env:PSModulePath missing.psm1))
+ Test-Path $path | Should -BeFalse
+
+ Get-Module -ListAvailable -FullyQualifiedName $path | Should -BeOfType ([System.Management.Automation.PSModuleInfo])
+ Get-Module -ListAvailable -Name $path | Should -BeOfType ([System.Management.Automation.PSModuleInfo])
+ }
+
+ It 'writes error for missing manifest module' {
+ $path = [System.IO.Path]::GetFullPath((Join-Path $pwd missing))
+ Test-Path $path | Should -BeFalse
+
+ $err = { Get-Module -ListAvailable -FullyQualifiedName $path -ErrorAction Stop } | Should -Throw -PassThru
+ $err.Exception.Message | Should -BeLike '*Update the Name parameter*'
+
+ $err = { Get-Module -ListAvailable -Name $path -ErrorAction Stop } | Should -Throw -PassThru
+ $err.Exception.Message | Should -BeLike '*Update the Name parameter*'
+ }
+
+ It 'writes error for missing manifest module under $env:PSModulePath' {
+ $path = [System.IO.Path]::GetFullPath((Join-Path $env:PSModulePath missing))
+ Test-Path $path | Should -BeFalse
+
+ $err = { Get-Module -ListAvailable -FullyQualifiedName $path -ErrorAction Stop } | Should -Throw -PassThru
+ $err.Exception.Message | Should -BeLike '*Update the Name parameter*'
+
+ $err = { Get-Module -ListAvailable -Name $path -ErrorAction Stop } | Should -Throw -PassThru
+ $err.Exception.Message | Should -BeLike '*Update the Name parameter*'
+ }
+
+ Context 'Locating existing script module' {
+ BeforeAll {
+ # Script modules
+ #
+ # Under $env:PSModulePath. TODO: Is this a supported scenario?
+ $inPSModulePathLooseFilePath = Join-Path $env:PSModulePath 'loose.psm1'
+ New-Item -ItemType File -Force $inPSModulePathLooseFilePath > $null
+ #
+ # Under $pwd
+ $inCwdLooseFilePath = Join-Path $pwd 'loose.psm1'
+ New-Item -ItemType File -Force $inCwdLooseFilePath > $null
+ }
+
+ AfterAll {
+ Remove-Item $inPSModulePathLooseFilePath
+ Remove-Item $inCwdLooseFilePath
+ }
+
+ # TODO: This looks like a bug.
+ It 'wrongly writes error instead of returning module information for existing script module under $env:PSModulePath using basename' {
+ Test-Path (Join-Path $env:PSModulePath loose.psm1) | Should -BeTrue
+ $path = Join-Path $env:PSModulePath loose
+
+ $err = { Get-Module -ListAvailable -Name $path -ErrorAction Stop } | Should -Throw -PassThru
+ $err.Exception.Message | Should -BeLike '*Update the Name parameter*'
+
+ $err = { Get-Module -ListAvailable -FullyQualifiedName $path -ErrorAction Stop } | Should -Throw -PassThru
+ $err.Exception.Message | Should -BeLike '*Update the Name parameter*'
+ }
+
+ # TODO: This looks like a bug.
+ It 'wrongly writes error instead of returning module information for existing script module using basename' {
+ Test-Path (Join-Path $pwd loose.psm1) | Should -BeTrue
+ $path = Join-Path $pwd loose
+
+ $err = { Get-Module -ListAvailable -Name $path -ErrorAction Stop } | Should -Throw -PassThru
+ $err.Exception.Message | Should -BeLike '*Update the Name parameter*'
+
+ $err = { Get-Module -ListAvailable -FullyQualifiedName $path -ErrorAction Stop } | Should -Throw -PassThru
+ $err.Exception.Message | Should -BeLike '*Update the Name parameter*'
+ }
+
+ It 'returns module information for existing script module using file name' {
+ $path = Join-Path $pwd loose.psm1
+ Test-Path $path | Should -BeTrue
+
+ $actual = Get-Module -ListAvailable -Name $path
+ $actual | Should -HaveCount 1
+ $actual[0].Path | Should -BeExactly $path
+
+ $actual = Get-Module -ListAvailable -FullyQualifiedName $path
+ $actual | Should -HaveCount 1
+ $actual[0].Path | Should -BeExactly $path
+ }
+
+ It 'returns module information for existing script module under $env:PSModulePath using file name' {
+ $path = Join-Path $env:PSModulePath loose.psm1
+ Test-Path $path | Should -BeTrue
+
+ $actual = Get-Module -ListAvailable -Name $path
+ $actual | Should -HaveCount 1
+ $actual[0].Path | Should -BeExactly $path
+
+ $actual = Get-Module -ListAvailable -FullyQualifiedName $path
+ $actual | Should -HaveCount 1
+ $actual[0].Path | Should -BeExactly $path
+ }
+ }
+
+ Context 'When argument contains wildcards' {
+ BeforeAll {
+ # Manifest modules under $env:PSModulePath
+ #
+ # Versioned, manifest
+ $inPSModulePathWithManifestFileName = 'existing'
+ $inPSModulePatWithManifestDirectory = Join-Path $env:PSModulePath $inPSModulePathWithManifestFileName '0.0.1'
+ $inPSModulePathWithManifestFilePath = Join-Path $inPSModulePatWithManifestDirectory "$inPSModulePathWithManifestFileName.psm1"
+ New-Item -ItemType File -Force $inPSModulePathWithManifestFilePath > $null
+ New-ModuleManifest -Path (Join-Path $inPSModulePatWithManifestDirectory "$inPSModulePathWithManifestFileName.psd1")
+ #
+ # Versioned, manifest
+ $inPSModulePathWithManifestFileName2 = 'existing2'
+ $inPSModulePatWithManifestDirectory2 = Join-Path $env:PSModulePath $inPSModulePathWithManifestFileName2 '0.0.1'
+ $inPSModulePathWithManifestFilePath2 = Join-Path $inPSModulePatWithManifestDirectory2 "$inPSModulePathWithManifestFileName2.psm1"
+ New-Item -ItemType File -Force $inPSModulePathWithManifestFilePath2 > $null
+ New-ModuleManifest -Path (Join-Path $inPSModulePatWithManifestDirectory2 "$inPSModulePathWithManifestFileName2.psd1")
+
+ # Manifest modules under $pwd
+ #
+ # Versioned, manifest
+ $inCwdhWithManifestFileName = 'existing'
+ $inCwdWithManifestDirectory = Join-Path $pwd $inCwdhWithManifestFileName '0.0.1'
+ $inCwdWithManifestFilePath = Join-Path $inCwdWithManifestDirectory "$inCwdhWithManifestFileName.psm1"
+ New-Item -ItemType File -Force $inCwdWithManifestFilePath > $null
+ New-ModuleManifest -Path (Join-Path $inCwdWithManifestDirectory "$inCwdhWithManifestFileName.psd1")
+ #
+ # Versioned, manifest
+ $inCwdWithManifestFileName2 = 'existing2'
+ $inCwdWithManifestDirectory2 = Join-Path $pwd $inCwdWithManifestFileName2 '0.0.1'
+ $inCwdWithManifestFilePath2 = Join-Path $inCwdWithManifestDirectory2 "$inCwdWithManifestFileName2.psm1"
+ New-Item -ItemType File -Force $inCwdWithManifestFilePath2 > $null
+ New-ModuleManifest -Path (Join-Path $inCwdWithManifestDirectory2 "$inCwdWithManifestFileName2.psd1")
+
+ # Script modules
+ #
+ # Under $env:PSModulePath. TODO: Is this a supported scenario?
+ $inPSModulePathLooseFilePath = Join-Path $env:PSModulePath 'loose.psm1'
+ New-Item -ItemType File -Force $inPSModulePathLooseFilePath > $null
+ #
+ #
+ # Under $pwd
+ $inCwdFilePath = Join-Path $pwd 'loose.psm1'
+ New-Item -ItemType File -Force $inCwdFilePath > $null
+ }
+
+ AfterAll {
+ Remove-Item -Force -Recurse $inPSModulePatWithManifestDirectory
+ Remove-Item -Force -Recurse $inPSModulePatWithManifestDirectory2
+
+ Remove-Item -Force -Recurse $inCwdWithManifestDirectory
+ Remove-Item -Force -Recurse $inCwdWithManifestDirectory2
+
+ Remove-Item $inPSModulePathLooseFilePath
+ Remove-Item $inCwdFilePath
+ }
+
+ It 'returns existing manifest modules under $env:PSModulePath when using the -Name parameter' {
+ $moduleManifestPath1 = Join-Path $env:PSModulePath existing 0.0.1,existing.psd1
+ $moduleManifestPath2 = Join-Path $env:PSModulePath existing2 0.0.1,existing2.psd1
+ Test-Path $moduleManifestPath1 | Should -BeTrue
+ Test-Path $moduleManifestPath2 | Should -BeTrue
+
+ $actual = Get-Module -ListAvailable -Name (Join-Path $env:PSModulePath 'existing*')
+ $actual | Should -HaveCount 2
+ $actual[0].Path | Should -BeExactly $moduleManifestPath1
+ $actual[1].Path | Should -BeExactly $moduleManifestPath2
+ }
+
+ # TODO: This looks like a bug.
+ It 'wrongly returns $null for existing manifest modules under $env:PSModulePath when using the -FullyQualifiedName parameter' {
+ $actual = Get-Module -ListAvailable -FullyQualifiedName (Join-Path $env:PSModulePath 'existing*')
+ $actual | Should -Be $null
+ }
+
+ It 'returns existing manifest modules when using the -Name parameter' {
+ $moduleManifestPath1 = Join-Path $pwd existing 0.0.1,existing.psd1
+ $moduleManifestPath2 = Join-Path $pwd existing2 0.0.1,existing2.psd1
+ Test-Path $moduleManifestPath1 | Should -BeTrue
+ Test-Path $moduleManifestPath2 | Should -BeTrue
+
+ $actual = Get-Module -ListAvailable -Name (Join-Path $pwd 'existing*')
+ $actual | Should -HaveCount 2
+ $actual[0].Path | Should -BeExactly $moduleManifestPath1
+ $actual[1].Path | Should -BeExactly $moduleManifestPath2
+ }
+
+ # TODO: This looks like a bug.
+ It 'wrongly returns $null for existing manifest modules when using the -FullyQualifiedName parameter' {
+ $actual = Get-Module -ListAvailable -FullyQualifiedName (Join-Path $pwd 'existing*')
+ $actual | Should -Be $null
+ }
+
+ It 'returns module information for existing script module under $env:PSModulePath' {
+ $actual = Get-Module -ListAvailable -Name (Join-Path $env:PSModulePath 'loose*')
+ $actual | Should -HaveCount 1
+ $actual[0].Path | Should -BeExactly (Join-Path $env:PSModulePath loose.psm1)
+ }
+
+ It 'returns module information for existing script module using file name' {
+ $path = Join-Path $pwd loose.psm1
+ Test-Path $path | Should -BeTrue
+
+ $actual = Get-Module -ListAvailable -Name $path
+ $actual | Should -HaveCount 1
+ $actual[0].Path | Should -BeExactly $path
+
+ $actual = Get-Module -ListAvailable -FullyQualifiedName $path
+ $actual | Should -HaveCount 1
+ $actual[0].Path | Should -BeExactly $path
+ }
+ }
+}
+
Describe 'Get-Module -ListAvailable with path' -Tags "CI" {
BeforeAll {
$moduleName = 'Banana'