Okay, the expected "output" of this script is fairly obvious, and it works. It copies files:
Get-ChildItem -Recurse -Directory | ForEach-Object{
if ($_.Name.ToUpper().Contains("INCLUDE_PUBLIC")){
Get-ChildItem -Recurse -File -Include "*.h" -Path $_.FullName | ForEach-Object {Copy-Item ($_.FullName) ("e:\IncTest")}
}
}
However, if I move the opening brace {
to the next line, I get the following output:
cmdlet ForEach-Object at command pipeline position 2
Supply values for the following parameters:
Process[0]:
Am I getting tricked by some other syntax issue, or does ForEach-Object require its brace to be on the same line?
Curly braces in PowerShell variable names allow for arbitrary characters in the name of the variable. If there are no "pathological" characters in the variable name, then the braces are not needed and have no effect.
In the PowerShell programming language, a script block is a collection of statements or expressions that can be used as a single unit. A script block can accept arguments and return values.
Different programming languages have various ways to delineate the start and end points of a programming structure, such as a loop, method or conditional statement. For example, Java and C++ are often referred to as curly brace languages because curly braces are used to define the start and end of a code block.
PowerShell has two distinct parsing modes, owing to the fact that it is both a shell and a scripting language.
Argument mode is line-oriented and is shell-like: It applies to a single command invoking an executable / script / cmdlet / alias / function, or a series of such commands chained with the pipe symbol (|
) to form a pipeline.
Each individual command must be on its own line and the only way to spread it across multiple lines is to use line continuation, which requires escaping the very end of each interior line with `
(a backtick).
However, that practice is both visually subtle and brittle (placing even just spaces or tabs after the `
breaks the command).
In order to spread the individual commands of a pipeline across multiple lines (without having to use line continuation), end each line but the last with |
.
Expression mode works as it does in other programming languages in which whitespace isn't significant: as long as the construct is syntactically complete, it can straddle any number of lines.
It's important to note that a single command can involve a mix of parsing modes, such as when you pass a script block to a cmdlet, where the cmdlet parses its arguments in argument mode, but the script block itself is parsed in expression mode (see below).
The official documentation on parsing modes is in the conceptual about_Parsing
help topic.
ForEach-Object
is a cmdlet and therefore parsed in argument mode.
Thus, unless you use line continuation, it will not look for arguments on subsequent lines, and executing ForEach-Object
on its own, without arguments, causes it to prompt for those of its arguments that are mandatory - the -Process
script block - which is what you saw.
By contrast, ... | ForEach-Object {
works, despite the script block continuing on subsequent lines, because the script block itself is parsed in expression mode, allowing it to be spread across multiple lines, whereas starting the script block - the opening {
- on the same line as ForEach-Object
is sufficient for ForEach-Object
to recognize it.
An example that contrasts cmdlet ForEach-Object
, parsed in argument mode, with the foreach
statement, parsed in expression mode[1]
:
# Argument mode
1, 2 | ForEach-Object { # opening brace must be on same line
"Element: $_"
}
# Expression mode
foreach ($el in 1, 2) # expression mode - OK to place opening { on next line
{
"Element: $el"
}
[1] Note that, perhaps confusingly, ForEach-Object
has an alias also named foreach
, which blurs the distinction between the cmdlet and the foreach
statement. It is the situational parsing mode that determines wether foreach
is interpreted as the alias (and therefore refers to ForEach-Object
)
or as the statement.
PS C:\> Get-Help -Name 'ForEach-Object'
SYNTAX
ForEach-Object [-MemberName] <String> [-ArgumentList <Object[]>] [-Confirm] [-InputObject <PSObject>] [-WhatIf] [<CommonParameters>]
ForEach-Object [-Process] <ScriptBlock[]> [-Begin <ScriptBlock>] [-Confirm] [-End <ScriptBlock>] [-InputObject <PSObject>] [-RemainingScripts <ScriptBlock[]>] [-WhatIf] [<CommonParameters>]
By using a script block, you're using positionally-bound parameters of the ForEach-Object
cmdlet. In this case, the -Process { }
parameter set. If you escape the end of the line (in essence, escaping the newline), you can get around that, but it's generally a bad practice.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With