Arrays and Hash Tables in PowerShell

Arrays and Hash Tables

No matter how many results a command returns, you can always store the results in a variable because of a clever trick. PowerShell automatically wraps results into an array when there is more than one result. In this chapter, you’ll learn how arrays work. You’ll also discover a special type of array, a hash table. While normal arrays use a numeric index to access their elements, hash tables use key-value-pairs.

PowerShell Commands Return Arrays

If you store the result of a command in a variable and then output it, you might at first think that the variable contains plain text:

$a = ipconfig
$a
Windows IP Configuration
Ethernet adapter LAN Connection
Media state
. . . . . . . . . . . : Medium disconnected
Connection-specific DNS Suffix:
Connection location IPv6 Address . : fe80::6093:8889:257e:8d1%8
IPv4 address . . . . . . . . . . . : 192.168.1.35
Subnet Mask . . . . . . . . . . . . : 255.255.255.0
Standard Gateway . . . . . . . . . . : 192.168.1.1

However, that’s not true. Each line is stored as a separate value in your variable and the variable is really an array. This happens automatically whenever a command returns more than one result.

Storing Results in Arrays

This is how you identify arrays:

$a = "Hello"
$a -is [Array]
False
$a = ipconfig
$a -is [Array]
True

If the result is an array, you can find the number of elements stored in it by using the Count property:

$a.Count
53

In this example, the ipconfig command returned 53 single results that are all stored in $a. If you’d like to examine a single array element, specify its index number. If an array has 53 elements, its valid index numbers are 0 to 52 (the index always starts at 0).

# Show the second element:
$a[1]

Whether or not the result is an array depends on the number of results that were returned. If more than one, PowerShell returns an array. Otherwise, it returns the result directly so the same command can behave differently from case to case, depending on the number of results.

$result = Dir
$result -is [array]

True

$result = Dir C:autoexec.bat
$result -is [array]

False
$result = @(Dir)
$result.Count

Use the construct @() if you’d like to force a command to always return its result in an array. This way the command will always return an array, even ifthe command returns only one result or none at all. This way you find out the number of files in a folder:

Or in a line:

@(Dir).Count

 Further Processing of Array Elements in a Pipeline:

ipconfig returns each line of text as array, enabling you to process them individually:

# Store result of an array and then pass along a pipeline to Select-String:
$result = ipconfig
$result | Select-String "Address"
Connection location IPv6 Address . . . : fe80::6093:8889:257e:8d1%8
IPv4 address . . . . . . . . . . . : 192.168.1.35
Connection location IPv6 Address . : fe80::5efe:192.168.1.35%16
Connection location IPv6 Address . . . : fe80::14ab:a532:a7b9:cd3a%11
# Everything in one line: output only lines including the
# word "address":
ipconfig | Select-String "Address"
Connection location IPv6 Address . . . : fe80::6093:8889:257e:8d1%8
IPv4-Adress . . . . . . . . . . . . : 192.168.1.35
Connection location IPv6 Address . . . : fe80::5efe:192.168.1.35%16
Connection location IPv6 Address . . . : fe80::14ab:a532:a7b9:cd3a%11

 The result of ipconfig was passed to Select-String, which is a text filter that allows only text lines that include the searched word through the PowerShell pipeline. With minimal effort, you can reduce the results of ipconfig to the information you find relevant.

Working with Real Objects in PowerShell

ipconfig is a legacy command, not a modern PowerShell cmdlet. While it is a command that returns individual information stored in arrays, this individual information consists of text. Real PowerShell cmdlets return rich objects, not text, even though this is not apparent at first:

Dir
Directory: Microsoft.PowerShell.CoreFileSystem::C:Users
Tobias Weltner
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 10/01/2007 16:09 Application Data
d---- 07/26/2007 11:03 Backup
d-r-- 04/13/2007 15:05 Contacts
d---- 06/28/2007 18:33 Debug
d-r-- 10/04/2007 14:21 Desktop
d-r-- 10/04/2007 21:23 Documents
d-r-- 10/09/2007 12:21 Downloads
(...)

Let’s check if the return value is an array: 

$result = Dir
$result.Count
82

 Every element in an array represents a file or a directory. So if you output an element from the array to the console, PowerShell automatically converts the object back into text:

# Access the fifth element:
$result[4]
Directory: Microsoft.PowerShell.CoreFileSystem::C:Users
Tobias Weltner
Mode LastWriteTime Length Name
---- ------------- ------ ----
d-r-- 04.10.2007 14:21 Desktop

You will realize that each element is much more than plain text when you pass it to the Format-List cmdlet and use an asterisk to see all of its properties:

# Display all properties of this element:
$result[4] | Format-List *
PSPath : Microsoft.PowerShell.CoreFileSystem::
C:UsersTobias WeltnerDesktop
PSParentPath : Microsoft.PowerShell.CoreFileSystem:: C:UsersTobias Weltner
PSChildName : Desktop
PSDrive : C
PSProvider : Microsoft.PowerShell.CoreFileSystem
PSIsContainer : True
Mode : d-r--
Name : Desktop
Parent : Tobias Weltner
Exists : True
Root : C:
FullName : C:UsersTobias WeltnerDesktop
Extension :
CreationTime : 04/13/2007 01:54:53
CreationTimeUtc : 04/12/2007 23:54:53
LastAccessTime : 10/04/2007 14:21:20
LastAccessTimeUtc : 10/04/2007 12:21:20
LastWriteTime : 10/04/2007 14:21:20
LastWriteTimeUtc : 10/04/2007 12:21:20
Attributes : ReadOnly, Directory

 Creating New Arrays in PowerShell

 You can create your own arrays, too. The easiest way is to use the comma operator:

$array = 1,2,3,4
$array
1234

Specify the single elements that you want to store in the array and then separate them by a comma. There’s even a special shortcut for sequential numbers:

$array = 1..4
$array
1234

Polymorphic Arrays

Just like variables, individual elements of an array can store any type of value you assign. This way, you can store whatever you want in an array, even a mixture of different data types. You can separate the elements using commas:

$array = "Hello", "World", 1, 2, (Get-Date)
$array
Hello
World
12
Tuesday, August 21, 2013 12:12:28

Why is the Get-Date cmdlet in the last example enclosed in parentheses?Just try it without parentheses. Arrays can only store data. Get-Date is a command and no data. Since you want PowerShell to evaluate the command first and then put its result into the array, you need to use parentheses. Parentheses identify a sub-expression and tell PowerShell to evaluate and process it first.

 Arrays With Only One (Or No) Element in PowerShell

  How do you create arrays with just one single element? Here’s how:

$array = ,1
$array.Length
1

You’ll need to use the construct

@(...)

to create an array without any elements at all:

$array = @()
$array.Length
0
$array = @(12)
$array
12
$array = @(1,2,3,"Hello")
$array
1
2
3
Hello

Addressing Array Elements in PowerShell:

Every element in an array is addressed using its index number. Negative index numbers count from last to first. You can also use expressions that calculate the index value:

# Create your own new array:
$array = -5..12
# Access the first element:
$array[0]
-5
# Access the last element (several methods):
$array[-1]
12
$array[$array.Count-1]
12
$array[$array.length-1]
12
# Access a dynamically generated array that is not stored in a variable:
(-5..12)[2]
-3

Remember, the first element in your array always has the index number 0. The index -1 will always give you the last element in an array. The example demonstrates that the total number of all elements will be returned in two properties: 

Count and Length. Both of these properties will behave identically.

Choosing Several Elements from an Array:

You can use square brackets to select multiple elements in an array. In doing that, you get a new array containing only the selected elements from the old array:

# Store directory listing in a variable:
$list = dir
# Output only the 2nd, 5th, 8th, and 13th entry: $list[1,4,7,12]
Directory: Microsoft.PowerShell.CoreFileSystem::C:Users
Tobias Weltner
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 07/26/2007 11:03 Backup
d-r-- 08/20/2007 07:52 Desktop
d-r-- 08/12/2007 10:21 Favorites
d-r-- 04/13/2007 01:55 Saved Games

 The second line selects the second, fifth, eighth, and thirteenth elements (remember that the index begins at 0). You can use this approach to reverse the contents of an array: 

# Create an array with values from 1 to 10
$array = 1..10
# Select the elements from 9 to 0 (output array contents
# in reverse order):
$array = $array[($array.length-1)..0]
$array
10
9
...
1

Reversing the contents of an array

Uusing the approach (described above) is not particularly efficient because PowerShell has to store the result in a new array. Instead, you should use the special array functions of the .NET Framework. They enable you to reverse the contents of an array very efficiently:

# Create an array containing text and output contents:
$a = ipconfig
$a
# Reverse array contents and then output it again:
[array]::Reverse($a)
$a

Adding Elements to an Array and Removing Them

Arrays always contain a fixed number of elements. You’ll have to make a new copy of the array with a new size to add or remove elements later. You can simply use the “+=” operator to do that and then add new elements to an existing array:

# Add a new element to an existing array:
$array += "New Value"
$array
123
New Value

Array sizes can’t be modified so PowerShell will work behind the scenes to create a brand-new, larger array, copying the contents of the old array into it, and adding the new element. PowerShell works exactly the same way when you want to delete elements from an array. Here, too, the original array is copied to a new, smaller array while disposing of the old array. For example, the next line removes elements 4 and 5 using the indexes 3 and 4:

$array = $array[0..2] + $array[5..10]
$array.Count
9

Using Hash Tables

Hash tables store “key-value pairs.” So, in hash tables you do not use a numeric index to address individual elements, but rather the key you assigned to a value.

Creating a New Hash Table

To create a new hash table, use @{} instead of @(), and specify the key-value pair that is to be stored in your new hash table. Use semi-colons to separate key-value pairs:

# Create a new hash table with key-value pairs
$list = @{Name = "PC01"; IP="10.10.10.10"; User="Tobias Weltner"}
Name                   Value
----                   -----
Name                   PC01
IP                     10.10.10.10
User                   Tobias Weltner
# Access to the key "IP" returns the assigned value:
$list["IP"]
10.10.10.10
# As for arrays, several elements can be selected at the same time:
$list["Name", "IP"]
PC01
10.10.10.10 
# A key can also be specified by dot notation:
$list.IP
10.10.10.10
# A key can even be stored in a variable:
$key = "IP"
$list.$key
10.10.10.10
# Keys returns all keys in the hash table:
$list.keys
Name
IP
User
# If you combine this, you can output all values in the hash table
$list[$list.keys]
PC01
10.10.10.10
Tobias Weltner

The example shows that you retrieve the values in the hash table using the assigned key. There are two forms of notation you can use to do this:

  • Square brackets: Either you use square brackets, like in arrays;
  • Dot notation: Or you use dot notation, like with objects, and specify respectively the key name with the value you want to return. The key name can be specified as a variable.

The square brackets can return several values at the same time exactly like arrays if you specify several keys and separate them by a comma. Note that the key names in square brackets must be enclosed in quotation marks (you don’t have to do this if you use dot notation).

Storing Arrays in Hash Tables

You can store classic array inside of hash tables, too. This is possible because hash tables use the semi-colon as key-value-pair separators, leaving the comma available to create classic arrays:

# Create hash table with arrays as value:
$test = @{ value1 = 12; value2 = 1,2,3 }
# Return values (value 2 is an array with three elements):
$test.value1
12
$test.value2
1
2
3

 Inserting New Keys in an Existing Hash Table

If you’d like to insert new key-value pairs in an existing hash table, just specify the new key and the value that is to be assigned to the new key. Again, you can choose between the square brackets and dot notations.

# Create a new hash table with key-value pairs
$list = @{Name = "PC01"; IP="10.10.10.10"; User="Tobias Weltner"}
# Insert two new key-value pairs in the list (two different
# notations are possible):
$list.Date = Get-Date
$list["Location"] = "Hanover"
# Check result:
$list
Name Value
---- -----
Name PC01
Location Hanover
Date 08/21/2007 13:00:18
IP 10.10.10.10
User Tobias Weltner

 Because it’s easy to insert new keys in an existing hash table you can create empty hash tables and then insert keys as needed:

# Create empty hash table
$list = @{}
# Subsequently insert key-value pairs when required
$list.Name = "PC01"
$list.Location = "Hanover"
(...)

Modifying and Removing Values

If all you want to do is to change the value of an existing key in your hash table, just overwrite the value:

# Overwrite the value of an existing key with a new value (two possible notations):
$list["Date"] = (Get-Date).AddDays(-1)
$list.Location = "New York"
Name Value
---- -----
Name PC01
Location New York
Date 08/20/2007 13:10:12
IP 10.10.10.10
User Tobias Weltner

If you’d like to completely remove a key from the hash table, use Remove() and as an argument specify the key that you want to remove:

$list.remove("Date")

Using Hash Tables for Output Formatting

An interesting use for hash tables is to format text. Normally, PowerShell outputs the result of most commands as a table and internally uses the cmdlet Format-Table:

# Both lines return the same result:
Dir
Dir | Format-Table

If you use Format-Table, you can pass it a hash table with formatting specifications. This enables you to control how the result of the command is formatted. Every column is defined with its own hash table. In the hash table, values are assigned to the following four keys:

  • Expression: The name of object property to be displayed in this column.
  • Width: Character width of the column.
  • Label: Column heading.
  • Alignment: Right or left justification of the column

All you need to do is to pass your format definitions to Format-Table to ensure that your listing shows just the name and date of the last modification in two columns:

# Setting formatting specifications for each column in a hash table:
$column1 = @{expression="Name"; width=30; `
label="filename"; alignment="left"}
$column2 = @{expression="LastWriteTime"; width=40; `
label="last modification"; alignment="right"}
# Output contents of a hash table:
$column1
Name Value
---- -----
alignment left
label File name
width 30
expression Name
# Output Dir command result with format table and
# selected formatting:
Dir | Format-Table $column1, $column2
File Name Last Modification
--------- ---------------
Application Data 10/1/2007 16:09:57
Backup 07/26/2007 11:03:07
Contacts 04/13/2007 15:05:30
Debug 06/28/2007 18:33:29
Desktop 10/4/2007 14:21:20
Documents 10/4/2007 21:23:10
(...)

 Copying Arrays and Hash Tables

Copying arrays or hash tables from one variable to another works, but may produce unexpected results. The reason is that arrays and hash tables are not stored directly in variables, which always store only a single value. When you work with arrays and hash tables, you are dealing with a reference to the array or hash table. So, if you copy the contents of a variable to another, only the reference will be copied, not the array or the hash table. That could result in the following unexpected behavior:

$array1 = 1,2,3
$array2 = $array1
$array2[0] = 99
$array1[0]
99

 Although the contents of $array2 were changed in this example, this affects $array1 as well, because they are both identical. The variables $array1 and $array2 internally reference the same storage area. Therefore, you have to create a copy if you want to copy arrays or hash tables,:

$array1 = 1,2,3
$array2 = $array1.Clone()
$array2[0] = 99
$array1[0]
1

Whenever you add new elements to an array (or a hash table) or remove existing ones, a copy action takes place automatically in the background and its results are stored in a new array or hash table. The following example clearly shows the consequences:

# Create array and store pointer to array in $array2:
$array1 = 1,2,3
$array2 = $array1
# Assign a new element to $array2. A new array is created in the process and stored
in $array2:
$array2 += 4
$array2[0]=99
# $array1 continues to point to the old array:
$array1[0]
1

Strongly Typed Arrays

Arrays are typically polymorphic: you can store any type of value you want in any element. PowerShell automatically selects the appropriate type for each element. If you want to limit the type of data that can be stored in an array, use “strong typing” and predefine a particular type. You should specify the desired variable type in square brackets. You also specify an open and closed square bracket behind the variable type because this is an array and not a normal variable:

# Create a strongly typed array that can store whole numbers only:
[int[]]$array = 1,2,3
# Everything that can be converted into a number is allowed
# (including strings):
$array += 4
$array += 12.56
$array += "123"
# If a value cannot be converted into a whole number, an error
# will be reported:
$array += "Hello"
The value "Hello" cannot be converted into the type "System.Int32".
Error: "Input string was not in a correct format."
At line:1 char:6
+ $array <<<< += "Hello"

In the example, $array was defined as an array of the Integer  type. Now, the array is able to store only whole numbers. If you try to store values in it that cannot be turned into whole numbers, an error will be reported.

Summary

Arrays and hash tables can store as many separate elements as you like. Arrays assign a sequential index number to elements that always begin at 0. Hash tables in contrast use a key name. That’s why every element in hash tables consists of a key-value pair.

You create new arrays with @(Element1, Element2, …). You can also leave out @() for arrays and only use the comma operator. You create new hash tables with @{key1=value1;key2=value2; …). @{} must always be specified for hash tables. Semi-colons by themselves are not sufficient to create a new hash table. You can address single elements of an array or hash able by using square brackets. Specify either the index number (for arrays) or the key (for hash tables) of the desired element in the square brackets. Using this approach you can select and retrieve several elements at the same time.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Powered by WordPress.com.

Up ↑

%d bloggers like this: