Value for switch parameter from pipeline - powershell

I need to pass parameters to a script from pipeline input by importing the required values from CSV file. The original script has more than 15 parameters to be passed and input values are stored in a CSV file. I am posting a simple example to express the issue.
Below are the contents of CSV file (Input.csv)
ResourceGroupName,SetThrottling
TestRG1,1
TestRG2,0
TestRG3,1
TestRG4,0
TestRG5,0
Script file - Switch-FromPipelineTest.ps1
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)]
[String]$ResourceGroupName,
[Parameter(ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)]
[Switch]$SetThrottling
)
Begin {}
Process {
Function TestingValues {
Param(
$ResourceGroupName,
$SetThrottling
)
Write-Host "$ResourceGroupName is set to $SetThrottling"
}
TestingValues -ResourceGroupName $ResourceGroupName -SetThrottling $SetThrottling
}
End {}
If I run the command Import-Csv .\Input.csv | .\Switch-FromPipelineTest.ps1 it gives an error as given below:
C:\Scripts\Switch-FromPipelineTest.ps1 : Cannot process argument
transformation on parameter 'SetThrottling'. Cannot convert value
"System.String" to type "System.Management.Automation.SwitchParameter".
Boolean parameters accept only Boolean values and numbers, such as
$True, $False, 1 or 0.
At line:1 char:26
+ Import-Csv .\Input.csv | .\Switch-FromPipelineTest.ps1
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (#{ResourceGroup...etThrottling=1}:PSObject) [Swith-FromPipelineTest.ps1], ParameterBindingArgumentTransformationException
+ FullyQualifiedErrorId : ParameterArgumentTransformationError,Switch-FromPipelineTest.ps1
In order to make this work, I have to run below command:
Import-Csv -Path .\Input.csv -Verbose |
Select-Object -Property ResourceGroupName,#{n='SetThrottling';e={[bool][int]$_.SetThrottling}} |
.\Switch-FromPipelineTest.ps1
Is there a way we can omit the type casting done by using custom property expression in the second command? As in the original script, I have several [switch] parameters and I need to do the same thing for each [switch] parameter.

Convert the string values to boolean values upon importing the CSV:
Import-Csv 'C:\path\to\input.csv' |
Select-Object -Property *,#{n='SetThrottling';e={
[bool][int]$_.SetThrottling
}} -Exclude SetThrottling | ...
You need to do this for every switch parameter you want to import from a CSV.
If you want to avoid this, change your parameters from switches to set-validated parameters and adjust the parameter evaluation accordingly:
[Parameter(ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)]
[ValidateSet('0', '1')]
[string]$SetThrottling

Related

Inconsistent binding when piping DirectoryInfo objects

I've often used a construct similar to this (using aliases for brevity):
gci -ad | %{$_ | gci}
which works fine. But when trying to help another user on this forum, I found that the following doesn't work:
gci -ad | %{$_.Parent | gci}
throws the following error for each iteration:
gci : Cannot find path 'C:\Users\keith\Documents\Documents' because it does not exist.
At line:1 char:25
+ gci -ad | %{$_.Parent | gci}
+ ~~~
+ CategoryInfo : ObjectNotFound: (C:\Users\keith\Documents\Documents:Stri
ng) [Get-ChildItem], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemC
ommand
even though:
gci -ad | %{$_.GetType() -eq $_.Parent.GetType()}
produces a scrennfull of True.
I'm pretty sure it has something to do with parameter binding, but would like to understand the apparent inconsistancy....
Easiest way to see what's happening is to emulate the binding with a test function, but the issue is, as you might know in .NET Framework when a DirectoryInfo instance is coerced into a string the result will be it's .Name property value as opposed to .NET where the coercion results in the .FullName property.
Since the object returned by calling the .Parent property does not have the ETS Property .PSPath, the input object will be bound the the Path Parameter and coerced to a string.
Assuming you have both versions of PowerShell, you can try the following to see the difference:
function Test-Binding {
[CmdletBinding(DefaultParameterSetName='Items')]
param(
[Parameter(ParameterSetName='Items', Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[string[]]
${Path},
[Parameter(ParameterSetName='LiteralItems', Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
[Alias('PSPath')]
[string[]]
${LiteralPath}
)
process {
$PSBoundParameters
$PSBoundParameters.Clear()
}
}
(Get-Item .), (Get-Item .).Parent | Test-Binding

Parameter validateset wildcard

Is it possible to make a Paramater validateset work with a wildcard?
I would want on the * places to accept 0-100.
param
(
[Parameter(Mandatory=$True)]
[validateset("6.1.*.*")]
[string]$variable
)
Error message:
Cannot validate argument on parameter 'variable'. The argument "6.1.1.0" does not belong to the set "6.1.." specified by the ValidateSet attribute. Supply an argument that is in the set and then try the command again.
+ CategoryInfo : InvalidData: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : ParameterArgumentValidationError
Since it looks like you're looking to validate a version, you may want to declare the parameter of the type [version] and use the ValidateScript attribute to validate the value rather than using string matching:
function Test-Version {
param(
[ValidateScript({
$_.Major -eq '6' -and
$_.Minor -eq '1' -and
$_.Build -in (0..100) -and
$_.Revision -in (0..100) -or
$(throw 'Wrong Version')
})]
[version]$Version
)
}
No, that's what [ValidatePattern()] is for:
param(
[Parameter(Mandatory=$True)]
[ValidatePattern('6\.1\.\d{1,3}\.\d{1,3}')]
[string]$variable
)
It takes a regular expression as the parameter.
[ValidateSet()] is meant to be used if there is a small, constant set of values. PowerShell also provides autocompletion for these. For example:
[ValidateSet('Windows', 'Mac', 'Linux')
$OperatingSystem
See this article for more parameter validation attributes.

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

Passing function arguments by position: Do explicit values get used before pipeline input?

I'm experimenting with creating a function that can take multiple arguments by position, including pipeline input.
Here's a simple test function:
function TestMultiplePositionalParameters
{
param
(
[Parameter(
Position=0,
Mandatory=$true)
]
[String]$FirstParam,
[Parameter(
Position=1,
Mandatory=$true,
ValueFromPipeline=$true)
]
[String]$SecondParam
)
begin
{
Write-Host '================================='
}
process
{
Write-Host '$FirstParam:' $FirstParam
Write-Host '$SecondParam:' $SecondParam
Write-Host ''
}
}
When I call it, it works fine:
"Input1","Input2" | TestMultiplePositionalParameters 'ExplicitArgument'
Which results in:
=================================
$FirstParam: ExplicitArgument
$SecondParam: Input1
$FirstParam: ExplicitArgument
$SecondParam: Input2
However, if I change the parameter which takes the value from the pipeline:
function TestMultiplePositionalParameters
{
param
(
[Parameter(
Position=0,
Mandatory=$true,
ValueFromPipeline=$true)
]
[String]$FirstParam,
[Parameter(
Position=1,
Mandatory=$true)
]
[String]$SecondParam
)
# etc...
}
And again call it:
"Input1","Input2" | TestMultiplePositionalParameters 'ExplicitArgument'
This time I get an error:
TestMultiplePositionalParameters : The input object cannot be bound to any parameters
for the command either because the command does not take pipeline input or the input
and its properties do not match any of the parameters that take pipeline input.
At C:\...\DEMO_Function_Pipeline_MultiPositionalParams.ps1:77 char:21
+ "Input1","Input2" | TestMultiplePositionalParameters 'ExplicitArgument'
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (Input1:String)
[TestMultiplePositionalParameters ], ParameterBindingException
+ FullyQualifiedErrorId : InputObjectNotBound,TestMultiplePositionalParameters
My question is: Why does the second version of the function pass the explicit argument 'ExplicitArgument' to the first parameter? I would have thought that given only the first parameter can accept pipeline input the function should have passed the pipeline input to the first parameter and the explicit argument to the second parameter.
Why does the second version of the function pass the explicit argument
'ExplicitArgument' to the first parameter?
Because $FirstParam is marked as being at position '0' and the first parameter passed to the cmdlet is 'ExplicitArgument'. Essentially, the binding of 'ExplicitArgument' to $FirstParam has already happened by the time PowerShell is looking for a parameter that will accept pipeline input.
To get your expected behaviour, you would have to execute the following:
"Input1","Input2" | TestMultiplePositionalParameters -SecondParam 'ExplicitArgument'
The reason for this is that you can have more than one parameter that accepts its value from the pipeline. If you had the following, what would you expect to happen in your second example?
param
(
[Parameter(
Position=0,
Mandatory=$true,
ValueFromPipeline=$true)
]
[String]$FirstParam,
[Parameter(
Position=1,
Mandatory=$true,
ValueFromPipeline=$true)
]
[String]$SecondParam
)
There has to be a precedence and it is that named and positional arguments have higher priority than the pipeline arguments.

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
)