PowerShell: Multi-threading, Progress Bars and GUI Input, Part 2

And we’re back! Please see the first installment to get started with building your PowerShell scripts.

So, we’ve got our text box, we’ve got data returned from that text box … now what do we do with that data? Well, to start doing something with our data, we need to first ask ourselves what we WANT to do and what data do we want to do it with? Okay, so I didn’t think that far ahead. I went and got elbow deep in a series of blog posts and didn’t think of a practical example. Give me a second…..

Okay, I’ve got it. I want to ping some machines. Yes, I KNOW that’s not very original or exciting, but it’s something we all have had to do at one time or another, and it’s actually something that you sometimes have to do against several machines. Oh, and I don’t actually use ping. I use Test-Connection. ‘Cause P-shell, yo!

When you use test-connection, you have a lot of data that can get returned, and if you don’t believe me, try this:

Test-Connection Mirazon.com -count 1 |fl *

That will list all the attribute values that are returned as a result of a single ping against Mirazon.com. Lots of info. For the purposes of our script, let’s focus on a select few of our attributes. In that screenshot above, you’ll notice that there’s a source, destination, IPV4Address, IPV6Address, Bytes and Time(ms). Well, you’d think that those would correlate directly to attributes of the same name, right? Normally you’d be right. In this case, though, it doesn’t always match up. The attribute for Source is actually PSComputerName, Destination is Address, Bytes is ReplySize, Time(ms) is actually ResponseTime. Why did Microsoft do this? I don’t know. If by some happenstance the individual from Microsoft that did this reads this blog post, I’d appreciate it if you’d reach out and explain yourself.

We’ve established which attributes are pertinent, however there’s one more feature of Test-Connection that I want to point out. That’s Test-Connection -quiet:

This is my favorite part of this cmdlet because it returns a True / False value that can be used for all sorts of things. Just a single example of how this can be used for awesomeness is listed in the cmdlet’s own help file:

Pretty cool, huh? It’s this part of Test-Connection that we’ll be taking advantage of in our script.

Now, let’s clear the air. I know for a FACT that someone out there, probably someone that is so far into scripting and programming that they write love letters in binary, is going to say, “This isn’t true multi-threading,” or “That’s just parallel processing” and there’s going to be all kinds of reasons behind why that is, and that person will get their million internet points for pointing it out. Okay — you’re right. It probably isn’t true multi-threading. Heck, it might not even truly qualify as parallel processing. I call it multi-threading because it allows me to do a bunch of things at the same time. So maybe we can agree and call it “PowerShell assisted Multi-Tasking” or something.

Let’s start writing a script, shall we? We’ve already got the function for creating the text box for input, let’s instantiate a variable to hold that data and call the function to bring up the text box for input:

Don’t try copying that. It’s a screenshot. I’ll type out the whole script at the end, I promise, but for right now, screenshots look better for the formatting.

In that one line, I created a variable called $serverlist that is an array, and called the TextBox function with its two passed parameters, which if you recall from part one are the Label and LabelText for our input box. Since our TextBox function calls the TextToArray function when you click ok, it dumps to the array and then returns that array to our $serverlist variable.

Now it’s time for a bit of a code dump. Don’t worry, I’ll walk through it with you, and it’s commented, so you won’t get too lost.

#Setting Initialization Script variable – True.

$initscriptTrue = {Function PINGServerTrue {

    param(

    [Parameter(Mandatory=$true,valuefrompipeline=$true)]

    [string]$comp

       )

    While ((Test-Connection -ComputerName $comp -Quiet) -eq $True)

        {

        #Blank Space to kill time during while loop

        }

                    }

                    }

#Setting Initialization Script variable – False.

$initscriptFalse = {Function PINGServerFalse {

    param(

    [Parameter(Mandatory=$true,valuefrompipeline=$true)]

    [string]$comp

       )

    While ((Test-Connection -ComputerName $comp -Quiet) -eq $False)

        {

        #Blank Space to kill time during while loop

        }

                    }

                    }

We’re going to be using jobs to thread this and the way I’ve always done this is through writing a function that sits in a variable that I can pass to the Start-Job cmdlet. Start-Job is a fun little cmdlet that lets us run background jobs. Instead of the traditional linear way of running a script where it goes one line at a time, one item at a time, Start-Job allows you to spin up code as background jobs that will run while your script does other things.

Here’s where we call the two functions:

start-job -Name $srv -ScriptBlock {param([string]$srv)PINGServerTrue $srv} -InitializationScript $initscriptTrue -ArgumentList $srv |Out-Null

And

start-job -Name $srv -ScriptBlock {param([string]$srv)PINGServerFalse $srv} -InitializationScript $initscriptFalse -ArgumentList $srv |Out-Null

There’s a lot more that I surround it with, but I wanted to discuss this first before we go into the while loop or the progress bar to show us where we are in our script. Keen-eyed observers will notice that I don’t have a $srv variable in anything that I’ve shown so far, all I’ve got is a $serverlist variable. That’s because I’ve got each of these lines nested in a foreach loop:

foreach ($serv in $serverlist)

So I want it to start a job, named $srv (the server name, for easy identification if I need to look at the running jobs for some reason), I want it to run a scriptblock which is essentially my function of PINGServerTrue of PINGServerFalse, feeding that server name into the function. Next comes the initialization script. You might be wondering what this is for. Well, remember what I said about variable scoping earlier? Well, that comes into play here. I have my two PING functions set up as being inside variables. If I don’t feed it the variable as it’s initialization script, it doesn’t know that the underlying function exists and therefore won’t be able to run it. Could I just make them global functions and leave out the initialization script? Probably. I’m sure that it can be done. This is just how I’ve always done it, and I haven’t had a need to further streamline or find out if it can be done, so here’s your chance. Play with it and let me know. I’m always open to learn new ways to do things, and with PowerShell, there’s always a new way to do something.

This seems like a good stopping point for today. Next post, I want to spend some time discussing progress bars and then how we’re going to put all of this together into something that’s functional. Until next time, folks!

If you have questions about how to use PowerShell to get more out of your environment, send us an email or give us a call at 502-240-0404!