Powershell param declaration alternatives - powershell

What's the difference between:
function foo ([int] i, [string] s) { ... }
And:
function foo {
param (
[int] i,
[string] s
)
{ ... }
}
The help on MSDN does not explain this. Thanks!

Except for convenience, there's no difference. Both ways are valid. Using param (in my opinion) is more readable especially in advanced functions where a parameter declaration may contain a few lines of code and you can use indentation and line breaks.

Param statements are needed in scripts and scriptblocks, where there's not a natural place to define parameters like there would be in a function. Manojlds is correct that you have a lot of opportunities to use advanced function "options" to supercharge your function parameters if you use a param statement that you can't use in the more traditional parameter list.

No difference in this case. You might need to use the param declaration for advanced function parameters

Related

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…

How to define the return type / OutputType of a function

Why is the following changing type?
function SomeFunction($SomeParameter){
return $SomeParameter
}
I guess I need to set a return type, but how?
An example is using:
$NewFolder=Join-Path $CurrentFolder -ChildPath $FolderName
$Tmp=SomeFunction($NewFolder)
Now $Tmp is an array and not just a path
While this answer explains the behavior you're seeing, here I will attempt to answer the actual question: how to declare the expected output type of a function!
You do so by adding an [OutputType] attribute to the param() block of your function - so the first thing you'll want to do is to skip the C#-style param list and declare a proper param block instead:
function SomeFunction
{
param($SomeParameter)
return $SomeParameter
}
Now we just need to add the [OutputType] attribute decorator:
function SomeFunction
{
[OutputType([string])]
param($SomeParameter)
return $SomeParameter
}
since we're just returning the parameter argument value as-is in this example, we should play nice and make sure it's actually also a string:
function SomeFunction
{
[OutputType([string])]
param(
[string]$SomeParameter
)
return $SomeParameter
}
Worth noting that [OutputType()] makes no guarantees as to the type of objects emitted during execution, it's simply a way for the author of a function to indicate the intended output type.
Read more about [OutputType] in the about_Functions_OutputTypeAttribute help file
Your issue is per "design". PowerShell will return an array in chunks so that it can be forwarded the PowerShell pipeline.
Example:
SomeFunction -SomeParameter #(1,2,3,4) | Where-Object { $_ -gt 2 }
Without this behavior pipelining the output of the function to another function/cmdlet won't be possible.
If you want to return an array you can change to code to:
function SomeFunction($SomeParameter){
<#
# Through the unary operator we can return an array with one entry.
# This entry contains the original array.
#>
,$SomeParameter
}
Another option would be the use of #() when at the calling side:
function SomeFunction($SomeParameter){
# return to pipelin
$SomeParameter
}
$array = #(SomeFunction -SomeParameter 1,2,3,4)
There is also this reddit answer explaining the behavior in more detail.
Hope that helps.

Powershell - Optional parameter in method of a class

In powershell functions i can have something like
Function myFunction {
Param(
[Parameter(Mandatory=$True)][string]$foo,
[Parameter(Mandatory=$False)][string]$bar = "whatever"
)
....
}
But this seems limited to functions - is there something similar for method?
class MyClass {
...
[void]MethodA {
Param(
....
dont works for me.
The interpreter complains about the missing '(' in the class method parameter list.
Adding methods to classes works just like in most other scripting languages one might know. Instead of [void]MethodA {Param()...} you might want to add a block like described in this blog post or here:
class MyClass {
#...
[void]MethodA ($param) {
#...
}
}
As your title says optional parameters (but your question doesn't) a short word on that...
Usually you want multiple signatures for such cases. That means you create a method MethodA($arg1, $arg2) and a delegating method like MethodA($arg1) {MethodA($arg1, $null)}

Get params by paramset?

In Powershell, you can assign params to paramsets. You can check which paramset using $PSCmdlet.ParameterSetName. Is there anything more intelligent you can do? Easily get all the params in that paramset that were passed?
I want to require that one and only one param in a paramset was passed. Wondering if there's a more elegant, built-in solution than checking and counting $PSBoundParams against a static list of keys.
Use $PSBoundParameters to find the parameters that are user-populated. It is a [HashTable] with a couple bonuses.
Ex.ps1:
[CmdletBinding()]
Param([Parameter(ParameterSetName='Custom1',Position=0)]
[String]$ex)
$PSBoundParameters.Keys
PS C:\>ex.ps1 Example!
> ex
Alternatively, you can use a Switch on $PSCmdlet.ParameterSetName to do things with the parameters.
Switch ($PSCmdlet.ParameterSetName)
{
'__AllParameterSets' { }
'Custom1' { }
}

Can I use a constant in the ValidateSet attribute of a PowerShell function parameter?

I am using the ValidateSet attribute on one of my PowerShell function parameters like so:
[ValidateSet('Development','Test','Production')]
[string]$Context
I have repeated this is many places throughout a scripting project. Can these literal strings be replaced with a constant?
No, it has to be a literal or a scriptblock. The scriptblock option seems pointless since it seems to use the literal (string) value of the scriptblock instead of executing it.
So effectively, from my testing, you must use literals.
If you use a dynamic parameter instead you could achieve this, but that's way overkill just to be DRY.
If you try to use a variable, it won't work (and ISE will give you the red squiggly). The help text erroneously says it must be a constant, but it means literal.
I created a constant with:
Set-Variable -Option Constant
And it still does not work.
Adding this to help others searching for a similar solution. I was looking for a way to validate parameters against the keys of a global hash table. This is what I ended up doing:
$global:MyHash = #{
"anyitem" = #{"name" = "somename1"; "count" = 42 };
"someitem" = #{"name" = "another name"; "count" = 1337 };
}
function Test-Hash
{
param
(
[Parameter(mandatory = $true)] [ValidateScript( { $_ -in $global:MyHash.Keys } )] [string[]] $items
)
}
Test-Hash -items anyitem, someitem
I ended up replacing ValidateSet with ValidateScript as I realized (as mentioned in this thread as well) that the code block in ValidateSet does not work at all. Instead validating against the keys of a hash table one could easily use something like
$validParams = #('opt1', 'opt2')
and in the ValidateScript codeblock
{ $_ -in $validParams }
This is basically what I assume should answer the question.