0

I have a script which I want to do a Test-Connection on a list of machines and then remove the offline machines from the list. This is what I have:

[System.Collections.ArrayList]$Systems = Get-Content -Path C:\temp\test.txt

Foreach ($System in $Systems){
  if ($false -eq (Test-Connection -CN $System -Count 1 -Quiet)) {
    $Systems.Remove("$System")
  } 
}
$Systems

This fails with this error:

Collection was modified; enumeration operation may not execute. At C:\temp\test.ps1:3 char:10 + Foreach ($System in $Systems){ + ~~~~~~~ + CategoryInfo : OperationStopped: (:) [], InvalidOperationException + FullyQualifiedErrorId : System.InvalidOperationException

Lance U. Matthews
  • 15,725
  • 6
  • 48
  • 68
IanB
  • 271
  • 3
  • 13

2 Answers2

1

You can't modify a collection ($Systems) while there is an enumeration in progress on that same collection (or, to be precise, you can't continue enumerating a collection after it's been modified). Instead of removing offline computers from an existing list, add online computers to a new list.

[System.Collections.ArrayList]$Systems = Get-Content -Path C:\temp\test.txt
[System.Collections.ArrayList]$OnlineSystems = New-Object -TypeName 'System.Collections.ArrayList'

Foreach ($System in $Systems){
  if ($true -eq (Test-Connection -CN $System -Count 1 -Quiet)) {
    $OnlineSystems.Add("$System")
  } 
}
$OnlineSystems

Note that you don't need to compare an expression to a boolean when it already evaluates to a boolean, and you don't need to use string interpolation on a variable that is already a [String], so you can simplify the body of the Foreach loop to...

if (Test-Connection -CN $System -Count 1 -Quiet) {
    $OnlineSystems.Add($System)
} 

Even more concise code that avoids the enumeration conflict entirely would be to use the pipeline and Where-Object cmdlet...

[String[]] $OnlineSystems = @($Systems | Where-Object { Test-Connection -CN $_ -Count 1 -Quiet })
Lance U. Matthews
  • 15,725
  • 6
  • 48
  • 68
0

This happens because modifying a collection while iterating it is not allowed. The underlying reasons are a bit complex, but well explained in other answers.

To change a collection, foreach won't do but a for loop does. ArrayList's RemoveAt() will remove elements by index. Like so,

for($i = 0; $i -lt $Systems.Count;  ++$i) {
    if( -not (Test-Connection -CN $systems[$i] -Count 1 -Quiet)) {
        $systems.RemoveAt($i)
    }
}
vonPryz
  • 22,996
  • 7
  • 54
  • 65
  • Thank you all! So many options here and I have a solution now. I have also now learnt that modifying a collection while iterating it is not allowed! – IanB Nov 27 '19 at 06:35