r/PowerShell 18d ago

Solved Would someone mind helping me understand how "foreach ($computer in $computers)" works?

I know foreach iterates through an array, of sorts. I'm just wondering, since I didn't define $computer, how does it know to single out each individual item from my $computers array? Is it a predefined variable, or could I just place anything there? Like "foreach ($x in $computers)".

$computers = get-adcomputer ##just an example that the latter variable was defined by me, but $computer was not.

I think I convinced myself that I'm right in thinking I could place anything there, like $x or $flapjacks or whatever, but some confirmation would be nice. And if I'm wrong then being corrected is even more welcome. Thank you.

68 Upvotes

86 comments sorted by

62

u/TheEdonReddit 18d ago

You are correct. You are defining it there and can name it just about whatever you want.

47

u/sysiphean 18d ago

Exactly this. So have fun with it!

foreach ($💻 in $Computers) {}
Foreach ($🤷🏻‍♂️ in $Users) {}
Foreach ($😁 in $Beers) {}

13

u/bahbahbahbahbah 18d ago edited 18d ago

So question because I’ve been wondering this since I started my poweshell experience: are we basically saying foreach OBJECT in the array? And by object I mean, each line, say, in a csv?

And then a follow up question, $_ is the current object we’re iterating through? Is it the same as using whatever we specified in the foreach? So example, $_.Samaccountname is the same is $computer.samaccountname?

14

u/Stashmouth 18d ago

this is right. you're telling PS to perform the same action on each item in the array.

10

u/sysiphean 18d ago

Not quite, but almost.

$_ and $PSItem (same thing) are for when the “do thing for each item on this set” is run without specifying the name. This works when you pipe to ForEach-Object, or run the .ForEach{} built-in method of an array, or inside a Switch statement, or a bunch of other spots.

But when you run foreach ($Thing in $Things) {} you are opting to use a name *other than* $_/$PSItem. (You are also opting not to pass to the pipeline among other things.)

6

u/grahamfreeman 18d ago

If you named the object then no, you can't use $_ you can only use the name.

Foreach ($computer in $computers) ... you can't use $_ you must use $computer

If you're using Foreach-object, or piping, then you can use $_

Also note, in my experience the object's property names ARE case-sensitive, so in your example if there's an object property $computer.samaccountname you wouldn't be able to reference it using $_.Samaccountname

5

u/Kirsh1793 18d ago

In general, PowerShell is case-insensitive. BUT, when using hashtables, the keys are case-sensitive. For comparisons there are specific operators if you need case-sensitivity.

9

u/surfingoldelephant 18d ago

Hash table literals are case-insensitive (like y_Sensei showed).

Other sources may or may not be depending on the source. ConvertFrom-Json -AsHashtable in PS v7.3+ for example treats JSON as case-sensitive and emits a case-sensitive Management.Automation.OrderedHashtable. But if you omit -AsHashtable, the JSON is treated as case-insensitive.

If you're interested, I spent quite some time documenting PS case-sensitivity (most of those findings are now reflected in about_Case-Sensitivity).

4

u/Kirsh1793 18d ago

Illuminating! I wasn't aware how confusing it all gets when you dig deep. PowerShell scripting is my daily job, yet I still learn something new all the time.

6

u/y_Sensei 18d ago

Not exactly.

When instantiating a Hashtable through PoSh, the keys are case-insensitive.

When instantiating one via .NET using the default (empty) constructor, the keys are case-sensitive.

This difference is most likely caused by PoSh instantiating a Hashtable using one of the constructors that require an IEqualityComparer as parameter (for example Hashtable(IEqualityComparer)), and that parameter defines case-insensitivity, behind the scenes.

Check this:

$ht_posh = @{
  "Key1" = "Val1"
  "kEy2" = "Val2"
  "KeY3" = "Val3"
}

Write-Host $ht_posh.GetType().FullName # prints: System.Collections.Hashtable

Write-Host $ht_posh.key1 # prints: Val1
Write-Host $ht_posh.key2 # prints: Val2
Write-Host $ht_posh.key3 # prints: Val3

Write-Host $("-" * 32)

$ht_net = [Hashtable]::New()
$ht_net.Add("Key1", "Val1")
$ht_net.Add("kEy2", "Val2")
$ht_net.Add("KeY3", "Val3")

Write-Host $ht_net.GetType().FullName # prints: System.Collections.Hashtable

Write-Host $ht_net.key1 # prints: (nothing)
Write-Host $ht_net.key2 # prints: (nothing)
Write-Host $ht_net.KeY3 # prints: Val3

1

u/Kirsh1793 18d ago

I simply noticed one instance where a hashtable had case-sensitivity and assumed it to be always the case. Thanks for pointing this out!

2

u/StartAutomating 16d ago

Don't worry: it's a safer assumption to make.

Additionally, there are quite a few modules / cmdlets out there that create Hashtables this way, because it's an easy mistake to make (especially if you're a C# person writing PowerShell cmdlets).

There's a pretty easy fix if you run into one of these:

Just create a new hashtable and use the also lesser-known PowerShell trick of adding hashtables together.

``` $ht_posh = @{ "Key1" = "Val1" "kEy2" = "Val2" "KeY3" = "Val3" }

$ht_posh.GetType().FullName # prints: System.Collections.Hashtable

$ht_posh.key1 # prints: Val1 $ht_posh.key2 # prints: Val2 $ht_posh.key3 # prints: Val3

$("-" * 32)

$ht_net = [Hashtable]::New() $ht_net.Add("Key1", "Val1") $ht_net.Add("kEy2", "Val2") $ht_net.Add("KeY3", "Val3")

$ht_net.GetType().FullName # prints: System.Collections.Hashtable

$ht_net.key1 # prints: (nothing) $ht_net.key2 # prints: (nothing) $ht_net.KeY3 # prints: Val3 $("-" * 32)

Add the case-sensitive hashtable to a case-insensitive hashtable

$ht_add = @{} + $ht_net

And now it works

$ht_add.GetType().FullName

$ht_add.key1 $ht_add.key2 $ht_add.key3 ```

Took out the Write-Hosts in the previous example, because we don't need them.

2

u/Mr_Kill3r 15d ago

Oh, lets not open the write-host can of worms.

2

u/GnuInformation 14d ago

I think 10 or so years I been messing with power shell, and I learned answers to what does $_ do and mean. I have searched for probably dozen of times , and countless blogs, and I don't think anyone explicitly explained that. * You and the comment above, thank you for that clarification. Soo many scripts I have expanded out into possibly more complicated format.

1

u/BlackV 17d ago

yes and the is a perfect example, each $row in a $csv, each $item in $array, each $computer in $allcomputers

$CSVData = import-csv -path xxx.csv
$results = foreach ($Row in $csvdata){
    do-task $row.propertyxx
    }
$results
xxx, chaged
yyy, unchanged

1

u/366df 14d ago

using a placeholder variables in a loop going through arrays or json isn't totally unfamiliar to me, but what I don't get is, at least in this context is that how does powershell know how to read the data?

are they placeholders variables or something powershell has built in? because the data structure seems slightly different for a csv-file, an array, etc.

2

u/BlackV 13d ago edited 13d ago

All your objects have object types, and generally in an array they would be of 1 type

It's really the import-csv that is handling the "how to read data" bits

For the loop powershell has all the objects collected into the array called $csvdata, in the first step it assigned a single item to the variable $row and uses that so you can access the properties of the single object

It doesn't really care what the actual individual item is, that is were you want to start looking more into the properties of individual items

Er.. does that help

1

u/BirdsHaveUglyFeet 17d ago

I don't know if others agree but, I prefer and recommend for-eachobject.

It supports parallel processing and follows the PowerShell naming standards, which I think is pretty neat.

1

u/BlackV 13d ago

requires 7.x though

follows the PowerShell naming standards

what do you mean when you say this ?

1

u/BirdsHaveUglyFeet 13d ago

Yes PowerShell 7

It follows the verb-noun naming convention.

And you can pipe it

1

u/BlackV 13d ago

You just mean foreach-object literally follows verb noun?

1

u/BirdsHaveUglyFeet 12d ago

Yeah, the basics. Sorry for the typo.

1

u/BlackV 12d ago

Ha I can't call people out on typos I make a good 500 a day

4

u/purplemonkeymad 18d ago

${You can also have whatever you want as a variable name. Like almost this whole post! As long as use this syntax!} = "example"

1

u/StartAutomating 16d ago

This variable syntax is a good trick to know!

Related tricks:

``` ${function:Functions can be set using the provider} = {$myInvocation}

& "Functions can be set using the provider"

${function:Function Names Can Be Phrases} = {$myInvocation}

& "Functions can be set using the provider" & "Function Names Can Be Phrases" ```

Enjoy!

2

u/Creative-Type9411 18d ago

the second one made me laugh it might as well just be set to while(true)

1

u/sysiphean 18d ago

☺️

1

u/Zugas 18d ago

Can ps read emojis?!

7

u/Forward_Dark_7305 18d ago

If I start seeing emojis outside of `Write-*` arguments in PowerShell I’ll… well, I’ll… well… ☹️

6

u/ankokudaishogun 18d ago

You will have your Write-🤣 cmdlet and you will like it!

4

u/sysiphean 18d ago

Emoji are just UTF-8 characters, so yes. And they are not spaces, punctuation, or digits so they can be uses as the starting (or sole) character of a variable name.

I only use them as variables when I want to mess with someone.

2

u/StartAutomating 16d ago

Yes. Also, PowerShell Commands can be emoji

See this handy module I made for Emoji 😉

It lets you look up emoji and easily create emoji commands.

0

u/BlackV 17d ago

yes it can (kinda), should you do that?, no,, not unless you are an ass clown of the highest order

1

u/StartAutomating 16d ago

Or you happen to ship a crazy cool module that shares the name of a common of a common Emoji... 😉

This is why 🐢is an alias to Get-Turtle in Turtle

🤷 I may also be a clown of the highest order. That is for others to decide, not me. 🤷

BTW, if you want more 🐢, the examples page is kinda crazy. It may 🤯.

2

u/BlackV 16d ago

HA Valid!

12

u/mixduptransistor 18d ago

Yes, in the statement ($x in $y) the $x can be anything you want it to be. The $y has to be an actual array, of course, but what it is doing is for every run of the loop it's putting a single value of $y into $x. People name it similarly to the array to make the code readable but there's no requirement to do that

6

u/Trevski13 18d ago

Just to add to this, $y isn't limited to just arrays but can be any kind of collection: Array, List, Arraylist, Hashtable, Dictionary, Queue, Stack. If you want to get technical it can be anything that implements the IEnumerable interface (or to get really technical it can be anything that returns an enumerator via a GetEnumerator() method)

5

u/mrbiggbrain 18d ago

Let's get technical! Not everything that implements the interface because at least a few types have a short circuit that prevents it from working.

S /home/test> $string = "hello"                
PS /home/test> $string.GetEnumerator()
h
e
l
l
o
PS /home/test> foreach($char in $string) {$char}
hello

A string is enumerable and implements System.Collections.Generic.IEnumerable<char> so it has GetEnumerator(), yet it can not be looped over. This might seem odd at first but just imagine the chaos that the following would cause:

$array = Get-OneOrManyItems
Foreach($thing in $array) { <# Do Something #> }

If a single object is returned then $array is directly assigned a single object, one string.

If multiple objects are returned then $array would be an array of strings.

It is reasonable to assume what you want in nearly all cases is to perform the loop once for the single string. But if strings could be directly looped over the first example would loop over the chars, after all it would be a single object that implemented IEnumerable<T>.

This matters because other types might NOT be short circuited. Imagine if the cmdlet returned an array itself, not a bunch of items but an actual array. Instead of looping over an outer array you would loop over an inner array when a single item was returned.

The things I know of that are short circuited are Strings, Hashtables, and XML Nodes. I think all of these make sense for usability, but you can absolutely force them using various techniques.

3

u/surfingoldelephant 18d ago

$y can be an object of any type in PowerShell, so not just collections but scalars also. It can also be $null or AutomationNull (in which case, the {...} statement block just won't be entered).

$y = 1
foreach ($x in $y) { $x } # 1

PowerShell tries to unify collection/scalar handling. This way you can treat nothing, one object or multiple in the same manner if you want.

Also worth noting that strings, IDictionary and a few other types are special-cased as scalar in PowerShell, so if you want to iterate over the key/value pairs of a hash table or other dictionary type for example, you'd need to call GetEnumerator() explicitly.

2

u/ankokudaishogun 18d ago

It can also be $null or AutomationNull (in which case, the {...} statement block just won't be entered).

Which is incredibly useful when you loop possibly empty collections as you don't need to bother with the logic of checking up if the collection has elements.

1

u/StartAutomating 16d ago

foreach ($x in $y) {}

Is the tip of the iceberg.

foreach($process in Get-Process) { $process }

Works too!

So does a range:

foreach ($n in 1..10) { $n }

So does a subexpression:

``` foreach ($n in @(1..3;3..1)) {

} ```

We can spread it across multiple lines:

foreach ($n in @( 1..10 -1..-10 )) { $n }

What's way cooler is that we can assign the results of any expression:

$numbers = foreach ($n in @(1..3;3..1)) { $n } $numbers

Foreach is as overpowered as the rest of PowerShell.

3

u/InfoZk37 18d ago

Thank you, this makes a lot more sense now. I'm fairly new to powershell, but I did get that learn powershell on lunch book so when that comes in I'll be working on that.

11

u/nitroed02 18d ago

It's recommended to avoid using ($computer in $computers) and instead use something like ($computer in $computerList). This greatly reduces the likelihood of accidentally referencing the $computers aray inside the loop and do unexpected things.

3

u/InfoZk37 18d ago

Thank you, that's good advice. I have definitely made some simple typing mistakes before, and I'm sure I'll continue to do so in the future.

3

u/tingnossu 18d ago

yep you convinced yourself correctly, $computer is just a variable you're declaring on the fly for that loop's scope. $flapjacks works, $x works, literally anything valid. the naming convention of making it singular when your collection is plural ($computer in $computers, $user in $users) is just a readability habit the community gravitates toward, not a requirement.

3

u/coaster_coder 18d ago

The variable before in is a reference to the item from the array currently being processed. The foreach loop will execute one time for every object in the array. You can absolutely define this variable as anything you want. Typical patterns include a a single letter variable where the letter is the first letter of the variable that contains the array. In your example that would be $c as the current item being processed from the $computers.

The important thing to remember is that the thing to left of in is what is currently being evaluated and that the thing on the right of in is the thing the the left of in came from.

1

u/InfoZk37 18d ago

Thanks. That copied section is a bit confusing but your last part made a lot of sense.

3

u/PinchesTheCrab 18d ago edited 18d ago

Personally I'd rather see $tooth in $flapjacks because in addition to being funny, a simple typo can cause you to perform an action on the entire array.

Not all cmdlets accept an array, but some do, and you can perform destructive actions if you accidentally remove $files instead of $file.

2

u/InfoZk37 18d ago

Oh I'm not advanced enough for files yet, or moving things. I was just checking the SecureBootUEFIStatus for the new CA updates coming in June. Asking for information from powershell is all I'm worthy of right now.

E: I learned my lesson when I tried to disable all Windows 10 devices and accidentally disabled all devices. Thank God if you're already logged in you can still do work, so I was able to re-enable everything and then go from there.

2

u/An-kun 18d ago

Try running things once with -whatif added to anything that will do an actual change. Most cmdlets support it and it can save you your job.

1

u/InfoZk37 18d ago

Thanks for the tip. Do I just throw it at the end of the entire command, or does it have to follow a specific cmdlet?

2

u/gregortroll 18d ago

All of your questions so far are well answered in powershell built-in help (Get-help). (You may have to download it)

A great resource for powershell is SS64.com

It is a clean, easy to use website, to learn of specific commands, syntax, etc., and offers examples and explanations beyound the bare minimum in Get-Help.

Good luck.

1

u/dathar 18d ago

You can think of cmdlets like little CLI programs/tools/apps/whatever. Each one has their own set of arguments that you pass along depending on the author(s) of that cmdlet. Some are great and have extra features like -whatif or accept some global (to your terminal session) args like $ErrorActionPreference to control it. Some aren't that great. Help files or the author's published pages will help you figure out if something like that exists or if there's a certain way to use it. You know, if they managed to write one and keep it up to date with their stuff.

2

u/PinchesTheCrab 17d ago

One issue I have with telling beginners about whatif is that many developers, and even some first party modules, don't implement it correctly or at all.

It's just not a safe bet to assume it will work unless you're referring to a specific, familiar module.

1

u/dathar 17d ago

Yeah. That hurdle was what made it hard for me to grasp PowerShell early on back in the 2.0 days. I wanted to choke the Exchange modules back then. Once I finally figured and accepted that it isn't PowerShell being a dick and it is all singular modules doing things their own way, it finally clicked and all these different-working pieces made sense. And after that, I figured out how to write such cmdlets and functions of my own.

1

u/StartAutomating 16d ago

Sadly true. -WhatIf/-Confirm are some of the best things to support in any potentially sensitive or destructive scenario (and lots of Cmdlet developers did not get the memo, because long ago it stopped being my job to give them said memo).

I personally prefer -WhatIf to be more helpful, so I often use this pattern:

if ($WhatIfPreference) { return $ParametersToWhatIWouldDo } if (-not $psCmdlet.ShouldProcess('Should I do this?')) { return } Get-SomeStuff @parametersToWhatIWouldDo

This is especially useful in debugging/testing Invoke-RestMethod wrappers.

2

u/mrmattipants 18d ago edited 17d ago

I was doing the same, this morning, as I found that most of our computers had the KB5083769 Update, but don't have the 2023 Certificates, yet.

Personally, I've seen good results with the following script/method, as the Microsoft Scripts tend to be bloated and convoluted.

https://directaccess.richardhicks.com/2025/12/04/windows-secure-boot-uefi-certificates-expiring-june-2026/

https://github.com/richardhicks/uefi

3

u/branhama 18d ago

Variables in PowerShell are usually configurable. There are exceptions such as environment variables. For instance "$env:COMPUTERNAME", this variable is built into PowerShell as a system variable and is static. However based on your example the sky is the limit.

$myComps = ('comp1', 'comp2', 'comp3')

foreach ($singleComp in $myComps) { Write-Host $singleComp }

For the most part you always want to keep your variables self documented. For instance I would not use $x unless you are at the CLI. In scripts, if you want to make scripts others will use, always define your variable properly. $Computers or $Comps... If I look at code and all your variables are random, I will look right past your code and not use it. Makes troubleshooting problematic.

3

u/Fallingdamage 18d ago

foreach ($object in $array)

All you're doing is assigning a variable name to individual objects from the array. The machine doesn't care what you call it.

3

u/_litz 18d ago

You did define it. ForEach handles that for you.

As for its type, it derives that when each element of the array is, in sequence, assigned to it.

3

u/jaivibi 17d ago

Yep, you got it right, $computer is just a variable you're defining on the fly for that loop, not something PowerShell has predefined. You could write foreach ($flapjacks in $computers) and it works exactly the same way, PowerShell assigns, each item from the collection to whatever name you put there on each pass through the loop. The name only matters for readability, so picking something descriptive like $computer instead of $x just..

2

u/chaosphere_mk 18d ago

Yep, you could definitely do foreach($x in $computers). $x in this instance is you telling powershell how you want to refer to the current item within the foreach loop.

2

u/ca_269 18d ago

You’re right… you could name it anything you want. The variable is created for the loop, and on each iteration, an item from the array is assigned to that variable. As you said, you could use $x, but in my experience, it’s better to use a meaningful name, like:

foreach ($student in $classList) { $fn = $student.FirstName }

2

u/digital-plumber 18d ago

This might be a deeper answer than you're looking for, if so, sorry, but it should explain how foreach works internally, to the best of my knowlege.

firstly, the actual variable is defined as you reference it, as others have said.

PowerShell objects are .net framework objects, foreach in powershell is leveraging how arrays and collections work in .net

There's a thing called an interface, which is like a contract in that if something agrees to implement an interface, it must define the members (generally properties and methods) of that interface.

There are two key interfaces related to enumeration in .net
IEnumerator<T> and IEnumerable<T>, where T is a placeholder for the type of object being enumerated.

IEnumerable defines GetEnumerator, which just signals that a type can be enumerated, with the returned enumerator

IEnumerator defines the properties and methods for actually enumerating, in particular
T Current - gets the current objects
bool MoveNext() - Moves the enumerator to the next object in the collection (accessble through Current)

when you do a foreach in powershell, or .net, it's actually enumerating the collection using it's enumerator, which it gets by calling GetEnumerator

if you did this
foreach($x in $collection)
{
//Your code here.

}

It might look something like this in PowerShell if you did it manually
$e = $collection.GetEnumerator()
while($e.MoveNext()}
{
$x = $e.Current
//Your code here

}

Hopefully that helpful to someone.

2

u/bdanmo 18d ago

if you wanted, you could do foreach ($shartsicle in $computers) to keep your code fun and interesting for yourself and others

2

u/OK_it_guy 18d ago

I've never really thought about "how" it works, just know that it does; however, I salute you for not being afraid to ask something that we take for granted.

2

u/inperbio 17d ago

yep you nailed it, $computer is just a variable you're declaring on the fly right there in the loop syntax. powershell creates it automatically each iteration and assigns the current item to it. you could write foreach ($flapjacks in $computers) and it works exactly the same way, $flapjacks just holds each computer object as it cycles through.

2

u/HelloFelloTraveler 16d ago edited 16d ago

I always think of it as using a spreadsheet. Where $computers is the spreadsheet and $computer becomes a variable of each row as the for each loop goes through it.

So if you needed to use $computers.deviceName to get a list of all device names in it. You could also use $computer.deviceName to get the specific deviceName from each row as the loop goes through.

Each header(key) points to a value in that column.

If you’re using a list, the. You no longer use keys with your $computer variable, so then you’re just doing a loop through each comma separated value which is what you would get in your $computer variable.

You can make the variable name whatever you’d like it to be, it’s always grabbing the row/value, so you could make it foreach ($bob in $computers) and get the same results..

1

u/ShoeBillStorkeAZ 18d ago

For every a in your list of computers do this.

1

u/true_zero_ 18d ago

Your next task is to learn the -Parallel flag. Your CPU may get hammered while your loops run if you use -Throttle 10 , try a lower number to see how it works.

It’s in Powershell 7+ (pwsh.exe) which I highly recommend for its PSReadline predictonViewStyle alone which i have new hires configure to flatten the powershell learning curve.

Be careful with -Parallel and trying to update an outside variable in each loop with the loop’s results. -Parallel creates seperate runspaces each with their own pipeline, copy of variables, and memory space so you can’t just update an outer variable from inside the loop like you can when not using -Parallel. You have to output objects from the parallel block into a variable.

2

u/surfingoldelephant 18d ago

It’s in Powershell 7+ (pwsh.exe) which I highly recommend for its PSReadline predictonViewStyle alone

PS v7 isn't required for Set-PSReadLineOption -PredictionViewStyle. You can configure ListView/InlineView in Windows PowerShell v5.1 too if you update the included PSReadLine version to at least v2.2.2.

(Not that I disagree with using v7 whenever possible, it's just not always available.)

1

u/fdeyso 18d ago

Only use -parallel locally, not with cloud (azure, sharepoint, etc) in my experience it keeps creating random issues and not worth the hassle.

1

u/techster79 17d ago

Each item in the $y or $itemvariables is treated as $x or $itemvariable or whatever you name it. When dealing with computers it's intuitive to do foreach ($computer in $computers) {<cmdlet> -ComputerName $computer}

foreach ($x in $y)

{loops through items in $y and each loop is $x}

foreach ($itemvariable in $itemvariables){
Do something with $itemvariable
}

1

u/mc_trigger 17d ago

A lot of times when building a script, I’ll do $computer = $computers | select-object -first 1

Then when I’m ready to iterate, I change it to the foreach and the PS does the same thing I did, except to each item.

1

u/ddBuddha 17d ago

“how does it know to single out each individual item from my $computers array?

It’s because the code you’re writing is telling it what to do “for each” one of those elements

1

u/teamhog 17d ago

Line up your family.
It’s a special day and your mother is handing out $100 bills.

She takes a bill hands it to each of the people. Then we she gets done she goes and get birthday cake.

She just executed a for/each on the family array.

1

u/Ecrofirt 16d ago edited 16d ago

I haven't read the comments here, but I'll describe it similarly to how I'd describe it to students in a class.

In your case, $computers is an array of objects. Rather than thinking like a programmer, think of it like the tracks on an album, say Paranoid by Black Sabbath.

I don't know about you, but I like to listen to albums from start to finish. To do that, I play the album. When it starts up, I'm listening to track #1, War Pigs. Each track has a different name, length, etc. To make things easy, I'm going to refer to whichever track I'm currently listening to as $computer.

When the album is playing, only one track is playing at a time. So if I asked you the name of the current track with a question like "What is $computer.Name?" you'd be able to tell me "War Pigs".

Now War Pigs is over and it's time for track 2, Paranoid. Our current track, $computer, is now Paranoid. If I asked you again "What is $computer.Name?" You'd be able to tell me "Paranoid". And so on and so forth as we worked our way through the album until we finally finished the last song, Fairies Wear Boots. At that point, the album is done and you've listened to each track.

Those names seem a bit silly for music though, don't they? Let's make it look a bit more like code.

$album = Paranoid by Black Sabbath
foreach ($track in $album)
{
Write-Host "Track name:" -NoNewLine
Write-Host $track.Name
}

1

u/TofuBug40 18d ago

It works the same way as literally every other functional and object oriented language ever.

foreach ($Computer in $Computers) {}

Is tokenized to

[Keyword (foreach)] ( [indexed Item ($Computer)] [Keyword (in)] [Collection ($Computers)] ) [scriptblock ( {} )]

The interpreter in this case first checks how a foreach loop is supposed to be structured once that passes it one at a time (but not in the same guaranteed order they are set) it shoves each item from the [Collection] into the [Indexed Item] and then executes the [scriptblock] with [Indexed Item] scoped to [scriptblock]

This is just one version of foreach in powershell there are two others.

ForEach-Object which is a Cmdlet which takes advantage of the pipeline to add other cmdlets downstream.

$SomeCollection.ForEach() $SomeCollection.ForEach{}

Which are methods off of anything Collection shaped nearly the fastest and gives you the ability to chain calls or output directly to a new Collection.

Both of these utilize the standard $_ variable which you can read as just the currently [Indexed Item] but you don't have to explicitly define it though you could say

foreach ($_ in $Computers) {}

3

u/surfingoldelephant 18d ago

and then executes the [scriptblock] with [Indexed Item] scoped to [scriptblock]

foreach's {...} isn't a script block. It's a statement block, which is less complex. There's no new scope, various automatic variables don't require managing, etc.

Which are methods off of anything Collection shaped

The intrinsic ForEach()/Where() (and the new PSForEach()/PSWhere() aliases added in PS v7.6) are available to all objects of any type (providing there's no type-native method of the same name), so not just collections but scalars too (including $null).

nearly the fastest

ForEach() is significantly slower than the foreach statement. It's also slower than piping to a process-containing script block. It is faster than ForEach-Object, but not because ForEach() avoids the pipeline, but because ForEach-Object is inefficiently implemented (see issue #10982).

"nearly the fastest" misrepresents how ForEach() actually performs.

1

u/TofuBug40 18d ago

The point was not to go that detailed OP is clearly someone coming in with little to no programming experience.

you are of course right Statement blocks are different than scriptblocks but when its all said and done and at this level of learning its a distinction that really doesn't add anything meaningful to the OP understanding things.

.ForEach method is measurably faster for in memory collections more from the overhead of managing the pipeline but again wasn't trying to get into the weeds just to point out there are multiple ways to process over a collection for OP's awareness and to point out $_

But since you ask

  • If I'm dealing with anything like paginated data like from an API or external service or the processing time to load everything would be crippling
    • ForEach-Object
      • dealing with basically streamed data is what its for
  • If I'm dealing with complete in memory collections and I need speed and/or loop control
    • foreach statement
      • Its straight to the bare bones as far as IL so of course it will always be faster.
  • If I'm dealing with complete in memory collections and I want to lean into the tried and true filter and map approach
    • .ForEach()
      • I've ran multiple measurement tests and for similar data sets the difference between foreach statement and the method is almost negligible.
      • But what I more than often do is precede it with the .Where() method because combined with the System.Management.Automation.WhereOperatorSelectionMode enumeration I can do something like $collection.Where({$_ -like '*string'},[[System.Management.Automation.WhereOperatorSelectionMode]::Split).ForEach{ do something with $_} to do something like split, or until, with straight cmdlets gets needlessly complicated

Personally i tend to use the methods far more than the statements because I'm rarely in a case where the extra bit of speed i get is worth trading the fluent style of a where() into a foreach()

I was aware they extended the .ForEach() and .Where() methods right into the ETS so now they work for everything not just the original collections which is frankly awesome.

in the end I was going for the broadest but closest to technical definition of how a foreach loop works not just in PowerShell but anywhere because OP is clearly looking for a conceptual understanding rather than this specific code is not working kind of understanding and that is knowledge he can carry to pretty much any other language

2

u/surfingoldelephant 18d ago edited 18d ago

you are of course right Statement blocks are different than scriptblocks but when its all said and done and at this level of learning its a distinction that really doesn't add anything meaningful to the OP understanding things.

Conflating the two is a commonly spread misconception, so much so I regularly encounter PS users who believe a foreach statement creates a new scope. Saying something like, "scoped to [scriptblock]" is not only inaccurate but also fuels that widespread misconception.

.ForEach method is measurably faster for in memory collections more from the overhead of managing the pipeline

No, ForEach() is faster than ForEach-Object mainly because of how ForEach-Object itself is implemented internally. Issue #10982 outlines this.

"Pipeline overhead" is not the reason. Using the pipeline efficiently is faster than the ForEach() method with in-memory collections, despite the pipeline being widely perceived as slow (another common misconception that stems mainly from ForEach-Object/Where-Object inefficiencies).

I've ran multiple measurement tests and for similar data sets the difference between foreach statement and the method is almost negligible.

Language constructs are always going to be noticeably faster. ForEach() when invoked requires creation of a new CommandProcessor and PipelineProcessor. It requires invocation of a script block for every iteration. Local variable optimization is also disabled because the script block is dot sourced.

Your observation doesn't align with community consensus either (see this for example). foreach is at least an order of magnitude faster than ForEach().

Factor Secs (10-run avg.) Command                                TimeSpan
------ ------------------ -------                                --------
1.00   0.074              $null = foreach ($item in $arr) { $_ } 00:00:00.0741404
14.67  1.088              $null = $arr | & { process { $_ } }    00:00:01.0879236
28.99  2.149              $null = $arr.ForEach{ $_ }             00:00:02.1493531

# $arr is an array containing 1M objects.

 

I was aware they extended the .ForEach() and .Where() methods right into the ETS so now they work for everything not just the original collections

Yes, it's been that way since v5. They're not part of ETS though. They're engine intrinsic members that are special-cased and handled exclusively in the member binder. When a method gets invoked that doesn't exist, there's a fallback routine that checks if the invoked member name matches ForEach/Where. Aside from tab completion, they don't show up anywhere (like $obj.psextended, hence not being part of ETS).

in the end I was going for the broadest [...]

That's fine and certainly understandable given the OP's experience. It still doesn't mean we shouldn't strive to be accurate, even when talking conceptually. I'd also say your original comment goes into enough PS-specific detail for it to be considered more than just "conceptual", so another reason to be mindful of accuracy.

1

u/TofuBug40 18d ago

I already said you were right about the statement. though my point does stand at this level for the OP that's more depth than is needed here

As for the issues I'm aware they exist but even IF it was fixed there is a threshold of collection size or speed where .ForEach() is absolutely still faster than the Cmdlet (and the pure foreach statement is faster than both). It has almost NONE of the initialization, boxing, unrolling, etc. and pipeline maintenance the Cmdlet does even if it was at the optimal version of the code. If i gave you and another person a deck of cards to go through at the kitchen table but told you (the cmdlet) you first need to run through the entire house and open all the doors and turn on the lights because you might have to do something with each card in each room and then i stood there and handed you one card at a time but the other guy just sits there at the table with the entire deck of cards handed to him it does not matter HOW fast you are (because other guy gets to be just as fast as you but is literally doing LESS things) there is absolutely multiple scenarios where the guy sitting is done before you even get the lights on.

It really comes down to what you are doing. We've been going back and forth on the speed and I still stand by for like to like comparisons there is not enough difference between foreach and .ForEach() if you are that strapped for speed then reaching in and doing some add-type with some raw C# and some LINQ for those operations are going to cook just about anything else PowerShell can do.

For me its less about the speed than it is about the flexibility the .Where() and .ForEach() methods give me that do not exist without significant boilerplate using standard cmdlets

I mentioned the old filter and map approach but another one I use a lot because I deal with a lot of classes where i want to force types I can do something with the methods pretty easy like

$PSObjectCollection.
  ForEach([SpecificType]).
  ForEach{ 
    [Class]::MethodExpectingSpecificType($_) 
  }

For me specifically The general span of what I can do with just those two methods with minimal relatively readable code almost always outshines any speed gained by going with the statement or the power and flexibility gained by going with the cmdlets.

As for the accuracy normally I am right where you are but that's usually because we're dealing with someone with a modicum of knowledge about programming OP seemed genuinely at such a baseline level that being a little rough with the details for my response to his question but still getting most of the conception across was worth more. Having spent the better part of 20 years teaching this stuff to my junior engineers its easier than you think to overload them with stuff they don't need to fully understand to just get in and work with it. That kind of deep dive, few of us ever get the chance to really do because the work itself just needs to be done. I think it is absolutely fun and necessary to do but lots of time that level of understanding is all they need to move on to the next stage of learning.

-1

u/nodiaque 18d ago

In PowerShell, you don't define anything. Everything get define as you go.

In a for each loop, in the background, it's like doing a

For ($x =0; $x < $array.size; $x++) { $computer = $computers[$x] (your code here) }

That's what a foreach loop does.

The object type doesn't mind in PowerShell, it is inherit from the array object. If it's unknown, it will just be an object.