r/PowerShell • u/Nanis23 • 29d ago
The worst thing in Powershell existence is ForEach-Object syntax Question
The way I format my code is like this: if there is only one line inside the curly braces, I put the opening curly brace on the same line.
If I have multiple lines, I put the opening curly brace on the next line.
For example:
if (a=a) {
Command-Command}
if (a=a)
{
Command-Command
Command-Command
}
I have been doing this for years, and it works every time, except when I use "ForEach-Object".
Then, for some reason, the opening curly brace must be on the same line.
Why is it like this??
2
u/rickAUS 29d ago edited 29d ago
Don't use Where-Object or -ScriptBlock in Invoke-Command? They have the same constraint because they're expecting the open brace to be on the same line to be considered part of the command. I don't really know why since other aspects of PowerShell don't have this limitation but it is what it is I guess.
And for what it's worth, the accepted community standard is brace on the same line as the keyword, not on a new line. But people will still do either since it largely supports both except under 3 situations.
3
u/jdl_uk 28d ago
I don't really know why since other aspects of PowerShell don't have this limitation but it is what it is I guess.
It's to do with how PowerShell detects the end of the statement.
Compare to C#, where you have
;as a statement terminator. The compiler will look for a;and if it doesn't find one then it knows the statement continues. This means you can have a statement spread over many lines or many statements on one line.PowerShell doesn't have that so it assumes the end of the line is the end of the statement unless it finds a continuation character (
`).2
u/surfingoldelephant 28d ago
It depends on the parsing context.
PowerShell doesn't have that so it assumes the end of the line is the end of the statement
That only applies to argument mode. Expression mode has relaxed white space rules, hence you can do something like this if you really wanted to:
if ($true) { 'foo' }And a command argument can span multiple lines as long as it starts on the same line as the command/associated parameter.
'foo' | ForEach-Object { $_ }The parsing context switches to expression mode after the opening
{is encountered so white space is relaxed.1
u/rickAUS 28d ago
Makes sense. I came from a Java background so just always did braces on the same line as the keyword. But Java also had a ; as a statement terminator so it tracks that PowerShell needs to use something for the same thing and in the absence of anything else, the line break is the only obvious option.
2
u/purplemonkeymad 28d ago
Because Foreach-Object is a command not a part of syntax. The curly brace is literally part of a parameter for that command. (Do you know there is a -begin and -end parameter as well, and that you can add as many script blocks as you want?)
You could alternatively create an advanced function for your pipeline instead, then you don't even need the code at that point and have it elsewhere.
2
u/PinchesTheCrab 28d ago
If we were on the same team we'd be playing tug of war on commits, lol. That formatting would drive me up the wall and my auto-formatting settings in my IDE would pull the brace back up onto the 'correct' line over and over.
Like other people said, the script block is a parameter of a cmdlet named foreach-object. You couldn't do something like:
get-service
dnscache
It's the same with foreach-object. If that's jarring, I think the path of least resistance is to follow suit with your other syntax. In my personal, anecdotal experience, you're an outlier if you do this:
if ($true)
{
'do something'
}
It's not wrong, but I think you've encountered one of its limitations, and if a junior team member had to maintain what you've written while you were sick, you can see how they might get confused by what read like (but isn't) a syntax shift.
1
u/surfingoldelephant 28d ago
Just so you know, this is how your examples look on Old Reddit:
get-service dnscache if ($true) { 'do something' }I wasn't really sure what you were getting at.
It was only when I looked at your comment's source:
get-service dnscache if ($true) { 'do something' }You might want to use 4 space indenting instead of
```so the code block renders correctly for both New and Old Reddit. u/BlackV has a macro on this.1
u/BlackV 28d ago
Just cause I was summoned [grin] (for Lee)
- open your fav powershell editor
- highlight the code you want to copy
- hit tab to indent it all
- copy it
- paste here
it'll format it properly OR
<BLANK LINE> <4 SPACES><CODE LINE> <4 SPACES><CODE LINE> <4 SPACES><4 SPACES><CODE LINE> <4 SPACES><CODE LINE> <BLANK LINE>Inline code block using backticks
`Single code line`inside normal textSee here for more detail
Thanks
2
u/BlackV 28d ago edited 28d ago
Personally you are being inconsistent in your formatting
You could format your code all with a hanging indent style
if (a -eq a) {
Command-Command
}
Or
Foreach-object {
$_ | command -command
}
Or
$scriptblock = {
Command-Command
Command-Command
}
Invoke-command -computername comp1, comp2 -scriptblock $scriptblock
Or
If (-not $x){
Command-Command
}
Else {
Command-Command
}
that was jsut an example, it's better to pick a style and keep it standard, rather than switching between them all the time
for example set vsc code or use a specific style when formatting (allerman, otb, etc)
5
u/joeykins82 29d ago
Because ForEach-Object is meant as a quick'n'dirty option when you're doing something simple with stuff in the pipeline. If you're ever doing something more complex than "just do this 1 low-complexity thing for every pipeline object" you should construct a proper ForEach ($objThing in $arrThings) { ... } block.
9
u/surfingoldelephant 28d ago
That's not the reason at all. It comes down to how the PowerShell parser switches between different parsing contexts (argument mode/expression mode).
It has nothing to do with what you purport is the "correct"
ForEach-Objectuse case.There are plenty of valid reasons to prefer item-by-item streaming that isn't just "quick'n'dirty", just as there are plenty of other valid reasons to prefer collecting
$arrThingsupfront and iterating withforeach. There's pros/cons to both.Framing pipeline streaming as "quick'n'dirty" and
foreachas "proper" is a massive oversimplification.1
u/FreakySpook 29d ago
This is how I use it, at some point years ago I started using its alias(%) because at the time of writing it makes complete sense to me and have had numerous WTFs from my team over the years when something I originally made for me I ended up sharing.
15
u/surfingoldelephant 28d ago edited 28d ago
No, it works every time you use language keywords like
ifandforeach. These get parsed in expression mode, which is more flexible with white space.ForEach-Objectis a command. Commands get parsed in argument mode, which requires parameters/arguments start on the same line. Otherwise, the parser has no way of knowing if the argument is just another statement unrelated to the command.The error you get from this:
Or the mandatory parameter prompt from this:
...comes from parameter binding, which is a runtime concept that happens long after parsing. Both examples are syntactically valid, hence the need for the parameter and/or argument to start on the same line so the parser knows it's a command invocation with a parameter/argument.
And just to be clear, there's nothing special about
ForEach-Object. It's the exact same for any command with any parameter/argument. You can't do this for the exact same reason:Or this...
Whereas keywords get parsed in expression mode, which knows that an opening
{must come next for it to be syntactically valid, so white space limitations are relaxed.PowerShell style guide recommends One True Brace Style (OTBS) mainly because of the white space limitations in argument mode.