Reusing PowerShell Code—What is Best?

Weekend Scripter

Microsoft Scripting Guy, Ed Wilson, is here. It has been really fun these past few weeks reviewing all of the scripts that were submitted for the 2011 Scripting Games. One of the questions I have seen crop up on the Official Scripting Guys Forum goes something like this, “Why did that script get five stars in the 2011 Scripting Games, and my script only got one star?” The question has also cropped up on Twitter and in emails to scripter@microsoft.com, and I would venture to say that someone pasted it to at least one billboard somewhere along Interstate 77 in downtown Charlotte, North Carolina. If you think me facetious, I beg your indulgence, because I am naturally that way; the result is unintentional, I am sure.

I actually like this question and the myriad of variations of the central theme because it hits one of my favorite topics: Windows PowerShell best practices. The questioner is actually asking why one script is better than another script. Without resorting to sophistry, the answer depends on how you define “best.” Put another way to clarify our central thesis, “What is the best script?”

I could be completely justified in saying that the best script is the one that serves your purpose—the script works as intended. That is it in a nutshell—there is no secret. Windows PowerShell is for Windows automation, it is not compiled C++, it is not used for navigation on the Space Shuttle, and no one whips out a “one liner” to control a pacemaker. It is for ad hoc management needs. Now, it is a tribute to Windows PowerShell that it is so powerful that it can do much more—and many sophisticated scripts have been and will continue to be written, many of which sport graphical interfaces written in WPF.

So what is the best script? Let’s go back to the question, “How do you define best?” To answer the question of best, you need to examine your priorities (and I am not talking about something like getting more exercise or flossing your teeth once a day)—I mean your priorities in your approach to scripting.

Suppose you are going to write a script that will run on 10,000 servers that host your company’s global Internet presence. Your design goals here would possibly include the following:

Error handling: You want the script to safely handle all exceptions.

Dependency checks : Any external dependency must be checked. This includes things like modules, .NET Framework assemblies, and specific rights.

Logging: You want to log results from error handling, dependency checks, as well as exit codes.

Modular design: This includes reusable code and single-purpose functions that return objects.

Comments: You want to consider comment-based Help, as well as other comments as required.

Readable code: Code should be readable, understandable, and not use aliases or positional parameters.

Performance: The code should use the fast code that is appropriate to the task. The script should be the best for performance and optimized as required. It might take a very long time to run against 10,000 servers. However, stability must not be compromised.

As you can see, even in this scenario, there could be conflicting “best practices.” Therefore, in deciding on the approach to use, one must weigh one design goal against another. For example, the desire for copious comments might conflict with performance. At times, too many comments can even make the code less readable. Some of the scripts that were submitted in this year’s Scripting Games actually fell into this category—it is like, “Where’s the code?” Too many dependency checks can significantly slow down the script. At times, it might be quicker to let the script fail on one system, and to log that fact, than to perform a myriad of checks to see if the script will actually run. This is where testing will come into play.

There is a myth about code reuse. At times, code reuse is not always the end goal. Most of the time, a Windows PowerShell script is a bespoke creation; and like clothes, it often is neither advantageous nor desirable to adopt a one size fits all solution. There are certainly times when it makes sense to write reusable code, but guess what? That is generally when you anticipate reusing the code. In this respect, I will admit that a few of the events were a little misleading because I would not really put the extra time into attempting to make the function reusable. I tried to model the behavior by emphasizing different things in the requirements. But in the end, some contestants still felt compelled to write 25 lines of comments and other accoutrements to support one line of Windows PowerShell code.

So what are my guidelines for creating reusable code? I will list them.

1. If I intend to use the code, I will write the code as reusable. An example of this is a function that will be something I use on a regular basis. For example, the Out-TempFile exercise from the 2011 Scripting Games is a perfect case in point. Whereas, I might very well include it in a script that I am writing (in fact, I might get the idea that I need to write to a temporary file), and as I develop the essential characteristics for the function, I decide to expand it into an advanced function because I think it is useful.

2. If I can make the modification to an existing function with minimal effort, I will write the function as reusable. At times when I am creating a function, it will be single purpose and relatively portable. In these cases, I will make a few changes to make it a reusable function.

3. If the function will require significant redesign and much effort to make it reusable, I do not perform the additional work. Remember the ultimate goal of scripting is to write a script that does something. Things that do not directly contribute to “that something” are generally not in scope for the project. If a script would take 15 minutes to write, but you spend two hours trying to make one of the functions completely portable and reusable, then you may be working at cross purposes with your ultimate goal.

4. If I know I will never need the script again, I do not bother to make the code reusable. I was at a customer site a few years ago, and they had been hit with a virus. Unfortunately, after calling support for their particular antivirus company, they found that there was no updated detection signature available—they were one of the first companies to get this virus. The company did, however get the characteristics: a particular service was installed and running, certain registry keys were created, and specific files were created in a particular folder. The company’s CIO wanted to know how many computers were affected and how many had been cleaned, and he wanted to be kept up to date on the progress of the detection and the cleaning. I wrote a quick script to perform the detection and a script to do the cleaning. These were really quick scripts that I knew would never be reused. Essentially a one-off effort, and the scripts had the added emphasis of speed of creation. Code reuse was neither required nor desired in this situation.

Now that I am part of twitter I can become better at scripting because of the audience. When I was writing my code, I took reusable as "make it into a function that accepts everything as a variable that can be changed". I remember specifically one of my scripts would not work in a function but I kept trying to get it to work. I ended up having to declare all the variables as global and make sure I nulled them out at the end of the script. Way too much work to make it reusable, and I probably was using the wrong definition of reusable in the first place.

Klaus Schulte

30 Apr 2011 11:24 PM

Hello Ed,

a wonderful intro to reusable code!

The sentence in 4: "If I know I will never need the script again ..."

is mostly the beginning of a long time or even everlasting friendship to the customer who said this!

Just one point to add:

5. lways include a generalized error reporting mechanism, if you don't want to change your script each time an error occurs!

This has to be part of reusability!

Without that, you will always have to make changes to your script later on, if any errors occur!

Inlcude it from the beginning on!

kind regards, Klaus

Klaus Schulte

30 Apr 2011 11:48 PM

P.S: I did notice, that you mentioned error handling before!

But it should still be part of the guidelines.

btw: Resuability depends on the clients your script is addressing.

Reusability "just for me" or co-workers in my department is different from

reusablilty for the enduser in a very different department.

There are totally different requirements an issues involved if it comes to a handout to the end user.