Public/Get-ModuleImportCandidate.ps1
|
function Get-ModuleImportCandidate { <# .SYNOPSIS Reports the version, path, and scope that would be imported for a given module name. .DESCRIPTION Searches PSModulePath in order to determine which version of a module would be imported by Import-Module, mimicking PowerShell's module resolution logic. .PARAMETER Name The name or array of names of the module to check. .PARAMETER Scope Limits the search to modules installed for the CurrentUser, AllUsers, or Any (default) scope. .OUTPUTS A full [System.Management.Automation.PSModuleInfo] object with a Scope and custom type name (DLLPickle.ModuleImportCandidate) added. .EXAMPLE Get-ModuleImportCandidate -Name "Pester" Gets the effective module information for Pester from any scope. .EXAMPLE Get-ModuleImportCandidate "PSReadLine" -Scope CurrentUser Gets the effective module information for PSReadLine only from the CurrentUser scope. .EXAMPLE "Pester", "PSReadLine" | Get-ModuleImportCandidate Gets the effective module information for multiple modules from any scope. #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidateNotNullOrEmpty()] [string[]]$Name, [Parameter(Mandatory = $false)] [ValidateSet('CurrentUser', 'AllUsers', 'Any')] [string]$Scope = 'Any' ) begin { # Set a flag to indicate if the module path was found. $FoundModule = $false # Split PSModulePath into individual paths, using a switch statement to only get paths in the specified scope or default to any scope. $ScopeFilter = switch ($Scope) { 'CurrentUser' { [ScriptBlock] { $_ -like "$HOME*" -and (Test-Path -Path $_ -PathType Container -ErrorAction SilentlyContinue) } } 'AllUsers' { [ScriptBlock] { $_ -notlike "$HOME*" -and (Test-Path -Path $_ -PathType Container -ErrorAction SilentlyContinue) } } 'Any' { [ScriptBlock] { Test-Path -Path $_ -PathType Container -ErrorAction SilentlyContinue } } } $ModulePaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object -FilterScript $ScopeFilter } # end begin process { foreach ($ModuleName in $Name) { $FoundModule = $false foreach ($BasePath in $ModulePaths) { # Get the full module path and scope to check. $ModuleFolder = Join-Path -Path $BasePath -ChildPath $ModuleName $BasePathScope = if ($BasePath -like "$HOME*") { 'CurrentUser' } else { 'AllUsers' } if (Test-Path -Path $ModuleFolder) { # Found the module, now get the highest version for directories that can be parsed as versions. $VersionFolders = Get-ChildItem -Path $ModuleFolder -Directory -ErrorAction SilentlyContinue | Where-Object { [version]::TryParse($_.Name, [ref]$null) } | Sort-Object { [version]$_.Name } -Descending if ($VersionFolders) { $HighestVersion = $VersionFolders | Select-Object -ExpandProperty Name -First 1 $ModuleInfo = Get-Module -Name $ModuleName -ListAvailable | Where-Object { $_.Version -eq [version]$HighestVersion } $ModuleInfo | Add-Member -MemberType NoteProperty -Name Scope -Value $BasePathScope $ModuleInfo.PSObject.TypeNames.Insert(0, 'DLLPickle.ModuleImportCandidate') } else { # Module folder exists but no version sub-folders. $ModuleInfo = Get-Module -Name $ModuleName -ListAvailable | Sort-Object -Property Version -Descending -Unique | Select-Object -First 1 $ModuleInfo | Add-Member -MemberType NoteProperty -Name Scope -Value $BasePathScope $ModuleInfo.PSObject.TypeNames.Insert(0, 'DLLPickle.ModuleImportCandidate') } # end if VersionFolders # Stop searching after first match $FoundModule = $true break } # end if Test-Path ModuleFolder } # end foreach BasePath if ($FoundModule) { # Output the module info object $ModuleInfo } else { # Module not found in any path. Return an empty, but named object with null properties. Write-Warning "Module '$ModuleName' not found in PSModulePath (Scope: $Scope).`n" $null } # end if FoundModule } # end foreach ModuleName } # end process } # end function Get-ModuleImportCandidate |