All PowerShell commands can have one or more parameters, sometimes called arguments. If you’re not using PowerShell parameters in your PowerShell functions, you’re not writing good PowerShell code!
In this article, you’re going to learn just about every facet of creating and use PowerShell parameters or arguments!
This is a sample from my book PowerShell for SysAdmins. If you want to learn PowerShell or learn some tricks of the trade, check it out!
Why Do You Need a Parameter?
When you begin to create functions, you’ll have the option to include parameters or not and how those parameters work.
Let’s say you have a function that installs Microsoft Office. Maybe it calls the Office installer silently inside of the function. What the function does, doesn’t matter for our purposes. The basic function looks like this with the name of the function and the script block.
function Install-Office {
## run the silent installer here
}
In this example, you just ran Install-Office
without parameters and it does it’s thing.
It didn’t matter whether the Install-Office
function had parameters or not. It apparently didn’t have any mandatory parameters; else, PowerShell would not have let us run it without using a parameter.
When to Use a PowerShell Parameter
Office has lots of different versions. Perhaps you need to install Office 2013 and 2016. Currently, you have no way to specify this. You could change the function’s code every time you wanted to change the behavior.
For example, you could create two separate functions to install different versions.
function Install-Office2013 {
Write-Host 'I installed Office 2013. Yippee!'
}
function Install-Office2016 {
Write-Host 'I installed Office 2016. Yippee!'
}
Doing this works, but it’s not scalable. It forces you to create a separate function for every single version of Office that comes out. You’re going to have to duplicate lots of code when you don’t have to.
Instead, you need a way to pass in different values at runtime to change the function’s behavior. How do you do this?
Yep! Parameters or what some people call arguments.
Since we’d like to install different versions of Office without changing the code every time, you have to add at least one parameter to this function.
Before you quickly think up a PowerShell parameter to use, it’s essential first to ask yourself a question; “What is the smallest change or changes you expect will be needed in this function?”.
Remember that you need to rerun this function without changing any of the code inside of the function. In this example, the parameter is probably clear to you; you need to add a Version
parameter. But, when you’ve got a function with tens of lines of code, the answer won’t be too apparent. As long as you answer that question as precisely as possible, it will always help.
So you know you need a Version
parameter. Now what? You now can add one, but like any great programming language, there are multiple ways to skin that cat.
In this tutorial, I’ll show you the “best” way to create parameters based on my nearly decade of experience with PowerShell. However, know that this isn’t the only way to create a parameter.
There is such a thing as positional parameters. These parameters allow you to pass values to parameters without specifying the parameter name. Positional parameters work but aren’t considered “best practice.” Why? Because they are harder to read especially when you have many parameters defined on a function.
Creating a Simple PowerShell Parameter
Creating a parameter on a function requires two primary components; a param block and the parameter itself. A param block is defined by the param
keyword followed by a set of parentheses.
function Install-Office {
[CmdletBinding()]
param()
Write-Host 'I installed Office 2016. Yippee!'
}
At this point, the function’s actual functionality hasn’t changed a bit. We’ve just put together some plumbing, preparing us for the first parameter.
Once we’ve got the param block in place, you’ll now create the parameter. The method that I’m suggesting you create a parameter includes the Parameter
block, followed by the type of parameter, it is followed by the parameter variable name below.
function Install-Office {
[CmdletBinding()]
param(
[Parameter()]
[string]$Version
)
Write-Host 'I installed Office 2016. Yippee!'
}
We’ve now created a function parameter in PowerShell but what exactly happened here?
The Parameter
block is an optional yet recommended piece of every parameter. Like the param block, it is “function plumbing,” which prepares the parameter for adding additional functionality. The second line is where you define the type of parameter it is.
In this case, we’ve chosen to cast the Version
parameter to a string. Defining an explicit type means that any value passed to this parameter will always attempt to be “converted” to a string if it’s not already.
The type isn’t mandatory but is highly encouraged. Explicitly defining the type of parameter it is will significantly reduce many unwanted situations down the road. Trust me.
Now that you’ve got the parameter defined, you can run the Install-Office
command with the Version
parameter passing a version string to it like 2013. The value passed to the Version
parameter is sometimes referred to as parameters’ arguments or values.
PS> Install-Office -Version 2013
I installed Office 2016. Yippee!
What’s going on here anyway? You told it you wanted to install version 2013, but it’s still telling you it’s installed 2016. When you add a parameter, you must then remember to change the function’s code to a variable. When the parameter is passed to the function, that variable will be expanded to be whatever value was passed.
Change the static text of 2016 and replace it with the Version
parameter variable and converting the single quotes to double quotes so the variable will expand.
function Install-Office {
[CmdletBinding()]
param(
[Parameter()]
[string]$Version
)
Write-Host "I installed Office $Version. Yippee!"
}
Now you can see that whatever value you pass to the Version
parameter will then get passed into the function as the $Version
variable.
The Mandatory Parameter Attribute
Recall that I mentioned the [Parameter()]
line was just “function plumbing” and needed to prepare the function for further work? Adding parameter attributes to a parameter is the additional work I was speaking about earlier.
A parameter doesn’t have to be a placeholder for a variable. PowerShell has a concept called parameter attributes and parameter validation. Parameter attributes change the behavior of the parameter in a lot of different ways.
For example, one of the most common parameter attributes you’ll set is the Mandatory
keyword. By default, you could call the Install-Office
function without using the Version
parameter and it would execute just fine. The Version parameter would be optional. Granted, it wouldn’t expand the $Version variable inside the function because there was no value, but it would still run.
Many times when creating a parameter, you’ll want the user always to use that parameter. You’ll be depending on the value of the parameter inside of the function’s code somewhere, and if the parameter isn’t passed, the function will fail. In these cases, you want to force the user to pass this parameter value to your function. You want that parameter to become mandatory.
Forcing users to use a parameter is simple once you’ve got the basic framework built as you have here. You have to include the keyword Mandatory
inside of the parameter’s parentheses. Once you have that in place, executing the function without the parameter will halt execution until a value has been input.
function Install-Office {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$Version
)
Write-Host "I installed Office $Version. Yippee!"
}
PS> Install-Office
cmdlet Install-Office at command pipeline position 1
Supply values for the following parameters:
Version:
The function will wait until you specify a value for the Version
parameter. Once you do so and hit Enter, PowerShell will execute the function and move on. If you provide a value for the parameter, PowerShell will not prompt you for the parameter every time.
PowerShell Parameter Validation Attributes
Making a parameter mandatory is one of the most common parameter attributes you can add, but you can also use parameter validation attributes. In programming, it’s always essential to restrict user input as tightly as possible. Limiting the information that users (or even you!) can pass to your functions or scripts will eliminate unnecessary code inside your function that has to account for all kinds of situations.
Learning by Example
For example, in the Install-Office
function, I demonstrated passing the value 2013
to it because I knew it would work. I wrote the code! I’m assuming (never do this in code!) that it’s obvious that anyone who knows anything would specify the version as either 2013 or 2016. Well, what’s obvious to you may not be so obvious to other people.
If you want to get technical about versions, it’d probably be more accurate to specify the 2013 version as 15.0
and 2016 as 16.0
if Microsoft were still using the versioning schema, they have in the past. But what if, since you’re assuming they’re going to specify a version of 2013 or 2016, you’ve got the code inside of the function that looks for folders with those versions in it or something else?
Below is an example of where you may be using the $Version
string in a file path. If someone passes a value that doesn’t complete a folder name of Office2013
or Office2016
, it will fail or do something worse, like remove unexpected folders or change things you didn’t account for.
function Install-Office {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$Version
)
Get-ChildItem -Path "\\SRV1\Installers\Office$Version"
}
PS> Install-Office -Version '15.0'
Get-ChildItem : Cannot find path '\SRV1\Installers\Office15.0' because it does not exist.
At line:7 char:5
Get-ChildItem -Path "\\SRV1\Installers\Office$Version"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CategoryInfo : ObjectNotFound: (\SRV1\Installers\Office15.0:String) [Get-ChildItem], ItemNotFoundExcep
tion
FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand
To limit the user to what you expect them to input, you can add some PowerShell parameter validation.
Using the ValidateSet Parameter Validation Attribute
There are various kinds of parameter validation you can use. For a full listing, run Get-Help about_Functions_Advanced_Parameters
. In this example, the ValidateSet
attribute would probably be the best.
The ValidateSet
validation attribute allows you to specify a list of values that are allowed as the parameter value. Since we’re only accounting for the string 2013 or 2016, I’d like to ensure that the user can only specify these values. Otherwise, the function will fail immediately, notifying them of why.
You can add parameter validation attributes right under the original Parameter
keyword. In this example, inside the parameter attribute’s parentheses, you have an array of items; 2013 and 2016. A parameter validation attribute tells PowerShell that the only values that are valid for Version
are 2013 or 2016. If you try to pass something besides what’s in the set, you’ll receive an error notifying you that you only have a specific number of options available.
function Install-Office {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[ValidateSet('2013','2016')]
[string]$Version
)
Get-ChildItem -Path "\\SRV1\Installers\Office$Version"
}
PS> Install-Office -Version 15.0
Install-Office : Cannot validate argument on parameter 'Version'. The argument "15.0" does not belong to the set
"2013,2016" specified by the ValidateSet attribute. Supply an argument that is in the set and then try the command
again.
At line:1 char:25
Install-Office -Version 15.0
~~~~
CategoryInfo : InvalidData: (:) [Install-Office], ParameterBindingValidationException
FullyQualifiedErrorId : ParameterArgumentValidationError,Install-Office
The ValidateSet
attribute is a common validation attribute to use. For a complete breakdown of all of the ways parameter values can be restricted, check out the Functions_Advanced_Parameters
help topic by running Get-Help about_Functions_Advanced_Parameters
.
Parameter Sets
Let’s say you only want certain PowerShell parameters used with other parameters. Perhaps you’ve added a Path
parameter to the Install-Office
function. This path will install whatever version the installer is. In this case, you don’t want the user using the Version
parameter.
You need parameter sets.
Parameters can be grouped into sets that can only be used with other parameters in the same set. Using the function below, you can now use both the Version
parameter and the Path
parameter to come up with the path to the installer.
function Install-Office {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[ValidateSet('2013','2016')]
[string]$Version,
[Parameter(Mandatory)]
[string]$Path
)
if ($Version) {
Get-ChildItem -Path "\\SRV1\Installers\Office$Version"
} elseif ($Path) {
Get-ChildItem -Path $Path
}
}
This poses a problem, though, because the user could use both parameters. Besides, since both parameters are mandatory, they’re going to be forced to use both when that’s not what you want. To fix this, we can place each parameter in a parameter set like below.
function Install-Office {
[CmdletBinding()]
param(
[Parameter(Mandatory,ParameterSetName = 'ByVersion')]
[ValidateSet('2013','2016')]
[string]$Version,
[Parameter(Mandatory, ParameterSetName = 'ByPath')]
[string]$Path
)
if ($Version) {
Get-ChildItem -Path "\\SRV1\Installers\Office$Version"
} elseif ($Path) {
Get-ChildItem -Path $Path
}
}
By defining a parameter set name on each parameter, this allows you to control groups of parameters together.
The Default Parameter Set
What if the user tries to run Install-Office
with no parameters? This isn’t accounted for, and you’ll see a friendly error message.
To fix this, you’ll need to define a default parameter set within the CmdletBinding()
area. This tells the function to pick a parameter set to use if no parameters were used explicitly changing [CmdletBinding()]
to [CmdletBinding(DefaultParameterSetName = 'ByVersion')]
Now, whenever you run Install-Office
, it will prompt for the Version
parameter since it will be using that parameter set.
Pipeline Input
In the examples so far, you’ve been creating functions with a PowerShell parameter that can only be passed using the typical -ParameterName Value syntax. But, as you’ve learned already, PowerShell has an intuitive pipeline that allows you to seamlessly pass objects from one command to another without using the “typical” syntax.
When you use the pipeline, you “chain” commands together with the pipe symbol |
allowing a user to send the output of a command like Get-Service
to Start-Service
as a shortcut for passing the Name
parameter to Start-Service
.
The “Old” Way Using a Loop
In the custom function you’re working with; you’re installing Office and have a Version
parameter. Let’s say you’ve got a list of computer names in a CSV file in one row with the version of Office that needs to be installed on them in the second row. The CSV file looks something like this:
ComputerName,Version
PC1,2016
PC2,2013
PC3,2016
You’d like to install the version of Office that’s next to each computer on that computer.
First, you’ll have to add a ComputerName
parameter onto the function to pass a different computer name in for each iteration of the function. Below I’ve created some pseudocode that represents some code that may be in the fictional function and added an Write-Host
instance to see how the variables expand inside the function.
function Install-Office {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[ValidateSet('2013','2016')]
[string]$Version,
[Parameter()]
[string]$ComputerName
)
<#
## Connect to the remote with some code here
Invoke-Command -ComputerName $ComputerName -ScriptBlock {
## Do stuff to install the version of office on this computer
Start-Process -FilePath 'msiexec.exe' -ArgumentList 'C:\Setup\Office{0}.msi' -f $using:Version
}
#>
Write-Host "I am installing Office version [$Version] on computer [$ComputerName]"
}
Once you’ve got the ComputerName
parameter added to the function; you could make this happen by reading the CSV file and passing the values for the computer name and the version to the Install-Office
function.
$computers = Import-Csv -Path 'C:\ComputerOfficeVersions.csv'
foreach ($pc in $computers) {
Install-Office -Version $_.Version -ComputerName $_.ComputerName
}
Building Pipeline Input for Parameters
This method of reading the CSV rows and using a loop to pass each row’s properties to the function is the “old” way of doing it. In this section, you want to forego the foreach
loop entirely and use the pipeline instead.
As-is, the function doesn’t support the pipeline at all. It would make intuitive sense to assume that you could pass each computer name and version to the function using the pipeline. Below, we’re reading the CSV and directly passing it to Install-Office,
but this doesn’t work.
PS> Import-Csv -Path 'C:\ComputerOfficeVersions.csv' | Install-Office
You can assume all you want, but that doesn’t just make it work. We’re getting prompted for the Version
parameter when you know that Import-Csv
is sending it as an object property. Why isn’t it working? Because you haven’t added any pipeline support yet.
There are two kinds of pipeline input in a PowerShell function; ByValue (entire object) and ByPropertyName (a single object property). Which do you think is the best way to stitch together the output of Import-Csv
to the input of Install-Office
?
Without any refactoring at all, you could use the ByPropertyName
method since, after all, Import-Csv
is already returning the properties Version
and ComputerName
since they’re columns in the CSV.
To add pipeline support to a custom function is much simpler than you may be thinking. It’s merely a parameter attribute represented with one of two keywords; ValueFromPipeline
or ValueFromPipelineByPropertyName
.
In the example, you’d like to bind the ComputerName
and Version
properties returned from Import-Csv
to the Version
and ComputerName
parameters of Install-Office
so you’ll be using ValueFromPipelineByPropertyName
.
Since we’d like to bind both of these parameters, you’ll add this keyword to both parameters as shown below and rerun the function using the pipeline.
function Install-Office {
[CmdletBinding()]
param(
[Parameter(Mandatory,ValueFromPipelineByPropertyName)]
[ValidateSet('2013','2016')]
[string]$Version,
[Parameter(ValueFromPipelineByPropertyName)]
[ValidateNotNullOrEmpty()]
[string]$ComputerName
)
<#
## Connect to the remote with some code here
Invoke-Command -ComputerName $ComputerName -ScriptBlock {
## Do stuff to install the version of office on this computer
Start-Process -FilePath 'msiexec.exe' -ArgumentList 'C:\Setup\Office{0}.msi' -f $using:Version
}
#>
Write-Host "I am installing Office version [$Version] on computer [$ComputerName]"
}
That’s weird. It only ran for the last row in the CSV. What’s going on? It just executed the function for the last row because you skipped over a concept that isn’t required when building functions without pipeline support.
Don’t Forget the Process Block!
When you need to create a function that involves pipeline support, you must include (at a minimum) an “embedded” block inside of your function called. process
. This process block tells PowerShell that when receiving pipeline input, to run the function for every iteration. By default, it will only execute the last one.
You could technically add other blocks like begin
and end
as well, but scripters don’t use them near as often.
To tell PowerShell to execute this function for every object coming in, I’ll add a process
block that includes the code inside of that.
function Install-Office {
[CmdletBinding()]
param(
[Parameter(Mandatory,ValueFromPipelineByPropertyName)]
[ValidateSet('2013','2016')]
[string]$Version,
[Parameter(ValueFromPipelineByPropertyName)]
[ValidateNotNullOrEmpty()]
[string]$ComputerName
)
process {
<#
## Connect to the remote with some code here
Invoke-Command -ComputerName $ComputerName -ScriptBlock {
## Do stuff to install the version of office on this computer
Start-Process -FilePath 'msiexec.exe' -ArgumentList 'C:\Setup\Office{0}.msi' -f $using:Version
}
#>
Write-Host "I am installing Office version [$Version] on computer [$ComputerName]"
}
}
Now you can see that each object’s Version
and ComputerName
properties returned from Import-Csv
was passed to Install-Office
and bound to the Version
and ComputerName
parameters.
Resources
To dive deeper into how function parameters work, check out my blog post on PowerShell functions.
I also encourage you to check my Pluralsight course entitled Building Advanced PowerShell Functions and Modules for an in-depth breakdown of everything there is to know about PowerShell functions, function parameters, and PowerShell modules.