Foreach loop ends after first iteration

Hi All,

I have written a function, which simply resolve %XYZ% tags within strings with existing environment or powershell variables.

Using this function in a foreach loop causes the foreach loop to end after the first element completed.
Using this function in a while loop iterates correctly through the elements.
Why does the foreach loop end after the first element?

The powershell version I am using is as follows:

Name                           Value                                                                                                                                                                                 
----                           -----                                                                                                                                                                                 
PSVersion                      5.1.19041.1682                                                                                                                                                                        
PSEdition                      Desktop                                                                                                                                                                               
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}                                                                                                                                                               
BuildVersion                   10.0.19041.1682                                                                                                                                                                       
CLRVersion                     4.0.30319.42000                                                                                                                                                                       
WSManStackVersion              3.0                                                                                                                                                                                   
PSRemotingProtocolVersion      2.3                                                                                                                                                                                   
SerializationVersion           1.1.0.1    

My code to verfiy / replicate the issue looks as follows:

Function Resolve-Variable{
    PARAM([Parameter(Mandatory=$true,HelpMessage="Variable value to resolve")][string]$Value)
    Get-ChildItem -Path ENV: | %{$Value=$($Value -replace $('%'+$($_.Name)+'%'),$_.Value)}
    Get-Variable | Where-Object {$($_.Name.Length -gt 1) -and ($_.Name -ne 'Value')} | %{$Value=$($Value -replace $('%'+$($_.Name)+'%'),$_.Value)}
    return $Value}

$MYVAR='TESTVariable'
$variables=@('%SYSTEMDRIVE%\Windows','%WINDIR%','%MYVAR%')
clear

#loops through all iterations
foreach($value in $variables){
    Write-host "FOREACH LOOP: $value" -ForegroundColor Green}

#ends after first iteration
foreach($value in $variables){
    $v=Resolve-Variable -Value $value
    Write-host "FOREACH: $value -> $v" -ForegroundColor Red}

#loops through all iterations
$i=0;While($i -lt $variables.Length){$value=$variables[$i];$i++
    $v=Resolve-Variable -Value $value
    Write-host "WHILE: $value -> $v" -ForegroundColor Yellow}

The output is the following:

FOREACH LOOP: %SYSTEMDRIVE%\Windows
FOREACH LOOP: %WINDIR%
FOREACH LOOP: %MYVAR%
FOREACH: %SYSTEMDRIVE%\Windows -> C:\Windows
WHILE: %SYSTEMDRIVE%\Windows -> C:\Windows
WHILE: %WINDIR% -> C:\Windows
WHILE: %MYVAR% -> TESTVariable

Expected output would be:

FOREACH LOOP: %SYSTEMDRIVE%\Windows
FOREACH LOOP: %WINDIR%
FOREACH LOOP: %MYVAR%
FOREACH: %SYSTEMDRIVE%\Windows -> C:\Windows
FOREACH: %WINDIR% -> C:\Windows
FOREACH: %MYVAR% -> TESTVariable
WHILE: %SYSTEMDRIVE%\Windows -> C:\Windows
WHILE: %WINDIR% -> C:\Windows
WHILE: %MYVAR% -> TESTVariable

Thanks for your hints in advance :slight_smile:

Hi.

You may want to put a Write-Host $value in the Function. From what I’m seeing there is only one listing, for 'C:\Windows". When I put a Write-Host $value in the foreach loop it showed the $value to be “C:\Windows 21”.

Just a suggestion from an old maintanence programmer. :smiley:

-Jim

Try removing any return statements.

Thanks @JClunie & @krzydoug for your replies.
Below the modified code - unfortunately same result :frowning:
…I really do not get where the difference is

@JClunie: Not sure where the “21” within your tests come - I cannot see the same behaviour…

modified test code:

Function Resolve-Variable{
    PARAM([Parameter(Mandatory=$true,HelpMessage="Variable value to resolve")][string]$Value)
    Get-ChildItem -Path ENV: | %{$Value=$($Value -replace $('%'+$($_.Name)+'%'),$_.Value)}
    Get-Variable | Where-Object {$($_.Name.Length -gt 1) -and ($_.Name -ne 'Value')} | %{$Value=$($Value -replace $('%'+$($_.Name)+'%'),$_.Value)}
    Write-Host "VALUE: $Value"
    $Value}

$MYVAR='TESTVariable'
$variables=@('%SYSTEMDRIVE%\Windows','%WINDIR%','%MYVAR%')
clear

#loops through all iterations
foreach($value in $variables){
    Write-host "FOREACH LOOP: $value" -ForegroundColor Green}

#ends after first iteration
foreach($value in $variables){
    $v=Resolve-Variable -Value $value
    Write-host "FOREACH: $value -> $v" -ForegroundColor Red}

#loops through all iterations
$i=0;While($i -lt $variables.Length){$value=$variables[$i];$i++
    $v=Resolve-Variable -Value $value
    Write-host "WHILE: $value -> $v" -ForegroundColor Yellow}

Output of modified code:

FOREACH LOOP: %SYSTEMDRIVE%\Windows
FOREACH LOOP: %WINDIR%
FOREACH LOOP: %MYVAR%
VALUE: C:\Windows
FOREACH: %SYSTEMDRIVE%\Windows -> C:\Windows
VALUE: C:\Windows
WHILE: %SYSTEMDRIVE%\Windows -> C:\Windows
VALUE: C:\Windows
WHILE: %WINDIR% -> C:\Windows
VALUE: TESTVariable
WHILE: %MYVAR% -> TESTVariable

Ok … I found the reason - even tough I could not find something detailed about this in Microsoft about_foreach documentation - only a very short mentioning.

As soon as a foreach loop is started, powershell creates a variable $foreach
This variable seems holds the outstanding items and removes them as soon as they are read.
My “Get-Variable” command reads the $foreach variable as a whole and thus cleans the outstanding items. The “WHILE” loop is independent of any variable to be read and thus works as expected.

The solution is to use the following exclusion with the “Get-Variable” command.

Get-Variable -Exclude foreach |....

Hope this helps other stumbling across this strange behavior :slight_smile:

1 Like