How to use line breaks in PowerShell Method-Chaining - powershell

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).

Related

Creating a File Changes What's Written To Host When Jobs Are Created [duplicate]

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).

Why are (return) and return different?

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).

Is there a way to create an alias to a cmdlet in a way that it only runs if arguments are passed to the alias?

I am trying to create an alias (named which) of the Get-Command cmdlet in a way that it doesn't run if I'm not sending any arguments (because if it's run without arguments it outputs all the commands that are available).
I know this can be done using a function but I would like to keep the tab completion functionality without having to write a sizeable function that is to be placed into my $PROFILE.
In short, I only want the alias to work if it is being passed arguments.
You can't do it with an alias, because PowerShell aliases can only refer to another command name or path, and can therefore neither include arguments nor custom logic.
Therefore you do need a function, but it can be a short and simple one:
function which { if ($args.count) { Get-Command #args } else { Throw "Missing command name." } }
Note that while passing -? for showing Get-Command's help does work, tab completion of arguments does not.
In order to get tab completion as well, you'll need to write a wrapper (proxy) function or at least replicate Get-Command's parameter declarations - which then does make the function definition sizable.
If the concern is just the size of the $PROFILE file itself, you can write a proxy script instead - which.ps1 - which you can invoke with just which as well, assuming you place it in one of the directories listed in $env:Path[1]; see next section.
Defining a wrapper (proxy) script or function:
Defining a wrapper (proxy) function or script is a nontrivial undertaking, but allows you to implement a robust wrapper that supports tab completion and even forwarding to the original command's help.
Note:
Bug alert: As zett42 points out, as of PowerShell [Core] 7.1, System.Management.Automation.ProxyCommand.Create neglects to include dynamic parameters if the target command is an (advanced) function or script; however, compiled cmdlets are not affected; see GitHub issue #4792 and this answer for a workaround.
For simplicity, the following creates a wrapper script, which.ps1 , and saves it in the current directory. As stated, if you place it in one of the directories listed in $env:PATH, you'll be able to invoke it as just which.
The code below can easily be adapted to create a wrapper function instead: simply take the contents of the $wrapperCmdSource variable below and enclose it in function which { ... }.
As of PowerShell Core 7.0.0-preview.5, there are some problems with the auto-generated code, which may or may not affect you; they will be fixed at some point; to learn more and to learn how to manually correct them, see GitHub issue #10863.
# Create the wrapper scaffolding as source code (outputs a single [string])
$wrapperCmdSource =
[System.Management.Automation.ProxyCommand]::Create((Get-Command Get-Command))
# Write the auto-generated source code to a script file
$wrapperCmdSource > which.ps1
Note:
Even though System.Management.Automation.ProxyCommand.Create requires a System.Management.Automation.CommandMetadata instance to identify the target command, the System.Management.Automation.CommandInfo instances output by Get-Command can be used as-is.
Re comment-based help: By default, the proxy function simply forwards to the original cmdlet's help; however, you can optionally pass a string to serve as the comment-based help as the 2nd argument.
By using [System.Management.Automation.ProxyCommand]::GetHelpComments() in combination with output from Get-Help, you could start with a copy of the original command's help and modify it:
[System.Management.Automation.ProxyCommand]::GetHelpComments((Get-Help Get-Command))
You now have a fully functional which.ps1 wrapper script that behaves like Get-Command itself.
You can invoke it as follows:
./which # Same as: Get-Command; tab completion of parameters supported.
./which -? # Shows Get-Command's help.
You can now edit the script file to perform the desired customization.
Note: The auto-generated source code contains a lot of boilerplate code; however, typically only one or two places need tweaking to implement the custom functionality.
Specifically, place the following command as the first statement inside the begin { ... } block:
if (-not $MyInvocation.ExpectingInput -and -not ($Name -or $CommandType -or $Module -or $FullyQualifiedModule)) {
Throw "Missing command name or filter."
}
This causes the script to throw an error if the caller didn't provide some way of targeting a specific command or group of commands, either by direct argument or via the pipeline.
If you invoke the modified script without arguments now, you should see the desired error:
PS> ./which.ps1
Missing command name or filter.
...
Other common types of customizations are:
Removing parameters from the wrapper, by simply removing the parameter declaration.
Adding additional parameters to the invocation of the wrapped command, by modifying the following line in the begin block:
# Add parameters, as needed.
$scriptCmd = { & $wrappedCmd #PSBoundParameters }
Preprocessing pipeline input before passing it to the wrapped command, by customizing the process block and replacing $_ with your preprocessed input in the following line:
# Replace $_ with a preprocessed version of it, as needed.
$steppablePipeline.Process($_)
For an example of a complete implementation of a proxy function, see this answer.
[1] Caveat for Linux users: since the Linux file-system is case is case-sensitive, invocation of your script won't work case-insensitively, the way commands normally work in PowerShell. E.g., if your script file name is Get-Foo.ps1, only Get-Foo - using the exact same case - will work, not also get-foo, for instance.

Do chained powershell replace commands execute one after the other?

This would likely be a non-issue with expert regex comprehension. And only matters because I am running multiple chained replace commands that affect some of the same text in a text file. I also imagine partitioning the txt files based on how delimiter words --that are requiring multiple replaces-- are used, before replace, would help. With that said basic structural knowledge of powershell is useful and I have not found many great resources (open to suggestions!).
The question: Do chained powershell replace commands execute one after the other?
-replace "hello:","hello " `
-replace "hello ","hello:"
} | out-file ...
Would this silly example above yield hello:'s where there were initially hello:'s?
From working through some projects I gather that the above works most of the time. Yet there always seem to be some edge cases. Is this another aspect of the script or is the order that chained commands (decent number of them) execute in never variable?
What you have there are operators, not commands.
I say that not to be pedantic, but because "command" has a specific meaning in PowerShell (it is a general name encompassing functions, cmdlets, aliases, applications, filters, configurations (this is a DSC construct), workflows, and scripts), and because the way they can be used together is different.
Most operators are reserved words that begin with - (but other things count as operators, like casting), and you can indeed use them chained together. They also execute in order.
I need to clarify; they don't necessarily execute in the order given when you mix operators. Multiple of the same operator will because they all have the same precedence, but you should check about_Operator_Precedence to see the order that will be used when you combine them.
Note that some operators can "short-circuit" (which may sound like a malfunction, but it isn't), that is the result of certain boolean operators will not evaluate later operations if the boolean result can not change.
For example:
$true -or $false
In this example, the $false part of the expression will never actually be evaluated. This is important if the next part of the expression is complex or even invalid. Consider these:
$true -or $(throw)
$false -or $(throw)
The first will return $true because (presumably) nothing in the coming expression could make it $false.
The second line must evaluate the second expression, and in doing so it throws an exception, halting the program.
So, aside from that aside, yes, you can continue to chain your operators. You also don't need a line continuation character (backtick `) at the end of the line if the operator itself is at the end. More useful with boolean operators:
$a -and
$b -or
$c -xor
$false
A little awkward with something like replace:
'apple' -replace
'p',
'z'
Regarding this:
And only matters because I am running multiple chained replace
commands that affect some of the same text in a text file.
These operators aren't touching anything in a file, they are working with data in memory, as literals or variables in your script (what you do with it then, like writing to a file is your business).
Further, even then it doesn't change any values already in variables, it returns new ones, which you may assign to a variable or use in any other way.
$var = 'apple'
$var -replace 'p','Z'
$var
The value of the replacement will be returned, but nothing was done with it so it went out to the console. Then you can see that $var was not modified at all, as opposed to:
$var = 'apple'
$var = $var -replace 'p','Z'
$var
Where the value of $var was overwritten.
If there are edge cases, it's likely to be a misunderstanding of something in the sequence of events (an incorrect regular expression, not assigning or using a value, incorrect logic, etc.), as the order of operations will be consistent. If you have any such edge cases, please post them!

Indent output from private functions in Powershell

I have a bunch of functions that I call that produce output that is displayed to the console. Functions might look something like the following:
exec { & .\xunit.console.clr4 tests.xunit }
#or
exec { & .\nuget.exe pack $source_dir\ZocMonLib\NuSpec\ZocMon.nuspec -OutputDirectory $build_dir\local -Symbols -Version $version }
Now I know I could do something like powershell indentation but that only works if I control the output.
How do I do the indenting of output for these private functions?
Ok, I wrote a version that does the line wrapping right. But it's slightly complicated. I posted it on PoshCode http://poshcode.org/3386
That should work for Write-Host or Write-Verbose, but it will not work if those functions are actually outputting objects -- you'd have to pipe to Write-Host.
The function on PoshCode will (optionally) auto-indent based on the stack depth, but also allow you to specify -Pad 5 or something to manually indent, so you can call nuget.exe ... | write-host -pad 5 or just stick | Write-Host wherever you need it, and then set $WriteVerboseAutoIndent = $true ...
Hope that helps -- it does do manual line wrapping on the output of exes, so it should work.
There's not a great solution, because PowerShell doesn't always run in the console window. Other hosting applications might or might not support tab characters, and might not even support Write-Host. If your goal is strictly to support console display, consider writing a "Format-Console" function.
nuget list NuGetPowerTools | Format-Console
Inside that function, you can capture the pipeline input (which I presume would be strings since this is an external command). Each line of output would be a single String object, so...
Write-Host " $x"
Would display that indented by four spaces.
function Format-Console {
[CmdletBinding()]
param([Parameter(ValueFromPipeline=$True)][string[]]$inputObject)
PROCESS { Write-Host " $inputObject" }
}
That's kinda quick and dirty, but assuming you only ever pipe strings to it, it'll work. Building this as a function lets it be more reusable; using the Format- verb cues other users that the output of this isn't intended to be consumable. It technically isn't a true "Format" cmdlet since it doesn't output internal formatting directives, but it's consistent with the usage pattern for
Can't you assign the result of your private functions to a string and "tab" that string?
$x = nuget list NuGetPowerTools
Write-Host "`t`t$x"