{"id":12258,"date":"2016-06-27T06:00:00","date_gmt":"2016-06-27T13:00:00","guid":{"rendered":"https:\/\/www.sapien.com\/blog\/?p=12258"},"modified":"2016-06-21T17:15:10","modified_gmt":"2016-06-22T00:15:10","slug":"using-a-modulespecification-object","status":"publish","type":"post","link":"https:\/\/dev.sapien.com\/blog\/2016\/06\/27\/using-a-modulespecification-object\/","title":{"rendered":"Using a ModuleSpecification Object"},"content":{"rendered":"<p>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.<\/p>\n<h1>Using ModuleSpecification<\/h1>\n<p>Let&#8217;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:<\/p>\n<pre class=\"output\">PS C:\\ &gt; Get-Command *Expand-Archive*\r\n\r\nCommandType   Name             Version   Source\r\n-----------   ----             -------   ------\r\nFunction      Expand-Archive   1.0.0.0   PowerShellLogging\r\nFunction      Expand-Archive   1.0.0.0   PowerShellLogging\r\nFunction      Expand-Archive   1.0.0.0   Microsoft.PowerShell.Archive\r\nFunction      Expand-Archive   0.8.0.0   Microsoft.PowerShell.Archive\r\nCmdlet        Expand-Archive   3.2.1.0   Pscx<\/pre>\n<p>[Tip: <a href=\"https:\/\/www.sapien.com\/blog\/2016\/02\/10\/use-group-object-to-detect-command-name-conflicts\/\" target=\"_new\">To detect name conflicts<\/a> in your installed modules, use Group-Object.]<\/p>\n<p>When I run &#8216;Get-Command Expand-Archive,&#8217; PowerShell gets the command that actually runs when I type &#8216;Expand-Archive.&#8217; That command is determined by <a href=\"http:\/\/go.microsoft.com\/fwlink\/?LinkID=113214\">command precedence<\/a> 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&#8217;s complex enough on my own system, but if I&#8217;m running shared code in an arbitrary environment, I better make sure that I&#8217;m running the correct command.<\/p>\n<p>If I wanted the Expand-Archive command in the PSCX module, I could use a module-qualified name, such as:<\/p>\n<pre lang=\"PowerShell\">Get-Command PSCX\\Expand-Archive<\/pre>\n<p>But, I need to specify both the module name and the version. That&#8217;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.<\/p>\n<pre lang=\"PowerShell\">Get-Command -Name Expand-Archive `\r\n-FullyQualifiedModule @{ModuleName = 'Microsoft.PowerShell.Archive'; \r\n                       RequiredVersion = '1.0.0.0'}<\/pre>\n<p>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.<\/p>\n<pre lang=\"PowerShell\">Get-Command -Name Expand-Archive `\r\n-FullyQualifiedModule @{ModuleName = 'PowerShellLogging'; \r\n                        RequiredVersion = '1.0.0.0'; \r\n                        GUID='abc0b34-02de-453a-9726-6bb7b716a63f'}<\/pre>\n<p>I can even use the invoke\/call operator (&amp;) to run a command in a specific version of a specific module.<\/p>\n<pre lang=\"PowerShell\">$myCommand = Get-Command -Name Expand-Archive `\r\n-FullyQualifiedModule @{ModuleName = 'Microsoft.PowerShell.Archive'; \r\n                        RequiredVersion = '1.0.0.0'}\r\n\r\n&amp; $myCommand -Path .\\Myzip.zip -DestinationPath .\\Unzipped<\/pre>\n<h1>Where can I use ModuleSpecification?<\/h1>\n<p>One of the most important places to use a ModuleSpecification object is the value of the <b>#Requires -Module<\/b> parameter.<\/p>\n<pre lang=\"PowerShell\">#Requires -Module @{ModuleName='Pester'; ModuleVersion='3.4.0'}<\/pre>\n<p>Also, several cmdlets and functions in PowerShell 5.0 have parameters that take a ModuleSpecification object.<\/p>\n<pre class=\"output\">PS C:\\ &gt; .\\Get-ParameterType.ps1 -ParameterType ModuleSpecification\r\n\r\nCmdletName         Parameter\r\n----------         ---------\r\nExport-PSSession   FullyQualifiedModule\r\nGet-Command        FullyQualifiedModule\r\nGet-Module         FullyQualifiedName\r\nImport-Module      FullyQualifiedName\r\nImport-PSSession   FullyQualifiedModule\r\nRemove-Module      FullyQualifiedName\r\nSave-Help          FullyQualifiedModule\r\nUpdate-Help        FullyQualifiedModule<\/pre>\n<p>(See <a href=\"https:\/\/gist.github.com\/juneb\/6cbc1eba77186514512d0931caaa232c\" target=\"_new\">Get-ParameterType.ps1<\/a> on GitHub.)<\/p>\n<p>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.<\/p>\n<pre class=\"output\">PS C:\\&gt; (Get-Command Import-Module).ParameterSets.Parameters | where Name -like \"*Version\" | Sort Name | Select -Property Name, ParameterType -Unique\r\n\r\nName             ParameterType\r\n----             -------------\r\nMaximumVersion   System.String\r\nMinimumVersion   System.Version\r\nRequiredVersion  System.Version<\/pre>\n<p>Also, several commands, including the functions in the PowerShellGet module, have version parameters, even though they don&#8217;t have parameter that takes a ModuleSpecification object.<\/p>\n<pre lang=\"PowerShell\">Import-Module -Name Pester -RequiredVersion 3.4.0<\/pre>\n<p>Be aware that <b>Version<\/b> and <b>ModuleVersion<\/b> parameters often behave like MinimumVersion, not RequiredVersion. To verify for any given command, check the help.<\/p>\n<pre class=\"output\">PS C:\\&gt; C:\\ps-test\\Get-ParameterName.ps1 -ParameterName \"*Version\"\r\n\r\nCmdletName          Parameter      Type\r\n----------          ---------      ----\r\nFind-DscResource    MinimumVersion System.Version\r\nFind-Module         MinimumVersion System.Version\r\nFind-Script         MinimumVersion System.Version\r\nGet-InstalledModule MinimumVersion System.Version\r\n...\r\n<\/pre>\n<h1>Syntax of a ModuleSpecification Object<\/h1>\n<p>You can create a ModuleSpecification object from a hash table using particular keys. PowerShell converts the hash table to the correct object type.<\/p>\n<p>Here are the hash table keys. ModuleName is required and you must include exactly one of the version keys. GUID key is optional.<\/p>\n<ul>\n<li><b>ModuleName<\/b> &lt;string&gt; (required): Specifies one module name. Enter only one name string. Wildcard characters are not suppored.<\/li>\n<\/ul>\n<p>Select one of the following keys (required):<\/p>\n<ul>\n<li><b>ModuleVersion<\/b> &lt;String or System.Version&gt;: Specifies the minimum acceptable version.\n<pre lang=\"PowerShell\">@{ModuleName = 'Pester'; ModuleVersion = '3.4.0'}<\/pre>\n<\/li>\n<\/ul>\n<ul>\n<li><b>MaximumVersion<\/b> &lt;String or System.Version&gt;: Specifies the maximum acceptable version.\n<pre lang=\"PowerShell\">@{ModuleName = 'Pester'; MaximumVersion = '3.3.10'}<\/pre>\n<\/li>\n<\/ul>\n<ul>\n<li><b>RequiredVersion<\/b> &lt;String or System.Version&gt;: Specifies the required version.\n<pre lang=\"PowerShell\">@{ModuleName = 'Pester'; RequiredVersion = '3.3.9'}<\/pre>\n<\/li>\n<\/ul>\n<p>The GUID key is optional:<\/p>\n<ul>\n<li><b>GUID<\/b> &lt;String or System.Guid&gt;: Specifies the module GUID.\n<pre lang=\"PowerShell\">@{ModuleName = 'Pester'; ModuleVersion = '3.4.0'; GUID='a699dea5-2c73-4616-a270-1f7abb777e71'}<\/pre>\n<\/li>\n<\/ul>\n<p>By the way, examining the Microsoft.PowerShell.Commands.ModuleSpecification class, separate from its PowerShell implementation is not very helpful.<\/p>\n<p>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).<\/p>\n<pre class=\"output\">PS C:\\&gt; $ms = [Microsoft.PowerShell.Commands.ModuleSpecification]::New('PSScriptAnalyzer')\r\n\r\nPS C:\\&gt; $ms | Get-Member\r\n\r\nTypeName: Microsoft.PowerShell.Commands.ModuleSpecification\r\n\r\nName             MemberType Definition\r\n----             ---------- ----------\r\nEquals           Method     bool Equals(System.Object obj)\r\nGetHashCode      Method     int GetHashCode()\r\nGetType          Method     type GetType()\r\nToString         Method     string ToString()\r\nGuid             Property   System.Nullable[guid] Guid {get;}\r\nMaximumVersion   Property   string MaximumVersion {get;}\r\nName             Property   string Name {get;}\r\nRequiredVersion  Property   version RequiredVersion {get;}\r\nVersion Property version    Version {get;}\r\n\r\nPS C:\\ps-test&gt; $ms.Version = '1.0.0.0'\r\n'Version' is a ReadOnly property.\r\nAt line:1 char:1\r\n+ $ms.Version = '1.0.0.0'\r\n+ ~~~~~~~~~~~~~~~~~~~~~~~\r\n+ CategoryInfo : InvalidOperation: (:) [], RuntimeException\r\n+ FullyQualifiedErrorId : PropertyAssignmentException<\/pre>\n<p>And, the properties don&#8217;t match the hash table key names.<\/p>\n<pre class=\"output\">PS C:\\&gt; $ms\r\n\r\nName : PSScriptAnalyzer\r\nGuid :\r\nVersion :\r\nMaximumVersion :\r\nRequiredVersion :<\/pre>\n<p>So, forget the class and use the rules to create a hash table.<\/p>\n<h1>How does it work?<\/h1>\n<p>One word of caution. When the core commands use the ModuleSpecification object parameters, they don&#8217;t always return what you expect.<\/p>\n<p>For example, in PowerShell 5.1.14352.1002, when you specify a minimum version, Import-Module doesn&#8217;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.<\/p>\n<p>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.<\/p>\n<p><a href=\"https:\/\/www.sapien.com\/blog\/wp-content\/uploads\/2016\/06\/image.png\"><img loading=\"lazy\" decoding=\"async\" style=\"background-image: none; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border: 0px;\" title=\"image\" src=\"https:\/\/www.sapien.com\/blog\/wp-content\/uploads\/2016\/06\/image_thumb.png\" alt=\"image\" width=\"790\" height=\"282\" border=\"0\" \/><\/a><\/p>\n<p>We&#8217;ll look at these behaviors in a separate post.<\/p>\n<p><em>June Blender is a technology evangelist at SAPIEN Technologies, Inc. and a Windows PowerShell MVP. You can reach her at <\/em><a href=\"mailto:juneb@sapien.com\"><em>juneb@sapien.com <\/em><\/a><em>or follow her on Twitter at <\/em><a href=\"https:\/\/www.twitter.com\/juneb_get_help\"><em>@juneb_get_help<\/em><\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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&#8217;s start with an example. I want to get the Expand-Archive [&hellip;]<\/p>\n","protected":false},"author":31,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[941,1129,25],"tags":[1134,1133,934,1130,28,961,587,1132,1131,872],"class_list":["post-12258","post","type-post","status-publish","format-standard","hentry","category-powershell-5-0","category-powershell-5-1-14352-1002","category-windows-powershell","tag-fullyqualifiedmodule","tag-fullyqualifiedname","tag-juneb","tag-modulespecification","tag-powershell","tag-powershell-5-0","tag-version","tag-versionchaos","tag-versioning","tag-versions"],"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/posts\/12258","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/users\/31"}],"replies":[{"embeddable":true,"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/comments?post=12258"}],"version-history":[{"count":16,"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/posts\/12258\/revisions"}],"predecessor-version":[{"id":12318,"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/posts\/12258\/revisions\/12318"}],"wp:attachment":[{"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/media?parent=12258"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/categories?post=12258"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/tags?post=12258"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}