Table of Contents
If you want to follow along with the article, you will want to use either Windows PowerShell ISE or Microsoft Visual Studio Code with the PowerShell extension.
PowerShell Functions
A function is basically a named block of code. When you call the function name, the script block within that function runs. It is a list of PowerShell statements that has a name that you assign.
When you run a function, you type the function name. It is a method of saving time when tackling repetitive tasks. The syntax of a basic function below:
function Verb-Noun {
code
}
Every function starts out with the keyword of the function followed by a name (verb-noun) for the function. The proper way to name a function is by using an approved verb-noun format. All code between the curly braces will be the tasks the function will perform when called.
PowerShell functions do’s and don’ts
- Remember the KISS principle, meaning accomplishing a single task in the simplest way possible. The goal of a function is to do one job and do it well with the least amount of code as possible.
- Avoid using aliases and positional parameters in your code. You should format for readability, as the next person troubleshooting your code could be you!
- Don’t hard-code values. It’s best to create variables and use parameters.
Basic PowerShell functions
A function in PowerShell is declared with the function keyword followed by the function name and then an open and closing curly brace. The code that the function will execute is contained within those curly braces.
function Get-MyPSVersion {
$PSVersionTable.PSVersion
}
The function shown is a simple example that returns the version of PowerShell.
PS C:\> function Get-MyPSVersion {$PSVersionTable.PSVersion}
PS C:\>
PS C:\> Get-MyPSVersion
Major Minor Build Revision
----- ----- ----- --------
5 1 22621 1778
Once loaded into memory, you can see functions on the Function: PSDrive.
PS C:\> Get-ChildItem -Path Function:\*version
CommandType Name Version Source
----------- ---- ------- ------
Function Get-MyPSVersion
If you want to remove these functions from your current session, you’ll have to remove them from the Function PSDrive or close and reopen PowerShell.
PS C:\> Get-ChildItem -Path Function:\Get-MyPSVersion | Remove-Item
PS C:\> Get-MyPSVersion
Get-MyPSVersion : The term 'Get-MyPSVersion' is not recognized as the name of a cmdlet...
Parameters
Don’t statically assign values! Use parameters and variables. When it comes to naming your parameters, use the same name as the default cmdlets for your parameter names whenever possible.
function Test-MrParameter {
param (
$ComputerName
)
Write-Output $ComputerName
}
Why did I use ComputerName and not Computer, ServerName, or Host for my parameter name? It’s because I wanted my function standardized like the default cmdlets.
I’ll create a function to query all of the commands on a system and return the number of them that have specific parameter names.
function Get-MrParameterCount {
param (
[string[]]$ParameterName
)
foreach ($Parameter in $ParameterName) {
$Results = Get-Command -ParameterName $Parameter -ErrorAction SilentlyContinue
[pscustomobject]@{
ParameterName = $Parameter
NumberOfCmdlets = $Results.Count
}
}
}
As you can see in the results shown below, 31 commands that have a ComputerName parameter. There aren’t any cmdlets that have parameters such as Computer, ServerName, Host, or Machine.
PS C:\> Get-MrParameterCount -ParameterName ComputerName, Computer, ServerName, Host, Machine
ParameterName NumberOfCmdlets
------------- ---------------
ComputerName 31
Computer 0
ServerName 0
Host 0
Machine 0
I also recommend using the same case for your parameter names as the default cmdlets. Use ComputerName, not computername. This makes your functions look and feel like the default cmdlets. People who are already familiar with PowerShell will feel right at home.
The param statement allows you to define one or more parameters. The parameter definitions are separated by a comma (,). For more information, see about_Functions_Advanced_Parameters.
Advanced Functions
Turning a function in PowerShell into an advanced function is really simple. One of the differences between a function and an advanced function is that advanced functions have a number of common parameters that are added to the function automatically. These common parameters include parameters such as Verbose and Debug.
I’ll start out with the Test-MrParameter function that was used in the previous section.
function Test-MrParameter {
param (
$ComputerName
)
Write-Output $ComputerName
}
What I want you to notice is that the Test-MrParameter function doesn’t have any common parameters. There are a couple of different ways to see the common parameters. One is by viewing the syntax using Get-Command.
PS C:\> Get-Command -Name Test-MrParameter -Syntax
Test-MrParameter [[-ComputerName] <Object>]
Add CmdletBinding to turn the function into an advanced function.
function Test-MrCmdletBinding {
[CmdletBinding()] #<<-- This turns a regular function into an advanced function
param (
$ComputerName
)
Write-Output $ComputerName
}
Adding CmdletBinding adds the common parameters automatically. CmdletBinding requires a param block, but the param block can be empty.
PS C:\> Get-Command -Name Test-MrParameter -Syntax
Test-MrParameter [[-ComputerName] <Object>]
PS C:\> Get-Command -Name Test-MrCmdletBinding -Syntax
Test-MrCmdletBinding [[-ComputerName] <Object>] [<CommonParameters>]
Drilling down into the parameters with Get-Command shows the actual parameter names including the common ones.
PS C:\> (Get-Command -Name Test-MrCmdletBinding).Parameters.Keys
ComputerName
Verbose
Debug
ErrorAction
WarningAction
InformationAction
ErrorVariable
WarningVariable
InformationVariable
OutVariable
OutBuffer
PipelineVariable
SupportsShouldProcess
SupportsShouldProcess adds WhatIf and Confirm parameters. These are only needed for commands that make changes.
function Test-MrSupportsShouldProcess {
[CmdletBinding(SupportsShouldProcess)]
param (
$ComputerName
)
Write-Output $ComputerName
}
Notice that there are now WhatIf and Confirm parameters. You can also use Get-Command to return a list of the actual parameter names including the common ones along with WhatIf and Confirm.
PS C:\> Get-Command -Name Test-MrSupportsShouldProcess -Syntax
Test-MrSupportsShouldProcess [[-ComputerName] <Object>] [-WhatIf] [-Confirm] [<CommonParameters>]
Parameter Validation
Validate input early on. Why allow your code to continue on a path when it’s not possible to run without valid input? Always type the variables that are being used for your parameters (specify a datatype).
function Test-MrParameterValidation {
[CmdletBinding()]
param (
[string]$ComputerName
)
Write-Output $ComputerName
}
I’ve specified String as the datatype for the ComputerName parameter. This causes it to allow only a single computer name to be specified. If more than one computer name is specified via a comma-separated list, an error is generated.
PS C:\> Test-MrParameterValidation -ComputerName Server01, Server02
Test-MrParameterValidation : Cannot process argument transformation on parameter
'ComputerName'. Cannot convert value to type System.String.
At line:1 char:42
+ Test-MrParameterValidation -ComputerName Server01, Server02
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Test-MrParameterValidation],
+ FullyQualifiedErrorId : ParameterArgumentTransformationError,Test-MrParameterValidation
If you want to allow for more than one value for the ComputerName parameter, use the String datatype but add open and closed square brackets to the datatype to allow for an array of strings.
function Test-MrParameterValidation {
[CmdletBinding()]
param (
[string[]]$ComputerName
)
Write-Output $ComputerName
}
The problem with the current definition is that it’s valid to omit the value of the ComputerName parameter, but a value is required for the function to complete successfully. This is where the Mandatory parameter attribute comes in handy.
function Test-MrParameterValidation {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[string]$ComputerName
)
Write-Output $ComputerName
}
[Parameter(Mandatory=$true)] could be specified instead to make the function compatible with PowerShell version 2.0 and higher. Now that the ComputerName is required, if one isn’t specified, the function will prompt for one.
PS C:\> Test-MrParameterValidation
cmdlet Test-MrParameterValidation at command pipeline position 1
Supply values for the following parameters:
ComputerName:
Verbose Output
While inline comments are useful, especially if you’re writing some complex code, they never get seen by users unless they look into the code itself. The function shown in the following example has an inline comment in the foreach loop. While this particular comment may not be that difficult to locate, imagine if the function included hundreds of lines of code.
function Test-MrVerboseOutput {
[CmdletBinding()]
param (
[ValidateNotNullOrEmpty()]
[string[]]$ComputerName = $env:COMPUTERNAME
)
foreach ($Computer in $ComputerName) {
#Attempting to perform some action on $Computer <<-- Don't use
#inline comments like this, use write verbose instead.
Write-Output $Computer
}
}
A better option is to use Write-Verbose instead of inline comments.
function Test-MrVerboseOutput {
[CmdletBinding()]
param (
[ValidateNotNullOrEmpty()]
[string[]]$ComputerName = $env:COMPUTERNAME
)
foreach ($Computer in $ComputerName) {
Write-Verbose -Message "Attempting to perform some action on $Computer"
Write-Output $Computer
}
}
When the function is called without the Verbose parameter, the verbose output won’t be displayed. When it’s called with the Verbose parameter, the verbose output will be displayed.
PS C:\> Test-MrVerboseOutput -ComputerName Server01, Server02
Server01
Server02
PS C:\> Test-MrVerboseOutput -ComputerName Server01, Server02 -Verbose
VERBOSE: Attempting to perform some action on Server01
Server01
VERBOSE: Attempting to perform some action on Server02
Server02
Comment-Based Help
It’s considered to be a best practice to add comment based help to your functions so the people you’re sharing them with will know how to use them.
function Get-MrAutoStoppedService {
<#
.SYNOPSIS
Returns a list of services that are set to start automatically, are not
currently running, excluding the services that are set to delayed start.
.DESCRIPTION
Get-MrAutoStoppedService is a function that returns a list of services from
the specified remote computer(s) that are set to start automatically, are not
currently running, and it excludes the services that are set to start automatically
with a delayed startup.
.PARAMETER ComputerName
The remote computer(s) to check the status of the services on.
.PARAMETER Credential
Specifies a user account that has permission to perform this action. The default
is the current user.
.EXAMPLE
Get-MrAutoStoppedService -ComputerName 'Server1', 'Server2'
.EXAMPLE
'Server1', 'Server2' | Get-MrAutoStoppedService
.EXAMPLE
Get-MrAutoStoppedService -ComputerName 'Server1' -Credential (Get-Credential)
.INPUTS
String
.OUTPUTS
PSCustomObject
.NOTES
Author: Mike F Robbins
Website: http://mikefrobbins.com
Twitter: @mikefrobbins
#>
[CmdletBinding()]
param (
)
#Function Body
}
When you add comment-based help to your functions, help can be retrieved for them just like the default built-in commands.
All of the syntax for writing a function in PowerShell can seem overwhelming especially for someone who is just getting started. Often times if I can’t remember the syntax for something, I’ll open a second copy of the ISE on a separate monitor and view the “Cmdlet (advanced function) – Complete” snippet while typing in the code for my function. Snippets can be accessed in the PowerShell ISE using the the Ctrl+J key combination.