I was testing a function and was playing around with what I could do with return and stumbled accross a strange issue, in PowerShell 5.1 and PwSh 7.1, the return cmdlet seemd to not work in a group:
PS C:\Users\Neko> return "test"
test
PS C:\Users\Neko> (return "test")
return : The term 'return' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct
and try again.
At line:1 char:2
+ (return "test")
+ ~~~~~~
+ CategoryInfo : ObjectNotFound: (return:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
If I put return in a group, it seemed to become unrecognized by PowerShell.
I understand that return prints to the console and does not have "output" per say like Write-Host,
PS C:\Users\Neko> $test = return "test"
test
PS C:\Users\Neko> $test
PS C:\Users\Neko> $test = Write-Host "test"
test
PS C:\Users\Neko> $test
PS C:\Users\Neko>
but I can't fathom why having return in a grouping expression is causing such an issue.
Why is using return in groups causing a strange error and how can I remedy that?
This doesn't occur in subexpressions,
PS C:\Users\Neko> $(return "test")
test
#Neko Musume -- as per your request.
Because as per the MS Docs on the topic ...
About_Return | MS Docs
... it can only be used as defined. Meaning 'Exit the current scope, which can be a function, script, or script block.', thus using parens, as you are trying to do, does not meet that criteria.
return is a statement, whereas only commands and expressions are allowed inside (), the grouping operator.
All keyword-based constructs are statements; to name a few: other flow-control statements (exit, throw, ...) and loops and conditionals (if, while, switch, ...) - see about_Language_Keywords for the list of all keywords in PowerShell.
In order to group (embed in another statement) a statement (potentially even multiple ones, separated with ;, as usual) - you need $(), the subexpression operator.
Note that the operator's name is somewhat unfortunate; "sub-statement operator" would have been more descriptive.
The primary use of $(...) is inside expandable strings ("..."), and it is typically not needed elsewhere; instead, (...) should be used when possible, so as to avoid potential side effects (and prevent visual clutter) - see this answer.
That said, in this specific case, as the about_Return help topic states, return's purpose is to exit the enclosing scope (function, script, or script block), so using it as the RHS of a variable assignment never makes sense.
The reason for the can't-use-a-statement-as-an-expression restriction is rooted in the fundamentals of PowerShell's grammar, as summarized by a member of the PowerShell team in this GitHub issue (emphasis added):
PowerShell has pipelines contained by statements, not statements contained by pipelines. That's always been the case, and the way to put a statement within a pipeline is to use the $(...) syntax — that's its only purpose to exist outside of strings in the language.
The two most important consequences to end users are:
You cannot use a statement in a pipeline:
That is, something like foreach ($i in 1..3) { $i } | % { $_ + 1 } does not work.[1]
While you could use $(...) as a workaround($(foreach ($i in 1..3) { $i }) | % { $_ + 1 }, doing so forgoes the streaming benefits of the pipeline; that is, the output from the foreach loop is collected in memory first, in full before the results are sent through the pipeline.
In order to get the usual streaming behavior (one-by-one passing of output through the pipeline), enclose the loop in a script block ({ ... }) and invoke it with &, the call operator[2]:
& { foreach ($i in 1..3) { $i } } | % { $_ + 1 }
(Using expressions in a pipeline works, just fine, however: 1..3 | % { $_ + 1 })
You cannot use the flow-control statements return, exit, and throw with && and ||, the pipeline-chain operators:
That is, something like ls nosuchfile || exit 1 does not work.
Instead, you must use $(...): ls nosuchfile || $(exit 1)
Unfortunately, these restrictions cannot be lifted if backward compatibility must be maintained; quoting again from the previously linked GitHub issue:
[Lifting these restrictions amounts to] a huge change in PowerShell's grammar (effectively creating a fork in the language)
While PowerShell to date (as of v7.0) has had an ironclad commitment to backward compatibility which prevented serious breaking changes, a debate about whether to permit such changed and how to (carefully) manage them has now begun: see this GitHub issue.
If such changes are eventually permitted, many longstanding annoyances could finally be tackled - see this GitHub issue.
[1] Note that foreach is also a built-in alias for the ForEach-Object cmdlet, but the command shown uses the foreach loop statement - see about_ForEach.
[2] If the statement has side effects that you want the caller to see, use ., the dot-sourcing operator instead of &.
Note: This answer complements my (already lengthy) other one in order to address some of your analysis in the question:
I understand that return prints to the console and does not have "output" per say like Write-Host
It is Write-Host that has no output in the data sense: that is, it does not write to the success output stream (stream number 1 - see about_Redirection)
Instead, it writes to the host (the display; in PSv5+, it does so via stream 6, the information stream), bypassing the success output stream, preventing capturing or redirection of the output - see this answer for background information, including when use of Write-Host (rather than Write-Output / implicit output) is appropriate.
By contrast, return does send the output from its (optional) operand[1] to the success output stream.
As for the surprising behavior of:
PS> $test = return "test"
test
You've hit an edge case (discussed in this since-closed GitHub issue): something that is syntactically allowed, but doesn't make sense to do: the return statement effectively short-circuits the assignment: that is, "test" is sent directly to the success output stream (1), and the assignment is ignored, because the flow-control statement return exits the enclosing scope - not just the statement! - before the assignment occurs.
You can verify that return indeed still targets the success output stream as follows:
PS> $testOuter = & { $test = return "test" }; "[$testOuter]"
[test] # proof that $testOuter was able to capture the `return` value.
However, it's easy to avoid this problem: any expression or command (pipeline) can directly be used as the RHS.
# Do NOT use `return` in the RHS of an assignment.
PS> $test = "test"; "[$test]"
[test]
[1] While using return with an expression (such as return $foo) is probably most common, note that it supports an entire pipeline as its operand (e.g., return Get-Date | % Year).
Related
I am trying to use a Retry Service that is written in the fluent-api pattern.
The methods return the service and allow a chaining of methods.
However even though i am using --> ` <-- i see a lot of errors as shown below.
Is there any workaround or other possibility to not write everything into one line?
(I already checked the methods names and return types)
(Entry point of RetryService)
Unfortunately about_Methods doesn't seem to have a clarification on method chaining and its parsing rules. If you want to chain multiple methods on new lines, the dot . has to be at the end of each statement then a newline is allowed. The backticks are not needed.
In example:
[powershell]::Create().
AddScript({ "hello $args" }).
AddArgument('world').
Invoke()
Another way to chain method calls is to use the ForEach-Object command (alias %). This relies on the parameter set with the -MemberName parameter (implicitly by passing a string as the 1st argument).
PowerShell 7+ even lets you write the pipe symbol | on a new line:
[powershell]::Create()
|% AddScript { "hello $args" }
|% AddArgument 'world'
|% Invoke
If there are multiple method arguments, you have to separate them by , (parentheses are unnecessary).
For PS 5 and below you have to use a slightly different syntax because the pipe symbol has to be on the same line as the previous command:
[powershell]::Create() |
% AddScript { "hello $args" } |
% AddArgument 'world' |
% Invoke
Is this a better way than using member access operator .? I don't think so, it's just a different way. IMO it does look more consistent compared to regular PowerShell commands. Performance might be even worse than . but for high level code it propably doesn't matter (I haven't measured).
I've learnt from this thread how to append to an array of arrays. However, I've observed the weirdest behaviour ever. It seems to append stdout too! Suppose I want to append to an array of arrays, but I want to echo debug messages in the loop. Consider the following function.
function WTF {
$Result = #()
$A = 1,2
$B = 11,22
$A,$B | % {
Write-Output "Appending..."
$Result += , $_
}
return $Result
}
Now if you do $Thing = WTF, you might think you get a $Thing that is an array of two arrays: $(1,2) and $(11,22). But that's not the case. If you type $Thing in the terminal, you actually get:
Appending...
Appending...
1
2
11
22
That is, $Thing[0] is the string "Appending...", $Thing[1] is the string "Appending...", $Thing[2] is the array #(1,2), and $Thing[3] is the array #(11,22). Nevermind that they seem to be in a weird order, which is a whole other can of worms I don't want to get into, but why does the echoed string "Appending..." get appended to the result??? This is so extremely weird. How do I stop it from doing that?
EDIT
Oh wait, upon further experimenting, the following simpler function might be more revealing:
function WTF {
$Result = #()
1,2 | % {
Write-Output "LOL"
}
return $Result
Now if you do $Thing = WTF, then $Thing becomes an array of length 2, containing the string "LOL" twice. Clearly there is something fundamental about Powershell loops and or Write-Output that I'm not understanding.
ANOTHER EDIT!!!
In fact, the following even simpler function does the same thing:
function WTF {
1,2 | % {
Write-Output "LOL"
}
}
Maybe I just shouldn't be using Write-Output, but should use Write-Information or Write-Debug instead.
PowerShell doesn't have return values, it has a success output stream (the analog of stdout in traditional shells).
The PowerShell pipeline serves as the conduit for this stream, both when capturing command output in a variable and when sending it to another command via |, the pipeline operator
Any statement - including multiple ones, possibly in a loop - inside a function or script can write to that stream, typically implicitly - by not capturing, suppressing, or redirecting output - or explicitly, with Write-Output, although its use is rarely needed - see this answer for more information.
Output is sent instantly to the success output stream, as it is being produced - even before the script or function exits.
return exists for flow control, independently of PowerShell's output behavior; as syntactic sugar you may also use it to write to the output stream; that is, return $Result is syntactic sugar for: $Result; return, with $Result by itself producing implicit output, and return exiting the scope.
To avoid polluting the success output stream - intended for data output only - with status messages, write to one of the other available output streams - see the conceptual about_Redirection help topic.
Write-Verbose is a good choice, because it is silent by default, and can be activated on demand, either via $VerbosePreference = 'Continue', or, on a per-call basis, with the common -Verbose parameter, assuming the script or function is an advanced one.
Write-Host, by contrast, unconditionally prints information to the host (display), and allows control over formatting, notably coloring.
Outputting a collection (array) from a script or function enumerates it. That is, instead of sending the collection itself, as a whole, its elements are sent to PowerShell's pipeline, one by one, a process called streaming.
PowerShell commands generally expect streaming output, and may not behave as expected if you output a collection as a whole.
When you do want to output a collection as a whole (which may sometimes be necessary for performance reasons), wrap them in a transient aux. single-element array, using the unary form of ,, the array constructor operator: , $Result
A conceptually clearer (but less efficient) alternative is to use Write-Output -NoEnumerate
See this answer for more information.
Therefore, the PowerShell idiomatic reformulation of your function is:
function WTF {
# Even though no parameters are declared,
# these two lines are needed to activate support for the -Verbose switch,
# which implicitly make the function an *advanced* one.
# Even without it, you could still control verbose output via
# the $VerbosePreference preference variable.
[CmdletBinding()]
param()
$A = 1,2
$B = 11,22
# Use Write-Verbose for status information.
# It is silent by default, but if you pass -Verbose
# on invocation or set $VerbosePreference = 'Continue', you'll
# see the message.
Write-Verbose 'Appending...'
# Construct an array containing the input arrays as elements
# and output it, which *enumerates* it, meaning that each
# input array is output by itself, as a whole.
# If you need to output the nested array *as a whole*, use
# , ($A, $B)
$A, $B
}
Sample invocation:
PS> $nestedArray = WTF -Verbose
VERBOSE: Appending...
Note:
Only the success output (stream 1) was captured in variable $nestedArray, whereas the verbose output (stream 4) was passsed through to the display.
$nestedArray ends up as an array - even though $A and $B were in effect streamed separately - because PowerShell automatically collects multiple streamed objects in an array, which is always of type [object[]].
A notable pitfall is that if there's only one output object, it is assigned as-is, not wrapped in an array.
To ensure that a command's output is is always an array, even in the case of single-object output:
You can enclose the command in #(...), the array-subexpression operator
$txtFiles = #(Get-ChildItem *.txt)
In the case of a variable assignment, you can also use a type constraint with [array] (effectively the same as [object[]]):
[array] $txtFiles = Get-ChildItem *.txt
However, note that if a given single output object itself is a collection (which, as discussed, is unusual), no extra array wrapper is created by the commands above, and if that collection is of a type other than an array, it will be converted to a regular [object[]] array.
Additionally, if #(...) is applied to a strongly typed array (e.g., [int[]] (1, 2), it is in effect enumerated and rebuilt as an [object[]] array; by contrast, the [array] type constraint (cast) preserves such an array as-is.
As for your specific observations and questions:
I've learnt from this thread how to append to an array of arrays
While using += in order to incrementally build an array is convenient, it is also inefficient, because a new array must be constructed every time - see this answer for how to construct arrays efficiently, and this answer for how to use an efficiently extensible list type as an alternative.
Nevermind that they seem to be in a weird order, which is a whole other can of worms I don't want to get into
Output to the pipeline - whether implicit or explicit with Write-Output is instantly sent to the success output stream.
Thus, your Write-Output output came first, given that you didn't output the $Result array until later, via return.
How do I stop it from doing that?
As discussed, don't use Write-Output for status messages.
Using BorderAround emits "True" to the console.
$range = $sum_wksht.Range('B{0}:G{0}' -f ($crow))
$range.BorderAround(1, -4138)
This can be overcome by using one of the following.
$wasted = $range.BorderAround(1, -4138)
[void]$range.BorderAround(1, -4138)
Why is this needed? Am I not creating the range correctly? Is there a better workaround?
Why is this needed?
It is needed, because the BorderAround method has a return value and, in PowerShell, any command or expression that outputs (returns) data is implicitly output to the (success) output stream, which by default goes to the host, which is typically the console window (terminal) in which a PowerShell session runs.
That is, the data shows in the console/terminal, unless it is:
captured ($var = ...)
sent through the pipeline for further processing (... | ...; the last pipeline segment's command may or may not produce output itself)
redirected (... >)
or any combination thereof.
That is:
$range.BorderAround(1, -4138)
is (more efficient) shorthand for:
Write-Output $range.BorderAround(1, -4138)
(Explicit use of Write-Output is rarely needed.)
Since you don't want that output, you must suppress it, for which you have several options:
$null = ...
[void] (...)
... > $null
... | Out-Null
$null = ... may be the best overall choice, because:
It conveys the intent to suppress up front
While [void] = (...) does that too, it often requires you to enclose an expression in (...) for syntactic reasons; e.g., [void] 1 + 2 doesn't work as intended, only [void] (1 + 2); similarly, a command must always be enclosed in (...); [void] New-Item test.txt doesn't work, only [void] (New-Item test.txt) does.
It performs well with both command output (e.g., $null = Get-AdUser ...) and expression output (e.g., $null = $range.BorderAround(1, -4138)).
Conversely, avoid ... | Out-Null, because it is generally much slower (except in the edge case of a side effect-free expression's output in PowerShell (Core) 6+)[1].
However, if you need to silence all output streams - not just the success output, but also errors, verbose output, ... - you must use *> $null
Why does PowerShell produce output implicitly?
As a shell, PowerShell's output behavior is based on streams, as in traditional shells such as cmd.exe or Bash. (While traditional shells have 2 output streams - stdout and stderr - PowerShell has 6, so as to provide more sophisticated functionality - see about_Redirection.)
A cmdlet, script, or function can write to the output streams as often as it wants, and such output is usually instantly available for display but notably also to potential consumers, which enables the streaming, one-by-one processing that the pipeline provides.
This contrasts with traditional programming languages, whose output behavior is based on return values, typically provided via the return keyword, which conflates output data (the return value) with flow control (exit the scope and return to the caller).
A frequent pitfall is to expect PowerShell's return statement to act the same, but it doesn't: return <val> is just syntactic sugar for <val>; return, i.e., implicit output of <val> followed by an unconditional return of control to the caller; notably, the use of return does not preclude generation of output from earlier statements in the same scope.
Unlike traditional shells, PowerShell doesn't require an explicit write-to-the-output stream command in order to produce output:
While PowerShell does have a counterpart to echo, namely Write-Output, its use is rarely needed.
Among the rare cases where Write-Output is useful is preventing enumeration of a collection on output with -NoEnumerate, or to use common parameter -OutVariable to both output data and capture it in a variable (which is generally only needed for expressions, because cmdlets and advanced functions / scripts themselves support -OutVariable).
The implicit output behavior:
is generally a blessing:
for interactive experimentation - just type any statement - notably including expressions such as [IO.Path]::GetExtension('foo.txt') and [math]::Pow(2, 32) - and see its output (akin to the behavior of a REPL).
for writing concise code that doesn't need to spell out implied behavior (see example below).
can occasionally be a pitfall:
for users accustomed to the semantics of traditional programming languages.
due to the potential for accidental pollution of the output stream from statements that one doesn't expect to produce output, such as in your case; a more typical example is the .Add() method of the [System.Collections.ArrayList] class unexpectedly producing output.
Example:
# Define a function that takes an array of integers and
# outputs their hex representation (e.g., '0xa' for decimal 10)
function Get-HexNumber {
param([int[]] $numbers)
foreach ($i in $numbers) {
# Format the integer at hand
# *and implicitly output it*.
'0x{0}' -f $i.ToString('x')
}
}
# Call the function with integers 0 to 16 and loop over the
# results, sleeping 1 second between numbers.
Get-HexNumber (0..16) | ForEach-Object { "[$_]"; Start-Sleep 1 }
The above yields the following:
[0x0]
# 1-second pause
[0x1]
# 1-second pause
[0x2]
...
[0x10]
This demonstrates the streaming aspect of the behavior: Get-HexNumber's output is available to the ForEach-Object cmdlet call as it is being produced, not after Get-HexNumber has exited.
[1] In PowerShell (Core) 6+, Out-Null has an optimization if the only preceding pipeline segment is a side effect-free expression rather than a method or command call; e.g., 1..1e6 | Out-Null executes in almost no time, because the expression is seemingly not even executed. However, such a scenario is atypical, and the functionally equivalent Write-Output (1..1e6) | Out-Null takes a long time to run, much longer than $null = Write-Output (1..1e6).
ForEach is documented as an alias of ForEach-Object. When I use Get-Alias ForEach it tells that it is in alias of ForEach-Object.
But,
ForEach-Object Accepts parameters such as Begin Process and End, where ForEach doesn't accept them.
When we call ForEach-Object without any thing it prompts for parameter Process, and on calling ForEach it leaves a nested prompt with >>.
% and ForEach behave same, but ForEach-Object don't.
Here my questions are:
Is ForEach really an alias of ForEach-Object?
Which is better, ForEach or ForEach-Object?
Please share your thoughts.
Thank you!
There are two distinct underlying constructs:
The ForEach-Object cmdlet
This cmdlet has a built-in alias name: foreach, which just so happens to match the name of the distinct foreach statement (see next point).
The foreach loop statement (akin to the lesser used for statement).
What foreach refers to depends on the parsing context - see about_Parsing:
In argument mode (in the context of a command), foreach is ForEach-Object's alias.
In expression mode (more strictly: statement mode in this case), foreach is the foreach loop statement.
As a cmdlet, ForEach-Object operates on pipeline input.
Use it process output from other commands in a streaming manner, object by object, as these objects are being received, via the automatic $_ variable.
As a language statement, the foreach loop operates on variables and expressions (which may include output collected from commands).
Use it to process already-collected-in-memory results efficiently, via a self-chosen iterator variable (e.g., $num in foreach ($num in 1..3) { ... }); doing so is noticeably faster than processing via ForEach-Object.[1]
Note that you cannot send outputs from a foreach statement directly to the pipeline, because PowerShell's grammar doesn't permit it; for streaming output to the pipeline, wrap a foreach statement in & { ... }. (By contrast, simple expressions (e.g., 1..3) can be sent directly to the pipeline).
For more information, a performance comparison and a discussion of the tradeoffs (memory use vs. performance), including the .ForEach() array method, see this answer.
[1] However, note that the main reason for this performance discrepancy as of PowerShell 7.2.x isn't the pipeline itself, but ForEach-Object's inefficient implementation - see GitHub issue #10982.
This question already has answers here:
What is the difference between $a = $(Read-Host) and $a = (Read-Host)?
(2 answers)
Closed 5 years ago.
What's the difference between
Write-Host (Get-Date) # just paren
and
Write-Host $(Get-Date) # dollar-paren
Content within the parens could be anything, just going with a simple example. Is there any difference between the two?
I consider myself reasonably experienced with PS, but it's these little things that bug me, especially during code review and the like. Has anyone come across a good source for "here is how the language works" with enough detail to derive answers to these sorts of questions?
The sub expression ($(...)) contains a StatementBlockAst. It can take any number of statements, meaning keywords (if, foreach, etc), pipelines, commands, etc. Parsing is similar to the inside of a named block like begin/process/end.
The paren expression ((...)) can contain a single ExpressionAst which is a limited subset of the AST. The most notable difference between a statement and an expression is that keywords are not parsed.
$(if ($true) { 'It worked!' })
# It worked!
(if ($true) { 'It worked!' })
# if : The term 'if' is not recognized as the name of a cmdlet, function,
# script file, or operable program. Check the spelling of the name, or
# if a path was included, verify that the path is correct and try again.
# At line:1 char:2
# + (if ($true) { 'It worked' })
# + ~~
# + CategoryInfo : ObjectNotFound: (if:String) [], CommandNotFoundException
# + FullyQualifiedErrorId : CommandNotFoundException
Also as others have noted, the sub expression will expand in double quoted strings.
The () helps with order of operations
The $() helps with evaluating values inside of the ()
For instance, if you're trying to find today's date in a string you could do the following:
echo "The length of Bryce is (Get-Date)"
echo "The length of Bryce is $(Get-Date)"
You'll see that the output is different (in one it gives you literally "(Get-Date)" whereas in the other it gives you the evaluated expression of Get-Date)
You can read more about syntax operators here
Parenthesis are used to group and establish order just as they do in mathematics. Starting in PowerShell v3 you can also use them to evaluate a property of a group, such as getting the file names for the files in the current folder by running:
(Get-ChildItem).Name
A sub-expression $() evaluates the script within it, and then presents the output of that to be used in the command. Often times used within strings to expand a property of an object such as:
"Hello $($User.Name), would you like to play a game?"
It can also be useful when working with ComObjects, such as Excel where you may have a range that you want to test against a property of each item. While this does not work because the Range object does not have a Font property:
$Range.Font|Where{$_.Bold}
This would work, because it would output the Range as a collection of Cell objects, each of which have a Font property:
$($Range).Font|Where{$_.Bold}
You can think of sub-expressions as being a script within your script, since they can be several commands long, and the entire thing is evaluated at once so that the end output can be used for the parent command.