powershell mandatory parameter with default value shown - powershell

I am looking for a way to have an PowerShell script ask for an parameter which needs to be mandatory, but shown with an default value, e.g.:
.\psscript
Supply values for the following parameters:
parameter1[default value]:
parameter2[1234]:
I want to ask for input but provide some default values.
If I use the mandatory option it asks for the values nicely but doesn't show the default value or process the given value. If I don't make it mandatory then PowerShell doesn't ask for the value at all.
Here's some script examples I tried:
[CmdletBinding()]
Param(
[parameter(Mandatory=$true)] $SqlServiceAccount = $env:computername + "_sa",
[parameter(Mandatory=$true)] $SqlServiceAccountPwd
)
This script asks for parameters but does not show or process the default value if I just press enter on the first parameter.
[CmdletBinding()]
Param(
[parameter(Mandatory=$false)] $SqlServiceAccount = $env:computername + "_sa",
[parameter(Mandatory=$true)] $SqlServiceAccountPwd
)
This script doesn't ask for the first parameter, but processes the default value.

Here's a short example that might help:
[CmdletBinding()]
Param(
$SqlServiceAccount = (Read-Host -prompt "SqlServiceAccount ($($env:computername + "_sa"))"),
$SqlServiceAccountPwd = (Read-Host -prompt "SqlServiceAccountPwd")
)
if (!$SqlServiceAccount) { $SqlServiceAccount = $env:Computername + "_sa" }
...

By definition: mandatory parameters don't have default values. Even if you provide one, PowerShell will prompt for value unless specified when the command is called. There is however a 'hacky' way to get what you ask for. As variables (and as consequence - parameters) can have any name you wish, it's enough to define command with parameters that match the prompt you would like to see:
function foo {
param (
[Parameter(Mandatory = $true)]
[Alias('Parameter1')]
[AllowNull()]
${Parameter1[default value]},
[Parameter(Mandatory = $true)]
[Alias('Parameter2')]
[AllowNull()]
${Parameter2[1234]}
)
$Parameter1 =
if (${Parameter1[default value]}) {
${Parameter1[default value]}
} else {
'default value'
}
$Parameter2 =
if (${Parameter2[1234]}) {
${Parameter2[1234]}
} else {
1234
}
[PSCustomObject]#{
Parameter1 = $Parameter1
Parameter2 = $Parameter2
}
}
When called w/o parameters, function will present user with prompt that match parameter names. When called with -Parameter1 notDefaultValue and/or with -Parameter2 7, aliases will kick in and assign passed value to the selected parameter. As variables named like that are no fun to work with - it makes sense to assign value (default or passed by the user) to variable that matches our alias/ fake parameter name.

I'd do it this way
param(
[Parameter(Mandatory)][string]$aString
)
if([string]::IsNullOrWhiteSpace($aString))
{
$aString = "A Default Value"
}
In my opinion, if you're using Read-Host in a param() block, then you're doing something wrong. At that point, what's the point of using param() at all?

There isn't a way to do what you want with a mandatory parameter and powershell prompting for you.
You would instead have to make it optional (remove mandatory), then implement the prompting code yourself (Read-Host, but take blank response as a default; something like that).

Related

Pass multiple parameters to function by pipeline

I'm having trouble passing two parameters via pipeline to a function.
function Test{
[cmdletbinding()]
param(
[parameter(ValueFromPipeline=$true, Mandatory=$true,Position=0)]
[string]$jeden,
[parameter(ValueFromPipeline=$true, Mandatory=$true,Position=1)]
[string]$dwa
)
Process{write-host "$jeden PLUS $dwa"}
}
"one", "two"|Test
What I expected as an outcome was
one PLUS two
but what I got was
one PLUS one
two PLUS two
I'm obviously doing something wrong, since both parameters get used twice. Please advise.
I got it to work by creating pscustomobject and piping it to function, where ValueFromPipelineByPropertyName property is set to true for both parameters.
function Test{
[cmdletbinding()]
param(
[parameter(ValueFromPipelineByPropertyName=$true, Mandatory=$true,Position=0)]
[string]$jeden,
[parameter(ValueFromPipelineByPropertyName=$true, Mandatory=$true,Position=1)]
[string]$dwa
)
Process{write-host "$jeden PLUS $dwa"}
}
$Params = [pscustomobject]#{
jeden = “Hello”
dwa = “There”
}
$Params |Test
OUTPUT:
Hello PLUS There
Variable assigning can be skipped and [pscustomobject] can be piped directly.
Have u tried to pass both strings as single object? Seems its ur pipeline is treating dem as 2 obj...
#("one", "two") | Test
EDIT. Try to define test in order to accept array:
function Test {
[cmdletbinding()]
param(
[parameter(ValueFromPipeline=$true, Mandatory=$true,Position=0)]
[string[]]$strings
)
Process {
write-host "$($strings[0]) PLUS $($strings[1])"
}
}

Pass an argument from a module

I need help understanding how to pass on an argument from an imported module.
The module contains some custom arguments such as -one, -two, -three
I am trying to make a GUI using the commands from the module.
eg. If "One" is selected from the drop down menu, pass through the -one command.
However when I do so (using the example below), I get the error: "A positional parameter cannot be found that accepts argument '-one'."
I can see that using the code below, it adds single quotations around the command which probably breaks it.
I know I can run an IF statement (eg if combobox.text = "one", do this), however I would prefer to use a variable instead of having to make multiple if statements or a loop. The use of a variable seems like a simpler option.
I'm learning this language as I go so I'm not quite there yet with the knowledge :)
Thanks for any help. Hope this made sense.
$variable = $comboboxNumbers.Text
#example One is selected from the dropdown
Custom-ADCommand -identity "username" $variable
Below is simple example method:
function Set-SwitchParams {
[CmdletBinding()]
param (
# Parameter help description
[Parameter(Mandatory = $false)]
[switch]
$SwitchA,
[Parameter(Mandatory = $false)]
[switch]
$SwitchB
)
begin {
}
process {
}
end {
if ($SwitchA){
Write-Host "SwitchA is activated"
}
if ($SwitchB){
Write-Host "SwitchB is activated"
}
}
}
Put the method in a PS1 file, e.g. SwitchPlayground.ps1. Then source the file in PowerShell via:
. .\SwitchPlayground.ps1
Afterward, you can play around with the command, e.g.:
Set-SwitchParmas -SwitchA
I'd suggest studying the following links:
about functions basic
about functions advanced
about function parameters
Hope that helps.
An If statement if probably much nicer, but its possible to create a string and then execute the string in powershell.
As a simple example take this string
$string = '#("test","hello","whats up")'
I can then execute it and use it to create an array
$array = invoke-expression $string
Which will create an array with "test", "hello" and "whats up" and store it in $array
PS C:\temp> $string = '#("test","hi","what")'
PS C:\temp> $array = Invoke-Expression $string
PS C:\temp> $array
test
hi
what

How to output the referenced value?

I have the following function.
function Params {
param (
[Parameter(Mandatory = $true)]
[Alias('Param1')]
[AllowNull()]
${Param1[default value]}
)
[ref] $Param1 =
if (${Param1[default value]}) {
${Param1[default value]}
} else {
'default'
}
}
Params
$input1 = $null
"the param input is $([ref]$input1)"
If i input something for parameter on prompt or if i leave it to default value, i get this as output for $([ref]$input)
the param input is System.Management.Automation.PSReference`1[System.Management.Automation.LanguagePrimitives+Null]
Why am i not getting a value instead?
I want this output for example:
the param input is default
I ended up resorting to a different method to achieve what i want:
Defining this at the top of script:
[CmdletBinding()]
Param(
$Param1 = (Read-Host -prompt "Param1")
)
if (!$Param1) { "default" }
"the param input is $Param1"
The [ref] type accelerator (it's not a type accelerator in the usual sense, but it does create PSReference objects, so... it sort of is) gets you, as it tells you, a PSReference object.
In order to retrieve the value from it, you'd need to ask for it specifically. In your code, you can access it by pulling the Value property from the created reference object.
"the param input is $(([ref]$input1).Value)"
However, given that $input1 isn't assigned to, you might have to refactor a bit to get what you're after.

Why does parameter binding succeed with no arguments when there is one parameter set but fails with two?

I have two functions. The first has one parameter set, the second has two parameter sets as follows:
function OneSet{
[CmdletBinding()]
param ( $NoSet,
[parameter(ParameterSetName = 'A')]$A )
process { $PSCmdlet.ParameterSetName }
}
function TwoSets{
[CmdletBinding()]
param ( $NoSet,
[parameter(ParameterSetName = 'A',Mandatory = $true)]$A,
[parameter(ParameterSetName = 'B',Mandatory = $true)]$B )
process { $PSCmdlet.ParameterSetName }
}
Invoking the first one without arguments results in '__AllParameterSets' binding:
C:\> OneSet
__AllParameterSets
Invoking the second one without arguments throws an exception:
C:\> TwoSets
TwoSets : Parameter set cannot be resolved using the specified named parameters.
+ TwoSets
+ ~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [TwoSets], ParameterBindingException
+ FullyQualifiedErrorId : AmbiguousParameterSet,TwoSets
I don't see why the second case is any more ambiguous than the first. Why doesn't PowerShell bind to TwoSets using the "__AllParameterSets" parameter set?
Is there a (terse) way to have multiple parameter sets and still be able to call the function with no arguments?
It's because PowerShell can't figure out which parameter set you're trying to use. You can tell it what to default to in the CmdletBinding attribute.
function TwoSets{
[CmdletBinding(DefaultParameterSetName='A')]
param ( $NoSet,
[parameter(ParameterSetName = 'A')]$A,
[parameter(ParameterSetName = 'B')]$B )
process { $PSCmdlet.ParameterSetName }
}
Edit: I think None could be substituted for __AllParameterSets to achieve the goal of "creating a parameter set with no mandatory parameters at all". For more details see PowerShell/PowerShell#11237, #12619, and #11143
I don't see why the second case is any more ambiguous than the first. Why doesn't PowerShell bind to TwoSets using the "__AllParameterSets" parameter set?
PowerShell seems to use the __AllParameterSets parameter set as a default when there is only one user-named parameter set. On the other hand, when there is more than one user-named parameter set, PowerShell does not seem to consider any parameter set to be a default unless you specify it.
Is there a (terse) way to have multiple parameter sets and still be able to call the function with no arguments?
You can tell PowerShell to use __AllParameterSets as the default as follows:
function TwoSets{
[CmdletBinding(DefaultParameterSetName = '__AllParameterSets')]
param ( $NoSet,
[parameter(ParameterSetName = 'A', Mandatory = $true)]$A,
[parameter(ParameterSetName = 'B', Mandatory = $true)]$B )
process { $PSCmdlet.ParameterSetName }
}
Then invoking with no parameters, -A, or -B yields causes binding to the each of the three parameter sets as follows:
PS C:\> TwoSets
__AllParameterSets
PS C:\> TwoSets -A 'a'
A
PS C:\> TwoSets -B 'b'
B

Settings default parameter values with XML values

I read somewhere that the param section must be the very first thing that appears in a script or function so this is what I have come up with in order to set the default values of each of the params. Yes, it is unorthodox, but it works.
Param (
[Xml]$xmlObj = (Get-Content "Download-VBK_config.xml"),
[String]$dlFrom = $xmlObj.Configuration.Download.From,
[String]$dlTo = $xmlObj.Configuration.Download.To,
[String]$exTo = $xmlObj.Configuration.Extract.To
)
However, is there a better way I could go about setting a param's default value by loading values from an XML file?
You can leave the parameters without default values, then look at the $PSBoundParameters variable to see what parameters were passed in, and fill the ones in that we're not passed.
Param(
[string]$Param1,
[string]$Param2)
[xml]$defaults =Get-Content file.xml
if(!$PSboundParameters.ContainsKey("Param1"))
{
$Param1 = $defaults.Configuration.Defaults.Param1
}