complex default values for powershell script Params - powershell

I would like to set a default Param value that must be determined at script runtime (cannot be hardcoded).
Given powershell script file.ps1
<#
.PARAMETER arg1
The first argument. Defaults to ???
#>
Param (
[string] $arg1,
)
I would like:
Set $arg1 to a value that must be determined when the script runs, for example, the host IP address.
Print the default value of $arg1 within the .PARAMETER arg1 help message.
Typically I might add
$arg1_default = (Test-Connection -ComputerName "www.google.com" -Count 1).Address.IPAddressToString
<#
.PARAMETER arg1
The first argument. Defaults to $arg1_default.
#>
Param (
[string] $arg1 = $arg1_default,
)
However, in Powershell, the Param statement must be the first processed statement. If I add any complex statements before Param statement then it results in an error:
PS> .\file.ps1
Param: file.ps1:9
Line |
9 | Param (
| ~~~~~
| The term 'Param' is not recognized as a name of a cmdlet, function, script file, or executable program. Check the spelling of the
| name, or if a path was included, verify that the path is correct and try again.
How do I set default values for a powershell script Param ?
I'm using Powershell 7.

You can use a function with [CmdletBinding()] attribute like Toto in the file test.ps1 defining a default value for a particular parameter for this CmdLet with $PSDefaultParameterValues
# Test.ps1
$PSDefaultParameterValues["Toto:arg1"]=Invoke-Command -ScriptBlock {(Test-Connection -ComputerName "www.google.com" -Count 1).IPV4Address.IPAddressToString}
Function Toto
{
[CmdletBinding()]
Param ([string] $arg1)
Write-Output $arg1
}
Now in the script where you want to use this (or these) function(s) you can first dot source the.ps1 file that contains the function(s), then call your function toto
. /Test.ps1
Toto # without argument gives "172.217.18.196" for me now
Toto titi # With argument give "titi"

Related

How to check Number of arguments in powershell?

param (
[string]$Name = $args[0],#First argument will be the adapter name
[IPAddress]$IP = $args[1],#Second argument will be the IP address
[string]$InterfaceId = $args[3],#Second argument will be the IP address
[string]$VlanId = $args[4], #Fourth argument will be vlanid
[string]$SubnetIP = $args[5],#subnet mask
[string]$IPType = "IPv4",
[string]$Type = "Static"
)
Write-Host $Args.Count
I want to check if command line arguments are supplied to the powershell script or not and if its not supplied then i want to show the usage by write. I am running the script in admin mode. I found one method after searching that using $Args.Count we can get the arguments count while running the script but its always zero for me. what am i doing wrong?
enter image description here
Get rid of the $args[x] assignments and add [cmdletbinding()] on top.
[CmdLetbinding()]
param (
[string]$Name, #First argument will be the adapter name
[IPAddress]$IP, # etc...
[string]$InterfaceId,
[string]$VlanId,
[string]$SubnetIP,
[string]$IPType = "IPv4",
[string]$Type = "Static"
)
Then you can use $PSBoundParameters.Count to get the argument count.
$args is a special variable that is used when named parameter are not present.
Therefore, since you have named parameter, it will always give you a count of zero (except maybe if you add more arguments than there is named parameters)
If you use a param block, then you don't need to assign $args[0] and others. In fact, this is totally useless as they will be $null.
The other approach, although I recommend you to keep the param block, is to not use any named parameters at all. In that case, $args will work as you expect it to.
[string]$Name = $args[0]
[IPAddress]$IP = $args[1]
[string]$InterfaceId = $args[3]
[string]$VlanId = $args[4]
[string]$SubnetIP = $args[5]
[string]$IPType = "IPv4"
[string]$Type = "Static"
The main difference is that if you have a param block, you can call your script in the following ways:
.\MyScript.ps1 -Name "Hello" -Ip 127.0.0.1
.\MyScript.ps1 "Hello" 127.0.0.1
Without the param block, you have only option #2 available to call the script.

Dynamic invoke command with different parameters

In a PowerShell script, I want to read a CSV file that contains something like this:
Type Title Param1 Param2
---- ----- ------ ------
Type1 Foo type 1 ValueForType1
Type2 Foo type 2 ValueForType2
When type is Type1, I have to call a function named New-FooType1, when type is Type2, the funcation is named New-FooType2, and so on:
function New-FooType1{
param(
[Parameter(Mandatory=$true, ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[string]$Title,
[Parameter(Mandatory=$true, ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[string]$Param1
)
Write-Host "New-FooType1 $Title with $Param1"
}
function New-FooType2{
param(
[Parameter(Mandatory=$true, ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[string]$Title,
[Parameter(Mandatory=$true, ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[string]$Param2
)
Write-Host "New-FooType2 $Title with $Param2"
}
I'm trying to route the call to either of the functions, using a dynamic invocation:
$csv | % {
$cmdName = "New-Foo$($_.Type)"
Invoke-Command (gcm $cmdName) -InputObject $_
}
However, I always get an error:
Parameter set cannot be resolved using the specified named parameters
As you can see, different type mean different parameters set.
How can I solve this? I would like to avoid manipulating properties manually, because in my real life script, I have a dozen of different types, with up to 6 parameters.
Here is a complete repro sample of the issue:
$csvData = "Type;Title;Param1;Param2`nType1;Foo type 1;ValueForType1;;`nType2;Foo type 2;;ValueForType2"
$csv = ConvertFrom-csv $csvData -Delimiter ';'
$csv | ft -AutoSize
function New-FooType1{
param(
[Parameter(Mandatory=$true, ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[string]$Title,
[Parameter(Mandatory=$true, ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[string]$Param1
)
Write-Host "New-FooType1 $Title with $Param1"
}
function New-FooType2{
param(
[Parameter(Mandatory=$true, ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[string]$Title,
[Parameter(Mandatory=$true, ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[string]$Param2
)
Write-Host "New-FooType2 $Title with $Param2"
}
$csv | % {
$cmdName = "New-Foo$($_.Type)"
Invoke-Command (gcm $cmdName) -InputObject $_
}
The expected output of this script is:
New-FooType1 Foo type 1 with ValueForType1
New-FooType2 Foo type 2 with ValueForType2
Use the call operator &:
$CmdName = "New-FooType1"
$Arguments = "type1"
& $CmdName $Arguments
the call operator also supports splatting if you want the arguments bound to specific named parameters:
$Arguments = #{
"title" = "type1"
}
& $CmdName #Arguments
To invoke command by name you should use invoke operator &. Invoke-Command cmdlet support only ScriptBlock and file invocation, and file invocation only supported for remote calls.
For dynamic parameter binding you can use spatting, but in that case you have to convert PSCustomObjects, returned by ConvertFrom-Csv cmdlet, to Hashtable. You also have to strip any extra parameters from Hashtable because splatting will fail if you try to bind non-existing parameter.
Another approach for dynamic parameter binding would be to use binding from pipeline object. It looks like it is what you want to do, since you mark all your parameters with ValueFromPipelineByPropertyName option. And this approach will just ignore any extra property it can not bind to parameter. I recommend you to remove ValueFromPipeline option, because with this option in case of absence of property with parameter name PowerShell will just convert PSCustomObject to string (or to whatever type you use for parameter) and pass it as value for parameter.
So, all you need is to pass object by pipeline and use invoke operator for invocation of command with dynamic name:
$_ | & "New-Foo$($_.Type)"
dont know exactly what your trying to do, but
Invoke-Command (gcm $cmdName) ?
Try invoke-expression $cmdname

Why this code passing switch parameters to a PowerShell function fails

This question is about passing switch parameters. Let's see the code. I have this PowerShell 3.0 function:
#test1.ps1
param(
[switch] $param1 = $false
)
Write-Host "param1: $($param1.IsPresent)"
Write-Host
I have this main PowerShell function that invokes test.ps1 in four different ways:
#Test0.ps1
cls
$param1 = $True
# 1
.\test1.ps1 -param1
# 2
.\test1.ps1 -param1:$true
# 3
$potato = "-param1:`$$($param1)"
Write-Host "Parameter value: $potato"
.\test1.ps1 $potato
# 4
$command = ".\test1.ps1 -param1:`$$($param1)"
Write-Host "Command: $command"
iex $command
exit
Why is the 3rd way of doing it failing? I know I can do 4th way but I would love to understand why 3rd is failing.
Here is the output. As result all the parameters should be True but third one is False...
param1: True
param1: True
Parameter value: -param1:$True
param1: False
Command: .\test1.ps1 -param1:$True
param1: True
What happens is that:
The parser looks at the provided argument: "-param1:$true"
Fails to bind it to parameter param1, since the value you provided is a string, not a switch/bool
No specific parameter type is required at position 0, argument is ignored
If you make param1 positional, you can see how PowerShell fails to bind it properly:
function test-parambinding {param([Parameter(Position=0)][switch]$param1);$param1.IsPresent}
test-parambinding "-param1:`$true"
You'll see a ParameterArgumentTransformationException thrown before anything else happens
Mathias explains why your 3rd way of passing the parameter fails, but there is another way to pass parameters that lets you do roughly what you seem to be attempting here.
I've used a function here as it's a bit less to type when calling it, but your script file will work just the same:
PS C:\> function test1() {
param(
[switch] $param1 = $false
)
Write-Host "param1: $($param1.IsPresent)"
Write-Host
}
PS C:\> $param1 = $True
PS C:\> $potato = #{'param1'=$param1}
PS C:\> $potato
Name Value
---- -----
param1 True
PS C:\> test1 #potato
param1: True
So, instead of passing the argument and value as a single string, create a hash with the argument name as the key and the argument as the value. Then call the function or script using the # splatting operator. (See help about_Splatting for more detail).

Accepting an optional parameter only as named, not positional

I'm writing a PowerShell script that's a wrapper to an .exe. I want to have some optional script params, and pass the rest directly to the exe. Here's a test script:
param (
[Parameter(Mandatory=$False)] [string] $a = "DefaultA"
,[parameter(ValueFromRemainingArguments=$true)][string[]]$ExeParams # must be string[] - otherwise .exe invocation will quote
)
Write-Output ("a=" + ($a) + " ExeParams:") $ExeParams
If I run with the a named param, everything is great:
C:\ > powershell /command \temp\a.ps1 -a A This-should-go-to-exeparams This-also
a=A ExeParams:
This-should-go-to-exeparams
This-also
However, if I try to omit my param, the first unnamed param is assigned to it:
C:\ > powershell /command \temp\a.ps1 This-should-go-to-exeparams This-also
a=This-should-go-to-exeparams ExeParams:
This-also
I would expect:
a=DefaultA ExeParams:
This-should-go-to-exeparams
This-also
I tried adding Position=0 to the param, but that produces the same result.
Is there a way to achieve this?
Maybe a different parameter scheme?
By default, all function parameters are positional. Windows PowerShell assigns position numbers to parameters in the order in which the parameters are declared in the function. To disable this feature, set the value of the PositionalBinding argument of the CmdletBinding attribute to $False.
have a look at How to disable positional parameter binding in PowerShell
function Test-PositionalBinding
{
[CmdletBinding(PositionalBinding=$false)]
param(
$param1,$param2
)
Write-Host param1 is: $param1
Write-Host param2 is: $param2
}
The main answer still works in version 5 (according to comments, it may have been broken for a while in version 2).
There is another option: add Position to the ValueFromRemainingArgs parameter.
Sample CommandWrapper.ps1:
param(
$namedOptional = "default",
[Parameter(ValueFromRemainingArguments = $true, Position=1)]
$cmdArgs
)
write-host "namedOptional: $namedOptional"
& cmd /c echo cmdArgs: #cmdArgs
Sample output:
>commandwrapper hello world
namedOptional: default
cmdArgs: hello world
This appears to follow from PowerShell assigning parameter positions from the first parameter with a Position designated.

Powershell: Args/params not being populated

I have a PowerShell script:
param (
[Parameter(Mandatory=$true)][string]$input,
[Parameter(Mandatory=$true)][string]$table
)
Write-Host "Args:" $Args.Length
Get-Content $input |
% { [Regex]::Replace($_, ",(?!NULL)([^,]*[^\d,]+[^,]*)", ",'`$1'") } |
% { [Regex]::Replace($_, ".+", "INSERT INTO $table VALUES (`$1)") }
The Write-Host part is for debugging.
I run it as .\csvtosql.ps1 mycsv.csv dbo.MyTable (from powershell shell), and get
Args: 0
Get-Content : Cannot bind argument to parameter 'Path' because it is an empty s
tring.
At C:\temp\csvtosql.ps1:7 char:12
+ Get-Content <<<< $input |
+ CategoryInfo : InvalidData: (:) [Get-Content], ParameterBinding
ValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAl
lowed,Microsoft.PowerShell.Commands.GetContentCommand
I get exactly the same error with any parameters that I pass, also the same error if I try to use named parameters.
What can cause parameters not to be passed in?
UPDATE: PowerShell ISE asks me for these parameters using GUI prompts, then gives me the same error about them not being passed in.
Unless you marked a parameter with the ValueFromRemainingArguments attribute (indicates whether the cmdlet parameter accepts all the remaining command-line arguments that are associated with this parameter), Args is "disabled". If all you need is the arguments count call the special variable:
$PSBoundParameters.Count
Do not mix. Make use of $args or parameters.
Also do note that $input is a special variable, don't declare it as a parameter. http://dmitrysotnikov.wordpress.com/2008/11/26/input-gotchas/
You're calling your script with positional parameters (i.e. unnamed) and PowerShell doesn't know how to map them to your script parameters. You need to either call your script using the parameter names:
.\csvtosql.ps1 -input mycsv.csv -table dbo.MyTable
or update your script to specify your preferred order of positional parameters:
param (
[Parameter(Mandatory=$true,Position=0)]
[string]
$input,
[Parameter(Mandatory=$true,Position=1)]
[string]
$table
)