Using a ModuleSpecification Object

With the advent of side-by-side module versions in Windows PowerShell 5.0, the lovely, but obscure ModuleSpecification object has become your new best friend. Use it to make sure that the commands and module that you use are the ones that you intend.

Using ModuleSpecification

Let’s start with an example. I want to get the Expand-Archive command in the Microsoft.PowerShell.Archive cmdlet. I can use Get-Command, of course, but when I surround the command name with wildcard characters to make sure it searches, it returns this:

PS C:\ > Get-Command *Expand-Archive*

CommandType   Name             Version   Source
-----------   ----             -------   ------
Function      Expand-Archive   1.0.0.0   PowerShellLogging
Function      Expand-Archive   1.0.0.0   PowerShellLogging
Function      Expand-Archive   1.0.0.0   Microsoft.PowerShell.Archive
Function      Expand-Archive   0.8.0.0   Microsoft.PowerShell.Archive
Cmdlet        Expand-Archive   3.2.1.0   Pscx

[Tip: To detect name conflicts in your installed modules, use Group-Object.]

When I run ‘Get-Command Expand-Archive,’ PowerShell gets the command that actually runs when I type ‘Expand-Archive.’ That command is determined by command precedence and by the order in which PowerShell finds modules, which is, in turn, determined by the order of paths in the $PSModulePath environment variable. That’s complex enough on my own system, but if I’m running shared code in an arbitrary environment, I better make sure that I’m running the correct command.

If I wanted the Expand-Archive command in the PSCX module, I could use a module-qualified name, such as:

Get-Command PSCX\Expand-Archive

But, I need to specify both the module name and the version. That’s where the ModuleSpecification object comes in. This command gets the Expand-Archive function in the 1.0.0.0 version of Microsoft.PowerShell.Archive.

Get-Command -Name Expand-Archive `
-FullyQualifiedModule @{ModuleName = 'Microsoft.PowerShell.Archive'; 
                       RequiredVersion = '1.0.0.0'}

And, if I need to distinguish between two different modules with the same name and version (I created this conflict, but it could happen), you can add a GUID value to get the right module.

Get-Command -Name Expand-Archive `
-FullyQualifiedModule @{ModuleName = 'PowerShellLogging'; 
                        RequiredVersion = '1.0.0.0'; 
                        GUID='abc0b34-02de-453a-9726-6bb7b716a63f'}

I can even use the invoke/call operator (&) to run a command in a specific version of a specific module.

$myCommand = Get-Command -Name Expand-Archive `
-FullyQualifiedModule @{ModuleName = 'Microsoft.PowerShell.Archive'; 
                        RequiredVersion = '1.0.0.0'}
 
& $myCommand -Path .\Myzip.zip -DestinationPath .\Unzipped

Where can I use ModuleSpecification?

One of the most important places to use a ModuleSpecification object is the value of the #Requires -Module parameter.

#Requires -Module @{ModuleName='Pester'; ModuleVersion='3.4.0'}

Also, several cmdlets and functions in PowerShell 5.0 have parameters that take a ModuleSpecification object.

PS C:\ > .\Get-ParameterType.ps1 -ParameterType ModuleSpecification

CmdletName         Parameter
----------         ---------
Export-PSSession   FullyQualifiedModule
Get-Command        FullyQualifiedModule
Get-Module         FullyQualifiedName
Import-Module      FullyQualifiedName
Import-PSSession   FullyQualifiedModule
Remove-Module      FullyQualifiedName
Save-Help          FullyQualifiedModule
Update-Help        FullyQualifiedModule

(See Get-ParameterType.ps1 on GitHub.)

While Import-Module has a FullyQualifiedName parameter that takes a ModuleSpecification object, it also has version parameters that have the same effect, except for the GUID.

PS C:\> (Get-Command Import-Module).ParameterSets.Parameters | where Name -like "*Version" | Sort Name | Select -Property Name, ParameterType -Unique

Name             ParameterType
----             -------------
MaximumVersion   System.String
MinimumVersion   System.Version
RequiredVersion  System.Version

Also, several commands, including the functions in the PowerShellGet module, have version parameters, even though they don’t have parameter that takes a ModuleSpecification object.

Import-Module -Name Pester -RequiredVersion 3.4.0

Be aware that Version and ModuleVersion parameters often behave like MinimumVersion, not RequiredVersion. To verify for any given command, check the help.

PS C:\> C:\ps-test\Get-ParameterName.ps1 -ParameterName "*Version"

CmdletName          Parameter      Type
----------          ---------      ----
Find-DscResource    MinimumVersion System.Version
Find-Module         MinimumVersion System.Version
Find-Script         MinimumVersion System.Version
Get-InstalledModule MinimumVersion System.Version
...

Syntax of a ModuleSpecification Object

You can create a ModuleSpecification object from a hash table using particular keys. PowerShell converts the hash table to the correct object type.

Here are the hash table keys. ModuleName is required and you must include exactly one of the version keys. GUID key is optional.

  • ModuleName <string> (required): Specifies one module name. Enter only one name string. Wildcard characters are not suppored.

Select one of the following keys (required):

  • ModuleVersion <String or System.Version>: Specifies the minimum acceptable version.
    @{ModuleName = 'Pester'; ModuleVersion = '3.4.0'}
  • MaximumVersion <String or System.Version>: Specifies the maximum acceptable version.
    @{ModuleName = 'Pester'; MaximumVersion = '3.3.10'}
  • RequiredVersion <String or System.Version>: Specifies the required version.
    @{ModuleName = 'Pester'; RequiredVersion = '3.3.9'}

The GUID key is optional:

  • GUID <String or System.Guid>: Specifies the module GUID.
    @{ModuleName = 'Pester'; ModuleVersion = '3.4.0'; GUID='a699dea5-2c73-4616-a270-1f7abb777e71'}

By the way, examining the Microsoft.PowerShell.Commands.ModuleSpecification class, separate from its PowerShell implementation is not very helpful.

For example, you can create a ModuleSpecification object with no arguments or with a Name argument, but if you do, all of the other fields are blank, because they are read-only (get, not set).

PS C:\> $ms = [Microsoft.PowerShell.Commands.ModuleSpecification]::New('PSScriptAnalyzer')

PS C:\> $ms | Get-Member

TypeName: Microsoft.PowerShell.Commands.ModuleSpecification

Name             MemberType Definition
----             ---------- ----------
Equals           Method     bool Equals(System.Object obj)
GetHashCode      Method     int GetHashCode()
GetType          Method     type GetType()
ToString         Method     string ToString()
Guid             Property   System.Nullable[guid] Guid {get;}
MaximumVersion   Property   string MaximumVersion {get;}
Name             Property   string Name {get;}
RequiredVersion  Property   version RequiredVersion {get;}
Version Property version    Version {get;}

PS C:\ps-test> $ms.Version = '1.0.0.0'
'Version' is a ReadOnly property.
At line:1 char:1
+ $ms.Version = '1.0.0.0'
+ ~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : PropertyAssignmentException

And, the properties don’t match the hash table key names.

PS C:\> $ms

Name : PSScriptAnalyzer
Guid :
Version :
MaximumVersion :
RequiredVersion :

So, forget the class and use the rules to create a hash table.

How does it work?

One word of caution. When the core commands use the ModuleSpecification object parameters, they don’t always return what you expect.

For example, in PowerShell 5.1.14352.1002, when you specify a minimum version, Import-Module doesn’t look for the earliest qualified version. Instead, it imports the first instance of that module that it encounters with a version greater than or equal to the minimum.

Also, Get-Command cannot get a command in a non-default version of a module unless that version is already imported into the current session.

image

We’ll look at these behaviors in a separate post.

June Blender is a technology evangelist at SAPIEN Technologies, Inc. and a Windows PowerShell MVP. You can reach her at juneb@sapien.com or follow her on Twitter at @juneb_get_help.