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.)
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.
RT @SAPIENTech: Use Prefixes to Prevent Command Name Collision: In January, I had the honor of presenting to the Mississippi Po… https://…
RT @SAPIENTech: Use Prefixes to Prevent Command Name Collision: In January, I had the honor of presenting to the Mississippi Po… https://…
Command prefixes are 1 way to prevent #PowerShell command-name collisions, but there are some “gotchas.” @SAPIENTech https://t.co/BE5VGbtA9G
RT @juneb_get_help: Command prefixes are 1 way to prevent #PowerShell command-name collisions, but there are some “gotchas.” @SAPIENTech ht…
There are more gotchas with prefixes than that, @juneb_get_help @SAPIENTech
What name do you use within your module to call the command?
@Jaykul @SAPIENTech In .psm1, use the command name w/o prefix. But, to call commands after import, w/prefix. Yup, another gotcha.
@Jaykul @SAPIENTech You can’t. That’s why you can use Import-Module -Prefix to override the DefaultCommandPrefix. Not recommending prefixes.
There are other problems as well with prefixes. If you use DefaultCommandPrefix, and you attempt to invoke a command using the fully qualified command name, how you invoke it will vary depending on the version of PowerShell you are using (due to a bug or two). Between these issues and other details such as commands showing up due to modules being loaded implicitly in various scenarios, I never use DefaultCommandPrefix in a manifest and recommend others against it. It’s simply not reliable/consistent enough. There really needs to be a better way.
Prefixing isn’t the right solution at all in a command pool with potentially tens of thousands of commands. It is necessary for general ad-hoc use, where users work with a smaller collection of modules and can control what is installed/uninstalled, but ultimately it is insufficient when creating scripts that may run on systems where the script author does not have control over the modules that are installed. You can read about my thoughts on what really needs to happen in the comments on this connect post (that was copied over to uservoice, minus the comments — I’ll have to add those later): https://connect.microsoft.com/PowerShell/feedbackdetail/view/2065573/scripts-need-better-isolation-from-the-users-global-session-state. And since I just noticed Joey’s comment reply on that thread, I’m going to start an RFC issue soon discussing the need and proposing some language changes to make it happen.
RT @juneb_get_help: Command prefixes are 1 way to prevent #PowerShell command-name collisions, but there are some “gotchas.” @SAPIENTech ht…
“…For example, the cmdlets in the PowerForensics module are created with names that include “Forensic” so they’re likely to be unique….”
‘Forensic’ should be an example of what NOT to use for a prefix.
Now you have to type a minimum of 9 count ’em 9 characters from the noun before you can leverage tab completion effectively.
No thanks.
How about PFS or PWF?