Pages

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!

No comments: