Passing Boolean powershell parameters from a non-powershell terminal - powershell

Sometimes I invoke powershell scripts from Linux bash/shell scripts like so:
pwsh MyScript.ps1 win-x64 false
And in my MyScript.ps1 file, I set up parameters like so:
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string] $runtime,
[Parameter()]
[bool] $singleFile = $true
)
I get an error for the second parameter:
Cannot process argument transformation on parameter 'singleFile'. Cannot convert value "System.String" to type "System.Boolean". Boolean parameters accept only Boolean values and numbers, such as $True, $False, 1 or 0.
I tried passing '$false' as well as 0 but it treats everything as a string. When invoking powershell scripts from outside of a PWSH terminal, how do I get it to coerce my string-boolean value into an actual Powershell boolean type?

I propose to use [switch]
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string] $runtime,
[Parameter()]
[switch] $singleFile
)
Write-Host $runtime
It works for me with :
pwsh ".\MyScript.ps1" "toto" -singlefile
In fact your code is working with :
pwsh ".\MyScript.ps1" toto -singleFile 1

Related

PowerShell Parameter Set Requires Named Parameter

I have the following snippet of a functions parameters and their sets
function Test {
[CmdletBinding(DefaultParameterSetName='StringConsole')]
param (
[Parameter(Mandatory,
ValueFromPipelineByPropertyName,
ParameterSetName = 'ObjectFile')]
[Parameter(Mandatory,
ValueFromPipelineByPropertyName,
ParameterSetName = 'StringFile')]
[Alias("PSPath")]
[ValidateNotNullOrEmpty()]
[string]
$Path,
[Parameter(Mandatory,
ValueFromPipeline,
ParameterSetName='StringFile',
Position = 0)]
[Parameter(Mandatory,
ValueFromPipeline,
ParameterSetName='StringConsole',
Position = 0)]
[ValidateNotNullOrEmpty()]
[string]
$Message,
[Parameter(Mandatory,
ValueFromPipeline,
ParameterSetName='ObjectFile',
Position = 0)]
[Parameter(Mandatory,
ValueFromPipeline,
ParameterSetName='ObjectConsole',
Position = 0)]
[ValidateNotNullOrEmpty()]
[object]
$Object,
[Parameter(ParameterSetName='StringFile')]
[Parameter(ParameterSetName='StringConsole')]
[ValidateSet('Information', 'Verbose', 'Warning', 'Error', 'Object')]
[string]
$Severity = 'Information',
[Parameter(ParameterSetName='StringFile')]
[Parameter(ParameterSetName='StringConsole')]
[switch]
$NoPreamble,
[Parameter(ParameterSetName = 'StringConsole')]
[Parameter(ParameterSetName = 'ObjectConsole')]
[switch]
$Console
)
}
If I call the function using
Test 'Hello, World'
it properly uses the StringConsole default parameter set from CmdletBinding
If I call the function using
Test -Message 'Hello, World' -Path C:\SomeFile.txt
It properly uses the StringFile parameter set
But if I call the function using
Test 'Hello, World' -Path C:\SomeFile.txt
I get this error and the function doesn't execute:
Parameter set cannot be resolved using the specified named parameters
The error specifically states it couldn't resolve the parameter set using the NAMED parameters. If a parameter gets bound by position does it not also satisfy the "named" parameter? Or do you have to specifically bind the parameter using the name?
Is there anyway I could design the parameter sets to make my last example work and not throw an error?
The logic used for your parameter sets looks perfectly fine but the issue is that you have 2 parameters with Position = 0 (-Message and -Object), normally this wouldn't be a problem but one of them is of the type System.Object and since all objects inherit from this class, no matter what you pass as argument in position 0 it will match this parameter. Since the other parameter on Position = 0 is of type System.String then 'Hello, World' (a string but also an object) matches both parameter sets and the binder has no idea which one did you mean to use.
A very easy way of seeing this, without changing your current code and just adding $PSCmdlet.ParameterSetName to the function's body, would be to pass an integer as positional parameter and everything works as expected:
function Test {
[CmdletBinding(DefaultParameterSetName='StringConsole')]
param(
# same param block here
)
'Using: ' + $PSCmdlet.ParameterSetName
}
Test 0 -Path C:\SomeFile.txt # => Using: ObjectFile

How to fix a Powersell bug

I have this powershell example that does not throw any error or exception.
However, NOTHING is printed on the output.
I am not able to find the reason
Maybe I don't call the Add-Values function correctly?
<#
.Synopsis
To add two integer values
.DESCRIPTION
Windows PowerShell Script Demo to add two values
This accepts pipeline values
.EXAMPLE
Add-Values -Param1 20 -Param2 30
.EXAMPLE
12,23 | Add-Values
#>
function Add-Values
{
[CmdletBinding()]
[Alias()]
[OutputType([int])]
Param
(
# Param1 help description
[Parameter(Mandatory=$true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName=$true,
Position=0)]
#Accepts Only Integer
[int]$Param1,
#Accepts only interger
[Parameter(Mandatory=$true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName=$true,
Position=0)]
[int]$Param2
)
Begin
{
"Script Begins"
}
Process
{
$result = $Param1 + $Param2
}
End
{
$result
}
}
pause
Remove the pause? Then paste your whole function into powershell. After that, try your function.
It kinda works for me.
PS ~>Add-Values -Param1 1 -Param2 2
Script Begins
3
parameter binding by datatype will give you the wrong result (it will use the last value for both parameters.
PS ~>1, 2 | Add-Values
Script Begins
4

Powershell passing mandatory dynamic type variable to function

I am implementing a function in Powershell which will perform REST calls. One of the parameters may differ in contents, depending on given scenarios. For instance, the body of the REST call may be a string or a hash table. How do you implement this within the CmdletBinding() declaration?
For instance
Function doRESTcall(){
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[Hashtable]$headers
[Parameter(Mandatory=$true)]
[???????]$body # what type here??
)
.
.
.
}
To declare parameters where any type is allowed you can either not type-constrain the parameter at all or use type constraint [object] (System.Object), by doing so, no type conversion will be needed, since all objects in PowerShell inherit from this type.
It's worth mentioning that unconstrained parameters will allow $null as argument, to avoid this, [ValidateNotNull()] and / or [parameter(Mandatory)] can be used.
function Test-Type {
param(
[parameter(ValueFromPipeline, Mandatory)]
[object]$Value
)
process
{
[pscustomobject]#{
Type = $Value.GetType().FullName
IsObject = $Value -is [object]
}
}
}
PS /> 1, 'foo', (Get-Date) | Test-Type
Type IsObject
---- --------
System.Int32 True
System.String True
System.DateTime True
The correct way to tackel this is to create a ParameterSet:
Function doRESTcall(){
[CmdletBinding()]
param (
[Parameter(Mandatory=$true, ParameterSetName = 'StringBody', Position = 0)]
[Parameter(Mandatory=$true, ParameterSetName = 'HashBody', Position = 0)]
[Hashtable]$headers,
[Parameter(Mandatory=$true, ParameterSetName = 'StringBody', Position = 1)]
[String]$Stringbody,
[Parameter(Mandatory=$true, ParameterSetName = 'HashBody', Position = 1)]
[Hashtable]$Hashbody
)
Write-Host 'Parameter set:' $PSCmdlet.ParameterSetName
Write-Host 'StringBody:' $StringBody
Write-Host 'HashBody:' $HashBody
}
doRESTcall -?
NAME
doRESTcall
SYNTAX
doRESTcall [-headers] <hashtable> [-Hashbody] <hashtable> [<CommonParameters>]
doRESTcall [-headers] <hashtable> [-Stringbody] <string> [<CommonParameters>]
ALIASES
None
REMARKS
None
doRESTcall #{a = 1} 'Test'
Parameter set: StringBody
StringBody: Test
HashBody:
Note: to accept a larger variety of dictionaries (like [Ordered]), I would use a [System.Collections.Specialized.OrderedDictionary] (rather than [Hashtable]) type for the concerned parameters.

Using ValidateSet() with object type

I'm not sure if this is possible, but I have a parameter that is used in three different parameter sets, and based on which set it is in, the type will be different. While the snip below is syntactically correct, when I attempt to leverage it I'm receiving an error. I do something similar in another function where my validateset is a collection of classes I have defined which may be misleading as those are defined within the powershell module and not "proper" .Net types.
[parameter(Mandatory = $false, ParameterSetName = 'string')]
[parameter(Mandatory = $false, ParameterSetName = 'integer')]
[parameter(Mandatory = $false, ParameterSetName = 'number')]
[ValidateSet([string],[int],[decimal])]
$default,
Resulting error
Cannot validate argument on parameter 'default'. The argument "black" does not belong to the set "string,int,decimal" specified by the ValidateSet attribute. Supply an argument that is in the set and then try the command again.
Use a ValidateScript attribute instead:
[ValidateScript({$_ -is [string] -or $_ -is [int] -or $_ -is [decimal]})]
[object]$default,

Calling a powershell cmdlet from ps script

I have a cmdlet with the following defintion:
[CmdletBinding(DefaultParameterSetName="Path",
SupportsShouldProcess=$TRUE)]
param(
[parameter(Mandatory=$TRUE,Position=0)]
[String] $Pattern,
[parameter(Mandatory=$TRUE,Position=1)]
[String] [AllowEmptyString()] $Replacement,
[parameter(Mandatory=$TRUE,ParameterSetName="Path",
Position=2,ValueFromPipeline=$TRUE)]
[String[]] $Path,
[parameter(Mandatory=$TRUE,ParameterSetName="LiteralPath",
Position=2)]
[String[]] $LiteralPath,
[Switch] $CaseSensitive,
[Switch] $Multiline,
[Switch] $UnixText,
[Switch] $Overwrite,
[Switch] $Force,
[String] $Encoding="ASCII"
)
I put the cmdlet .ps1 file in the same folder as as a powershell script file that calls the cmdlet as following:
Invoke-Expression -Command .\Replace-FileString.ps1 "9595" "NewPort"
"c:\temp" -Overwrite
However, when I execute my ps script, I get the following error:
Invoke-Expression : A positional parameter cannot be found that
accepts argument '9595'.
How can I make it work?
Thanks.
Try:
Invoke-Expression -Command '.\Replace-FileString.ps1 "9595" "NewPort" "c:\temp" -Overwrite'
Your command includes arguments that uses quotemarks, so PS thinks that your command is over and those are new arguments(not a part of the -Command paramter).