Removing Objects from Arrays in PowerShell

 

A member of the PowerShell group on Facebook asked how to delete an object from an array. It’s a simple question, but the answer isn’t very simple at all. It’s one of those beginner questions that has an advanced answer.

When I first answered this question in about_Arrays, I didn’t really know much about the subject and I used the (very good) answer that the team gave me. But that answer was incomplete, and while there are many good posts on the topic, none really cover the whole scope of pitfalls.

[Be sure to start with about_Arrays. I’ll assume that you’ve read it and won’t repeat the basics of arrays that are described there.]

 

Why is removing difficult?

Removing objects from arrays should be simple, but the default collection type in Windows PowerShell, an object array (System.Object[]), has a fixed size. You can change objects in the array, but you can’t add or delete them, because that would change the size of the array.

Let’s look at the problem. I’ll create an array called $letters and save a few letters in the array.

 $letters = "a", "b", "c", "d"

 

Now, I’ll try to remove the “c” from the array.

    $letters = $letters - "c"

Method invocation failed because [System.Object[]] does not contain a method named ‘op_Subtraction’.
At line:1 char:2
+  $letters = $letters – “c”
+  ~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : InvalidOperation: (op_Subtraction:String) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound

That didn’t work, because object arrays [System.Object[]) don’t have a subtraction (op_Subtraction) method. Let’s find the methods that System.Object[] supports.

Typically, to get the type, methods, and properties of an object, you pipe it to the Get-Member cmdlet. But, when you pipe a collection of objects to Get-Member, Get-Member gets the members of objects in the collection. To get the members of the collection (not the objects that it contains), use its InputObject parameter.

 

image

This command reveals that object arrays, like the one in the $letters variable have a Remove method. Let’s try the Remove method.

image

Remove fails, because object arrays are a fixed size.

This is where beginners get stuck and even experienced users hit Google, Facebook, or a PowerShell forum for help.

Create a new array

One way to remove an object from a fixed-size array is to create a new array that includes only selected objects from the original array. In this command, we use a ForEach loop go through every letter in the $letters array. We add all of the objects in $letters to the $newLetters array, except for “c”. To add objects to the new array, use the + or += operators; the statements are equivalent.

    $newLetters = @()
    foreach ($letter in $letters)
    {
        if ($letter -ne "c")
        {
            #$newLetters = $newLetters + $letter
            $newLetters = $newLetters += $letter
        }
    }

PS C:\> $newLetters
a
b
d

 

Interestingly, when we use the + or += operators, Windows PowerShell actually creates a new array each time in the background, because you can’t change the size of array. Sadly, there is no –= operator. Also, in this very simple example, it’s easier use the Where-Object cmdlet to filter out the “c”, but we can assume that you’re using an array for a more complex task.

$newLetters = $letters | Where-Object { $_ne "c" }

 

You can also use array index notation to exclude items from the new array. In this command, we add the items at indexes 0, 1, and 3 from $letters to the $newLetters array, but we exclude the item at index 2.

    $newLetters = $letters[0, 1, 3]

 

You can reuse the $letters variable name, but you are still creating a new $letters array that is independent of the original one.

    $letters = $letters[0, 1, 3]

 

 

 

 

Use an ArrayList

My favorite solution to the “collection was of a fixed size” problem is to create an ArrayList (System.Collections.ArrayList), instead of the default object array (System.Object[]). ArrayLists behave like arrays, but unlike arrays, ArrayList objects don’t have a fixed size.

To create an array list, cast the variable:

    [System.Collections.ArrayList]$caps = "A", "B", "C", "D"

 

Or, cast the objects. (Don’t forget the parentheses.)

    $caps = [System.Collections.ArrayList]("A", "B", "C", "D")

 

Use the InputObject parameter of Get-Member to get the properties and methods of an array list. There’s a Remove method of ArrayList objects and this one works.

image

 

 

Remove objects from an array list

As the help shows, the Remove method of an ArrayList removes the first instance of the item from the array list. It returns Void, which means nothing. The Remove method changes the original array list. It doesn’t return a new array list.

image

PS C:\ps-test> $caps
A
B
D

 

To remove multiple instances of an object, use a While loop.

    [System.Collections.ArrayList]$caps = "A", "B", "C", "D", "C", "E"
    while ($caps -contains "C") {
        $caps.Remove("C")
    }

You can also use a ForEach loop on an array list, but you can’t remove objects from the array list that you’re traversing with ForEach. In the background, ForEach uses positional indexes and the position of each object in an array list (or any collection) changes when you remove an object.

In the following example, the code uses a ForEach loop to remove the svchost processes from an array list of processes.

    [System.Collections.ArrayList]$p = Get-Process
    foreach ($process in $p)
    {
        if ($process.Name -eq "svchost")
        {
            $p.Remove($process)
        }
    }

Collection was modified; enumeration operation may not execute.
At line:1 char:10
+ foreach ($process in $p)
+ ~~~~
+ CategoryInfo          : OperationStopped: (:) [], InvalidOperationException
+ FullyQualifiedErrorId : System.InvalidOperationException

 

Instead, create an array of svchost processes ($s) and use a ForEach loop to remove each svchost process from $p. Because ForEach is traversing the $s array, you can use it to remove objects from $p.

    [System.Collections.ArrayList]$p = Get-Process
    $s = Get-Process | where ProcessName -eq "svchost"
    foreach ($svchost in $s)
    {
        $p.Remove($svchost)
    }

 

RemoveAt: Remove Objects by Index

You can also use the RemoveAt method of ArrayLists to remove objects. RemoveAt takes the index of the object that you want to remove. In this example, we’ll use a value of 2, which is the index of “C”.

image

PS C:\ps-test> $caps
A
B
D
E

 

It was easy to find the index of “C” in such a small array list, but it you have a larger array list, you can use the IndexOf method to find the index and then use the RemoveAt method to remove the object at that index. You can call the methods in two separate commands or in a single command.

 

    $index = $caps.IndexOf("C")
    $caps.RemoveAt($index)

-or-

    $caps.RemoveAt($caps.IndexOf("C"))

To use the RemoveAt method to remove multiple objects, use a For loop. The For loop is well suited to this task because it use indexes.

But, be careful! When you remove an object from a collection, you change the indexes of every subsequent object in the collection. For example, in the following array list, we want to remove the “C” objects at indexes 2, 3, and 4. But after we remove the “C” object at index 2, the index of the second “C” object changes from 3 to 2. If we advance the index from 2 to 3, we’ll miss the “C” that is now at index 2.

image

 

To compensate, whenever you remove an object, decrement the index by 1. You can use any notation to reduce it’s value by one:  $i = $i – 1, $i –= 1, or $i–.

    [System.Collections.ArrayList]$caps = "A", "B", "C", "D", "C", "E"
    for ($i = 0; $i -lt $caps.count; $i++)
    {
        if ($caps[$i] -eq "C")
        {
            $caps.RemoveAt($i)
            $i--
        }
    }

 

Here’s the same technique used to remove the svchost processes from the $p array list.

    [System.Collections.ArrayList]$p = Get-Process
    for ($i = 0; $i -lt $p.Count; $i++)
    {
        if ($p[$i].Name -eq "svchost")
        {
            $p.RemoveAt($i)
            $i--
        }    
    }

 

Summary

For most scripts, you don’t manipulate arrays directly. Instead, you use Where-Object to create a new collection that includes only the items that you need. But if you need to manage an array directly, you can add and remove objects.

June Blender is a technology evangelist at SAPIEN Technologies, Inc. You can reach her at juneb@sapien.com or follow her on Twitter at @juneb_get_help.