Importing multiple versions of Powershell Modules loads them all?
Asked Answered
J

2

7

Importing multiple versions of modules (this is PS7):

Import-Module -Name MyModule -RequiredVersion 1.0.28
Import-Module -Name MyModule -RequiredVersion 1.0.29

Will load both versions into your session:

Get-Module -Name MyModule 

Outputs:

ModuleType Version    PreRelease Name
---------- -------    ---------- ---- 
Script     1.0.28                MyModule
Script     1.0.29                MyModule

This becomes quite confusing when you're trying to write CI/CD scripts, because someone may have multiple versions loaded locally. In my case I want to ensure a specific version is loaded before continuing.

Which version does PS actually use in this scenario? Is it the last loaded rather than the highest version?

Is there an easy way to ensure a specific version is being used, short of looping over all other versions and removing them?

Jorin answered 22/4, 2021 at 14:23 Comment(0)
M
10

Which version does PS actually use in this scenario?

The most recently imported version takes precedence, which the output of Get-Module indicates in reverse order of precedence (as of PowerShell Core 7.2.0-preview.5); that is, the last version listed in Get-Module output for a given module is the one in effect (for commands of the same name).

For a given command, you can use Get-Command to see what module (version) it comes from.[1]

Caveat: The most recently imported version only takes precedence in the sense that it shadows commands of the same name from previously imported versions. Therefore, if a previously imported version has additional commands not defined (anymore) by the most recently imported version, they are still in effect.


Is there an easy way to ensure a specific version is being used, short of looping over all other versions and removing them?

If you want to ensure that no other versions are loaded, use Remove-Module <name>, which removes all versions of the module with the given name from the session.

By contrast, if you wanted to unload a specific version, you'd pass a fully qualified module name to Remove-Module's -FullyQualifiedName parameter; e.g.:

Remove-Module -FullyQualifiedName @{ ModuleName = 'MyModule'; RequiredVersion = '1.0.28' }

Note the use of the RequiredVersion key to specify the exact version to unload; by contrast, using key ModuleVersion would unload the given version and all higher ones.

Also note:

  • As noted in Cpt.Whale's helpful answer:

    • See caveats re auto-loading modules below.
    • You can similarly use -FullyQualifiedName or -RequiredVersion with Import-Module for version-specific importing.
    • You can choose to import selectively, i.e. a subset of a given module's exports, using the -Cmdlet, -Function, -Alias and -Variable parameters.
      • Note: Using any of these parameters means that you must then explicitly specify all exports you want to import, across the relevant parameters; e.g., if you use -Alias @() in order to effectively exclude exported aliases from import, you must also use the -Cmdlet / -Function parameter to specify what cmdlets / functions you do want to import; however, you can use -Cmdlet * / -Function * to select them all.
  • Caveats re auto-loading modules - i.e. those placed in one of the directories listed in $env:PSModulePath, as is typical:

    • If you have removed all versions of a module with Remove-Module but at least one version is auto-loading, using any of its exported commands later triggers its (re)import, with all its exports getting imported.

    • Similarly, if you have imported a specific version of a module and a (different) version of the same module is (also) auto-loading, using an exported command that is exclusive to the latter triggers the latter's auto-loading, and thereby implicitly shadows those exports that have the same name from the originally imported, specific version

    • The risk of that happening increases if you have used -Cmdlet, -Function, -Alias and -Variable for selective importing of the originally imported, specific version, because then use of any of the non-imported exports that are also present in the auto-loading version trigger auto-loading of the latter.

    • A simple example: Say you manually import version 1.0 of module Foo in order to load the Get-Foo cmdlet (Import-Module Foo -RequiredVersion 1.0). However, the auto-loading version of Foo is version 2.0, and also has a Set-Foo command. When you call Set-Foo later, version 2.0 is implicitly (automatically) imported, and its Get-Foo implementation now shadows the one from the originally imported 1.0 version of Foo.


[1] Note that you can even get a reference to a currently shadowed command by requesting it via its specific module version using the -FullyQualifiedModule parameter (e.g.
Get-Command Get-Foo -FullyQualifiedModule @{ ModuleName='Foo'; RequiredVersion='1.0.28' }
), which you can then invoke with &, the call operator. However, due to the bug detailed in GitHub issue #15298, present as of PowerShell Core 7.2.0-preview.5, this only works if the targeted module version was explicitly imported first (e.g., Import-Module Foo -RequiredVersion 1.0.28).

Management answered 22/4, 2021 at 14:42 Comment(2)
@Boologne, please see my update (start with "Also note:").Management
very thorough answer - I was having fun trying to duel run versions 4 and 5 of Pester - I need 4 for PSKoans but for other learning I'm doing I need 5 - but using this answer I've add a function to my Profile to dynamically import the correct Pester depending on what I'm working on - thank you!Sinuation
S
3

Adding more details that I've had trouble with to mklement0's great answer:

  • Get-Module returns a list of ExportedFunctions,ExportedCmdlets, and ExportedCommands for each loaded module version. Perfect!

  • Be wary of nested modules! Powershell will separate information for the named module, and list any secondary modules under the NestedModules property of the one you asked for.

  • You can specify which commands and module version when you import-module, which can let you manage your running environment better than just importing the entire module at once.

Examples:

# Here I have two versions loaded, but only one has any exported cmdlets:
Get-Module ExchangeOnlineManagement | Format-List Version,ExportedFunctions,ExportedCmdlets,ExportedCommands,NestedModules

Version           : 2.0.4
ExportedFunctions : {[Connect-ExchangeOnline, Connect-ExchangeOnline], …}
ExportedCmdlets   : {[Get-EXOCasMailbox, Get-EXOCasMailbox], …}
ExportedCommands  : {[Get-EXOCasMailbox, Get-EXOCasMailbox], …}
NestedModules     : {Microsoft.Exchange.Management.RestApiClient,
                    Microsoft.Exchange.Management.ExoPowershellGalleryModule}

Version           : 2.0.5
ExportedFunctions : {[Connect-ExchangeOnline, Connect-ExchangeOnline], …}
ExportedCmdlets   : {}
ExportedCommands  : {[Connect-ExchangeOnline, Connect-ExchangeOnline], …}
NestedModules     : {}

# I can see function Connect-ExchangeOnline was loaded twice, so to see which version is active:
Get-Command Connect-ExchangeOnline 

CommandType : Function
Name        : Connect-ExchangeOnline
Version     : 2.0.5
Source      : ExchangeOnlineManagement

# I use this to see current loaded assemblies, and module version should be in the path
[appdomain]::CurrentDomain.GetAssemblies() |
    Where { $_.Modules.FullyQualifiedName.contains('ExchangeOnlineManagement') } |
    Select -ExpandProperty Modules |
    Format-List Name,Assembly,FullyQualifiedName

Name               : Microsoft.Exchange.Management.AdminApiProvider.dll
Assembly           : Microsoft.Exchange.Management.AdminApiProvider, Version=17.0.0.0
FullyQualifiedName : \path\to\Modules\ExchangeOnlineManagement\2.0.4\netCore\Microsoft.Exch 
                     ange.Management.AdminApiProvider.dll

Name               : Microsoft.Exchange.Management.ExoPowershellGalleryModule.dll
Assembly           : Microsoft.Exchange.Management.ExoPowershellGalleryModule, Version=17.0.0.0
FullyQualifiedName : \path\to\Modules\ExchangeOnlineManagement\2.0.4\netCore\Microsoft.Exch 
                     ange.Management.ExoPowershellGalleryModule.dll

Swinson answered 22/4, 2021 at 16:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.