{"id":11094,"date":"2016-02-15T06:00:38","date_gmt":"2016-02-15T14:00:38","guid":{"rendered":"https:\/\/www.sapien.com\/blog\/?p=11094"},"modified":"2016-02-10T08:23:30","modified_gmt":"2016-02-10T16:23:30","slug":"use-prefixes-to-prevent-command-name-collision","status":"publish","type":"post","link":"https:\/\/dev.sapien.com\/blog\/2016\/02\/15\/use-prefixes-to-prevent-command-name-collision\/","title":{"rendered":"Using Prefixes to Prevent Command Name Collision"},"content":{"rendered":"<p>In January, I had the honor of presenting to the <a href=\"http:\/\/mspsug.com\/\" target=\"_blank\">Mississippi PowerShell User Group (MSPSUG)<\/a>. I&#8217;ve known the organizers, <a href=\"https:\/\/twitter.com\/mikefrobbins\" target=\"_blank\">Mike Robbins<\/a> and <a href=\"https:\/\/twitter.com\/magicrohn\" target=\"_blank\">Rohn Edwards<\/a> for years, and truly respect them. The PSUG is online-only, which makes it a challenge for presenters, but they attract a very sophisticated audience, so my talks there are really conversations. This was a perfect venue for my &#8220;<a href=\"http:\/\/mspsug.com\/2016\/01\/05\/mspsug-january-2016-meeting-powershell-avoiding-version-chaos-in-a-multi-version-world\/\" target=\"_blank\">Avoiding Version Chaos<\/a>&#8221; talk. (More at <a href=\"http:\/\/powershellsaturday.com\/010\/conference\/powershell-saturday-010-tampa-florida-march-19th-2016\/\" target=\"_blank\">PowerShell Saturday<\/a> in Tampa on March 19, 2016.)<\/p>\n<p style=\"padding-left: 30px;\"><a href=\"https:\/\/www.sapien.com\/blog\/wp-content\/uploads\/2016\/02\/Screenshot-2016-02-06-16.58.52.png\" rel=\"attachment wp-att-11154\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-11154\" src=\"https:\/\/www.sapien.com\/blog\/wp-content\/uploads\/2016\/02\/Screenshot-2016-02-06-16.58.52.png\" alt=\"Screenshot 2016-02-06 16.58.52\" width=\"194\" height=\"213\" \/><\/a><\/p>\n<p>In one part of the talk, I demonstrated how to use noun prefixes to distinguish among commands with the same names. The demo flopped &#8212; we ended up with duplicate commands &#8212; so I&#8217;ll use this blog post to show how prefixes works and what went wrong.<\/p>\n<p><em>TIP:\u00a0\u00a0 To detect commands with the same name on the same machine, <a title=\"Permanent Link: Using Group-Object to Detect Command Name Conflicts\" href=\"https:\/\/www.sapien.com\/blog\/?p=11110\" rel=\"bookmark\">Use Group-Object to Detect Command Name Conflicts<\/a>.<\/em><\/p>\n<p>&nbsp;<\/p>\n<h1>Define Unique (Prefixed) Names<\/h1>\n<p>One way to prevent command name conflicts is to define command names that are likely to be unique.<\/p>\n<p>For example, the cmdlets in the <a href=\"https:\/\/github.com\/Invoke-IR\/PowerForensics\" target=\"_blank\">PowerForensics module<\/a> are created with names that include &#8220;Forensic&#8221; so they&#8217;re likely to be unique. (Note that some commands in the module, like ConvertFrom-BinaryData, are not prefixed, because they are intended for a more general use.)<\/p>\n<pre class=\"output\">PS C:\\&gt; Get-Command -Module PowerForensics\r\n\r\nommandType     Name                                  Version    Source\r\n-----------     ----                                 ------    ------\r\nCmdlet          ConvertFrom-BinaryData               1.1.1      PowerForensics\r\nCmdlet          ConvertTo-ForensicTimeline           1.1.1      PowerForensics\r\nCmdlet          Copy-ForensicFile                    1.1.1      PowerForensics\r\nCmdlet          Get-ForensicAlternateDataStream      1.1.1      PowerForensics\r\nCmdlet          Get-ForensicAmcache                  1.1.1      PowerForensics\r\nCmdlet          Get-ForensicAttrDef                  1.1.1      PowerForensics\r\nCmdlet          Get-ForensicBitmap                   1.1.1      PowerForensics\r\nCmdlet          Get-ForensicBootSector               1.1.1      PowerForensics\r\n...\r\n<\/pre>\n<h1>Add a Default Command Prefix<\/h1>\n<p>To prevent name conflicts, module authors can also create commands with more generic names and then specify a default command prefix in the module manifest (the .psd1 file of the module). Then, when the module is imported, <a href=\"http:\/\/go.microsoft.com\/fwlink\/p\/?linkid=289591\">Import-Module<\/a> cmdlet prepends the default prefix to the nouns of all commands in the module.<\/p>\n<p>To specify a default prefix, use the <b>DefaultCommandPrefix<\/b> key in the module manifest.<\/p>\n<pre lang=\"PowerShell\">DefaultCommandPrefix =<\/pre>\n<p>To get modules with a default command prefix, look for a value in the <b>Prefix<\/b> property of the module.<\/p>\n<pre class=\"output\">PS C:\\&gt; Get-Module -ListAvailable | where Prefix\r\n\r\n    Directory: C:\\Users\\JuneBlender\\Documents\\WindowsPowerShell\\Modules\r\n\r\nModuleType Version    Name                    ExportedCommands\r\n---------- -------    ----                    ----------------\r\nManifest   1.2.0.0    HardwareManagement      {Get-CIMHardwareInventory, Get-CIMBootOrder,<\/pre>\n<p>For example, the <a href=\"https:\/\/www.powershellgallery.com\/api\/v2\/\" target=\"_new\">HardwareManagement<\/a> module has several functions with names that might appear in other modules, such as Get-Account and Get-Computer System. So, the module author defined a default prefix, CIM. Let&#8217;s look at it.<\/p>\n<p>This command gets the path to module manifest and then converts the manifest to hash table, so it&#8217;s easier to examine.<br \/>\n(h\/t @LeeHolmes for the command format. It converts any hash table string a hash table).<\/p>\n<pre lang=\"PowerShell\">#Convert the module manifest to a hash table # The manifest path is in the module's Path property\r\nPS C:\\&gt; $manifest = Invoke-Expression (Get-Content -Raw -Path ((Get-Module -List HardwareManagement).Path))<\/pre>\n<p>Here&#8217;s the DefaultCommandPrefix key. It has a value of &#8216;CIM&#8217;.<\/p>\n<pre class=\"output\">PS C:\\&gt; $manifest.DefaultCommandPrefix\r\nCIM<\/pre>\n<p>The manifest also reveals that the functions in the HardwareManagement module, as defined, don&#8217;t have the &#8216;CIM&#8217; prefix in the name.<\/p>\n<pre class=\"output\">PS C:\\&gt; $manifest.FunctionsToExport\r\nClear-RecordLog\r\nConvertTo-OctetString\r\nDisable-Account\r\nEnable-Account\r\nGet-Account\r\nGet-AccountMgmtService\r\nGet-BootOrder\r\nGet-ComputerSystem\r\nGet-ConsoleRedirection\r\n...<\/pre>\n<p>However, when you import the modules into the session, the nouns in the function names have the &#8216;CIM&#8217; prefix.<\/p>\n<pre class=\"output\">PS C:\\&gt; Import-Module HardwareManagement\r\nPS C:\\&gt; Get-Command -Module HardwareManagement\r\n\r\nCommandType     Name                                               Version    Source\r\n-----------     ----                                               -------    ------\r\nFunction        Clear-CIMRecordLog                                 1.2.0.0    HardwareManagement\r\nFunction        ConvertTo-CIMOctetString                           1.2.0.0    HardwareManagement\r\nFunction        Disable-CIMAccount                                 1.2.0.0    HardwareManagement\r\nFunction        Enable-CIMAccount                                  1.2.0.0    HardwareManagement\r\nFunction        Get-CIMAccount                                     1.2.0.0    HardwareManagement\r\nFunction        Get-CIMAccountMgmtService                          1.2.0.0    HardwareManagement\r\nFunction        Get-CIMBootOrder                                   1.2.0.0    HardwareManagement\r\n...<\/pre>\n<p>Get-Help recognizes the command name with its prefix. Note that Get-Help automatically includes the prefix in the Name, Syntax, and Examples, but not in descriptions and other written text.<\/p>\n<pre class=\"output\">PS C:\\&gt; Get-Help Clear-CIMRecordLog -full\r\n\r\nNAME\r\nClear-CIMRecordLog\r\n\r\nSYNOPSIS\r\nClears a record log\r\n\r\nSYNTAX\r\nClear-CIMRecordLog -CimSession -InstanceID [-UseRecordLogProfile] [-WhatIf] [-Confirm]\r\n[]\r\n\r\nClear-CIMRecordLog [-CimRecordLog] [-UseRecordLogProfile] [-WhatIf] [-Confirm] []\r\n\r\nDESCRIPTION\r\nRemoves all entries from a specific record log from managed node based on support of Record Log Profile\r\n\r\nMore details about the Record Log Profile can be found here:\r\n\r\nhttp:\/\/www.dmtf.org\/sites\/default\/files\/standards\/documents\/DSP1010_1.0.pdf\r\nhttp:\/\/www.dmtf.org\/sites\/default\/files\/standards\/documents\/DSP1010_2.0.pdf\r\n\r\nPARAMETERS\r\n-CimRecordLog\r\n...\r\n<\/pre>\n<p>And, you can run the command as usual with the prefix.<\/p>\n<pre class=\"output\">PS C:\\&gt; Clear-CimRecord -CimSession $cs InstanceID 1<\/pre>\n<h1>Specify a custom prefix<\/h1>\n<p>The DefaultCommandPrefix is just a default. You can specify a custom prefix for the commands any module. If the module has a default command prefix, it is ignored and the custom prefix that you specify is used instead.<\/p>\n<p>To specify a custom prefix for the commands in a module, use the Prefix parameter of Import-Module.<br \/>\nFor example, because I have both the Microsoft.PowerShell.Archive and PSCX module on my test machine, I have two commands named Expand-Archive. (Note the wildcard in the command.)<\/p>\n<pre class=\"output\"><span style=\"color: lime;\">#Note the wildcard. Otherwise, it would return only the function.<\/span>\r\nPS C:\\&gt; Get-Command Expand-Archive* \r\n\r\nCommandType     Name                     Version    Source\r\n-----------     ----                    -------    ------\r\nFunction        Expand-Archive          1.0.0.0    Microsoft.PowerShell.Archive\r\nCmdlet          Expand-Archive          3.2.1.0    Pscx\r\n<\/pre>\n<p>By default, Windows PowerShell runs the Expand-Archive function, because functions take precedence over cmdlets. So, to make it easier to run the PSCX cmdlet, I specify a &#8216;PSCX&#8217; prefix when I import the PSCX module.<\/p>\n<pre class=\"output\">PS C:\\&gt; Import-Module PSCX -Prefix PSCX\r\nPS C:\\&gt; Get-Command Expand-*Archive\r\n\r\nCommandType     Name                  Version    Source\r\n-----------     ----                  -------    ------\r\nFunction        Expand-Archive        1.0.0.0    Microsoft.PowerShell.Archive\r\nCmdlet          Expand-PSCXArchive    3.2.1.0    PSCX<\/pre>\n<p>Now, it&#8217;s easy to distinguish the commands and use the one I want.<\/p>\n<pre class=\"output\">PS C:\\ &gt; Expand-PSCXArchive -OutputPath ...\r\nPS C:\\ &gt; Expand-Archive -DestinationPath ...<\/pre>\n<p>If a module has a DefaultCommandPrefix, the prefix that you specify in your Import-Module command is used instead of the default. For example, the default command prefix for the HardwareManagement module is &#8216;CIM&#8217;, but I prefer &#8216;Hardware&#8217;.<\/p>\n<p>By default, the command prefix is CIM.<\/p>\n<pre class=\"output\">PS C:\\&gt; Import-Module HardwareManagement\r\nPS C:\\&gt; Get-Command -Module HardwareManagement | Sort Name\r\n\r\nCommandType     Name                             Version    Source\r\n-----------     ----                             -------    ------\r\nFunction        Clear-CIMRecordLog               1.2.0.0    HardwareManagement\r\nFunction        ConvertTo-CIMOctetString         1.2.0.0    HardwareManagement\r\nFunction        Disable-CIMAccount               1.2.0.0    HardwareManagement\r\nFunction        Enable-CIMAccount                1.2.0.0    HardwareManagement\r\nFunction        Get-CIMAccount                   1.2.0.0    HardwareManagement\r\nFunction        Get-CIMAccountMgmtService        1.2.0.0    HardwareManagement\r\nFunction        Get-CIMBootOrder                 1.2.0.0    HardwareManagement\r\n...<\/pre>\n<p>Specify the &#8216;Hardware&#8217; value of the Prefix parameter.<\/p>\n<pre class=\"output\">PS C:\\&gt; Remove-Module HardwareManagement\r\nPS C:\\ps-test&gt; Import-Module HardwareManagement -Prefix Hardware\r\nPS C:\\ps-test&gt; Get-Command -Module HardwareManagement | Sort Name\r\n\r\nCommandType     Name                                 Version    Source\r\n-----------     ----                                 -------    ------\r\nFunction        Clear-HardwareRecordLog              1.2.0.0    HardwareManagement\r\nFunction        ConvertTo-HardwareOctetString        1.2.0.0    HardwareManagement\r\nFunction        Disable-HardwareAccount              1.2.0.0    HardwareManagement\r\nFunction        Enable-HardwareAccount               1.2.0.0    HardwareManagement\r\nFunction        Get-HardwareAccount                  1.2.0.0    HardwareManagement\r\nFunction        Get-HardwareAccountMgmtService       1.2.0.0    HardwareManagement\r\nFunction        Get-HardwareBootOrder                1.2.0.0    HardwareManagement\r\n...<\/pre>\n<h1>Limits of Command Prefixes<\/h1>\n<p>Prefixes are a great solution for avoiding name conflicts, right? Well, sometimes. But a lot of things can go wrong. One of them went wrong in my demo, but that&#8217;s actually a good reminder.<\/p>\n<p>As pointed out by one of the Mississippi PowerShell User Group participants (one of many really great conversations), it&#8217;s not a good idea to use prefixes in a script or module that you share with others. You cannot predict what else is in the session and you might actually create a name conflict, rather than resolving one.<\/p>\n<p>Also, because modules are imported automatically, it&#8217;s easy to end up with multiple copies of the same command in the session. That&#8217;s what happened in my demo (but, unfortunately, not in my practice sessions).<\/p>\n<p>First I showed that the commands in the module had no intrinsic noun prefix.<\/p>\n<pre class=\"output\">PS C:\\&gt; (Invoke-Expression (Get-Content -Raw (Get-Module HardwareManagement -List).Path )).FunctionsToExport | Sort\r\nClear-RecordLog\r\nConvertTo-OctetString\r\nDisable-Account\r\nEnable-Account\r\nGet-Account\r\nGet-AccountMgmtService\r\nGet-BootOrder<\/pre>\n<p>Next, I showed that PowerShell automatically used the specified DefaultCommandPrefix value of CIM.<\/p>\n<pre class=\"output\">PS C:\\&gt; Get-Command -Module HardwareManagement\r\n\r\nCommandType     Name                                   Version    Source\r\n-----------     ----                                   -------    ------\r\nFunction        Clear-HardwareRecordLog                1.2.0.0    HardwareManagement\r\nFunction        ConvertTo-HardwareOctetString          1.2.0.0    HardwareManagement\r\nFunction        Disable-HardwareAccount                1.2.0.0    HardwareManagement\r\nFunction        Enable-HardwareAccount                 1.2.0.0    HardwareManagement\r\nFunction        Get-HardwareAccount                    1.2.0.0    HardwareManagement\r\nFunction        Get-HardwareAccountMgmtService         1.2.0.0    HardwareManagement\r\nFunction        Get-HardwareBootOrder                  1.2.0.0    HardwareManagement\r\n...<\/pre>\n<p>Then, I showed how to use the <b>Prefix<\/b> parameter of the <b>Import-Module<\/b> cmdlet to define your own prefix.<\/p>\n<pre class=\"output\">PS C:\\&gt; Import-Module HardwareManagement -Prefix Hardware<\/pre>\n<p>But, when I displayed the commands in my session, I had both commands with a CIM prefix and commands with a Hardware prefix.<\/p>\n<pre class=\"output\">PS C:\\&gt; Get-Command -Module HardwareManagement\r\n\r\nCommandType     Name                                   Version    Source\r\n-----------     ----                                   -------    ------\r\nFunction        Clear-CIMRecordLog                     1.2.0.0    HardwareManagement\r\nFunction        Clear-HardwareRecordLog                1.2.0.0    HardwareManagement\r\nFunction        ConvertTo-CIMOctetString               1.2.0.0    HardwareManagement\r\nFunction        ConvertTo-HardwareOctetString          1.2.0.0    HardwareManagement\r\nFunction        Disable-CIMAccount                     1.2.0.0    HardwareManagement\r\nFunction        Disable-HardwareAccount                1.2.0.0    HardwareManagement\r\nFunction        Enable-CIMAccount                      1.2.0.0    HardwareManagement\r\nFunction        Enable-HardwareAccount                 1.2.0.0    HardwareManagement\r\nFunction        Get-CIMAccount                         1.2.0.0    HardwareManagement\r\n...<\/pre>\n<p>I thought PowerShell might be at fault, but the fault was mine. The Get-Command command auto-loaded the module with the CIM-prefixed commands. Then, I explicitly imported the Hardware-prefixed commands. This isn&#8217;t a practical problem, because running the commands with either name would work, but it&#8217;s certainly confusing.<\/p>\n<p>I&#8217;ll be talking and blogging about module and command conflicts over the next few months. If you have questions or suggestions, please let me know. And, thanks to the Mississippi PowerShell User Group for the great participation.<\/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:\/\/twitter.com\/juneb_get_help\"><em>@juneb_get_help<\/em><\/a><em>.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In January, I had the honor of presenting to the Mississippi PowerShell User Group (MSPSUG). I&#8217;ve known the organizers, Mike Robbins and Rohn Edwards for years, and truly respect them. The PSUG is online-only, which makes it a challenge for presenters, but they attract a very sophisticated audience, so my talks there are really conversations. [&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":[932,1034,2,283,941,703,1024,25],"tags":[551,28,961,57,997],"class_list":["post-11094","post","type-post","status-publish","format-standard","hentry","category-beginners","category-best-practices","category-general","category-howto","category-powershell-5-0","category-powershell-studio","category-usergroup","category-windows-powershell","tag-modules","tag-powershell","tag-powershell-5-0","tag-scripting","tag-windows-powershell"],"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/posts\/11094","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=11094"}],"version-history":[{"count":20,"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/posts\/11094\/revisions"}],"predecessor-version":[{"id":11156,"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/posts\/11094\/revisions\/11156"}],"wp:attachment":[{"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/media?parent=11094"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/categories?post=11094"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/tags?post=11094"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}