Thursday, March 12, 2009

Smart function names in PowerShell

As you all know, official syntax of PowerShell is <verb>-<noun>. For example Write-Script.

However this is not always the case – there are many functions that doesn’t follow this naming convention and reason is usually that function name is already taken or author wants to make obvious to which technology is function related.

You get afterwards function names like SCCMImport-Server or SCCM-ImportServer.

I prefer to use “functionality scopes” (my term, sorry, couldn’t come with something better ;)).

Consider example where I want to create new object in Citrix. Logically I would like to call function New-Object, however that was already taken by PowerShell team ;)

Typically I will create function with name Citrix-NewObject or better New-CitrixObject.

What I can do instead is that I can prefix verb-noun:

Citrix:New-Object

It is much easier to use and read, also allows you to use simple dumps (Dir Function:Citrix:* to get all Citrix-related functions).

17 comments:

Poshoholic said...

Martin,

I have to be frank with you. Loud alarms ring in my head when I see things like this. It is a *very bad* idea to name your functions in this manner. Why? Because:

1. Colon is a delimiter commonly used in paths, so you're only asking for trouble by using a colon character in a command name.
2. You're functions will likely not be properly syntax highlighted nor will Intellisense work for them in the majority of PowerShell editors out there because they aren't designed to support very unusual names like this.
3. You're breaking the PowerShell verb-noun syntax. If PowerShell supports retrieving functions by verb or noun in the future, your functions will not be retrievable using the verb because searching for functions with the verb Get will not discover Citrix:Get unless the developer coding such support (which could be in a cmdlet) does custom work for this.
4. It's absolutely inconsistent with PowerShell command naming standards.

I strongly urge you to reconsider other approaches. If you want simple function discovery of Citrix related functions you write, name them verb-Citrixnoun and then write a function called Get-CitrixFunction (alias ctxfx) that does this: gci Function:*-Citrix*. Frankly that's pretty easy to type anyway, so there really is no need for obscure function names here.

Did I mention that this is a really bad idea? Any funky naming schemes like this result in a lot of work in the community to support those naming schemes, and therefore we should follow the consistent naming standards already in place. Verb-PrefixedNoun. That's it. No need to reinvent the wheel.

Kirk out.

x0n said...

As well as backing 110% what Kirk, said, I'd like to reinforce it.

Don't do it.

The most acceptable way is to prefix your nouns with "Citrix." Any other way is just weird and will cause many many problems down the line.

Martin Zugec said...

Hi guys,

I was also very skeptical when I first thought about this approach. My problem was that different people were using different naming conventions when they encountered some technology-related functions... Usually it was Citrix-ImportMachine (which is not according to naming convention); or even worse, CitrixImportMachine. Another issue I run into was conflicting script names - in one environment I noticed that 3 different scripts were using New-Server as function name ;)

First time I thought about this was one year ago - and it took me 6 months till I decided I will give it a try.

Currently I got this naming convention applied to +- 5000 lines of code (framework, and one of advantages of frameworks is that it is much easier to discover bugs and different problems). Everything works exactly the way it should, even more complicated stuff like scoping (Global:Citrix:Get-Server), so far I haven't run into single issue.

As I said, I was not really convinced it is good idea, but I slowly changed my mind. Applying such prefixes is natural for people - as I said, since introduction of them I noticed there were no more problems with incorrect function names. I like to think about it as "extension" of naming convention to help out with situation where more and more people are writing functions with same name and simply listing Function: provider doesn't help too much. I can imagine getting this naming convention expansion from Microsoft itself - ideally in way where you could specify prefix for your module...

PowerShell is new and shiny - and huge advantage of PS (for me) that it breaks common habits and shows us that "It can be done better", so I don't see reason why we shouldn't think about way how to improve it even more.

I am trying to get some feedback from Jeffrey right now, I would really like to know his opinion. Working in environment where people are not PowerShell professionals (I am consultant), you can see where they run into problems with PowerShell and naming convention is one of them.

Martin Zugec said...

Hi Kirk,

I just noticed you have blog post about same issue as we are discussing (for those who doesn't read excellent Kirk's blog, check out http://poshoholic.com/2007/09/20/the-trouble-with-the-tribbles/)...

And I don't see any different compared to my scenario except the fact that some naming convention standart should be provided by Microsoft itself (which I fully agree and if there would be any such option, I would be glad to immediately throw out my approach and follow official way)...

Your proposed solution is however fully functional even now:
PS Variable:\> function StarTrek::Get-Tribble {Write-Host "XXX"}
PS Variable:\> StarTrek::Get-Tribble
XXX

I was also thinking about "Using" approach and I would love to see that, however that can be achieved also - all you will need to do is just create private functions for each variable based on preffix (so if "namespace" is Citrix, it will create private functions based on all functions that starts with Citrix:*)

Regarding colon as delimiter I choosed, reason is pretty simple. I tried few different delimiters and most people naturally preffered colon. Colon is not used only in path, however in PowerShell also as provider (Function:, Variable:...) and people naturally like to think they are using function X-Y from container of Citrix functions (therefore Citrix:X-Y).

My main reason however was little bit different - I created framework that contains multiple modules (for SCCM, Citrix, Altiris etc...) and you use these functions in same environment.

I will try to summarize pro\cons and different approaches in separate blog post soon...

x0n said...

You are also severely inhibiting the usefulness of tab completion. When anyone wants to 'get' something in powershell, the standard thing to do is to type "Get-" and hit .tab. Your naming scheme breaks this entirely. The general convention with functions is to prefix the noun. This way, all related functions are grouped together in tab completion/intellisense.

PowerShell has been around 3 years nearly now, and we've been using verb-(optional prefix)noun all this time without (much) complaint. With v2 and the advent of modules, you will find other ways to isolate groups of functions. But for v1, I beg you to reconsider.

The Verbs should be verbs, not weird compound tokens. PowerShell's infrastructure and entire discoverability model is based around verbs and nouns. Don't run against that in the face of everything else. Yes, there are other vendors coming up with wacky naming systems for the cmdlets too at the moment, but we are tracking them down and trying to show them the light also.

I'm not sure what you're trying to say about colons. Paths and providers are not two separate notions - they are one and the same. The colon is special-cased by the parser as a path delimiter. It is not meant for this purpose.

All that said, it is good to see someone like yourself so interested in powershell. Keep up the good work.

-Oisin

http://www.nivot.org/

Martin Zugec said...

Hi x0n,

first of all, don't get me wrong, I am not stubborn and I am not saying that my solution is "a must have" and ideal solution that will save the world ;)

I will write another post where I will write in detail what&why, together we could try to summarize advantages and disadvantages...

My solution doesn't fully break tab functionality - if I want to "Get something related to Citrix", all I need to do is type Citrix:Get and hit tab (as opposed to do Get-Citrix and hit tab). What is true however is that *x: + tab doesn't work (logically).

I would love to see namespaces from Microsoft though - with "Using" functionality or ability to load functions from specific module with namespace prefix (Load-Module XXX -Namespace Citrix could prefix all functions from XXX module with Citrix::* or Citrix:*).

x0n said...

Hi Martin,

If I could be so direct, I think you're missing the point *entirely*. You know to type "Citrix:Get" because you wrote it.

The raison-d'etre of the verb/noun pairing is so people - who do NOT know your module/function suite as well (or at all) - can DISCOVER commands. There is a recommended FIXED list of verbs. People use this fixed list to discover commands. The noun list is open; Extend this instead.

Cheers

- Oisin

Martin Zugec said...

I understand tab completition and how it should work. However you are talking about interactive session or simple sessions, while I talk about frameworks (where you could easily get 100+ Get-* commands).

For me tab completition is not important - what is more important however is ability to see all functions related to one of technologies. This is due to fact that framework I am using is designed to be used by multiple consultants at once (each with different specialization)...

x0n said...

Btw,

PowerShell V2 modules provide namespaces. It's as easy as renaming your .ps1 file to a .psm1 file.

You then have a choice of:

ps> import-module citrix
ps> citrix\get-server

or

ps> import-module citrix -prefix citrix
ps> get-citrixserver

Cheers

-Oisin

Martin Zugec said...

I know :( However because I am focused at enterprise automation, for huge enterprises it really takes 1-2 years to adopt new technologies, so PS v2 features are really interesting, however I cannot afford to use them for production systems :(

I am however thinking about changing my "scopes" delimiter from ":" to "\"

x0n said...
This comment has been removed by the author.
x0n said...

(edited - accidentally posted half completed post)

Again, "\" is already used as a path delimiter. Even if you manage to shoehorn that in, you'll find it will probably need to be escaped with a backtick.

I've said all I can really say on this. Noone can stop you inventing your own scheme. I understand that you're trying to overcome a limitation of v1 and are just throwing out ideas. You blogged it, inviting comments. We commented.

Martin Zugec said...

And comments were really appriciated :)

I probably didn't get right what you wrote before:
"ps> import-module citrix
ps> citrix\get-server"

I thought that "\" is used here to prefix function loaded from module, isn't it? And that you were talking about way how this is handled in PowerShell v2.

You are right, I really need to solve some issues that came with PS v1 and I cannot afford to wait for official solution (will write about this later on - I am designing frameworks and there you need decentralized developement with Subversion, where you experience completely different issues than with centralized development, for example need to give different developers some "ranges" for naming convention), however I would like to keep it as compatible with upcoming PS versions as possible, therefore it is very important for me to get information like "If we will decide one day that we will implement prefixing, it will be done like prefix\verb-noun"...

Reminds me of official post at PowerShell blog about reservations - for long term development or frameworks such reservations are extremely important :(

x0n said...

Hi Martin,

If you want to stick with MSFT's plans in the future, then optional prefixing *is* implemented for functions imported within a psm1 module in v2. there is a difference between my two examples:

ps> citrix\get-server

In this case, the citrix\ is the name of the script module (or binary snapin).

Now, look at this:

ps> import-module citrix -prefix ctx
ps> Get-CtxServer

This is part of v2 *now*. You may import a batch of functions (or binary cmdlets) from a psm1 file (or a DLL) and have powershell prefix the NOUN with a sequence of letters of YOUR choice. This allows easier disambiguation.

So, if you want to follow FUTURE plans, use the latter scheme.

-Oisin

x0n said...

sorry, didn't answer one of your questions:

ps> citrix\get-server

This is a v1 and v2 example. The "Citrix" in the case is the name of the binary snapin that contains the cmdlet get-server. This is a valid syntax in v1. The prefix here is NOT part of the cmdlet's name. If you look at the cmdlets in v1 now:

PS> get-command get-childitem | select pssnapin

PSSnapIn
--------
Microsoft.PowerShell.Management

This is the prefix:

ps> microsoft.powershell.management\get-childitem

this is how you differentiate between cmdlets with the same name in v1. In v2, "snapin" is equivalent to module, except in v2 we can use this also for functions.

Unfortunately, there is no way for you to leverage this in v1.0 for functions.

- Oisin

huddledmasses.org said...

Why hasn't anyone mentioned that there's actually a spec for this stuff? The Microsoft command line standard is pretty clear ;)

But aside from that, I have to agree with x0n, if you're trying to write script functions in PowerShell 1 and want them to work in v2, you should stick to the Verb-PrefixNoun format which the majority of developers are using when they can't be convinced to just allow the users to specify the prefix (since that only works in v2).

Some companies (including Citrix) are doing strange awful weird things, but as you can see, they're being strongly criticized by influential bloggers, and my guess is, even they will probably relent.

Martin Zugec said...

Hi guys,

@x0n: yes, I understand, however that syntax (namespace\verb-noun) works also in PowerShell 1, which is what I wanted to know:
Function Citrix\Logoff-Users {}

@huddledmasses: thanks, that is perfect link :)

I don't try to write functions for PSv1 that will work in PSv2 - what I am trying to achieve is that I need to use namespaces (will post about reasons later today) and I would like to have PSv1 functions that will "mimic" behavior of PSv2...

As I mentioned before, today I would like to write follow up post regarding this topic.