|
| 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