Powershell Cmdlet with Mandatory Parameters - powershell

I'm trying to create a simple powershell cmdlet that would have a few mandatory parameters. I've found the following code for doing so however, I cannot get it to execute:
function new-command() {
[CmdletBinding()]
PARAM (
[Parameter(Mandatory=$true)]
[string]$Name
)
}
new-command
Returns the following error:
Missing closing ')' in expression." Line: 5 Char: 3 + [ <<<< string]$Name
What am I doing wrong?

The explanation is that you are running this script in PowerShell V1.0 and these function attributes are supported in PowerShell V2.0. Look at $host variable for you PowerHhell version.

Try this instead:
function new-command {
[CmdletBinding()]
PARAM (
[Parameter(Mandatory=$true)]
[string]$Name
)
}
new-command
You don't need parentheses after the function name.

In PS 2.0 mandatory parameters are controlled through the CmdLetBinding and Parameter attributes as shown in the other answers.
function new-command {
[CmdletBinding()]
PARAM (
[Parameter(Mandatory=$true)]
[string]$Name
)
$Name
}
new-command
In PS 1.0 there are not direct constructs for handling mandatory attributes but you can for example throw an error if a mandatory parameter hasn't been supplied. I often use the following construct.
function new-command {
param($Name=$(throw "Mandatory parameter -Name not supplied."))
$Name
}
I hope this helps.

You'll have the same error message even with Powershell v2.0 if Param(...) hasn't been declared at the beginning of the script (exclude comment lines). Please refer to powershell-2-0-param-keyword-error

Try below syntax and also kindly check whether have missed any double quotes or brackets.
Param([parameter(Mandatory=$true, HelpMessage="Path to file")] $path)

Related

What is the proper way to define a dynamic ValidateSet in a PowerShell script?

I have a PowerShell 7.1 helper script that I use to copy projects from subversion to my local device. I'd like to make this script easier for me to use by enabling PowerShell to auto-complete parameters into this script. After some research, it looks like I can implement an interface to provide valid parameters via a ValidateSet.
Based on Microsoft's documentation, I attempted to do this like so:
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[ValidateSet([ProjectNames])]
[String]
$ProjectName,
#Other params
)
Class ProjectNames : System.Management.Automation.IValidateSetValuesGenerator {
[string[]] GetValidValues() {
# logic to return projects here.
}
}
When I run this, it does not auto-complete and I get the following error:
❯ Copy-ProjectFromSubversion.ps1 my-project
InvalidOperation: C:\OneDrive\Powershell-Scripts\Copy-ProjectFromSubversion.ps1:4
Line |
4 | [ValidateSet([ProjectNames])]
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| Unable to find type [ProjectNames].
This makes sense since the class isn't defined until after the parameters. So I moved the class above the parameters. Obviously this is a syntax error. So how do I do this? Is it not possible in a simple PowerShell script?
Indeed, you've hit a catch-22: for the parameter declaration to work during the script-parsing phase, class [ProjectNames] must already be defined, yet you're not allowed to place the class definition before the parameter declaration.
The closest approximation of your intent using a stand-alone script file (.ps1) is to use the ValidateScript attribute instead:
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[ValidateScript(
{ $_ -in (Get-ChildItem -Directory).Name },
ErrorMessage = 'Please specify the name of a subdirectory in the current directory.'
)]
[String] $ProjectName # ...
)
Limitations:
[ValidateScript] does not and cannot provide tab-completion: the script block, { ... }, providing the validation is only expected to return a Boolean, and there's no guarantee that a discrete set of values is even involved.
Similarly, you can't reference the dynamically generated set of valid values (as generated inside the script block) in the ErrorMessage property value.
The only way around these limitations would be to duplicate that part of the script block that calculates the valid values, but that can become a maintenance headache.
To get tab-completion you'll have to duplicate the relevant part of the code in an [ArgumentCompleter] attribute:
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[ValidateScript(
{ $_ -in (Get-ChildItem -Directory).Name },
ErrorMessage = 'Please specify the name of a subdirectory in the current directory.'
)]
[ArgumentCompleter(
{
param($cmd, $param, $wordToComplete)
# This is the duplicated part of the code in the [ValidateScipt] attribute.
[array] $validValues = (Get-ChildItem -Directory).Name
$validValues -like "$wordToComplete*"
}
)]
[String] $ProjectName # ...
)

How to pass a switch parameter to another PowerShell script?

I have two PowerShell scripts, which have switch parameters:
compile-tool1.ps1:
[CmdletBinding()]
param(
[switch]$VHDL2008
)
Write-Host "VHDL-2008 is enabled: $VHDL2008"
compile.ps1:
[CmdletBinding()]
param(
[switch]$VHDL2008
)
if (-not $VHDL2008)
{ compile-tool1.ps1 }
else
{ compile-tool1.ps1 -VHDL2008 }
How can I pass a switch parameter to another PowerShell script, without writing big if..then..else or case statements?
I don't want to convert the parameter $VHDL2008 of compile-tool1.ps1 to type bool, because, both scripts are front-end scripts (used by users). The latter one is a high-level wrapper for multiple compile-tool*.ps1 scripts.
You can specify $true or $false on a switch using the colon-syntax:
compile-tool1.ps1 -VHDL2008:$true
compile-tool1.ps1 -VHDL2008:$false
So just pass the actual value:
compile-tool1.ps1 -VHDL2008:$VHDL2008
Try
compile-tool1.ps1 -VHDL2008:$VHDL2008.IsPresent
Assuming you were iterating on development, it is highly likely that at some point you are going to add other switches and parameters to your main script that are going to be passed down to the next called script. Using the previous responses, you would have to go find each call and rewrite the line each time you add a parameter. In such case, you can avoid the overhead by doing the following,
.\compile-tool1.ps1 $($PSBoundParameters.GetEnumerator() | ForEach-Object {"-$($_.Key) $($_.Value)"})
The automatic variable $PSBoundParameters is a hashtable containing the parameters explicitly passed to the script.
Please note that script.ps1 -SomeSwitch is equivalent to script.ps1 -SomeSwitch $true and script.ps1 is equivalent to script.ps1 -SomeSwitch $false. Hence, including the switch set to false is equivalent to not including it.
According to a power shell team's blog (link below,) since V2 there is a technique called splatting. Basically, you use the automatic variable #PsBoundParameters to forward all the parameters. Details about splatting and the difference between # and $ are explained in the Microsoft Docs article (link below.)
Example:
parent.ps1
#Begin of parent.ps1
param(
[Switch] $MySwitch
)
Import-Module .\child.psm1
Call-Child #psBoundParameters
#End of parent.ps1
child.psm1
# Begin of child.psm1
function Call-Child {
param(
[switch] $MySwitch
)
if ($MySwitch){
Write-Output "`$MySwitch was specified"
} else {
Write-Output "`$MySwitch is missing"
}
}
#End of child.psm1
Now we can call the parent script with or without the switch
PS V:\sof\splatting> .\parent.ps1
$MySwitch is missing
PS V:\sof\splatting> .\parent.ps1 -MySwitch
$MySwitch was specified
PS V:\sof\splatting>
Update
In my original answer, I sourced the children instead of importing it as a module. It appears sourcing another script into the original just makes the parent's variables visible to all children so this will also work:
# Begin of child.ps1
function Call-Child {
if ($MySwitch){
Write-Output "`$MySwitch was specified"
} else {
Write-Output "`$MySwitch is missing"
}
}
#End of child.ps1
with
#Begin of parent.ps1
param(
[Switch] $MySwitch
)
. .\child.ps1
Call-Child # Not even specifying #psBoundParameters
#End of parent.ps1
Maybe, this is not the best way to make a program, nevertheless, this is the way it works.
About Splatting(Microsoft Docs)
How and Why to Use Splatting (passing [switch] parameters)
Another solution. If you declare your parameter with a default value of $false:
[switch] $VHDL2008 = $false
Then the following (the -VHDL2008 option with no value) will set $VHDL2008 to $true:
compile-tool1.ps1 -VHDL2008
If instead you omit the -VHDL2008 option, then this forces $VHDL2008 to use the default $false value:
compile-tool1.ps1
These examples are useful when calling a Powershell script from a bat script, as it is tricky to pass a $true/$false bool from bat to Powershell, because the bat will try to convert the bool to a string, resulting in the error:
Cannot process argument transformation on parameter 'VHDL2008'.
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.

PowerShell how to unsplat

I know how to use splatting to pass parameters to a cmdlet.
How would I write a cmdlet to "unsplat"? I want to get all the parameters passed to my function as a hashtable.
For example:
function Test-Unsplat
{
[cmdletbinding()]
param(
$ParamA,
$ParamB
)
# WHAT TO DO HERE TO GET #{ParamA = '...', ParamB = '...'}
}
I found the answer with
Get-Help about_Splatting
The answer I was looking for is $PSBoundParameters.

Powershell parameters string or file

I have a script that i'd like the user to be able to enter a string or use a file(array) that my script can cycle through.
Can this be done with a parameter?
I'd like to be able to do something like this
script.ps1 -file c:\users\joerod\desktop\listofusers.txt
or
script.ps1 -name "john doe"
Sure, but you will need to pick the default parameterset to use when a positional parameter is used since the type of both parameters is a string e.g.:
[CmdletBinding(DefaultParameterSetName="File")]
param(
[Parameter(Position=0, ParameterSetName="File")]
[string]
$File,
[Parameter(Position=0, ParameterSetName="Name")]
[string]
$Name
)
if ($psCmdlet.ParameterSetName -eq "File") {
... handle file case ...
}
else {
... must be name case ...
}
Where the DefaultParameterSetName is essential is when someone specifies this:
myscript.ps1 foo.txt
If the default parametersetname specified, PowerShell can't tell which parameterset should be used since both position 0 parameters are the same type [string]. There is no way to disambiguate which parameter to place the argument in.

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.