Perform functions default action, unless pipeline input was given? - powershell

Lets say I have a function like this:
Function Hello(){
    Write-Host "Hello, World"}
when used on its own it works perfect but I would also like for it to receive pipeline input:
$MyString = "Something Else please"
$MyString | Hello
In the second example, Something Else please should be printed instead of the default value of Hello, World.
I have searched and searched and have turned up empty handed. everything out there keeps mentioning default parameters. I am not looking to use parameters.
Someone please point me in the right direction.

Declare a parameter, and:
assign it a default value.
make it pipeline-binding, and be sure to process each pipeline input object in the function body, namely via a process block
Note: Declaring a parameter pipeline-binding (with [Parameter(ValueFromPipeline)] below) implicitly makes a function an advanced one, which has (generally beneficial) behavioral implications - see this answer.
function Hello {
param(
[Parameter(ValueFromPipeline)]
$InputObject = 'Hello, World' # default value
)
process {
# Called for each pipeline input object, or once with the default value.
Write-Host $InputObject
}
}
For robustness, it's generally preferable to declare parameters explicitly, as shown above.
The - less desirable - parameter-less, simple-function alternative is the following, which collects all pipeline input up front, as it too implicitly runs in an end block, and uses the automatic $input variable and $MyInvocation.ExpectingInput to detect if pipeline input was provided, as suggested by Santiago Squarzon:
function Hello {
if ($MyInvocation.ExpectingInput) { # input from the pipeline
$input | Write-Host # pass each pipeline input object to Write-Host
} else { # no pipeline input -> use default value
'Hello, World' | Write-Host
}
}
As for what you tried in your answer:
By not using a process block, in effect only the last input object from the pipeline is bound to parameter $InputObject, because a function body without (any one of) begin, process and end blocks implicitly runs in an end block, i.e. after all pipeline input has been received.
Generally, there's no good reason to type a parameter [PSObject] or [PSObject[]], given that [psobject] is a usually invisible helper type used behind the scenes.
Not typing a parameter is the same as typing it [object], which is what should be used to accept arguments of any type.
Typing it [array] is the same as typing it [object[]], but note that if you type a pipeline-binding parameter as an array, each individual input object is automatically converted to an array - which slows down processing.
Only if you need to accept multiple values as a single command-line argument (as opposed to via the pipeline) is declaring a parameter as an array warranted - and when you do, you then need to enumerate each bound parameter value in your process block, as it may itself be a true array.
As an aside: That pipeline-binding parameters declared with scalar types only accept multiple values via the pipeline, but not also implicitly as an argument is the subject of GitHub issue #4242

By using this syntax for you function you can create what I think you're after.
Clear-Host
Function Hello {
Param (
[Parameter(Mandatory=$False,ValueFromPipeline=$True)]
[String] $MyText = "Hello"
)
Write-Host "$MyText, World"
}
Hello
"It's A Wonderful" | Hello
Output:
Hello, World
It's A Wonderful, World
PS>

Here is what I arrived at after #Santiago Squarzon pointed me to the right direction
Function Hello {
Param (
[Parameter(Mandatory=$False,ValueFromPipeline=$True)]
[PSObject[]] $InputObject,
)
if ($PSCmdlet.MyInvocation.ExpectingInput) {
"Data received from pipeline input: '$($InputObject)'"
}
else {
Write-host "Hello World"
}
}

Related

Default condition of Switch parameter never gets triggered

I have a switch statement inside a function like so:
function SomeFunc {
[CmdletBinding()]
Param(
[Parameter(Position = 0)]
[switch]$History
)
Process {
switch ($PSBoundParameters.keys) {
'History' {$PSBoundParameters.keys}
Default {write-host "No Parameters used"}
}
}
}
If I invoke SomeFunc -History "History" gets printed, as expected.
but for the life of me, I cannot get the default condition to trigger, I am expecting "No Parameters used" to print when I invoke only "SomeFunc"
Any help on this would be really wellcome.
tl;dr
Because $PSBoundParameters.Keys is an empty collection when no arguments are passed, the switch statement's body is never entered.
Use $PSBoundParameters.Count -eq 0 to detect if no parameters were passed.
It isn't obvious, but the switch statement
enumerates its input, just like the pipeline does.
That is, if you provide an enumerable object to switch, the enumerated elements are processed, one by one.
Typical examples of enumerables are arrays or collections, though not hashtables. See the bottom section of this answer for details on what PowerShell considers enumerable.
It follows that if there is nothing to enumerate, no processing takes place at all - not even the default branch is entered.
In short: Any empty enumerable causes the switch statement's body to be skipped, notably with:
an empty array or collection (one without elements), such as the empty [System.Collections.Generic.Dictionary`2+KeyCollection[string, object]] instance that the automatic $PSBoundParameters variable's .Key property returns when no parameters are bound.
a call to a command that produces no output
The reason is that PowerShell represents the no-output case with a special object sometimes called "AutomationNull", which can be thought of as an "enumerable $null"; that is, in an enumeration context it behaves like an empty collection rather than $null - see this post for details.
Two simple examples in which a switch statement's body is skipped:
# An empty array has nothing to enumerate -> body is not entered.
switch (#()) {
default { 'Never get here!' }
}
# Ditto for a command that produces no output.
switch ((Get-ChildItem -Filter *NoSuchFiles*)) {
default { 'Never get here!' }
}

Two PowerShell methods: SetString() accept a string from the user and PrintString() print the string in upper case

What can I do so that I write the desired phrase in brackets and it will be accepted?
Where exactly to get the "basis" in order to make a return using the "PrintString" method?
# Example 1
$Object.SetString("gamarjoba")
# Example 2
$Object.PrintString()
# Returns
GAMARJOBA
Here is one of my attempts:
class Object {
[string]$gamarjoba
[string]SetString() {
return Write-Host "gamarjoba"
}
[string]PrintString() {
return Write-Host "gamarjoba".ToUpper()
}
}
I understand that this is a very basic question, but I have already spent too much time on it.
You're probably looking for this:
class MyObject {
[string] $gamarjoba = '' # instance variable
# Method has no return type; accepts a string parameter
# and assigns it to instance variable $this.gamarjoba
[void] SetString([string] $gamarjoba) {
$this.gamarjoba = $gamarjoba
}
# Returns the uppercased form of the $this.gamarjoba instance variable.
# Do NOT use Write-Host.
[string] PrintString() {
return $this.gamarjoba.ToUpper()
}
}
$obj = [MyObject]::new() # create an instance
$obj.SetString('aha') # set the string
$obj.PrintString() # print the uppercased string
Note that I've named the class MyObject, because Object would clash with the root class of the .NET type hierarchy.
As for what you tried:
return Write-Host "gamarjoba".ToUpper()
Fundamentally, do NOT use Write-Host to return or output data - it is meant for printing information to the display (console), and bypasses the success output stream and thereby the ability to send output to other commands, capture it in a variable, or redirect it to a file - see this answer for more information.
In the context of class definitions, use only return to return data from a method, passing it an expression or command as-is (e.g, return 'foo'.ToUpper() or return Get-Date -Format yyy)
PowerShell classes participate in the system of output streams only in a very limited way:
returning a value from a method writes it to the success output stream.
Notably, implicit output and use of Write-Output (i.e. what you would use in a script or function) are not supported and quietly ignored. (While you can use return Write-Output ..., there's no good reason to do so.)
throwing an error writes it to the error stream - but you'll only see that if you catch or silence such a fatal-by-default error.
Notably, using Write-Error to write non-terminating errors does not work and is quietly ignored.
However, you can write to all other output streams using their respective Write-* cmdlets, such as Write-Warning.

In powershell is there a difference between having param in a function or putting parameters in the function?

In powershell you can make functions with function name {commands} and make those functions take arguments with this:
function myFunction {
param($var1, $var2)
}
but you can also accomplish this with
function myFunction($var1, $var2) {}
and they would be the same.
For example, if I made a function func1 be:
function func1 {
param($var1, $var2)
echo "$var1 $var2"
}
I would call it by using func1 1 2 where $var1 would be equal to 1 and $var2 would be equal to 2.
Input:
PS C:\Users\Neko> func1 1 2
Output:
1 2
However, if I do the same thing but instead I did the other method of passing arguments to functions:
function func2($var1, $var2) {
echo "$var1 $var2"
}
I would also call it the same exact way, calling it by using func2 1 2 where $var1 would be equal to 1 and $var2 would be equal to 2 like the previous function.
Input:
PS C:\Users\Neko> func2 1 2
Output:
1 2
So everything seems the same and constant between the two renditions of the function, so my question is, is there a difference between the two methods of passing arguments to functions or are they both actually the same? Even if it is the most minor of details, or just a parsing difference, I would like to know any differences between the two in functions specifically since param has other uses as well.
UPDATE: The arguments you can do in param like [parameter(Mandatory=$true, ValueFromPipeline=$true)] and [String[]] are not unique to param. You can also accomplish this in the other 'non-param' example by doing:
function func2(
[parameter(Mandatory=$true, ValueFromPipeline=$true, etc)]
[String[]]
$var1, $var2
) {
echo "$var1 $var2"
}
To complement 7cc's helpful answer:
While the two syntax forms are mostly interchangeable when you define a function's parameters, only the param(...) block syntax works in the following circumstances:
If you want to use a [CmdletBinding()] attribute and its properties to (explicitly) make your function or script an advanced function or script.[1]
If you're writing a script file(*.ps1) or script block ({ ... }): the only way to declare parameters for them is is by placing a param(...) block at the beginning.
Therefore, you may opt to always use the param(...) block syntax, for consistency across function and script parameter definitions.
If a [CmdletBinding(...)]) attribute is used, it must directly precede the param(...) block.
As for:
I would call it by using func1(1)(2)
No, you would call it as follows:
func1 1 2
That is, PowerShell functions are called like shell commands: without parentheses, separated by whitespace; while your invocation happens to work too, the use of (...) around the arguments can change their interpretation:
without the enclosing (...) the arguments are parsed in argument mode, where, notably, strings needn't be quoted
with the enclosing (...), are parsed in expression mode, where strings do need to be quoted.
See this answer for more information.
[1] While you can place a [CmdletBinding(...)] attribute inside the parentheses with the function Foo (...) { ... } syntax without provoking an error, doing so is effectively ignored. Separately, in the absence of an (effective) explicit [CmdletBinding(...)] attribute, with either syntax, if you happen to decorate at least one parameter with a [Parameter()] attribute, you get the default behaviors of an advanced function (e.g., support for automatic common parameters such as -Verbose), because using [Parameter()] implicitly makes a function an advanced one (as if a [CmdletBinding()] attribute - without explicit property values - were in effect). However, if you need an explicit [CmdletBinding(...)] attribute, so as to opt into non-default advanced-function behaviors, via property values such as PositionalBinding=$false or SupportsShouldProcess=$true, use of a param(...) block is your only option.
One thing is that the CmdletBinding attribute requires Param
function Echo-Confirm
{
# Here
[CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="High")]
Param ($val=1)
if ($PSCmdlet.ShouldProcess($val) -eq $true) {
Write-Output "Confirmed $val"
}
}
Edit after this comment
The syntax is fine, but CmdletBinding has no effect
Function foo (
[CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="High")]
[Parameter()]$val=1
) {
# never confirm
if ($PSCmdlet.ShouldProcess($val) -eq $true) {
Write-Output "always here"
}
else {
Write-Output "never here"
}
}
foo -Confirm
# throws an error
foo: A parameter cannot be found that matches parameter name 'confirm'.
From About Functions - Functions with Parameters - Named Parameters:
You can define any number of named parameters. You can include a
default value for named parameters, as described later in this topic.
You can define parameters inside the braces using the Param keyword,
as shown in the following sample syntax:
function <name> {
param ([type]$parameter1[,[type]$parameter2])
<statement list>
}
You can also define parameters outside the braces without the Param
keyword, as shown in the following sample syntax:
function <name> [([type]$parameter1[,[type]$parameter2])] {
<statement list>
}
…
While the first method is preferred, there is no difference between
these two methods.
Edit
#mklement0 thanks for helpful explication of the latter (emphasized) statement.
This statement (there is no difference between these two methods) is valid despite of 7cc's improper guesswork.
7cc's answer is right, explained in mklement0's comments below and his updated answer.
In About Functions Advanced Parameters => Attributes of parameters, there are some allusions to the relation of CmdletBinding and Parameter attributes and advanced functions (advanced functions use the CmdletBinding attribute to identify them as functions that act similar to cmdlets):
… if you omit the CmdletBinding attribute, then to be recognized
as an advanced function, the function must include the Parameter
attribute…
… to be recognized as an advanced function, rather than a simple
function, a function must have either the CmdletBinding attribute
or the Parameter attribute, or both.
I can't comprehend PowerShell inventors' motivation for such (confusing for me) design…

Use function parameter as both a variable and switch [duplicate]

I have the powershell function below
Function Test
{
Param
(
[Parameter()]
[string]$Text = "default text"
)
Write-Host "Text : $($Text)"
}
And I would like to be able to call this function like below :
Test -Text : should display the default text on the host
Test -Text "another text" : should display the provided text on the host
My issue is that the first syntax is not allowed in powershell ..
Any ideas of how I can achieve this goal ?
I would like a kind of 'switch' parameter that can take values other than boolean.
Thanks
The problem you're running into is with parameter binding. PowerShell is seeing [string] $Text and expecting a value. You can work around this like so:
function Test {
param(
[switch]
$Text,
[Parameter(
DontShow = $true,
ValueFromRemainingArguments = $true
)]
[string]
$value
)
if ($Text.IsPresent -and [string]::IsNullOrWhiteSpace($value)) {
Write-Host 'Text : <default text here>'
}
elseif ($Text.IsPresent) {
Write-Host "Text : $value"
}
}
Note: this is a hacky solution and you should just have a default when parameters aren't passed.
tl;dr
PowerShell does not support parameters with optional values.
A workaround is possible, but only for a single parameter.
Maximilian Burszley's helpful answer provides a workaround for a single parameter, via a catch-all parameter that collects all positionally passed arguments via the ValueFromRemainingArguments parameter property.
Fundamentally, though, what you're asking for is unsupported in PowerShell:
PowerShell has no support for parameters with optional values as of 7.2 - except for [switch] parameters, which are limited to [bool] values.
That is:
Any parameter you declare with a type other than [switch] invariably requires a value (argument).
The only other option is to indiscriminately collect any unbound positional arguments in a ValueFromRemainingArguments-tagged parameter, but you won't be able to associate these with any particular other bound parameter.
In other words:
If you happen to need just one optional-argument parameter, the ValueFromRemainingArguments can work for you (except that you should manually handle the case of mistakenly receiving multiple values), as shown in Maximilian Burszley's answer.
If you have two or more such parameters, the approach becomes impractical: you'd have to know in which order the parameters were passed (which PowerShell doesn't tell you) in order to associate the remaining positional arguments with the right parameters.
With [switch] parameters (using an imagined -Quiet switch as an example):
The default value - if you just pass -Quiet -is $true.
$false is typically indicated by simply not specifying the switch at all (that is, omitting -Quiet)
However, you may specify a value explicitly by following the switch name with :, followed by the Boolean value:
-Quiet:$true is the same as just -Quiet
-Quiet:$false is typically the same as omitting -Quiet; in rare cases, though, commands distinguish between an omitted switch and one with an explicit $false value; notably, the common -Confirm parameter allows use of -Confirm:$false - as opposed to omission of -Confirm - to override the value of the $ConfirmPreference preference variable.
While : as the separator between the parameter name and its argument (as opposed to the usual space char.) is supported with all parameters, with [switch] parameters it is a must so as to unequivocally signal that what follows is an argument for the switch parameter (which by default needs no argument) rather than an independent, positional argument.
The above tells us that PowerShell already has the syntax for general support of optional-argument parameters, so at some point in the future it could support them with any data type, as suggested in GitHub issue #12104.
I like #Maximilian Burszley's answer (and his name!) for String, I tweaked it for Ints:
function Optional-SwitchValue {
[CmdletBinding()]
param (
[Switch]
$Bump,
[Int]
$BumpAmount
)
Begin {
# nifty pattern lifted from https://stackoverflow.com/questions/58838941/powershell-special-switch-parameter
# default Bump to 1
if ($Bump.IsPresent -and -not $BumpAmount) {
$BumpAmount = 1
}
}
Process {
if($Bump) {
#use $BumpAmount in some way
}
}
}

How to define named parameter as [ref] in PowerShell

I'm trying to use [ref] named parameters. However, I am getting an error:
workflow Test
{
Param([Parameter(Mandatory=$true)][String][ref]$someString)
write-verbose $someString -Verbose
$someString = "this is the new string"
}
cls
$someString = "hi"
Test -someString [ref]$someString
write-host $someString
#Error: Cannot process argument transformation on parameter 'someString'. Reference type is expected in argument.
How can I fix this problem?
I noticed that you are using a "workflow" in your example of a [ref] parameter.
For simplicity, let's call it a "function" and get back to "workflow" later.
There are three things you need to change in your code:
When passing a [ref] parameter to function, you need to enclose the parameter in parenthesis ().
When using a [ref] parameter within a function refer to $variable.value
Remove [string] type from your parameter definition. It can be a [string], or [ref], but not both.
Here is code that works:
function Test
{
Param([Parameter(Mandatory=$true)][ref]$someString)
write-verbose $someString.value -Verbose
$someString.value = "this is the new string"
}
cls
$someString = "hi"
Test -someString ([ref]$someString)
write-host $someString
As for "workflows". They are very restricted, read PowerShell Workflows: Restrictions. In particular you can't invoke a method on an object within workflow. This will break the line:
$someString.value = "this is the new string"
I don't think that using [ref] parameters in a workflow is practical, because of workflow restrictions.
I felt I needed to write this complementary very simplistic answer since this was the first google hit when searching for info on using reference parameters in Powershell functions. Although your question was not about functions but workflows:
Example using reference parameters in functions (doesn't work with workflow):
Function myFunction ([ref]$aString) {
$aString.Value = "newValue";
}
$localVariable = "oldValue"
Write-Host $localVariable # Outputs: oldValue
myFunction ([ref]$localVariable);
Write-Host $localVariable # Outputs: newValue
With functions you can define parameter to be both a reference and another type, like this (but not with workflows):
Function myFunction ([ref][string]$aString) {
$aString.Value = "newValue";
}
$localVariable = "oldValue"
Write-Host $localVariable # Outputs: oldValue
myFunction ([ref]$localVariable);
Write-Host $localVariable # Outputs: newValue
I agree with Jan, you should not be trying to use reference parameters in workflows because of workflow restrictions (Method invocation on objects): https://blogs.technet.microsoft.com/heyscriptingguy/2013/01/02/powershell-workflows-restrictions/