Cmdlet name clashes in PowerShell: What to do?

This question has been asked in various ways over the last few years and I don't believe an answer that suits everyone has been proffered yet. I think this is part of a broader problem space that needs to be solved, one that I (and many others) have spent a bit thinking about -- for me personally, it's been mostly in the pub strangely enough, usually with a pint in hand -- and while I don't profess to have the answer, I do spent most of my powershell time tinkering with providers and have some views on this ;-)

Firstly, if you haven't seen this suggestion I've raised on connect (Allow providers other than the filesystem provider to surface commands) then take a gander at it now. It suggests allowing providers other than the FileSystemProvider to surface commands by using a new ProviderCapabilities flag. For those not able to read this suggestion, the bottom line is that currently one can execute a command on the filesystem by using the following syntax:

ps c:\> .\test.bat
hello, world

However, if you had another provider that linked into a mobile device, the amazon s3 service or MSL skydrive (when are they going to release an API?) for example, you would allow execution of commands with the same syntax, e.g.

ps skydrive:\> cd ppts
ps skydrive:\ppts> .\mydemo.ppt
The term '.\mydemo.ppt' is not recognized as a cmdlet, function, operable program, or script file. Verify the term and try again.
At line:1 char:8
+ .\mydemo.ppt <<<<

As you can see, this doesn't work.

What's important is having the same experience with similarly capable providers; e.g. those that can host executable content. Yes, you can implement support for invoke-item, but it's a bit discordant. One of the nicest features of powershell (and sometimes the most confusing) is that all providers - variable, function, environment, filesystem etc all hook into the same framework. There are some philosophically irksome differences like the fact that the  variable drive is the "default" provider, since dollar-qualified expressions are assumed to point there if not qualified with a drive name:

ps c:\> $host
Name             : ConsoleHost
Version          :
InstanceId       : 9d8a29bf-3d84-4ce6-8651-e0c72afb404b
UI               : System.Management.Automation.Internal.Host.InternalHostUserInterface
CurrentCulture   : en-CA
CurrentUICulture : en-US
PrivateData      : Microsoft.PowerShell.ConsoleHost+ConsoleColorProxy

so let's give a drive name this time:

ps c:\> ${c:test.bat}
@echo hello, world

This is a lot easier to understand once you realise that the '$' prefix is a grammatical shortcut into the IContentReader/IContentWriter interfaces on every provider; Much easier than just blindly committing to memory the method to read a variable and the method to read a file (imho). Once you introduce this capability into other providers, you then have to address ye olde $env:path variable. Currently this variable is imported from the system environment. The system, aka Windows, knows nothing about powershell and its drives. When using Get-Command, the search order for discovering commands is:

Aliases, Functions, Cmdlets, Scripts, Commands located in the directories specified by the Path environment variable, External scripts.

As we can see, Commands (5) are discovered via the $env:path variable. The other items (apart from 3 - cmdlets) all live in a flat namespace, so there's no path involved there.  I'd love if somehow it were possible to add any powershell path to this variable, even if they were limited to drive-qualified paths:

ps c:\> $env:path
c:\windows; c:\windows\system32; ...; s3:\utilities; "mobile:\storage card"; ...

Perhaps this information could be persisted inside PowerShell only so when the shell is exited, the path environment variable remains unchanged when viewed from the external windows system. This might mean that PowerShell paths would have to be appended in your profile at each load, but this isn't a bad thing either, IMO. So finally, on to the crux of the matter: disambiguation of identically named Cmdlets. Ultimately I don't believe there is a magic answer. This is solved in Windows by using the path variable, and so I believe it isn't such a bad idea to solve it with a path variable in powershell too. Behold $env:cmdletpath

ps c:\> $env:cmdletpath
Microsoft.PowerShell.Core; Microsoft.PowerShell.Host; Microsoft.PowerShell.Management; Microsoft.PowerShell.Security; Microsoft.PowerShell.Utility; VMWare.Commands.Utility

It's simple. It's optional. Snap-in qualified commands would continue to work, overriding the cmdlet search path. Instead of having to alias multiple commands when using a snapin that replaces a suite of built-in cmdlets, you can just re-jig the search path. Done. It's not the answer to everything, but it sure would make life a bit easier, no?

blog comments powered by Disqus

About the author

Irish, PowerShell MVP, .NET/ASP.NET/SharePoint Developer, Budding Architect. Developer. Montrealer. Opinionated. Montreal, Quebec.

Month List

Page List