Wednesday, February 6, 2013

Sustainable Powershell - Cmdlets

Since we're automating work, and (currently) a lot of the work involves Windows ecosystems, the natural tool of choice is Powershell. Let's look at some general guidelines for writing sustainable Powershell cmdlets.

For the purpose of this post, I'll use functions and cmdlets interchangeably as they can both be considered atomic work units.

Powershell Cmdlet Guidelines

Cmdlets shall:

Use the Verb-Noun Naming Scheme

Using this scheme forces you to think more about what your functions/cmdlets should be doing. It's the decided 'best practice' from Microsoft and when you import a module that violates this scheme, you get a nice nagging message from Powershell telling you exactly that.

Protip: Not that you should use it, but you can pass the -DisableNameChecking parameter to Import-Module to suppress the warning. You should know, however, that if you need to suppress the warning, you're probably developing your cmdlets the wrong way.

Use Camel Case for Capitalization

For instance, Get-DetailsFromReallyLongName.

The corollary to this is use descriptive names. The year is 2013. We have plenty of disk to store code with long names. Use it.

Cmdlets Do Not Hold State

 Cmdlets do stuff, they do not hold things. If you're familiar with RESTful interfaces and/or stateless architecture, move to the next point because you already understand.

For the rest of us: A cmdlet should not hold onto a value. That is, it should not store something in a global/environment variable. It can check some environment variables if it needs to make a decision, but it should not communicate with the user/other cmdlets via manipulating those variables.

If you need data, put it in/get it from a data source, such as a database, the Windows registry, or even a flat text file. Don't store it in memory (e.g. a variable) because you'll create problems for yourself that distract you from the real work.

Use "Approved" Verbs

Personally, I'm a little bitter that the creators of Powershell drew a line around what we can and cannot use when writing our own modules but that doesn't change the fact that we have to deal with it. And besides, it does help to keep you in the right mindset when writing functions/cmdlets.

When writing modules, your functions need to start with 'approved' verbs. To get a list of those verbs, use the command Get-Verb or see Microsoft's writeup.

Do One Thing. Do It Well.

See also the Unix Philosophy of software development. Forty years of tireless development and crushing testing environments have made this philosophy even more relevant.

Use Command-Line Parameters

When thinking of how to pass information to your cmdlet, think in the same way you write your cmdlets/functions: simple and atomic. Don't pass an object representing information to your cmdlet (because that breeds complexity, which leaves you prone to errors). Instead, simply pass the information.

For example, if a cmdlet needs information about a server, don't create an object just to represent that server. Instead, have your cmdlet accept the text version of that information.

Don't Do This
Create-NewServer -serverDetails complicatedObject 
Where complicatedObject is an object or a hash with values describing the server.

Instead, Do This
Create-NewServer -ServerName worker01 -IP 192.168.10.1 -Netmask 255.255.255.0

Writing your function will take more time and the incantations will be more verbose. B-U-T in the future when (not 'if', but 'when') you need to update that function, making the required changes will be so much easier than updating code that expects more complex input.

In summary, this is the state we're in today. The environment will change and constraints will improve but, in the meanwhile, following these guidelines will reduce (not eliminate!) tomorrow's maintenance cost of the software you write today.