Jan 282011
 

Recently, a challenge came across my desk which included comparing very large sets of data against one another. Specifically, a list of all computers in our domain compared to a list of all computers registered with a specific application. This posed an interesting question to me, “What would be the fastest way to accomplish this?”

I set out to look for different ways of comparing lists. I can think of three. The first two would be to load all of the items into an array and then search the array, item by item, for the value using either the –match operator or the –contains operator. The third method would be to load all the items into a hash table with empty values and then search the keys to see if they exist. Now since I know that loading up a hash table should take more time than loading an array, so I want to time the entire process not just the searches.

To actually do the timing, I will use the measure-command cmdlet. If you haven’t ever used this, you should really play with it. It’s a great tool for figuring out how long any given code block takes to run. That can be useful for things like filling in a time on you write-progress applet, or reporting the time to execute back to a user. Really you can look at it as a way to avoid setting a variable to get-date and then creating a new-timespan after the command completes. It is essentially rolling it all into one.

So, it’s a race between searching Hash Tables, and Searching arrays using both –match and –contains. Here is the code I used:

 $Checks = Get-Content u:scriptworkstations.txt

$ArrayContainsTime = Measure-Command {
    $Array = @(Get-Content u:scriptworkstations.txt)
    $Found = 0
    foreach ($Name in $Checks){
        If ($Array -contains $Name){$found++}
    }
}
"Array Contains Count: t$($Array.Count)"
"Array Contains Found: t$($found)"

$ArrayMatchTime = Measure-Command {
    $Array = @(Get-Content u:scriptworkstations.txt)
    $Found = 0
    foreach ($Name in $Checks){
        If ($Array -match $Name){$found++}
    }
}
"Array Matches Count: t$($Array.Count)"
"Array Matches found: t$($found)"

$HashTime = Measure-Command {
    $HashTable = @{}

    ForEach ($Line in Get-Content u:scriptworkstations.txt){
        $HashTable.Add($Line,"1")
    }
    $Found = 0
    foreach ($Name in $Checks){
        If ($Hashtable.ContainsKey($Name)){$found++}
    }
}
"Hash Table Count: t$($HashTable.Count)"
"Hash Table Found: t$($found)"


"Milliseconds for Array Contains:t$($ArrayContainsTime.TotalMilliseconds)"
"Milliseconds for Array Matches:t$($ArrayMatchTime.TotalMilliseconds)"
"Milliseconds for Hast Table Contains:t$($HashTime.TotalMilliseconds)"

I have loaded up the text file with 2,000 entries, so we are basically comparing 2,000 items to 2,000 items. Every single one will be a match, so we can see that it’s working by making sure that the found and count values are the same. If you wanted to take this code and load two different lists, then you would see a difference there. So, without further delay, it’s off to the races!

Array Contains Count:   2000
Array Contains Found:   2000
Array Matches Count:    2000
Array Matches found:    2000
Hash Table Count:   2000
Hash Table Found:   2000
Milliseconds for Array Contains:    532.6136
Milliseconds for Array Matches: 9839.4498
Milliseconds for Hast Table Contains:   51.2049

So we have a winner! As you can see all the methods work, but the hash table is substantially faster than the other two, array based methods searching through all 2,000 items in under one tenth of a second! I think array using the –contains operator is still a very reasonable time, and is probably easier and more comfortable for the average scripter to use. As well, it should be said that the array with the –match operator isn’t insanely slow by any means, and is by far the most robust method for searching as it can match any portion of the name in the array. This should be used with caution, though, as it can actually create false positives. Let’s say you are looking for “Computer” in a list that contains “Computer1”. You may not expect this to be a match, but it will.

So, there you have it. If you need to search a massive list for some reason and speed is top on your mind, use a hash table!

Jan 282011
 

A co-worker of mine posed the question of how to get the same type of pop-up that you get when you are using a simple wscript.echo in VBS. Since I am always telling him that PowerShell is typically easier than VBS to do something I was a bit annoyed, because he found one case where VB is easier.

Searching online you find that most people try to create a new object that is a VBS session and then pass the wscript.echo command over there. I don’t like that solution because it’s a bit kludgy. Instead I figured that there was a .NET object that you could use to do this, and indeed there is.

Here is a link to the MSDN Article for System.Windows.Forms.MessageBox:
System.Windows.Forms.Messagebox

Alright, so we know that there is a .NET class, so we can just make a new object, right? Well, normally yes. But there is a bit of an annoying statement on that site, “You cannot create a new instance of the MessageBox class.” So that means that I have to call the methods with a static call. Here’s the basic gist

[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[System.Windows.Forms.MessageBox]::Show("Hello, World.")

Made with powershell and .NET object

So that is the addition of the assembly to our current shell session and then calling it via a static method. As you can see it gives a really ugly window. We can add to that, though by overloading the show method with more stuff. Specifically, let’s fill out the title, the type of buttons we’ll use and the icon it will show.

[System.Windows.Forms.MessageBox]::Show("This is the Text","This is the title","AbortRetryIgnore","Warning")

Alright, so how did I know what text to type to make those buttons and icon appear? What others are available to me? Well you can refer to Microsoft’s site to find out:
Message Box Buttons
Message Box Icons

So, you can see that this can be fairly powerful, but not quite as “easy” as in VBS. Now, if you care about the input that the user gives, it’s really easy to get. All you need to do is grab the output.

$Testing = [System.Windows.Forms.MessageBox]::Show("Testing Output","","AbortRetryIgnore","Warning")
#Imaginary click of the "Retry button"
$Testing
>> Retry

So you can see that we can work with the output now in other ways, but what if we want something really detailed, like we could get in VBS with the Inputbox command. Well, that’s a giant pain. I played with this for a while, but since there is no equivalent .NET object, it’s not very easy. Basically, you have to create a blank form then add all your objects to it. Set what all the buttons do and then set what happens when the form closes. I found a good tutorial written by Microsoft, so I won’t re-invent the egg:
Creating a Custom Input Box

Jan 132011
 

Ever wanted to make your output a little cleaner? Try using the built in way of inserting a tab or a new line into your string in PowerShell. It’s an easy way to make your output look much better. All you need to do is add “n” for a new line or “t” for a tab.

"This is a list of stuff -'nThing 1:'tSome Stuff'nThing 10:'tOther Stuff"
> This is a list of stuff -
> Thing 1:        Some Stuff
> Thing 10:       Other Stuff

That’s it. By the way, the backtick that you are seeing is the character to the left one the “1″ key on your keyboard.

Jan 122011
 

Have you ever worked with arrays and felt you weren’t able to put in all the data you wanted? Have you tried making two arrays and syncing all the data between the two? Sometimes you just need to store more than one thing. The answer is to make your own custom object, and then put that into the array. It really is easier than you might think.

First, you need to make a new system object. To do this we’ll use the New-Object command. Now if you know what type of object, as defined by .NET, that you would like to create, then you can specify it here. But the point here is that we want to make our own object. So we can make a generic, blank “object” by using a system.object.

$myObject = New-Object System.Object

This makes a new object, but a new object isn’t really anything at all. Think of an object as just that, something that you can feel, that is tangible. If I were to tell you that you were holding an object, what would that mean to you? Probably nothing until you had some idea of what it was. Let’s pretend that what I just handed you is a user’s workstation, now this object has some meaning,but past giving it a name,we still haven’t defined what the object really is. Objects have properties that define them. In the case of a computer there are things like its manufacturer, how fast its processor is, and how much memory it has. So we want to track all of that in our object.

In Powershell what we need to do is add these properties to our object. We can do this with the Add-Member command. We need to tell it what type of property we are adding, what it’s called and what its value is.

$myObject | Add-Member -type NoteProperty -name Name -Value "Ryan_PC"
$myObject | Add-Member -type NoteProperty -name Manufacturer -Value "Dell"
$myObject | Add-Member -type NoteProperty -name ProcessorSpeed -Value "3 Ghz"
$myObject | Add-Member -type NoteProperty -name Memory -Value "6 GB"

Now we have a full object. It contains several properties that serve to define it and seperate it from other objects, even other PCs. We can work with the object in Powershell by calling it as a whole or by grabing any single property.

> $myObject

Name     Manufacturer   ProcessorSpeed   Memory
----     ------------   --------------   ------
Ryan_PC  Dell           3 Ghz            6 GB

> $myObject.Memory

6GB

> Test-Connection $myObject.Name -quiet

True

> $myObject.Manufacturer = "HP"
> $myObject

Name     Manufacturer   ProcessorSpeed   Memory
----     ------------   --------------   ------
Ryan_PC  HP             3 Ghz            6 GB

So now that you have this really cool object and can work with, you will of course want to have many, many more, and keep them well organized and in one place.  You can do this, and it too is very easy.  Simply take your custom objects, and put them into an array.  To show you, lets first make two more objects.

$myObject2 = New-Object System.Object

$myObject2 | Add-Member -type NoteProperty -name Name -Value "Doug_PC"
$myObject2 | Add-Member -type NoteProperty -name Manufacturer -Value "Dell"
$myObject2 | Add-Member -type NoteProperty -name ProcessorSpeed -Value "2.6 Ghz"
$myObject2 | Add-Member -type NoteProperty -name Memory -Value "4 GB"

$myObject3 = New-Object System.Object

$myObject3 | Add-Member -type NoteProperty -name Name -Value "Julie_PC"
$myObject3 | Add-Member -type NoteProperty -name Manufacturer -Value "Compaq"
$myObject3 | Add-Member -type NoteProperty -name ProcessorSpeed -Value "2.0 Ghz"
$myObject3 | Add-Member -type NoteProperty -name Memory -Value "2.5 GB"

Now we have three different objects,  so let’s create an empty array to place them in.

$myArray = @()

Okay, so now is simply adding the objects into the array.  You can use the += operator to do it.

$myArray += $myObject
$myArray += $myobject2, $myObject3

Notice that you can add more than one object to an array at once, if you like. 

Now that this is complete we have a list of all our custom objects.  This can be displayed nicely on the screen or even sent to out-gridview to give you a nice Excel-like view which can be searched, sorted, and filtered.  You can also use the array in a ForEach statement to run code against.  Keep in mind that you can also create the array of objects by adding one at a time in any given loop.  This is exactly how I create the server lists for the companies I work for.  It’s also worth stating that the “Add-Member” commandlet must have the value given, but you can simply give and empty string “” if you want to define it later.

> $myArray

Name         Manufacturer    ProcessorSpeed   Memory
----         ------------    --------------   ------
Ryan_PC      Dell            3 Ghz            6 GB
Doug_PC      Dell            2.6 Ghz          4 GB
Julie_PC     Compaq          2.0 Ghz          2.5 GB

> $myArray | Select-Object name

Name
----
Ryan_PC
Doug_PC
Julie_PC

Enjoy!

Jan 032011
 

While writing Powershell scripts you often want to know what types of errors are occurring within your scripts and deal with them appropriately. Powershell give several ways to track errors but one of the lesser known ones is the “Dollar Hook”. This is a variable that always exists and holds a variable stating whether the last command ended with an error or not. This is useful because instead of setting up a generic trap you can deal with a specific command that you know has a good chance of failing. Take this example:

Get-Process "ANonExistantProcess" -ErrorAction SilentlyContinue
$?
>>False

So how do you use it to deal with an error? Well that is easy, you can now run an “if” statement on it.

Get-Process "ANonExistantProcess" -ErrorAction SilentlyContinue
IF (!$?){ "There was an error!" }
>>There was an error!

Also, if you really want to look for a very specific error there is another variable that holds the exact output: $lastexitcode. This guy works just like the errorlevel idea in DOS. When you run a command the error number is stored in the variable where 0 is success.

ping "ANonExistantComputer"
$LastExitCode
>>1

Given that, there are a lot of commands that don’t support this notation, so I would just use a dollar hook and then check for your number by looking into the $error[0] variable at that time.

This gives us the tools, but know there are some annoying restrictions. First Dollar Hooks use the last COMMAND, so if you want to test for something that throws an error but isn’t a command, tuff luck. Try it with a divide by zero error, it won’t work. Next the $LastExitCode variable has to be supported by the command itself. A lot of times you’ll know that the command threw an error and what error number, but the return code will still be 0 (success). This is especially true for errors that are non-terminating errors.

Jan 032011
 

It may turn up that you need to use Pi for some random reason. Well instead of trying to make a variable in which you manually type it, you can use the version built into System.Math which holds Pi to 20 decimal places.

$Pi = [System.Math]::Pi
3 * $Pi
>> 9.42477796076938