37

I'm trying to loop through a hash table and set the value of each key to 5 and PowerShell gives an error:

$myHash = @{}
$myHash["a"] = 1
$myHash["b"] = 2
$myHash["c"] = 3

foreach($key in $myHash.keys){
    $myHash[$key] = 5
}

An error occurred while enumerating through a collection:

Collection was modified; enumeration operation may not execute..
At line:1 char:8
+ foreach <<<< ($key in $myHash.keys){
    + CategoryInfo          : InvalidOperation: (System.Collecti...tableEnumer
   ator:HashtableEnumerator) [], RuntimeException
    + FullyQualifiedErrorId : BadEnumeration

What gives and how do I resolve this problem?

1

10 Answers 10

56

You can't modify Hashtable while enumerating it. This is what you can do:

$myHash = @{}
$myHash["a"] = 1
$myHash["b"] = 2
$myHash["c"] = 3

$myHash = $myHash.keys | foreach{$r=@{}}{$r[$_] = 5}{$r}

Edit 1

Is this any simpler for you:

$myHash = @{}
$myHash["a"] = 1
$myHash["b"] = 2
$myHash["c"] = 3

foreach($key in $($myHash.keys)){
    $myHash[$key] = 5
}
Sign up to request clarification or add additional context in comments.

8 Comments

Thanks zespri ---- that's brutally painful for what should be a straight forward operation....
Powershell is very flexible. There are many ways to accomplish the same thing. Admittedly, it takes time to learn, but once you are there this kind of stuff comes easy. I edited my answer to provide a way that may appear more intuitive for you. This resembles more the format you put it in originally.
This is the nature of enumerators. If you change the collection while you are enumerating, bad things could happen. Like skipping items, getting the same item twice, or worse.
@JasonMArcher: now I agree with you, but just for arguments sake, don't you think, that what you've described would be likely to happen if you add / remove items in collection while iterating. This logically should not be a problem for substituting one value to another? On the second thought, with Hashtable, changing value, means that the stored object might need to move to another bucket, and keeping this in mind, this behaviour can make sense.
Yes, but it is easier to just not allow any modification, rather than try to figure out if the modification might be a problem or not.
|
21

There is a much simpler way of achieving this. You cannot change the value of a hashtable while enumerating it because of the fact that it's a reference type variable. It's exactly the same story in .NET.

Use the following syntax to get around it. We are converting the keys collection into a basic array using the @() notation. We make a copy of the keys collection, and reference that array instead which means we can now edit the hashtable.

$myHash = @{}
$myHash["a"] = 1
$myHash["b"] = 2
$myHash["c"] = 3

foreach($key in @($myHash.keys)){
    $myHash[$key] = 5
}

2 Comments

Re: ` It's exactly the same story in .Net.` ... Powershell IS .NET. Just sayin.
Thank you for describing why you used the @() operator. It appears in another answer but they didn't explain what was going on.
10

You do not need to clone the whole hashtable for this example. Just enumerating the key collection by forcing it to an array @(...) is enough:

foreach($key in @($myHash.keys)) {...

1 Comment

or we can use clone of keys (very similar)foreach($key in @($myHash.keys.clone()){..
5

Use clone:

foreach($key in ($myHash.clone()).keys){
    $myHash[$key] = 5
}

Or in the one-liner:

$myHash = ($myHash.clone()).keys | % {} {$myHash[$_] = 5} {$myHash}

3 Comments

Can someone explain why I got -2 recently? It would be helpful.
this works. thanks. can you explain why the hashtable needs to be cloned before modifying the value
(Powershell v2 don't know for later versions) you cannot modify an hashtable while enumerating on it.
3

I'm new to PowerShell, but I'm quite a fan of using in-built functions, because I find it more readable. This is how I would tackle the problem, using GetEnumerator and Clone. This approach also allows one to reference to the existing hash values ($_.value) for modifying purposes.

$myHash = @{}
$myHash["a"] = 1 
$myHash["b"] = 2
$myHash["c"] = 3

$myHash.Clone().GetEnumerator() | foreach-object {$myHash.Set_Item($_.key, 5)}

Comments

3

You have to get creative!

$myHash = @{}
$myHash["a"] = 1
$myHash["b"] = 2
$myHash["c"] = 3

$keys = @()
[array] $keys = $myHash.keys

foreach($key in $keys)
{
    $myHash.Set_Item($key, 5)
}

$myHash

Name                         Value
----                         -----
c                              5
a                              5
b                              5

1 Comment

You could improve the answer explaining why the OP script doesn't work.
0

As mentioned in a previous answer, clone is the way to go. I had a need to replace any null values in a hash with "Unknown" nd this one-liner does the job.

($record.Clone()).keys | %{if ($record.$_ -eq $null) {$record.$_ = "Unknown"}}

Comments

0
$myHash = @{
    Americas = 0;
    Asia = 0;
    Europe = 0;
}

$countries = @("Americas", "Asia", "Europe", "Americas", "Asia")

foreach($key in $($myHash.Keys))
{
    foreach($Country in $countries)
    {
        if($key -eq $Country)
        {
            $myHash[$key] += 1
        }
    }
}

$myHash

1 Comment

Update Hash value when array and hash key has same value by one. The above code is works fine for me.
0
$myHash = @{
    Americas = 0;
    Asia = 0;
    Europe = 0;
}

$countries = @("Americas", "Asia", "Europe", "Americas", "Asia")

foreach($key in $($myHash.Keys))
{
    foreach($Country in $countries)
    {
        if($key -eq $Country)
        {
            $myHash[$key] += 1
        }
    }
}

Updating a hash value if array elements matched with a hash key.

Comments

-1

It seems when you update the hash table inside the foreach loop, the enumerator invalidates itself. I got around this by populating a new hash table:

$myHash = @{}
$myHash["a"] = 1
$myHash["b"] = 2
$myHash["c"] = 3

$newHash = @{}
foreach($key in $myHash.keys){
    $newHash[$key] = 5
}
$myHash = $newHash

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.