how to go to the next item in a For loop in PowerShell
I got the following PowerShell question by mail :
Hello there, I am a System Engineer / System Admin who has been using Powershell to automate some tasks, and the one thing I can’t seem to find anywhere is documentation on how to go to the next item in a For loop. Here is an example:
Foreach
($line in $inFile)
{
If ($line –eq “”) { go to the next item in the for loop}
Write-host “This is not a blank line”
}
How is this accomplished in Powershell?
Best Regards,
EM (removed full name)
It is a bit tricky to find how to do this, but when we know how it is easy, there is a special variable $foreach in PowerShell that is filled when we use the Foreach Statement ( Note ! only the foreach statement does provide this $foreach variable not the Foreach-Object Cmdlet we use in the pipeline ! ) Bruce Payette does a good explanation in his book : PowerShell in Action , but when you did not read or have his book it's hard to search for and easy to overlook,
So I decided to answer his question on my blog.
I made 3 sample scripts to show how this works, ( you can just past the code into the PowerShell console to try the examples (see output below) :
* edit * I added another script to show how you can do the same thing with the foreach-object Cmdlet, and corrected my former solution that was incorrect.
First I made a script to show the members of the $foreach variable to be able to explore it, as it is filled by the foreach statement we need to create a loop first, that enables us to take a look at the content of the $foreach variable , ( note also the comma added to prevent the variable from enumerating ! )
Foreach ($i in 1) {
,$foreach | get-member
}
In the output we can see that the variable contains an object that contains the array we loop trough wrapped by an enumerator ( hence the comma was needed to show it )
TypeName: System.Array+SZArrayEnumerator
that we can use and that exposes the following functionality
MoveNext (Method)
Reset (Method)
ToString (Method)
Current (Property)
We can use the MoveNext method for our task in the following example I use it to skip the even numbers in a loop from one to ten:
* Edit * Warning ! this is actually a bad usage example, as it contains a bug and does NOT show the right way to do this, see the correct examples and explanation of the mistake I did make that I added in another edit you can find later in this post
Foreach ($i in 1..10) {
if ($i%2) {$foreach.moveNext()}
$i
}
As you see in the results a boolean is returned to indicate if the MoveNext() suceeded, as this is not wanted most of the times I cast it to [void] to disregard this information ( the out-Null cmdlet can also be used for this)
Foreach ($i in 1..10) {
if ($i%2) {[void]$foreach.moveNext()}
$i
}
Below you can see the results of pasting in the code above into the PowerShell Console :
[PoSH]> Foreach ($i in 1) {
>> ,$foreach | get-member
>> }
>>
TypeName: System.Array+SZArrayEnumerator
Name MemberType Definition
---- ---------- ----------
Clone Method System.Object Clone()
Equals Method System.Boolean Equals(Object obj)
GetHashCode Method System.Int32 GetHashCode()
GetType Method System.Type GetType()
get_Current Method System.Object get_Current()
MoveNext Method System.Boolean MoveNext()
Reset Method System.Void Reset()
ToString Method System.String ToString()
Current Property System.Object Current {get;}
[PoSH]>
[PoSH]> Foreach ($i in 1..10) {
>> if ($i%2) {$foreach.moveNext()}
>> $i
>> }
>>
True
1
True
3
True
5
True
7
True
9
[PoSH]> Foreach ($i in 1..10) {
>> if ($i%2) {[void]$foreach.moveNext()}
>> $i
>> }
>>
1
3
5
7
9
[PoSH]>
*edit* When I did make a foreach-object example, I found a bug in the examples given above in the even/odd logic used above and gave a wrong usage example, even as the output does seem to be correct.
did you spot the error in the logic allready ?
Using Foreach Object
let's start the search for the error I made with looking at the foreach-object example I created showing how to do the same thing with foreach-object :
1..10 |% {
if ($_%2) {return}
$_
}
We have no $foreach variable, but as foreach-object calls the provided scriptblock for every object in the pipeline, we can just use the return command to return from the scriptblock without proccessing the rest of the scriptblock for the current object and continue the loop.
but when we look at the output we see the results are different
[PoSH]> 1..10 |% {
>> if ($_%2) {return}
>> $_
>> }
>>
2
4
6
8
10
[PoSH]>
This version does show the even numbers and does skip the Odd numbers the exact opposite of my former foreach statement example.
As I did expect the result to be the same one of the two solutions had to be wrong, and on further examination it did turn out to be the former example using the foreach statement that was incorrect, as we can see if we start the range at an even number .
[PoSH]> Foreach ($i in 2..10) {
>> if ($i%2) {[void]$foreach.moveNext()}
>> $i
>> }
>>
2
3
5
7
9
[PoSH]>
We can see that in this case, the result of my solution is incorrect but as the reason behind this might still not be obvious
as a last hint before the spoiler, a correct solution in this case would have been :
[PoSH]> Foreach ($i in 1..10) {
>> if ($i%2) {[void]$foreach.moveNext()}
>> $foreach.current
>> }
>>
2
4
6
8
10
[PoSH]>
The $i variable did not get updated by the call to MoveNext() and we need to use $foreach.current to get the object that actualy is current.
The reason behind this and the thing to keep in mind when using the MoveNext Method is that
$foreach.moveNext() does NOT skip the rest of the code for the current loop at all and does NOT start a new loop to proccess either,
This is the reason that $i was not updated and actualy the old value still was returned, at the next loop the $i variable was updated again so that in reallity 2 items where skipped instead of one item as intended, as no new loop is started , the variable also $i is not updated hence it still contains the former item, while only $foreach.current is updated ,giving the false impression that all did work correctly.
I hope these examples, next to answering the question also show that in this case the variable $foreach might a bit hard to find if you do not know that it is there (as it is only there inside the loop itself so we can not use the variable:\ drive to find it ) but that after we know that it is there we can use the interactive nature of PowerShell to discover how this variable works and how we can use it !
Enjoy,
Greetings /\/\o\/\/