Functions, the Right Way

This chapter is essentially meant to be a warp-speed review of the material we presented in the core narrative of Learn PowerShell Toolmaking in a Month of Lunches (and its successor, PowerShell Scripting in a Month of Lunches). This material is, for us, “fundamental” in nature, meaning it remains essentially unchanged from version to version of PowerShell. Consider this chapter a kind of “pre-req check;” if you can blast through this, nodding all the while and going, “yup,” then you’re good to skip to the next Part of this book. If you run across something where you’re like, “Wait, what?” then a review of those foundational, prerequisite books might be in order, along with a thorough reading of this Part of this book.

Tool Design

We strongly advocate that you always begin building your tools by first designing them. What inputs will they require? What logical decisions will they have to make? What information will they output? What inputs might they consume from other tools, and how might their output be consumed? We try to answer all of these questions - often in writing! - upfront. Doing so helps us think through how our tool will be used, by different people at different times, and to make good decisions about how to build the tool when it comes time to code.

Start with a Command

Once we know what the tool’s going to do, we begin a console-based (never in a script editor) process of discovery and prototyping. Or, in plain English, we figure out the commands we’re going to need to run, figure out how to run them correctly, and, figure out what they produce and how we’re going to consume it. This isn’t a lightweight step - it can often be time-consuming, and it’s where all of your experimentation can occur.

A user in PowerShell.org’s forums once posted a request for help with the following:

And yup - that’s what “Start with a Command” means. We’d probably start by planning that out - inputs are clearly some kind of DFS root name or server name and an output path for the reports to be written. Then the discovery process would begin: how can PowerShell connect to a DFS root? How can it enumerate targets? How can it resolve the target physical location and query NTFS permissions? Good ol’ Google, and experience, would be our main tool here, and we wouldn’t go an inch further until we had a text file full of answers, sample commands, and notes.

Build a Basic Function and Module

With all the functional bits in hand, we begin building tools. We almost always start with a basic function (no [CmdletBinding()] attribute) located in a script module. Why a script module? It’s the end goal for us, and it’s easier to test. We’d fill in our parameters, and start adding the functional bits to the function itself. We tend to add things in stages. So, taking that DFS example, we’d first write a function that simply connected to a DFS root and spewed out its targets. Once that was working, we’d add the bit for enumerating the targets’ physical locations. Then we’d add permission querying… and so on, and so on, until we were done. None of that along-the-way output would be pretty - it’d just be verifying that our code was working.

Adding CmdletBinding and Parameterizing

We’d then professional-ize the function, adding [CmdletBinding()] to enable the common parameters. If we’d hard-coded any changeable values (we do that sometimes, during development), we’d move those into the Param() block. We’d also dress up our parameters, specifying data [types], mandatory-ness, pipeline input, validation attributes, and so on. We’d obviously re-test.

Emitting Objects as Output

Next, we work on cleaning up our output. We remove any “development” output created by Write-Output or Write-Host (yeah, it happens when you’re hacking away). Our function’s only output would be an object, and in the DFS example, it’d probably include stuff like the DFS root name, target, physical location, and a “child object” with permissions.

Using Verbose, Warning, and Informational Output

If we hadn’t already done so, we’d take the time to add Write-Verbose calls to our function so that we could track its progress. We tend to do that habitually as we write, almost instead of comments a lot of the time, but we have built that up as a habit. We’d add warning output as needed, and potentially add Write-Information calls if we wanted to create structured, queryable “sidebar” output.

Comment-Based Help

We’d definitely “dress up” our code using comment-based help if not full help (we cover that later in the book). We’d make sure to provide usage examples, documentation for each parameter, and a pretty detailed description of what the tool did.

Handling Errors

Finally, and again if we hadn’t habitually done so already, we’d anticipate errors and try to handle them gracefully. “Permission Denied” querying permissions on a file? Handled - perhaps outputting an object, for that file, indicating the error.

Are You Ready

That’s our process. The entire way through, we make sure we’re conforming as much as possible to PowerShell standards. Input via parameters only; output only to the pipeline, and only as objects. Standardized naming, including Verb-Noun naming for the function, and parameter names that reflect existing patterns in native PowerShell commands. We try to get our command to look and feel as much like a “real” PowerShell command as possible, and we do that by carefully observing what “real” PowerShell commands do.

Ok, if you’ve gotten this far and you’re still thinking, “Yup, got all that and good to go,” then you’re… well, you’re good to go. Proceed.