How do you write scripts?

The answer to the ‘Where do you get started?’ question

PowerShell is easy to learn. Like I said in the previous post, PowerShell is among the easiest languages to learn. Now, like mentioned in the previous post, you learnt to use the pipeline, and now, you are a one-liner star. But how do you go about writing full-blown scripts? You may have seen scripts running hundreds or thousands of lines. How do you write a complete script in PowerShell? Do you write every line of it? No?

Sitting down to actually script may seem intimidating. After years of doing it, I still sometimes feel a little overwhelmed by the enormity of some requirements. Know that no major project gets done overnight—small requirements like extracting a list of large files in a location and performing a maintenance task on them may be achievable within minutes or hours. But implementations that involve major automation do not get done in a day. As you gain experience, the time you take to complete a project get shorter, though. Remember, speed is not your goal. Writing a good script is more important.

Learn the system

This is because you must know what your framework can achieve. ‘What can Citrix XenApp do?’ for example. You need to know the different components in the system. For instance, you should know that XenApp has something called a Delivery Controller. You should know that you make management connections to the Delivery Controller, and it handles the management of the VDA servers.

How the different components of the system work is also important. Knowing this will enable to automate the process. If you do not know how the components interact, talk to that person who handles the system in the environment. They can tell you how the components work. Also, look at the output of each of the cmdlets that you are going to use. Chances are, you find ways to do something with less efforts than you imagined.

Plan your solution

Keep in mind the ultimate goal of your script. This is what you have to proceed towards. Now, start thinking of getting there. The previous step of knowing the capabilities will help you with this. Once you learn the system (or talk to a person who knows the system), you will be able to break down the activity into smaller chunks.

Next, plan how to put these together in a script. Developers out will tell you how important the flow diagram is. PowerShell scripting for infrastructure automation may not be as complex as creating an application, but the basic principles hold good. Write a basic flow. I use the comment system to do this. Here is a basic outline I wrote for a script:

# Test connectivity to a computer---custom ping-like with speed in mind
# Add database connectivity configuration

# MAJOR DEPENDENCIES
# Fetch a list of all agents from the database
# Load the Active Directory module

# MAIN BODY
# Loop through each server entry
  # Check for connectivity
  # Check if sponsor information exists in both AD and the database
  # Check if the sponsor entries match in AD and DB
  # If they do not match, update the table with information from AD
  # Check if the update was successful
  # Create a hash of all the relevant values and add it as a row to the output

You can drill down further—I typically do—but have at least a basic frame in mind before you start scripting. You may also want to consider making little notes on what tasks you want accomplished, which ones would you like to group into functions, and how the function interact with each other. This way, you avoid getting lost. If mental visualisation is not your thing, doodle. You do not have to show this to anyone. As long as you understand what you drew, you are fine.

Of course, if you are working on an entire module with tens of functions in it, you may want to take a more sophisticated approach. In a recent project, I used an Agile-like approach. Going pure Agile seemed a little too much for the project considering the requirements and that I was working on it alone at the time. But, you get the idea.

Don’t Repeat Yourself (DRY)

Write a function for every action chunk. Judgement of this varies from situation-to-situation and even person-to-person. How to do this also varies from situation to situation. Typically, write one function to perform a complete task. (And if you are making a function that outputs an object, make it in such a way that it returns an object of a single type.) For instance, if your script involves sending different kinds of email alerts, all to the same team, you may want to use a function to do this. Combine all the configuration, and pass the variables (the subject and the body of the message) to the function via a call. For example if you have a situation like this:

# Some cmdlets here

Send-MailMessage -From '[email protected]' -To '[email protected]' -SmtpServer 'smtp.domain.com' -Subject 'Subject One' -Body "Hi Team,`n`nHere is the body of the message I would like to send. This email says: Subject One."

# Some cmdlets here

Send-MailMessage -From '[email protected]' -To '[email protected]' -SmtpServer 'smtp.domain.com' -Subject 'Subject Two' -Body "Hi Team,`n`nHere is the body of the message I would like to send. This email says: Subject Two."

# Some cmdlets here

Send-MailMessage -From '[email protected]' -To '[email protected]' -SmtpServer 'smtp.domain.com' -Subject 'Subject Three' -Body "Hi Team,`n`nHere is the body of the message I would like to send. This email says: Subject Three."

# Some cmdlets here

Send-MailMessage -From '[email protected]' -To '[email protected]' -SmtpServer 'smtp.domain.com' -Subject 'Subject Four' -Body "Hi Team,`n`nHere is the body of the message I would like to send. This email says: Subject Four."

# Some cmdlets here

Send-MailMessage -From '[email protected]' -To '[email protected]' -SmtpServer 'smtp.domain.com' -Subject 'Subject Five' -Body "Hi Team,`n`nHere is the body of the message I would like to send. This email says: Subject Five."

# Some cmdlets here

You are probably better off doing this instead:

function Send-MyAlert {
    param (
        # The subject
        [Parameter(Mandatory=$true, Position=0)]
        [string]
        $Subject,

        # The body
        [Parameter(Mandatory=$true, Position=1)]
        [string]
        $Body
    )
    begin {
        $From = '[email protected]'
        $To = '[email protected]'
        $SmtpServer = 'smtp.domain.com'
    }
    process {
        Send-MailMessage -From $From -To $To -SmtpServer $SmtpServer -Subject $Subject -Body $Body
    }
}

# Some cmdlets here

Send-MyAlert 'Subject One' "Hi Team,`n`nHere is the body of the message I would like to send. This email says: Subject One."

# Some cmdlets here

Send-MyAlert 'Subject Two' "Hi Team,`n`nHere is the body of the message I would like to send. This email says: Subject Two."

# Some cmdlets here

Send-MyAlert 'Subject Three' "Hi Team,`n`nHere is the body of the message I would like to send. This email says: Subject Three."

# Some cmdlets here

Send-MyAlert 'Subject Four' "Hi Team,`n`nHere is the body of the message I would like to send. This email says: Subject Four."

# Some cmdlets here

Send-MyAlert 'Subject Five' "Hi Team,`n`nHere is the body of the message I would like to send. This email says: Subject Five."

# Some cmdlets here

This way, you are restricting configuration to a single point, and concentrating on what matters the most. Remember that automation is about avoiding repetition. If your solution does not respect this principle, your solution has failed its own purpose.

Do not flood the output with text

I see this often. Some of the scripts I see are verbose in what they do, and the command window gets flooded with text—necessary and unnecessary. Sometimes, scripter uses the window as a mural where they turn all expressive and throw in their share of design. This is a bad practice. Use Write-Verbose for irregular interactions, or interactions that the end user may not care about. This way, you display text when asked for, and not otherwise. The business that your script deals with is what matters. Output should be sensible, concise, and welcome.

Make PowerShell talk

Use the verbose output. Opposite of what the previous point says? Verbose output helps you with testing your scripts. Know what you expect each statement in your script to do. And then, use verbose logging to see whether the statement is accomplishing the intended task. Building a dynamic SQL query? Use verbose output to display the query during the script run while testing. You do not need to make everything verbose, but make the critical steps output a verbose output.

Verbose output shows you the actual flow. You can make it show variable values and what not. This will help you determine what goes on when you run the script.

But again, use Write-Verbose for it. Personally, I use the verbose output when I am testing my script. I use it during the first runs as well, to make sure the script is working as intended. After that, I do not use verbose output. But I like to know that I can call for the text when needed.

Test often

And feel free to leverage the ShouldProcess capability of cmdlets that support it. You can know if a cmdlet supports this when you run Get-Help; look for the WhatIf parameter. Test your functions. Check their output. Inspect the objects. Learn to use the debugging tools. They will go a long way in helping you spend your time scripting efficiently.

Test everything. Test your functions and scripts over and over. Test under different conditions. Use breakpoints and exploit the debug mode. Perform unit testing (test the stand-alone function), perform integration testing (test the connectivity between functions, and how and what objects get transferred), and in the end, perform system testing (test the script as a whole).

Hand-craft your workbench

Now to the enabling factors.

Use the right tools. Look for the tool that will help you the most. Use extensions. Work on customising the tool to suit your needs. Ensure that the tool allows the configuration to be portable. Save this configuration for reuse. And then, get ready to rock. Visual Studio Code works for me, but that does not mean that it has to work for you. You may have different needs. You may have different habits. Choose a tool that works best for you. Apart from reducing keystrokes, a good tool will make your workflow smoother, thereby enabling you to concentrate on what matters. Your tool should do the behind-the-scenes heavy-lifting. You should not have to fiddle around clicking all over the screen to make a git commit. You should not have to switch windows to manage your tasks.

Also, importantly, ensure that your tool is keyboard-friendly. Scripting is a keyboard activity, and you should not be moving your hand away too much. Moving your hand distracts you, disturbs your flow-of-thought, and makes scripting uncomfortable.

Communicate

My clients are comfortable with my scripts because I communicate to them what the script does and how. The transparency is important. Tell them the logic that your script follows, how it does what it does, what the dependencies are, what the potential points-of-failure are and how you have addressed them, what information your script collects, what systems it touches, how it affects the systems, what the risks are and what care you have taken to reduce adverse impact, and how the solution benefits them. Tell them how you have tested the script, and how your script fares. Give them evidence that gives them the confidence that your script will work as expected. Be honest.

Drink coffee

All right, this may not be a real thing. It may be a placebo. But how does a mug of warm beverage hurt? I am more of a tea person. And I down three mugs a day when scripting.

Remember: Every script starts with that first line. And that first line appears not by magic, but by putting your finger on the keyboard and letting the keys clack.

Want to learn PowerShell?

The award-winning book, PowerShell Core for Linux Administrators Cookbook, which I co-authored, uses the recipe-based learning approach to give you a deep understanding of PowerShell. And the best part is, the concepts discussed work across platforms!

The best new PowerShell books

powered by TinyLetter