Pages

Tuesday, March 11, 2014

Get-Member Methods, Properties, and Garbage

Get-Member. As you may or may not know (probably not if you are reading this blog), Powershell is an object-oriented language. In this sense, we work with cmdlets, which contains objects, wether it be a method, or a property, or a set of properties. Since here on Powershell Pancakes I only believe in the lightest of rubbish and nonsense, I have decided to go over this very briefly, and how it should make your life immensely easier (theoretically).

Every cmdlet in Powershell has a vast group of members associated with it, a lot of these are standard across all objects, especially when it concerns strings. For the experienced, usually we are aware of how to compare strings and the various operators associated with doing such tasks. But for objects that contain useful stuff, such as Get-Process, any of the WMI classes, objects returned from Active Directory - how would you know, exactly what kind of properties and methods you can perform related to that object? Unless you are like a close friend of mine, who attempted to memorize a book of part numbers for ball bearings (he failed, by the way), you wouldn't possibly be able to recollect everything about every object. Seldom do we write scripts each and every day that touch on all of our knowledge of everything we know about Powershell.

So let's get down to it, then;

Pancake:
Get-Process | gm

Two things to note - GM is an alias for Get-Member (who would have guessed?), and you need to pipe Get-Member after a cmdlet to retrieve it's properties and methods. Some cmdlets operate with a -Properties switch, which you will usually find on cmdlets from the VIM modules or from the Active Directory modules, as well as others for Exchange and what not. The ISE will help you out in determining what switches are available to you in each cmdlet. Also your trusty Clippy equivalent is Get-Help

For our Get-Process example above, it returned quite a large number of members. But how do we actually use these? The syntax below is incorrect;

Get-Process | Get-Member mainmodule

With that, you are just going to retrieve what that member actually is, not what it contains. As you see, MainModule is a MemberType that is a Property. To utilize this, try the below;

Get-Process | select MainModule

Now we have just listed the executables associated with a selection of processes. Let's try another one, referring to our Get-Process | gm list of members.

Get-Process | select processname,mainmodule

Aha! Now we are getting somewhere, arn't we? It should be becoming apparant, that to select the Property of an object, you need to pipe | Select Property1,Property2,... and so on. As many properties as you want. In the above example, we found out the process name as listed in the services mmc.

Ah yes but wait, when I ran Get-Process | Get-Member, I saw a lot of other rubbish as well, like - a method?! Yes. A member does not mean it is a Property, a member also denotes Methods, which can be used to work with objects in that cmdlet. Let's look at the rather conspicious names of some of the methods associated with Get-Process. We have, Kill, Refresh, Start, Close, and a bunch of other ones I don't really know how to use. But you use them all in essentially the same way. Let's give you an example, using Kill.

Get-Process | Where {$_.ProcessName -like "*powershell*"} | kill

Oh bugger, you just closed your Powershell window. But do you see? Using the Where {$_. } filter, we were able to select a process based on it's ProcessName. You can use any of the properties available to you to search for a process, and you can find (here it comes) all of those properties available to you by using Get-Member. Though you can use it in other ways too, you don't need to just kill things. Maybe I just want to get the process ID for anything with a process name of Powershell;

Get-Process | Where {$_.ProcessName -like "*Powershell*"} | Select ID

Hopefully this is rather apparant now, on how to better utilize different membertypes.

Saturday, February 15, 2014

Some useful notes

I've come accross a few oddities recently on jobs, passing variables to them, along with Invoke-Expression. As well as a new tip on throttling your jobs.

I was unsatisfied that my jobs were being removed as a form of throttle control. If we recall from an earlier blog post I had done here, we were looking at the following bit of code to control how many jobs we were running at the same time;

DO { 
Remove-Job -State Completed
} until (@(Get-Job).Count -le 25)

This of course works, however if you are running 50 jobs, when you do Get-Job after they have all kicked off, you're going to be left with information on only 25 jobs. Fortunately, this is as easy to fix as a hot pocket. All we need to do is change it to the following;

DO { 
Start-Sleep -Seconds 1
} until (@(Get-Job -State Running).Count -le 25)

This lets us halt the creation of new jobs as long as the running job count is 25. This is a much better solution, as we can still do Receive-Job on all of our prior jobs, and none of them get removed from our console.

Another oddity I came across was variables taking the place of other variables in -ArgumentList. There is no easy way to explain it, however I noticed my variables in my jobs were using other variables, or submitting a null value. After using Write-Host way too many times, I found that some of my variables were not being passed correctly to my job, or one variable, was showing the value of another variable! How odd is that? It came down to passing variables that didn't exist in the job, or missing a variable that you have forgotten to pass. In this case things get all screwey, and your variables are either blank, or using values from other variables, depending on their placement in -ArgumentList. If in doubt, using Write-Host and then using Receive-Job to see what values your variables have, is a good way to troubleshoot and issue like this.

I found this quite odd and this is the first time I've run into it.

Sunday, January 26, 2014

Creating output with objects

Reporting, ah yes. I know I covered this earlier, and in great detail, but actually I only detailed one real method of outputting useful reports. I want to cover something else, kind of like creating a here statement, except with a few special abilities. Your boss would never know if you did any work if it wasn't for the ability to run reports on things. So in our endless pursuit of trying to make scripts that output simpler, better, more attractive reports - this battle is endless.

So let's get right down to it. We've already got the points of turning objects into variables, and in general I think we are a little over the 'what does the $ mean?' phase. Basically what we are doing is using New-Object, to create a new Powershell Object (PSObject), and then we are going to add members to that object. Kind of like building your own object with properties, except you define the properties and what is contained in those fields. We will use the object we create to output to CSV.

Pancake:
$TestObj = New-Object PSObject

Alright, now we have an object, and empty one, but still, we have an object. We need to put stuff into this object. So what do we do? Well, let's use my favorite command, Get-WMIObject Win32_BIOS. I bet you didn't see that coming. So let's use our common need, loop through a file of computers, get the BIOS info, put it into a nice looking spreadsheet. What we have to do to accomplish this is add members to the object. As an example, if you type out Get-WMIObject Win32_BIOS, you are going to get a bunch of values associated with this object. In your prior experience, you can use the values of this object to put it into some form of output, or grab just the particular part that you need. We are doing the same thing with our very own object, except we determine what fields are in the object, and what are in those fields. Usually gathered from other sources. This may make it a bit easier to think about. So let's add a member to our virgin object.

$TestObj = New-Object PSOBject
$TestObj | Add-Member -MemberType NoteProperty -Name "Name" -Value "SmurfCakes"
$TestObj | Add-Member -MemberType NoteProperty -Name "Pancake Flavor" -Value "Blueberries"
$TestObj

So now we have two fields, populated with our values, which I have defined. They are static values though. Of course, we can put in variables read from a whole list of pancake flavors (or serial numbers..whatever). In this case, the name of the Field is "Name", and "Pancake Flavor", with the values SmurfCakes for a name, and Blueberries for the flavor profile. -MemberType defines exactly what you are adding, in this case we are adding a NoteProperty. There are many types of members, you can specify objects as Methods, Properties, ScriptMethods, etc. However for this case, we are creating a NoteProperty. In this instance, the NoteProperty -Name "" is the name of our column, while the Value is what goes in that column.

Let's get some information from the BIOS and make some nice output.
$GetBIOS = Get-WmiObject Win32_BIOS

#Create your object
$BIOSInfo = New-Object PSObject

#Get your values
$BIOSManufacturer = $GetBIOS.Manufacturer
$BIOSSerial = $GetBIOS.SerialNumber
$BIOSVersion = $GetBIOS.SMBIOSBIOSVersion

#Add your properties
$BIOSInfo | Add-Member -MemberType NoteProperty -Name "Manufacturer" -Value "$BIOSManufacturer" | Select -ExpandProperty Manufacturer
$BIOSInfo | Add-Member -MemberType NoteProperty -Name "Serial Number" -Value "$BIOSSerial" | Select -ExpandProperty SerialNumber
$BIOSInfo | Add-Member -MemberType NoteProperty -Name "SMB Version" -Value "$BIOSVersion" | Select -ExpandProperty SMBIOSBIOSVersion
#Export
$BIOSInfo | Export-CSV C:\Working\BIOSOutput.csv -NoTypeInformation
Nothing too crazy here, we are getting our BIOS info from $GetBIOS, and then we are selecting the properties we want and putting them into variables, and then we are adding them to our $BIOSInfo object. Finally after all of that, we do an Export-CSV to get our nice, pretty output. Note that this example was built with the purpose of leveraging this capacity, usually in your scripts you won't need to select all your variables like we had to do above, since you are probably reading them from a file, combined with some properties that you will need to select.

Of course, we could simply loop this script over a ton of machines, and add our computername to identify each one in the table.
$ComputerList = Get-Content C:\Working\Computerlist.txt

foreach ($Computer in $ComputerList)
{
$GetBIOS = Get-WmiObject Win32_BIOS

#Create your object
$BIOSInfo = New-Object PSObject

#Get your values
$BIOSManufacturer = $GetBIOS.Manufacturer
$BIOSSerial = $GetBIOS.SerialNumber
$BIOSVersion = $GetBIOS.SMBIOSBIOSVersion

#Add your properties
$BIOSInfo | Add-Member -MemberType NoteProperty -Name "Computer Name" -Value "$Computer"
$BIOSInfo | Add-Member -MemberType NoteProperty -Name "Manufacturer" -Value "$BIOSManufacturer" | Select -ExpandProperty Manufacturer
$BIOSInfo | Add-Member -MemberType NoteProperty -Name "Serial Number" -Value "$BIOSSerial" | Select -ExpandProperty SerialNumber
$BIOSInfo | Add-Member -MemberType NoteProperty -Name "SMB Version" -Value "$BIOSVersion" | Select -ExpandProperty SMBIOSBIOSVersion
#Export
$BIOSInfo | Export-CSV C:\Working\BIOSOutput.csv -NoClobber -NoTypeInformation
}
If you noticed we don't need to pipe a Select -ExpandProperty for the value $Computer, that is because it is already and isolated value and not an object container on its own.

I think that is about all for writing your own custom objects to CSV and such. A fairly easy and super useful tool in your PowershellPancake kitchen.


Monday, December 9, 2013

Looping, making your scripts useful

Ah, looping. You either think this is the easiest thing on earth or, you told your boss something like this about 20 minutes ago "Yeah, we can get a script to do that, like, maybe through a list or something." Luckily creating loops in Powershell is very simple, with a few ways you can do it, but they all essentially do the same thing. Which is stuff. Repeatedley. If you read any of my previous articles, you might have noticed the one about Do and While loops. This is actually a loop as well, though not necessarily in the typical sense that most people use them for.

I'm feeling less verbose than usual today, so let's get this party started.

Pancake:
foreach ($item in $var)
    {
    #Process stuff
    }

So there we go. That's your first loop. For most people, you're going to want to be reading stuff from a list and performing some tasks based off of that. So if we wanted to grab stuff from a .txt file, then we could make a script similiar to the one below.

$ComputerList = Get-Content "C:\Working\computerlist.txt"

foreach ($Computer in $ComputerList)
    {
    Write-Host $Computer
    }

So what happened here? First we used Get-Content. Get-Content is used for many things, but usually it is for opening the content of a file. In this case we are using a list of computers. When you create a list in a text file, each line is a new object, for as many lines as there are. Do note if you have empty lines entered (let's say perhaps with nothing but spaces) then you will get some garbage in your script. As far as Get-Content is concerned, those white spaces are indeed content.

Once we got the content of that text file inside of our $ComputerList variable, we created a variable foreach item in that list. Starting from top to bottom, one line at a time. We can name this variable whatever we want, in my example, we named it $Computer. Because that just makes sense. You can name it $Pancakes (preferred), $Pretzels, whatever you want really. After that we open a scriptblock { }, and inside of that script block we can do whatever our hearts desire. You can nest as many foreach loops as you can logically stand (I get really confused after I start heavy-nesting), use if/else statements, anything you want essentially. Just think of it as another little script, except only for that item that you specified. It will run through the scriptblock, and then do it again for the next item in your list, until your list is exhausted. Once your list is exhausted, it will exit the loop and continue processing the rest of the script, if you have anything left to process, that is.

This is it, a short simple sweet (no pun intended) article. Later I will talk about other uses foreach loops, including foreach-object, reading from an array, building arrays, and so much more fun stuff!



Sunday, November 24, 2013

Creating output to make your boss proud

Yes, it's possible. And easy. And annoying. Bosses love reports, I love to get reports. But I also hate to write reports, because usually we just look at them and say, "Yes! this looks like progress. Make me another next week". Preferrabley in those see-through sheets they used to put on the projectors in the 90's so we can compare one on top of the other. Yes that would be fantastic.

However outputting in Powershell is probably something you use on a daily basis when you are writing your scripts. Most commonly, we are using Write-Host to output directly to the console. At least that's what I do - I guess i'm weird like that. When I run a script in production I like to watch it in real time and look at the waterfalls of red text because I forgot a $ or "" or something, but that's another story. So what kind of outputting can we do in PowerShell? Well, the answer to that question is another question, what kind of outputting can we not do in PowerShell? I actually don't know the answer to that. I have a feeling I will soon though. So let's start with the basics. If we are coming from BATCH processing we are familiar with Echo, in PowerShell we use Write-Host. It is essentially the same thing. Like so;

Write-Host Pancakes are delicious.

Write-Host can do variables also;

$WriteVar = [string]"Pancakes"
Write-Host What is delicious? $WriteVar are delicious.

It is this most basic form of output you will most likely find the most useful, especailly when it comes to troubleshooting commands. A lot of times when I am running a command against AD and using variables inside of quotes (such as CN=), I need to make sure it's being represented correctly. Because things get weird when we are using variables with special characters with quotes inside of single-strings inside of quotes. Pain you will soon come to get used to. For this I recommend you arm yourself with the knowledge of escape characters. I will cover those in another post and link them here.

Sorry, anyways - I need to write things to a .txt file, or a .log file (they are the same thing). So how do I do that?

$WriteVar = [string]"Pancakes"
$WriteVar | Out-File -FilePath C:\Working\Output.txt -Append

Adding more than one variable can be tricky at first, for example:

$WriteVar = [string]"Pancakes"
$WriteVar2 = [string]"Pancakes are awesome"
$WriteVar,$WriteVar2 | Out-File -FilePath C:\Working\Output.txt -Append

You might think this would put them on the same line, however the comma acts as a delimeter, and output goes on seperate lines. If you need to put them on the same line seperated by a comma, just surround the whole statement in quotes.

$WriteVar = [string]"Pancakes"
$WriteVar2 = [string]"Pancakes are awesome"
"$WriteVar, $WriteVar2" | out-file -FilePath C:\Working\output.txt -Append

When you do this, you don't need the comma there, you can have just spaces or whatever. Since the statement is in quotes it will be literal. Note this isn't only for strings. In the following example we will get a user from Active Directory using Get-ADUser, and output the results to a text file.

$User = Get-ADUser -Identity PancakeBro
$User | Out-File -FilePath C:\Working\output.txt -Append

For those of you without RSAT, you can follow with this example;

$GetBIOS = Get-WMIObject Win32_BIOS
$GetBIOS | Out-File -FilePath C:\Working\Output.txt -Append

But wait, I don't need all of that junk. How do I just write certain pieces of a command to output? For instance, I don't need to know the Manufacturer, but I just need the SerialNumber? Aha, now we are getting into usefulness territory. So you need just the SN# do you?

$GetBIOS = Get-WmiObject Win32_BIOS
$GetBIOS | select SerialNumber | out-file -FilePath C:\Working\output.txt -Append

Now you've outputted just the SN# to your output path. But if you notice, it's a bit messy isn't it? There's a bunch of spaces and it just looks dumb. This was actually just asked of me the other day when a colleague of mine was compiling a report. What we need to use is the -ExpandProperty parameter on Select. So let's try the following bit of code, and it should look much nicer in Output.txt.

$GetBIOS = Get-WmiObject win32_bios
$GetBIOS | select -ExpandProperty SerialNumber | out-file -FilePath C:\Working\output.txt -Append

Protip: Put -ExpandProperty before the object.

You can do this with anything en masse, put it in a loop and collect massive amounts of information from your network. If I was to take a bet, this is what you will be using it for. PowerShell won't be writing your next college paper. But it could write one hell of a SN# report. But we need to go a little deeper, another level down. You have a list of SN#'s now, let's say, you have 1,234 of them. Awesome, but what do they go to? You could run two reports, and combine them yourself in excel, but what if some of the PC's are unreachable and the lines don't match up? Well Jimmys-AwesomePC is going to have the wrong serial number and audit is going to hit you for it. You provided the Serial number for Awesome-TorrentPC instead. Or you need to combine lots of data from different points, and not just two columns, but maybe 5. Or 321. Or 65,536. Who knows, I don't know, I don't care - what's nice is these all scale to infinity, so we don't really mind how much data there is.

This is where we start wanting to write things to another file type. Let's start with the most basic, CSV. A few pointers about CSV though, you need to provide headers to define your tables, as well as enter data into the correct columns. Otherwise there is no difference between a list in .txt and a list in .csv. Even if you put commas in your output, you won't get it to seperate into columns. Which honestly is a bit strange but whatever. So let's create two columns for a CSV. But before we get into that, you can export any cmdlet into a CSV like so;

Get-WMIObject Win32_BIOS | Export-CSV C:\Working\Output.csv -NoTypeInformation

Protip: -NoTypeInformation hides useless PS object info from getting into the output

Make note of -Append if you want to write multiple sets of data to the same file. However, let's note that -Append is only fully functional in V3 of PowerShell, which might cause you some headaches. If you are going to be appending to CSV in older versions, it is quite a bit more complicated. I will cover that in another post at another time. Essentially, however - you really want to upgrade to PS 3 or 4 as soon as you can. With that being said, if you just want to be lazy you can do it like this. Most likely there is going to be a lot of stuff you don't care about. We can use Select though, to make our life more awesome, and less disorganized. Note that in this fashion, you also create headers for your information. So if you are using the same command on different computers, but outputting to the same file, the header will be Serial Number, with the SN#'s listed, and Manufacturer, with the Manufacturers for each one listed as well.

Get-WmiObject win32_bios | select manufacturer,serialnumber | Export-Csv C:\Working\Output.csv -NoTypeInformation -Append

Maybe now you are getting the hang of making some simple reports. However what sucks about the above example is you are sort of constraining yourself to working with only items that are returned from Win32_BIOS. Because what you would want to include in this report is the hostname, obviously. Or you have no reference to what you are looking at. So back to the original goal, let's create our own headers and put whatever info we want into them, instead of piping our output and limiting ourselves. Break free! KHANNNNNNNN!

Let's start off by saying there are several ways to create a CSV file. You can create new PSO Objects, you can create it with a Here string (my preferred favorite), and probably more than that either. However they are all a little confusing, so I really recommend you mess around with them a little before you really get started. It's good to note that you should be able to run these commands on your computer directly from this guide to get a good feel for it and then start messing around with it. I like the HERE statements because it offers flexibility in the way the tables and strings are entered, as well as you can sort of just do it in each loop or at the end of a script. However you prefer to run it really. However with a here string it's a bit rigid, it doens't let you use things like comments and quotes and so on don't have so much effect. So, let's get started.

Pancake:
$csv = 
@"
name,manufacturer,serialnumber
$var1,$var2,$var3
"@

So what we are doing here is creating a variable, $csv, where we will store our data. The comma denotes the column on the top, and you enter data, like so, below on the second line. For the work I do and have seen others do, this is probably going to be the most used way. As usual, with $var1-3, we can enter any strings that we would like. But to understand how this works for us, we need to understand how this doesn't work for us. Let's look at the following example.

$WMInfo = Get-WmiObject win32_bios

$csv = 
@"
name,manufacturer,serialnumber
$WMInfo.name,$WMInfo.manufacturer,$WMInfo.serialnumber
"@

$csv > C:\working\outputstuff.csv
Import-Csv C:\working\outputstuff.csv

Here is the gotcha. Unfortunately this doesn't work. In any normal situation, this would be perfectly acceptable. You can select members of a cmdlet but just adding the . and the property of whatever it is you want to add. However in the Here string that isn't going to work. This will be a little more manual than maybe we would like, so we will have to add some things for this to work as expected. So what is it that we have to do? Not much, we just need to put those objects into a single variable, like so:

$WMInfo = Get-WmiObject win32_bios
$WMInfoName = $WMInfo.Name
$WMInfoManufacturer = $WMInfo.Manufacturer
$WMInfoSerial = $WMInfo.SerialNumber

$csv = 
@"
name,manufacturer,serialnumber
$WMInfoName,$WMInfoManufacturer,$WMInfoSerial
"@

$csv > C:\working\outputstuff.csv
Import-Csv C:\working\outputstuff.csv

So here we have it. The simple way, in my opinion, to make a CSV file with custom information. Just for an example, let's throw in something else, that is just our own string that we could be getting from anywhere.

$Hostname = [string]"Awesome-TorrentPC"

$WMInfo = Get-WmiObject win32_bios
$WMInfoManufacturer = $WMInfo.Manufacturer
$WMInfoSerial = $WMInfo.SerialNumber

$csv = 
@"
hostname,manufacturer,serialnumber
$Hostname,$WMInfoManufacturer,$WMInfoSerial
"@

$csv > C:\working\outputstuff.csv
Import-Csv C:\working\outputstuff.csv

Sufficiently, this gives you the structure to create a CSV file and fill the columns with whatever kind of data you want, and you can name the columns you want, or add columns (I guess up to infinity? But let's not get carried away here). But if you put this in a loop while you are gathering information, you can use this sort of structure to write all of the relevant output to a file.

I think that is enough for now, we will go over output again at another time, as there is just so much to cover. But in the meantime, make your boss proud!

Wednesday, November 6, 2013

Jobs, and how they save your life

No. I don't mean your job. You are reading this page because you have a job and you need your life to be easier, or you don't have a job, and cats got boring. What I do know is Powershell, and it's ability to thread workloads, has made me keep my current job, as well as other important things like sanity, clean underpants, hair, carpel-tunnel free hands. You get the idea. It's also a topic that when I was starting out learning about jobs, I had trouble finding many good, easy to use examples of how I could apply them for what I need.

There are many types of jobs in Powershell, and just as many different ways to execute them. However, for the most part, you either have a script you wish you could thread, or you have the requirement to thread a lot of things instead of running them sequentially. A few instances this might be used in is, for example, starting or stopping services on hundreds of servers, copying a file to a remote file system on each server, creating/nesting groups based off of a document in AD, grabbing log files from many different hosts, or whatever it is you may be doing. I hope the valuable application of this has already become apparant.

Lets get something clear first. Start-Job is expensive. It also starts a seperate instance of Powershell. This brings with it two unique challenges:
  1. You are going to catch your computer or server on fire (maybe)
  2. You can't pass variables from the host shell so easily
Number 1 is because Start-Job creates a new instance of powershell, which needs resources. Personally, I have gotten away with running about 150 Powershell instances concurrently, which required about 12GB of available memory. Number 2 is valid. Since it is spinning up a fresh clean instance of Powershell, it won't have any of your session variables stored, so you need to pass them to the job. This would be the function of -ArgumentList. We will go over this in this post.

Pancake:
Start-Job -Name JobNameWhatever -Scriptblock {..} -ArgumentList $var,$var2

Easy enough right? This is the basic breakdown of Start-Job, and how 99% of the worlds population will be using it. You can start a job based on a remote script as well, which is similar to Invoke-Expression (We won't cover that here), you can use a very large and complicated script block (what you will most likely be doing), or you can just enter any plain old boring Powershell command, which you will put into a script block anyways. So let's take another example of actually using Start-Job in a loop. This will basically serve all of the needs I think you need to use it for.

$Names = Get-Content C:\PowershellPancakes\AuntJemima.txt
foreach ($Name in $Names)
    {
    $MapleSyrup =
        {
        Param ($name)
        New-Item -Name $Name -ItemType Directory -Path C:\PowershellPancakes
        }

Start-Job -Name $Name -Scriptblock $MapleSyrup -ArgumentList $Name
    }

This is just a simple script in a loop, where we are creating some delicious folders based off the names in the text file. You can open the loop however you want, in any way you want. This is the basic function. So imagine your $MapleSyrup scriptblock. You can do whatever you want in this script block. You can even take an existing script that takes manual input, put it into a script-block like so, and start a job based off of it. I have been doing this increasingly so, when someone asks me, can we do this, like, all at once? Why yes, client A, we can do this all at once. To maintain your full head of hair I suggest you put the job name to a variable you can identify. That way if you are running several hundred jobs you can skim through a list with Get-Job.

I want to go into detail about the following bit, because this may cause you a lot of headaches when you get started with jobs. So here it is:

Param ($name)

Were you expecting more? This just needs to be noted. This is where your parameters will get transferred into your scriptblock, which need to be used in -ArgumentList by Start-Job. If you don't, the value of $name will be null when the job starts, which could create unforseen problems, as well as just not work. Another note, variables you create inside script blocks don't need to be added to -ArgumentList or Param. They are contained. As long as you add the external variables being passed from outside of the scriptblock through Param and -ArgumentList, you are okay. Also remember, seperate params by a comma, like so,

Param ($name,$plants,$syrup)


Or, personally,

Param ($name,
       $syrup,
       $plants)


You have just one line you need to run?

Start-Job -Name Blueberries -Scriptblock {robocopy \\serverB\1 \\serverB\2}

What's nice about starting jobs is since they use another instance, you're free to continue using the parent shell for whatever you want. Some of you may have run into the situation where if you were running a script in the ISE and wanted to do something else on another script in the same ISE, you couldn't. Well since Start-Job creates its own instance of Powershell, you don't have to worry about this.

So you've started a job and it's just...running...somewhere...

Get-Job 

Though no, you can't exactly expand the operating job, and you will need to have some confidence in your own script. In another post we will go over how you can add some error handling to pass things back to your parent shell, or to a log file using different commands, including Try and Catch, which we touched on earlier.

But I have a job I need to run, through 400 objects in my CSV file that i'm looping through. I only have 4gb of available memory! Oh what do I do? First I will refer you to a previous posting, which goes very closely with this one. That would be What Do Until loops can do for you. They are connected directly. But how? Let's take our earlier example, and put some job control on it.

$Names = Get-Content C:\PowershellPancakes\AuntJemima.txt
foreach ($Name in $Names)
    {
    $MapleSyrup =
        {
        Param ($name)
        New-Item -Name $Name -ItemType Directory -Path C:\PowershellPancakes
        }

Start-Job -Name $Name $MapleSyrup -ArgumentList $Name
DO {
    Start-Sleep -Seconds 5
    #Give it some time
    Remove-Job -State Completed
    } Until (@(Get-Job).Count -le 5)
    }

What just happened? Well, we just limited the number of active jobs at any given time. I probably don't need to tell you why you want to do this. Especially if you are working with just huge bits of data and lots of jobs need to be created to do it. Now you can create jobs that create sub-jobs, but that's just a bit unnecessary in my personal opinion, unless each job has a lot of items it needs to complete, that would be better suited by threading those as well. Just remember your hardware limit so you don't do anything too crazy and bring everything to a halt. Also consider your network/storage/hardware or whatever it is you will be working with. Let's not be reckless here.

I think that basically covers it for Jobs. An absolutely essential function of Powershell that I think could sometimes be misunderstood. Mess around with the examples in this post and you should get a very good feel for how to incorporate these Pancakes into your own recipes. 



Monday, November 4, 2013

What Do Until loops do for you

Lately I've been getting into a lot of multi-processing and threading features of powershell. Of course, being the Powershell Pancaker, I don't typically like most of the examples I see - as oftentimes they are just too bloated, and they hurt to read (variables in the ISE burn my retinas). But I really needed this. Almost as bad as I need coffee, or excercise, or underpants.

Have you ever wanted something to happen, until it happened correctly? What if you had a bit of code, that depends on something external, maybe you want to run a script, and then (for whatever reason) go manually complete a task, and the script will continue when you are done? I don't know why you would do that. But this can be particularly useful in scenarios where maybe you are waiting for some bit of AD replication, as I was, which prompted me to write this. This is essential if you are running jobs in Powershell.

The lazy mans way out of this predicament is to use the Start-Sleep cmdlet. Which basically means you have way too much free time to run this script, and obviously you really just don't care about pretty much anything. But this doesn't work if you have a deadline, and you need this to be done as fast as possible. And doubly so if you are running your function as a seperate job, where you either arn't particularly bothered to report results from each job or - it is just unnecessary. This also doesn't work if you need to execute 400 instances of Powershell and you only have 16GB of RAM. This could be troublesome if your lazy Start-Sleep timer wasn't long enough, and unbeknownst to you, your script failed or didn't meet your success criterion.

So let's get started.

Pancake:
DO { #Start a DO loop
    $a++
    Write-Host $a
    } Until ($a -eq 10) #Write $a++ until -eq 10

This is the basic breakdown. Until (...) accepts all sorts of input. This command is useful in a number of scenarios. Inside of the DO loop, you can do pretty much anything you want. Let's try something completely non-sensible.

DO {
    Start-Sleep 2
    Write-Host Waiting for folder..
    $Folder = Test-Path C:\PowerShellPancakes\Testdir
    } Until ($Folder -eq "True")

In the above, we are using one of the unlikely scenarios that you want to manually create a folder to return some sort of output. Let's say we need to wait until this folder is created, and then when it is, copy something into it? If you are interleaving copy jobs maybe you will find this useful. Otherwise no one else will ever use this. But these are Pancakes, remember? We can put whatever we want on them. To do this we just have to add whatever we want at the end of the loop to continue on with our script.

DO {
    Start-Sleep 2
    Write-Host Waiting for folder..
    $Folder = Test-Path C:\PowerShellPancakes\Testdir
    } Until ($Folder -eq "True")
Copy-Item -Path C:\PowershellPancakes\AuntJemima.txt -Destination C:\PowershellPancakes\testdir
#Script continues as normal

As you can imagine, we can use this bit of code to transform basically anything that requires a wait time, success criteria, value limit, into something of a more fire and forget system. Is there anything else we can do with this? Hmm, what about Try and Catch? This is what you need to know if you don't want a bunch of error output while you are waiting for your criterion to become true, or, whatever it is you are doing. Let's use the following example.

DO {
    Start-Sleep 2
    Write-Host Waiting for folder..
    $Folder = Move-Item C:\PowershellPancakes\testdir -Destination C:\PowershellPancakes\Destination
        } Until ($Folder -eq "True")

This is going to be super annoying. You haven't even created TestDir yet, how are we going to move it if it doesn't exist? It's okay. Powershell will definitely let you know that it doesn't exist. Every two seconds, actually. What if you aren't as stupid as it assumes you are, and you want it to chill out until you have created this folder so it can be moved. (Again. Let's forget practicality for a moment.) Well, this is where catch and try come into scope. With try, we can do just that - try things. There is a big difference between try and do, except in Powershell, we do try. (See what I did there?) So let's look at something that won't hurt our eyes quite so much.

$ErrorActionPreference = "Stop"
DO {
    Start-Sleep 1
    Write-Host Waiting for folder..
    try {
        $GetFolder = Get-ChildItem C:\PowershellPancakes\Testdir
        }
    catch { }
        } Until ($GetFolder -eq $null)
Copy-Item -Path C:\PowershellPancakes\AuntJemima.txt -Destination C:\PowershellPancakes\testdir

Ah, much better. Thank you for understanding Powershell, don't be so impatient. There is one important thing to note, however - try and catch work great, however only for terminating errors. The above is an example of a non-terminating error. The script will still go on. So that is why we need to set our $ErrorActionPreference variable. In the above we are doing exactly what we want, in the do loop we are going to try and get the contents of that folder, and we are going to keep trying every two seconds. However, with catch, we are capturing what comes out of that try statement. You can enter a lot of things in here, you can enter some additional processing, maybe a nice Write-Error that looks better, put it out to a log file, whatever - the possibilities here are endless. You can even use the system classes to catch particular types of errors from try, if you happen to be running more than one set of commands. When I write scripts I usually leave this blank as I haven't had a reason to fill in the catch block yet, I use it to just ignore the error output. But much like If and Else statements, they follow eachother naturally.

That's all for now - this should get you started with the Do Until, Try and Catch statements.