Skip to content

Commit 54f4648

Browse files
committed
feat: Add Get-EffectiveModule function to report module version, path, and scope
1 parent fdd45a9 commit 54f4648

File tree

1 file changed

+114
-0
lines changed

1 file changed

+114
-0
lines changed

General/Get-EffectiveModule.ps1

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
function Get-EffectiveModule {
2+
<#
3+
.SYNOPSIS
4+
Reports the version, path, and scope that would be imported for a given module name.
5+
6+
.DESCRIPTION
7+
Searches PSModulePath in order to determine which version of a module would be imported
8+
by Import-Module, mimicking PowerShell's module resolution logic.
9+
10+
.PARAMETER Name
11+
The name or array of names of the module to check.
12+
13+
.PARAMETER Scope
14+
Limits the search to modules installed for the CurrentUser, AllUsers, or Any (default) scope.
15+
16+
.OUTPUTS
17+
PSCustomObject with properties: Name, Version, Path, Scope, ManifestPath.
18+
19+
.EXAMPLE
20+
Get-EffectiveModule -Name "Pester"
21+
Gets the effective module information for Pester from any scope.
22+
23+
.EXAMPLE
24+
Get-EffectiveModule "PSReadLine" -Scope CurrentUser
25+
Gets the effective module information for PSReadLine only from the CurrentUser scope.
26+
27+
.EXAMPLE
28+
"Pester", "PSReadLine" | Get-EffectiveModule
29+
Gets the effective module information for multiple modules from any scope.
30+
#>
31+
[CmdletBinding()]
32+
[OutputType([PSCustomObject])]
33+
param(
34+
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
35+
[ValidateNotNullOrEmpty()]
36+
[string[]]$Name,
37+
38+
[Parameter(Mandatory = $false)]
39+
[ValidateSet('CurrentUser', 'AllUsers', 'Any')]
40+
[string]$Scope = 'Any'
41+
)
42+
43+
begin {
44+
# Set a flag to indicate if the module path was found.
45+
$FoundModule = $false
46+
47+
# Split PSModulePath into individual paths, using a switch statement to only get paths in the specified scope or default to any scope.
48+
$ScopeFilter = switch ($Scope) {
49+
'CurrentUser' { [ScriptBlock] { $_ -like "$HOME*" -and (Test-Path -Path $_ -PathType Container -ErrorAction SilentlyContinue) } }
50+
'AllUsers' { [ScriptBlock] { $_ -notlike "$HOME*" -and (Test-Path -Path $_ -PathType Container -ErrorAction SilentlyContinue) } }
51+
'Any' { [ScriptBlock] { Test-Path -Path $_ -PathType Container -ErrorAction SilentlyContinue } }
52+
}
53+
$ModulePaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator |
54+
Where-Object -FilterScript $ScopeFilter
55+
} # end begin
56+
57+
process {
58+
59+
foreach ($ModuleName in $Name) {
60+
$FoundModule = $false
61+
foreach ($BasePath in $ModulePaths) {
62+
# Get the full module path and scope to check.
63+
$ModuleFolder = Join-Path -Path $BasePath -ChildPath $ModuleName
64+
$BasePathScope = if ($BasePath -like "$HOME*") { 'CurrentUser' } else { 'AllUsers' }
65+
if (Test-Path -Path $ModuleFolder) {
66+
# Found the module, now get the highest version for directories that can be parsed as versions.
67+
$VersionFolders = Get-ChildItem -Path $ModuleFolder -Directory -ErrorAction SilentlyContinue |
68+
Where-Object { [version]::TryParse($_.Name, [ref]$null) } | # Faster than `$_.Name -as [version]`
69+
Sort-Object { [version]$_.Name } -Descending
70+
71+
if ($VersionFolders) {
72+
$HighestVersion = $VersionFolders | Select-Object -First 1
73+
$ManifestPath = Join-Path -Path $HighestVersion.FullName -ChildPath "$ModuleName.psd1"
74+
75+
[PSCustomObject]@{
76+
Name = $ModuleName
77+
Version = [version]$HighestVersion.Name
78+
Path = $HighestVersion.FullName
79+
Scope = $BasePathScope
80+
ManifestPath = if (Test-Path -Path $ManifestPath) { $ManifestPath } else { $null }
81+
}
82+
} else {
83+
# Module folder exists but no version sub-folders.
84+
$ManifestPath = Join-Path -Path $ModuleFolder -ChildPath "$ModuleName.psd1"
85+
86+
[PSCustomObject]@{
87+
Name = $ModuleName
88+
Version = $null
89+
Path = $ModuleFolder
90+
Scope = $BasePathScope
91+
ManifestPath = if (Test-Path -Path $ManifestPath) { $ManifestPath } else { $null }
92+
}
93+
} # end if VersionFolders
94+
95+
# Stop searching after first match
96+
$FoundModule = $true
97+
break
98+
} # end if Test-Path ModuleFolder
99+
} # end foreach BasePath
100+
101+
if (-not $FoundModule) {
102+
# Module not found in any path. Return an empty, but named object with null properties.
103+
Write-Warning "Module '$Name' not found in PSModulePath."
104+
[PSCustomObject]@{
105+
Name = $Name
106+
Version = $null
107+
Path = $null
108+
Scope = $Scope
109+
ManifestPath = $null
110+
}
111+
} # end if not FoundModule
112+
} # end foreach ModuleName
113+
} # end process
114+
} # end function Get-EffectiveModule

0 commit comments

Comments
 (0)