Using Prefixes to Prevent Command Name Collision

February 15th, 2016 by June Blender
Last updated on February 10th, 2016

 

In January, I had the honor of presenting to the Mississippi PowerShell User Group (MSPSUG). I’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. This was a perfect venue for my “Avoiding Version Chaos” talk. (More at PowerShell Saturday in Tampa on March 19, 2016.)

Screenshot 2016-02-06 16.58.52

In one part of the talk, I demonstrated how to use noun prefixes to distinguish among commands with the same names. The demo flopped — we ended up with duplicate commands — so I’ll use this blog post to show how prefixes works and what went wrong.

TIP:   To detect commands with the same name on the same machine, Use Group-Object to Detect Command Name Conflicts.

 

Define Unique (Prefixed) Names

One way to prevent command name conflicts is to define command names that are likely to be unique.

For example, the cmdlets in the PowerForensics module are created with names that include “Forensic” so they’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.)

PS C:\> Get-Command -Module PowerForensics

ommandType     Name                                  Version    Source
-----------     ----                                 ------    ------
Cmdlet          ConvertFrom-BinaryData               1.1.1      PowerForensics
Cmdlet          ConvertTo-ForensicTimeline           1.1.1      PowerForensics
Cmdlet          Copy-ForensicFile                    1.1.1      PowerForensics
Cmdlet          Get-ForensicAlternateDataStream      1.1.1      PowerForensics
Cmdlet          Get-ForensicAmcache                  1.1.1      PowerForensics
Cmdlet          Get-ForensicAttrDef                  1.1.1      PowerForensics
Cmdlet          Get-ForensicBitmap                   1.1.1      PowerForensics
Cmdlet          Get-ForensicBootSector               1.1.1      PowerForensics
...

Add a Default Command Prefix

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, Import-Module cmdlet prepends the default prefix to the nouns of all commands in the module.

To specify a default prefix, use the DefaultCommandPrefix key in the module manifest.

DefaultCommandPrefix =

To get modules with a default command prefix, look for a value in the Prefix property of the module.

PS C:\> Get-Module -ListAvailable | where Prefix

    Directory: C:\Users\JuneBlender\Documents\WindowsPowerShell\Modules

ModuleType Version    Name                    ExportedCommands
---------- -------    ----                    ----------------
Manifest   1.2.0.0    HardwareManagement      {Get-CIMHardwareInventory, Get-CIMBootOrder,

For example, the HardwareManagement 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’s look at it.

This command gets the path to module manifest and then converts the manifest to hash table, so it’s easier to examine.
(h/t @LeeHolmes for the command format. It converts any hash table string a hash table).

#Convert the module manifest to a hash table # The manifest path is in the module's Path property
PS C:\> $manifest = Invoke-Expression (Get-Content -Raw -Path ((Get-Module -List HardwareManagement).Path))

Here’s the DefaultCommandPrefix key. It has a value of ‘CIM’.

PS C:\> $manifest.DefaultCommandPrefix
CIM

The manifest also reveals that the functions in the HardwareManagement module, as defined, don’t have the ‘CIM’ prefix in the name.

PS C:\> $manifest.FunctionsToExport
Clear-RecordLog
ConvertTo-OctetString
Disable-Account
Enable-Account
Get-Account
Get-AccountMgmtService
Get-BootOrder
Get-ComputerSystem
Get-ConsoleRedirection
...

However, when you import the modules into the session, the nouns in the function names have the ‘CIM’ prefix.

PS C:\> Import-Module HardwareManagement
PS C:\> Get-Command -Module HardwareManagement

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Function        Clear-CIMRecordLog                                 1.2.0.0    HardwareManagement
Function        ConvertTo-CIMOctetString                           1.2.0.0    HardwareManagement
Function        Disable-CIMAccount                                 1.2.0.0    HardwareManagement
Function        Enable-CIMAccount                                  1.2.0.0    HardwareManagement
Function        Get-CIMAccount                                     1.2.0.0    HardwareManagement
Function        Get-CIMAccountMgmtService                          1.2.0.0    HardwareManagement
Function        Get-CIMBootOrder                                   1.2.0.0    HardwareManagement
...

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.

PS C:\> Get-Help Clear-CIMRecordLog -full

NAME
Clear-CIMRecordLog

SYNOPSIS
Clears a record log

SYNTAX
Clear-CIMRecordLog -CimSession -InstanceID [-UseRecordLogProfile] [-WhatIf] [-Confirm]
[]

Clear-CIMRecordLog [-CimRecordLog] [-UseRecordLogProfile] [-WhatIf] [-Confirm] []

DESCRIPTION
Removes all entries from a specific record log from managed node based on support of Record Log Profile

More details about the Record Log Profile can be found here:

http://www.dmtf.org/sites/default/files/standards/documents/DSP1010_1.0.pdf
http://www.dmtf.org/sites/default/files/standards/documents/DSP1010_2.0.pdf

PARAMETERS
-CimRecordLog
...

And, you can run the command as usual with the prefix.

PS C:\> Clear-CimRecord -CimSession $cs InstanceID 1

Specify a custom prefix

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.

To specify a custom prefix for the commands in a module, use the Prefix parameter of Import-Module.
For 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.)

#Note the wildcard. Otherwise, it would return only the function.
PS C:\> Get-Command Expand-Archive* 

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

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 ‘PSCX’ prefix when I import the PSCX module.

PS C:\> Import-Module PSCX -Prefix PSCX
PS C:\> Get-Command Expand-*Archive

CommandType     Name                  Version    Source
-----------     ----                  -------    ------
Function        Expand-Archive        1.0.0.0    Microsoft.PowerShell.Archive
Cmdlet          Expand-PSCXArchive    3.2.1.0    PSCX

Now, it’s easy to distinguish the commands and use the one I want.

PS C:\ > Expand-PSCXArchive -OutputPath ...
PS C:\ > Expand-Archive -DestinationPath ...

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 ‘CIM’, but I prefer ‘Hardware’.

By default, the command prefix is CIM.

PS C:\> Import-Module HardwareManagement
PS C:\> Get-Command -Module HardwareManagement | Sort Name

CommandType     Name                             Version    Source
-----------     ----                             -------    ------
Function        Clear-CIMRecordLog               1.2.0.0    HardwareManagement
Function        ConvertTo-CIMOctetString         1.2.0.0    HardwareManagement
Function        Disable-CIMAccount               1.2.0.0    HardwareManagement
Function        Enable-CIMAccount                1.2.0.0    HardwareManagement
Function        Get-CIMAccount                   1.2.0.0    HardwareManagement
Function        Get-CIMAccountMgmtService        1.2.0.0    HardwareManagement
Function        Get-CIMBootOrder                 1.2.0.0    HardwareManagement
...

Specify the ‘Hardware’ value of the Prefix parameter.

PS C:\> Remove-Module HardwareManagement
PS C:\ps-test> Import-Module HardwareManagement -Prefix Hardware
PS C:\ps-test> Get-Command -Module HardwareManagement | Sort Name

CommandType     Name                                 Version    Source
-----------     ----                                 -------    ------
Function        Clear-HardwareRecordLog              1.2.0.0    HardwareManagement
Function        ConvertTo-HardwareOctetString        1.2.0.0    HardwareManagement
Function        Disable-HardwareAccount              1.2.0.0    HardwareManagement
Function        Enable-HardwareAccount               1.2.0.0    HardwareManagement
Function        Get-HardwareAccount                  1.2.0.0    HardwareManagement
Function        Get-HardwareAccountMgmtService       1.2.0.0    HardwareManagement
Function        Get-HardwareBootOrder                1.2.0.0    HardwareManagement
...

Limits of Command Prefixes

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’s actually a good reminder.

As pointed out by one of the Mississippi PowerShell User Group participants (one of many really great conversations), it’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.

Also, because modules are imported automatically, it’s easy to end up with multiple copies of the same command in the session. That’s what happened in my demo (but, unfortunately, not in my practice sessions).

First I showed that the commands in the module had no intrinsic noun prefix.

PS C:\> (Invoke-Expression (Get-Content -Raw (Get-Module HardwareManagement -List).Path )).FunctionsToExport | Sort
Clear-RecordLog
ConvertTo-OctetString
Disable-Account
Enable-Account
Get-Account
Get-AccountMgmtService
Get-BootOrder

Next, I showed that PowerShell automatically used the specified DefaultCommandPrefix value of CIM.

PS C:\> Get-Command -Module HardwareManagement

CommandType     Name                                   Version    Source
-----------     ----                                   -------    ------
Function        Clear-HardwareRecordLog                1.2.0.0    HardwareManagement
Function        ConvertTo-HardwareOctetString          1.2.0.0    HardwareManagement
Function        Disable-HardwareAccount                1.2.0.0    HardwareManagement
Function        Enable-HardwareAccount                 1.2.0.0    HardwareManagement
Function        Get-HardwareAccount                    1.2.0.0    HardwareManagement
Function        Get-HardwareAccountMgmtService         1.2.0.0    HardwareManagement
Function        Get-HardwareBootOrder                  1.2.0.0    HardwareManagement
...

Then, I showed how to use the Prefix parameter of the Import-Module cmdlet to define your own prefix.

PS C:\> Import-Module HardwareManagement -Prefix Hardware

But, when I displayed the commands in my session, I had both commands with a CIM prefix and commands with a Hardware prefix.

PS C:\> Get-Command -Module HardwareManagement

CommandType     Name                                   Version    Source
-----------     ----                                   -------    ------
Function        Clear-CIMRecordLog                     1.2.0.0    HardwareManagement
Function        Clear-HardwareRecordLog                1.2.0.0    HardwareManagement
Function        ConvertTo-CIMOctetString               1.2.0.0    HardwareManagement
Function        ConvertTo-HardwareOctetString          1.2.0.0    HardwareManagement
Function        Disable-CIMAccount                     1.2.0.0    HardwareManagement
Function        Disable-HardwareAccount                1.2.0.0    HardwareManagement
Function        Enable-CIMAccount                      1.2.0.0    HardwareManagement
Function        Enable-HardwareAccount                 1.2.0.0    HardwareManagement
Function        Get-CIMAccount                         1.2.0.0    HardwareManagement
...

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’t a practical problem, because running the commands with either name would work, but it’s certainly confusing.

I’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.

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.

 

 
[Google+]   [Facebook]   [LinkedIn]   [StumbleUpon]   [Digg]   [Reddit]   [Google Bookmark]  

Tags: , , , ,