Default value of mandatory parameter in PowerShell - powershell

Is it possible to set a default value on a mandatory parameter in a function?
It works without having it set as an mandatory ...
Ie.
Function Get-Hello {
[CmdletBinding()]
Param([Parameter(Mandatory=$true)]
[String]$Text = $Script:Text
)
BEGIN {
}
PROCESS {
Write-Host "$Script:Text"
Write-Host "$Text"
}
END {
}
}
$Text = "hello!"
Get-Hello
Reason for asking this is because i have a function that has some required parameters and the function works perfect with calling it with the required parameters but i also want to make it possible for these variables to be defined in the scripts that use this function in a "better presentable & editable" way along with the function to be able to be run with defining the required parameters.
Hence if defined in the script scope it should take that as default else it should prompty for the value.
Thanks in Advance,

If you targeting to PowerShell V3+, then you can use $PSDefaultParameterValues preferences variable:
$PSDefaultParameterValues['Get-Hello:Text']={
if(Test-Path Variable::Script:Text){
# Checking that variable exists, so we does not return $null, or produce error in strict mode.
$Script:Text
}
}

Related

Use value of parameter in inner (global) function

In PowerShell, I'm trying to customise the prompt inside a function that creates a development shell. I do that by creating an inner function prompt, with global scropt.
function Enter-DevEnvironment {
Param(
[Parameter()] [ValidateSet('Debug', 'Release')] $flavor = 'Debug'
)
function global:prompt {
"[$flavor] $($executionContext.SessionState.Path.CurrentLocation)>"
}
}
The problem is that while the function Enter-DevEnvironment has a variable $flavor, this variable is not available for the prompt function.
I've workedaround this by creating a yet another global variable ($global:DevFlavor = $flavor), and using DevFlavor inside prompt, but it left me wonder, whether a cleaner solution is available. I.E. creating an inner function using values from the outer scope by value, and not refering to a variable that may or may not be defined.
This can be done without creating a global variable, by defining the prompt function using New-Item. This allows us to pass a ScriptBlock and use its method GetNewClosure() to bake the value of the -flavor parameter into the function.
function Enter-DevEnvironment {
Param(
[Parameter()] [ValidateSet('Debug', 'Release')] $flavor = 'Debug'
)
$null = New-Item Function:\global:prompt -Force -Value {
"[$flavor] $($executionContext.SessionState.Path.CurrentLocation)>"
}.GetNewClosure()
}

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 do I change the value of a variable using a function in powershell?

I want to check the count of the items in a variable and change the value of the variable depending upon the count. Further, I want to use this function to validate other variables as well. Below mentioned is the code.
$c=#()
function Status($s)
{
if($s.Count -eq "0"){
$s = "Fail"
}else{
$s="success"
}
}
Status $c
Here I expected the value of $c would be "Fail". But instead the value remains to be null.
Without fiddling around with scopes or sending variables by reference, why don't you simply have the function return 'Fail' or 'Success'?
BTW The .Count property is of type Int32, so you should not surround the value 0 with quotes, making it a string.
function Get-Status($s) {
if($s.Count -eq 0) { 'Fail' } else { 'Success' }
}
Now, if you want to overwrite the variable $c with the outcome of whatever the function returns, simply do:
$c = #()
$c = Get-Status $c # will reset variable $c to the string value of 'Fail'
P.S. I renamed the function so it conforms to the Verb-Noun naming convention in PowerShell
If you want to change multiple variables in one function you need references or scopes. Scopes will change the variable with the same name inside the function and globally. Calling a variable by reference is indifferent to the variable names outside the function.
Reference:
Working with references your variable in the function needs to be of type [ref] ( or System.Management.Automation.PSReference ). In that case the argument you use must be cast to [ref] and to this before calling the function enclose the var with brackets ([ref]$c). When using references, you can't just change the variable itself, but you need to work with its .value. The value of your reference represents your original variable. ([ref]$s.Value -eq $c)
Using that your code would look like this:
$c=#()
function Status([ref]$s) #Define $s as [ref]. only arguments of type [ref] are valid
{
if($s.value.Count -eq 0)
{
$s.Value = "Fail" #This will change the type of $c from array to string
}
else
{
$s.Value += "success" #This will recreate the array with an additional object (string)
}
}
Status ([ref]$c) #The variable must be cast to [ref] to be valid
$c
Scopes:
Normally a function is executed in a lower scope than the rest of the script. That means variables only exist in their scope and in lower scopes and changes in lower scopes won't reflect to higher scopes. However, you can directly address a variable in another scope using $<scope>:var ($script:s). The downside is you work with the variable itself. The name of the variable inside the function and outside must be the same. (reading the help for scopes is highly recommended)
Here is your code with scopes:
$s=#() #var needs to have the same name
function Status #No parameter here
{
if($script:s.Count -eq "0")
{
$script:s = "Fail" #This will change the type of $s from array to string
}
else
{
$script:s += "success" #This will recreate the array with an additional object (string)
}
}
Status
$s
For a more global function, here is a “get the value of the specified variable” function.
# PowerShell
function getVar($name){
return (Get-Variable -Name $name).Value
}
The only problem with this is that if you have two variables with different scopes, the function may return the wrong variable, therefore the wrong value.
Here is a function to set a variable. It suffers from the same cons as above though.
# PowerShell
function setVar($name, $value){
Set-Variable -Name $name -Value $value
}
You can use the -Scope $scope part to help if you need to.
Happy coding!

Powershell "special" switch parameter

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
}
}
}

Why does my mandatory hashtable parameter not take my value

I have a function with parameters like this:
function foo {
Param(
[Parameter(Mandatory)]
[string]$foo,
[Parameter(Mandatory)]
[hashtable]$bar
)
}
if i call it like this everything works fine:
foo -foo "abc" -bar #{"a"="b"}
but if I only call foo and PowerShell asks me to specify values for my mandatory parameters, it won't accept my hashtable.
PS C:\Users\abc> foo
cmdlet foo at command pipeline position 1
Supply values for the following parameters:
foo: "abc"
bar: #{"a"="b"}
Cannot convert the "#{"a"="b"}" value of type "System.String" to type "System.Collections.Hashtable".
why does PowerShell not take my specified value?
I also tried to write it different ways like #{a=b}, "a"="b", #{a=b;}, #{"a"="b";} etc.
When you mark a parameter as Mandatory and don't specify a value (argument) for it on invocation, PowerShell automatically prompts you for a value.
Unfortunately, this automatic prompting mechanism for missing mandatory parameter values is broken (it has never worked, and still doesn't as of PowerShell v7):
You currently cannot interactively supply a value for any of the following types:
[hashtable],[scriptblock], [bool], [switch] (the latter two being uncommon as mandatory parameters) - their literal forms #{ ... }, { ... } and $true / $false are simply not recognized.
There's also no way to interactively specify $null.
Additionally, the automatic prompts, when they do work, are inconvenient (no tab completion, no re-prompting on invalid input), and therefore currently of little use.
These problems are being discussed in this GitHub issue.
Given the above, it's better to bypass the automatic prompts altogether and instead throw an error when no value was provided:
function foo {
Param(
# Use a default value that throws an error.
[string] $foo = $(Throw "Please pass a value for -foo."),
# Use a default value that throws an error.
[hashtable] $bar = $(Throw "Please pass a value for -bar.")
)
}
Note that [Parameter(Mandatory)] then mustn't be used, as that would still trigger an automatic prompt, despite the presence of a default value.
Read-Host (which is invoked automatically when this happens) always returns strings. If you want to take hashtable input like that, you will need to process the input. I would do something like:
if ($foo -is [system.Collections.hashtable]) {
//proceed
} else if ($foo -is [system.array]) {
Foreach ($fooItem in $foo) {
$myhash.add($key, $fooItem)
}
} else if ($foo -is [system.string]) {
$fooItem = $foo.split(,)
...
} else {
//error
}