r/PowerShell • u/InfoZk37 • 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.
20
u/Panchorc 18d ago
The official docs covers that very well: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_foreach?view=powershell-7.6
3
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} helloA 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
$ycan be an object of any type in PowerShell, so not just collections but scalars also. It can also be$nullorAutomationNull(in which case, the{...}statement block just won't be entered).$y = 1 foreach ($x in $y) { $x } # 1PowerShell 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,
IDictionaryand 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 callGetEnumerator()explicitly.2
u/ankokudaishogun 18d ago
It can also be
$nullorAutomationNull(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 } $numbersForeach 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 @parametersToWhatIWouldDoThis 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.
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/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/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
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 configureListView/InlineViewin Windows PowerShell v5.1 too if you update the includedPSReadLineversion to at least v2.2.2.(Not that I disagree with using v7 whenever possible, it's just not always available.)
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/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 newPSForEach()/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 theforeachstatement. It's also slower than piping to aprocess-containing script block. It is faster thanForEach-Object, but not becauseForEach()avoids the pipeline, but becauseForEach-Objectis 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.WhereOperatorSelectionModeenumeration 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 complicatedPersonally 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
foreachstatement 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 thanForEach-Objectmainly because of howForEach-Objectitself 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 fromForEach-Object/Where-Objectinefficiencies).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 newCommandProcessorandPipelineProcessor. 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).
foreachis at least an order of magnitude faster thanForEach().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.
62
u/TheEdonReddit 18d ago
You are correct. You are defining it there and can name it just about whatever you want.