Putting It Together in PowerShell
It should come as no surprise that we often use PowerShell to build things, and I mean more than custom object output. We often need to build strings, arrays, and hash tables. We also need to manipulate dates and times. Very often we are building these things up in layers. I thought I would take a moment to look at some of the ways we can put these things together in PowerShell.
One thing to keep in mind is when you need to add things together. Do you have all of the elements you need or will you be processing and adding things as you go? This can make a difference in how you approach the problem.
Hashtable Additions
Let's start out with looking at adding to a hashtable.
$user = @{
Name = 'Roy G. Biv'
Title = 'Active Directory Administrator'
City = 'Green Bay'
State = 'WI'
}
Using the Add
Method
Perhaps later in your code you need to add a name value pair to the hashtable. You can do this by using the Add()
method.
$user.Add("Department", "IT Services")
The Add()
method takes parameters for the key, Department
, and the value, IT Services
.
PS C:\> $user
Name Value
---- -----
Department IT Services
Title Active Directory Administrator
City Green Bay
Name Roy G. Biv
State WI
Using the Add()
method makes it clear that you are adding a new key value pair to the hashtable.
Indirect Addition
However, you can also add a new key value pair to a hashtable by simply assigning a value to a key that does not exist.
$user['FirstName'] = $user.Name.Split()[0]
$user['LastName'] = $user.Name.Split()[-1]
Normally, you can use this technique to update an existing key value pair. But if the key does not exist, PowerShell will add it to the hashtable.
PS C:\> $user
Name Value
---- -----
City Green Bay
Department IT Services
Name Roy G. Biv
LastName Biv
State WI
Title Active Directory Administrator
FirstName Roy
These techniques can be used to build up a hashtable as you go.
Arrays
Arrays are a bit different.
$a = @('apple', 'banana', 'cherry', 'date', 'elderberry')
You can't add to an array in the same way you can add to a hashtable. You can't use the Add()
method on an array, even though the underlying object contains an Add()
method.
PS C:\> $a -is [Array]
True
PS C:\> $a.PSBase | Get-Member Add
TypeName: System.Management.Automation.PSMemberSet
Name MemberType Definition
---- ---------- ----------
Add Method int IList.Add(System.Object value)
PS C:\> $a.add("Frank")
MethodInvocationException: Exception calling "Add" with "1" argument(s): "Collection was of a fixed size."
Instead, use the +=
operator to add to an array.
$a+="Frank"
$a+="Grace","Henry","Irene","Jack"
The operator appends items to the array.
PS C:\> $a
Alice
Bob
Carol
David
Emily
Frank
Grace
Henry
Irene
Jack
Technically, there are implications to using the +=
operator. It creates a new array and copies the old array to the new array. This can be a performance issue if you are adding a lot of items to a very large array. But for most cases, it is not a problem and I seem to recall a fix for this in a recent version of PowerShell 7. The array syntax I'm demonstrating is very common in PowerShell scripting.
Joining
Another useful building technique is to join elements together, typically strings, or items that can be treated as strings. Here's a variable with an array of names.
$names = 'Alice', 'Bob', 'Carol', 'David', 'Emily'
You might want to display the names in a message.
PS C:\> Write-Host "Creating accounts for $names"
Creating accounts for Alice Bob Carol David Emily
Let's clean this up using the -join
operator.
PS C:\> $list = $names -join ','
PS C:\> Write-Host "Creating accounts for $list"
Creating accounts for Alice,Bob,Carol,David,Emily
The -join
operator joins the elements of an array into a single string. You can specify a delimiter, in this case, a comma. If you don't need a delimiter, you can write an expression with the -join
operator at the beginning of the PowerShell expression.
PS C:\> -join $names
AliceBobCarolDavidEmily
It feels a little odd to see the -join
operator at the beginning of the expression, but it works.
String Addition
Next, let's turn our attention to strings. I'm going to skip covering the .NET System.Text.StringBuilder
class because I think it is overkill for most PowerShell scripting. Instead, I'll show you how to build strings in PowerShell.
You might be tempted to use the +
operator to add to a string.
$s = '[' + (Get-Date).TimeOfDay + '] Starting the update process on ' + $env:COMPUTERNAME
I see code like this very often from PowerShell beginners. This is the technique we used back in the VBScript days.
When I see this, I think that the scripter is new to PowerShell and is still thinking about text and not objects. The +
operator should be used for adding numbers, not strings. Yes, it will work, but it is not the best PowerShell way to do it.
Instead, learn about subexpressions and variable expansion.
$s = "[$((Get-Date).TimeOfDay)] Starting the update process on $($env:COMPUTERNAME)"
This is a much better way that I think is easier to read and not as clunky as using the +
operator.
Format Operator
If you really want to get fancy, you can use the format operator, -f
.
$s = "[{0}] Starting the update process on {1}" -f (Get-Date).TimeOfDay, $env:COMPUTERNAME
The string on the left side contains placeholders, {0}
and {1}
. The -f
operator takes the values on the right side and inserts them into the placeholders.
PS C:\> $s
[10:34:09.6104735] Starting the update process on JEFF11
Join an Array
Another way to join strings is with an array.
$t = (Get-Date).TimeOfDay
$m = "Starting the update process"
$pc = $env:COMPUTERNAME
$x = @("[$t]",$m,$pc)
Then use the -join
operator to join the strings into the message.
PS C:\> $x -join " "
[10:39:26.6762178] Starting the update process JEFF11
Here's a more complex example that uses a variety of techniques I've shown today.
$computers = "Srv1", "Srv2", "Srv3"
$mHash = [ordered]@{}
foreach ($computer in $computers) {
$mHash['ToD'] = "[{0}]" -f (Get-Date).TimeOfDay
$mHash['Operation'] = "Starting"
$mHash['Message'] = "the update process on"
$mHash['Computer'] = $computer
$msg = $mHash.Values -join " "
Write-Host $msg -ForegroundColor Cyan
#simulate running the process
Start-Sleep -Seconds (Get-Random -min 2 -max 10)
$mHash['Operation'] = "Completed"
$mHash['ToD'] = "[{0}]" -f (Get-Date).TimeOfDay
$msg = $mHash.Values -join " "
Write-Host $msg -ForegroundColor Green
}
This is not necessarily the best or only way to do build messages, but this is definitely better than concatenating values with the +
operator.
Dates and Times
One other item you might want to build is a date.
$n = Get-Date "1/1/2025 12:00:00 PM"
You can add time to a date by using one of the Add
methods.
PS C:\> $n | Get-Member Add*
TypeName: System.DateTime
Name MemberType Definition
---- ---------- ----------
Add Method datetime Add(timespan value)
AddDays Method datetime AddDays(double value)
AddHours Method datetime AddHours(double value)
AddMicroseconds Method datetime AddMicroseconds(double value)
AddMilliseconds Method datetime AddMilliseconds(double value)
AddMinutes Method datetime AddMinutes(double value)
AddMonths Method datetime AddMonths(int months)
AddSeconds Method datetime AddSeconds(double value)
AddTicks Method datetime AddTicks(long value)
AddYears Method datetime AddYears(int value)
Invoking the method doesn't change the value of the date object. Instead, it returns a new date object.
PS C:\> $n.AddDays(90)
Tuesday, April 1, 2025 12:00:00 PM
If you need to get a datetime value in the past, use a negative number.
PS C:\> $n.AddHours(-168)
Wednesday, December 25, 2024 12:00:00 PM
TimeSpans
Finally, you can also build time spans.
$ts = New-TimeSpan -Minutes 10 -Seconds 30
The TimeSpan object has an Add()
method that is expecting a TimeSpan object.
PS C:\> $ts | Get-Member Add
TypeName: System.TimeSpan
Name MemberType Definition
---- ---------- ----------
Add Method timespan Add(timespan ts)
PS C:\> $ts2 = New-TimeSpan -Milliseconds 34561
PS C:\> $ts.Add($ts2)
Days : 0
Hours : 0
Minutes : 11
Seconds : 4
Milliseconds : 561
Ticks : 6645610000
TotalDays : 0.00769167824074074
TotalHours : 0.184600277777778
TotalMinutes : 11.0760166666667
TotalSeconds : 664.561
TotalMilliseconds : 664561
As with adding to a date, the Add()
method does not change the original TimeSpan object. It returns a new TimeSpan object.
Summary
There are many ways to build things in PowerShell. I've shown you a few techniques that I think are the most common and useful. I hope you find them helpful in your PowerShell scripting. Of course, next time I'll have to show you how to take things apart.