Okay, the expected \"output\" of this script is fairly obvious, and it works. It copies files:
Get-ChildItem -Recurse -Directory | ForEach-Object{
if ($
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.
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.