Hashtables: Performance

In the previous post I provided a quick introduction to creating and using a hashtable object. If you are paying attention, you might have asked “Why would I go to extra work to create a hashtable when I can make the original object work?” The answer is performance…but lets expand on this answer.

We can use Measure-Command to show how long each of these statements takes to execute. Here is a quick refresher on using Measure-Command to show how long it takes to execute a command…just replace “PUT EACH COMMAND HERE” with your actual command:


(Measure-Command { "PUT EACH COMMAND HERE" }).TotalMilliseconds

Using the examples from the previous post, we return Total Millisecond execution times of:

Example Execution Time (ms)
2: Select item from array object 19.0030
3: Create a hashtable from array object 19.2045
4: Select item from hashtable 0.0311

In this sample, returning a value from the hashtable (example 4) is 600 times faster than using the array object (example 2)! Just because this step was faster, it doesn’t mean hashtables are always best. If we take into account the overhead from creating the hashtable (add the execution times of example 3 and 4 together), then using the array object is actually more efficient. The performance gain is only noticeable if you plan to re-use the hashtable over several iterations.


# Example 5: Selecting multiple times from array object
(Measure-Command {
($colorCodes | Where-Object {$_.ColorName -eq 'Red'}).Code
($colorCodes | Where-Object {$_.ColorName -eq 'Blue'}).Code
($colorCodes | Where-Object {$_.ColorName -eq 'Green'}).Code
($colorCodes | Where-Object {$_.ColorName -eq 'Black'}).Code
}).TotalMilliseconds

# Example 6: Creating hashtable and selecting from it multiple times
(Measure-Command {
$colorCodeHT = @{}
$colorCodes | %{ $colorCodeHT.Add($_.ColorName, $_.Code) }
$colorCodeHT['Red']
$colorCodeHT['Blue']
$colorCodeHT['Green']
$colorCodeHT['Black']
}).TotalMilliseconds
Example Execution Time (ms)
5: Select from array object multiple times 57.7053
6: Create hashtable and select from it multiple times 17.8322

Even though a hashtable requires a bit of setup, it becomes more efficient with repetitive use. The example with HTML color codes only has around 290 items in the hashtable. This performance improvement is more noticeable with larger hashtables, especially when they are used many times within the script.

Posted in Scripting | Leave a comment

Hashtables: Introduction

I was recently working on a script for a co-worker who is trying to learn more powershell. In the script I had used a couple of hashtables to store information…which caused a couple of questions as he was reviewing the code. This post is an introduction to powershell hashtables.

A hashtable stores indexed key-value pairs in memory and is similar to the vbscript dictionary object. To help describe these concepts, I have listed a few examples based on a CSV file containing HTML color codes.

The examples below will:
1.) Import the CSV into a powershell array object,
2.) Show how to use the array object to return a specific code given a color name
3.) Convert the object from step 1 into a hashtable
4.) Show how to use the hashtable to return a specific code given a color name


# Example 1:
$colorCodes = Import-Csv ColorCodes.csv

# Example 2:
# Using just the array object created from Import-CSV
# we can select the color code for Red with just one line
($colorCodes | Where-Object {$_.ColorName -eq 'Red'}).Code

# Example 3:
# We could also create a hash table and store each item
# from the array using a couple lines of code
$colorCodeHT = @{}
$colorCodes | %{ $colorCodeHT.Add($_.ColorName, $_.Code) }

# Example 4
# And then we can retreive the code for red using either of the
# examples below. Note: you only need one as they return the same
# value.  The first sample is my preferred method, but either works.
$colorCodeHT['Red']
$colorCodeHT.Item('Red')
Posted in Scripting | Leave a comment

Using ADSI to clear an attribute

I have many scripts that write information into Active Directory. Sometimes I need to do the opposite — remove or blank out an attribute.

All of the following examples will require a distinguishedName value in AD, so I will set a common variable here. You will need a valid distinguishedName defined in a varible like this if you want to follow along.

$distinguishedName = "CN=Brian Wuchner,OU=Test Users,DC=bwuch,DC=local"

The first test we will do is to update the displayName of the user. This is easy…we don’t care what the existing value is, we are just going to set it to whatever string value we want. We will do this by creating an object representing the Directory Entry of my user, updating the displayName attribute of that object and finally updating/committing that change back to the directory as follows.


$objUser = New-Object DirectoryServices.DirectoryEntry "LDAP://$distinguishedName"
$objUser.displayName="Wuchner, Brian"
$objUser.SetInfo()

That is a rather straight forward process. However, it becomes slightly more complex if we want to clear the attribute and make it null. One would think you could simply think you could set the displayName equal to $null using something like this:


$objUser = New-Object DirectoryServices.DirectoryEntry "LDAP://$distinguishedName"
$objUser.displayName=$null

However, if you try that you will most likely get a chance to see the following error message:

Exception setting "displayName": "Value cannot be null.

As a follow up, you might think “I’ll just set the attribute to an empty string” like this:


$objUser = New-Object DirectoryServices.DirectoryEntry "LDAP://$distinguishedName"
$objUser.displayName=""
$objUser.SetInfo()

The only problem with that…setinfo will fail with the following error.

setinfo : Exception calling "setinfo" with "0" argument(s): "The attribute syntax specified to the directory service is invalid.

Does that mean its time to give up? Are we just going to have to live with bad data in AD? Never!

There are at least two ways to clean this up. The first way is my favorite (I’ve been using it since vbscript). You simply need to refer to the Microsoft article HOW TO: Use ADSI to Set LDAP Directory Attributes and you’ll realize this can be accomplished with PutEx.


$objUser = New-Object DirectoryServices.DirectoryEntry "LDAP://$distinguishedName"
$objUser.PutEx(1, 'displayName', 0)
$objUser.SetInfo()

In the above example, we use the constant ADS_PROPERTY_CLEAR by placing a ‘1’ as the first argument in our method, then specifying the displayName as the attribute we want to clear, and finishing up by providing the desired value. Since we want the value to be clear, and we’ve already specified the ‘1’ to clear, we just need to specify something so the method has enough parameters. I use 0 just for fun.

Another option is to call the Remove method and specify the value we want to remove. The downside is the attribute MUST have a value or you get an error when trying to clear it. We can overcome that with a simple if statement like this:


$objUser = New-Object DirectoryServices.DirectoryEntry "LDAP://$distinguishedName"
if ($objUser.displayName) { 
  $objUser.displayName.Remove("$($objUser.displayName)")
  $objUser.SetInfo()
}

It doesn’t matter which of the options I use, the end result is the same — a clear attribute. I hope this post helps if you ever need to clear out an attribute.

Posted in Messaging, Scripting | 2 Comments

Remove vCenter Scheduled Tasks with PowerCLI (Part 3)

In my most recent series on vCenter Scheduled Tasks with PowerCLI, I have provided three functions…Get-VIScheduledTasks, Get-VMScheduledSnapshots, and New-VMScheduledSnapshot. To complete the series, I have a final function Remove-VIScheduledTask. I don’t really see the need for this one, as you can easily bulk select scheduled tasks in the vCenter UI and delete them with a single click. However, in the interest of completeness, here is a rough function that will delete a vSphere scheduled task. [A more robust/efficient/complete function could be written that would accept input from the pipeline. If there is a good use case for this please leave a comment below.]

NOTE: This function requires an additional function: Get-VIScheduledTasks to find the task by name. In addition the usage example below requires Get-VMScheduledSnapshots. Both functions are available here: New vCenter Scheduled Tasks with PowerCLI (Part 1).


Function Remove-VIScheduledTask {
PARAM ([string]$taskName)
  (Get-View -Id ((Get-VIScheduledTasks -Full | ?{$_.Name -eq $taskName}).ScheduledTask)).RemoveScheduledTask()
}

Usage example:

# This example will find all VM Scheduled Snapshots which 
# are not scheduled to run again, then remove each one by name.
Get-VMScheduledSnapshots | 
?{$_.NextRunTime -eq $null} | %{ Remove-VIScheduledTask $_.Name }
Posted in Scripting, Virtualization | 2 Comments

New vCenter Scheduled Tasks with PowerCLI (Part 2)

In a recent post New vCenter Scheduled Tasks with PowerCLI (Part 1), I provided two functions that return information related to vCenter Scheduled Tasks. This was my first step towards creating a PowerShell function that I could run to create scheduled snapshot tasks. This post will cover the create scheduled snapshot function.

NOTE: This function requires two additional functions, Get-VMScheduledSnapshots and Get-VIScheduledTasks, available here: New vCenter Scheduled Tasks with PowerCLI (Part 1).


Function New-VMScheduledSnapshot {
PARAM (
  [string]$vmName,
  [string]$runTime,
  [string]$notifyEmail=$null,
  [string]$taskName="$vmName Scheduled Snapshot"
)

# Verify we found a single VM
$vm = (get-view -viewtype virtualmachine -property Name -Filter @{"Name"="^$($vmName)$"}).MoRef
if (($vm | Measure-Object).Count -ne 1 ) { "Unable to locate a specific VM $vmName"; break }

# Validate datetime value and convert to UTC
try { $castRunTime = ([datetime]$runTime).ToUniversalTime() } catch { "Unable to convert runtime parameter to date time value"; break }
if ( [datetime]$runTime -lt (Get-Date) ) { "Single run tasks can not be scheduled to run in the past.  Please adjust start time and try again."; break }

# Verify the scheduled task name is not already in use
if ( (Get-VIScheduledTasks | ?{$_.Name -eq $taskName } | Measure-Object).Count -eq 1 ) { "Task Name `"$taskName`" already exists.  Please try again and specify the taskname parameter"; break }

$spec = New-Object VMware.Vim.ScheduledTaskSpec
$spec.name = $taskName
$spec.description = "Snapshot of $vmName scheduled for $runTime"
$spec.enabled = $true
if ( $notifyEmail ) {$spec.notification = $notifyEmail}
($spec.scheduler = New-Object VMware.Vim.OnceTaskScheduler).runAt = $castRunTime
($spec.action = New-Object VMware.Vim.MethodAction).Name = "CreateSnapshot_Task"
$spec.action.argument = New-Object VMware.Vim.MethodActionArgument[] (4)
($spec.action.argument[0] = New-Object VMware.Vim.MethodActionArgument).Value = "$vmName scheduled snapshot"
($spec.action.argument[1] = New-Object VMware.Vim.MethodActionArgument).Value = "Snapshot created using $taskName"
($spec.action.argument[2] = New-Object VMware.Vim.MethodActionArgument).Value = $false # Snapshot memory
($spec.action.argument[3] = New-Object VMware.Vim.MethodActionArgument).Value = $false # quiesce guest file system (requires VMware Tools)

[Void](Get-View -Id 'ScheduledTaskManager-ScheduledTaskManager').CreateScheduledTask($vm, $spec)
Get-VMScheduledSnapshots | ?{$_.Name -eq $taskName }
}
# Create a snapshot of the VM test002 at 9:40AM on 3/2/13
New-VMScheduledSnapshot test002 "3/2/13 9:40AM"

# Create a snapshot and send an email notification
New-VMScheduledSnapshot test002 "3/2/13 9:40AM" myemail@mydomain.com

# Use all of the options and name the parameters
New-VMScheduledSnapshot -vmname 'test001' -runtime '3/2/13 9:40am' -notifyemail 'myemail@mydomain.com' -taskname 'My scheduled task of test001'

I hope someone finds this function useful.

Posted in Scripting, Virtualization | 11 Comments