A tedious and probably totally wrong post about idiomatic approaches to PowerShell

In PowerShell, there are many ways to do stuff.

This is a good thing. It’s what made Perl so attractive to me back when I wrote in unreadable languages. There was even an acronym. TIMTOWTDI. There Is More Than One Way To Do It.

This is good. It’s a great thing.

Which is what I thought of today, when browsing around the interwebs, I stumbled*, not for the first time, over a not-that-common but still sometimes-encountered PowerShell idiom for function declaration.

$func = {
    param($input)
    write "I am a function. Your input was $input"
}

&$func

So what’s wrong with this particular idiom? Well, at a glance, nothing. It works just fine. It looks clean and tidy. It has brevity, which is oft-remarked to be the soul of wit**. The intent is quite obvious.

It’s actually quite elegant looking. Kind of beautiful, even.

Oh, sure, there’s potential to confuse a skim-reader, but there’s that potential everywhere.

To explain what’s going on here: You’re declaring a ScriptBlock, and then explicitly executing it with an & operator. It’s pretty much the implicit/explicit method of function calls, as opposed to the explicit/implicit method

Function func {  # explicitly use the Function keyword with a name and a ScriptBlock
    param($input)
    write "I am a function. Your input was $input"
}

func   # implicitly call it without &

If you wanted to be explicit/explicit, you could even do this

Function func {  # explicitly use the Function keyword with a name and a ScriptBlock
    param($input)
    write "I am a function. Your input was $input"
}

& func   # explicitly call it with &

There’s really not much I can object to, right? It’s just another idiom

Well… there is one thing.

\> Set-Location Function:
\> gci | ? { $.Name -eq "func" }
\>

OK, so where the hell is my function?

\> Set-Location Variable:
\> gci | ? {$_.Name -eq "func" }

Name Value
---- -----
func ...

\>

Oh. There it is.

It’s a variable, not a function. Of course.

Using the implicitly-declared, explicity-called function idiom breaks your ability to reflect out your functions using the built-in Function: PSDrive. And therefore breaks your ability to inspect them and meddle with them at runtime.

OK, no biggie. If you know you’re using one way or the other you can know where to look. You can get round this objection. You can meddle with your ScriptBlock Variable: declarations just like you can meddle with your Function: declarations. And who does this anyway?***

But… Another problem appears when distributing scripts, and especially when it comes to packaging up a library of scripts as a module. You’re going to have to rewrite all your function declarations – sorry, your ScriptBlock variables – as actual Function declarations to do that. Because you can’t call Export-ModuleMember on a ScriptBlock variable. If you do, you get

Export-ModuleMember : Cannot evaluate parameter 'Function' because its argument 
is specified as a script block and there is no input. 
A script block cannot be evaluated without input.

“So what?” says the idiomatic man. “I can distribute my libraries as *.ps1 files instead, and let my users dot-source them. If anything, this method has more brevity. Look”

Import-Module scriptmodule.psm1
ipmo scriptmodule.psm1
. scriptmodule.ps1

“What’s shorter? Yeah. Got you on that one, smarty-pants” (yes, you have).

However, here’s a biggie. It does kinda break your ability to correctly and consistently mock – and therefore safely test – your code using Pester.

Pester is now the default choice for automated testing of PowerShell scripts, and with the scale of PowerShell deployments getting ever more ambitious, that’s a damn good thing. My team would not have half the confidence we do in running updates across 500+ servers at a time if we didn’t unit test our code before pushing the big red button.

So what’s my point?

My point, I suppose, is this. I like the ScriptBlock method, aesthetically speaking. It looks good to me. I love the fact that we can do it. It shows off PowerShell’s inherent flexibility and it lends an expressiveness that is refreshing to see. But do I think we should be doing it on a consistent basis?

No. Probably not.

I think we should be using Function declarations as a best practice, just as script analyser says we should be using full Cmdlet calls (Invoke-RESTMethod) instead of their shorter aliased (irm) brethren.

Write however you like for your throwaway code, but remember that throwaway code doesn’t always get thrown away. Sometimes it runs for a long, long time, and sometimes it has to grow and mature to match its environment, and when it gets there, you might have to make it adhere to some particular standards and practices

Just because there is more than one way to do it, doesn’t always mean you should do it the other way.

Then again, I’m just one guy and I only set coding standards for one pretty small team, and even then they’re quite free to ignore me. I’m not the arbiter of all things. But you know, maybe bear it in mind?

 

* I really do recommend this post, by the way. I am not criticising it or the author in any way. I love the way it’s written. But it did send me off on a train of thought.
** Notwithstanding the fact it’s first uttered by a Shakespearean character whose own lack of brevity makes the sentence itself into a satire.
*** Me

 

Leave a Reply

Your email address will not be published. Required fields are marked *