A PowerShell function is a block of code designed to perform a specific task. Once a function is created and tested, it can be used in multiple scripts, reducing coding effort and risk of errors. Using well-named functions also makes scripts easier to read and maintain. And since functions can return values that can be used as input to other functions or code blocks, they facilitate building complex operations.
This article explains everything you need to know to begin using PowerShell functions, including important best practices, and even provides guidance for diving into more advanced options.
Getting Started with PowerShell Functions
How to Create a Function in PowerShell
To define a function, you use the function keyword. The basic syntax is as follows:
function <Function-Name> {
<Code-Block>
}
The code inside the curly braces {} will be executed when the function is called.
If you choose, you can use the param keyword to specify function parameters, along with data types, default values and so on. Here is the syntax with parameters added:
function <Function-Name> {
[param(
[<ParameterType>]$<ParameterName>,
[<ParameterType>]$<ParameterName2> = <DefaultValue>
)]
<Code-Block>
}
This PowerShell function example will multiply two numbers and display the result:
function Multiply-Numbers {
param (
[int]$Number1,
[int]$Number2
)
$Result = $Number1 * $Number2
Write-Output "The multiplication value of $Number1 and $Number2 is $Result."
}
Saving a Function
To save a function, simply store it in a script file with a?.ps1?extension.
Calling a Function
To invoke a function in PowerShell, type the name of the function followed by any necessary named parameters. For example, here is how to call the function we defined above:
Multiply-Numbers -number1 6 -Number2 8
Running a Function as Part of the Current Session (Dot-Sourcing)
Dot-sourcing enables you to run a PowerShell script as if were defined directly within your current console session. To use dot-sourcing, precede the function name with a dot and space (. ) when calling it.
For instance, suppose we have defined the following function called Add-Numbers:
function Add-Numbers {
param (
[int]$Number1,
[int]$Number2
)
$Sum = $Number1 + $Number2
Write-Output "The sum of $Number1 and $Number2 is $Sum."
}
We can dot-source this function into our session as shown here:
Naming Your PowerShell Functions
To get the most value from PowerShell functions, be sure to follow these guidelines when naming your functions.
Use verb-noun format.
PowerShell provides a set of pre-built cmdlets whose names follow a specific format: a verb that species the action to be performed, followed by a noun that names the object to be acted upon. For example, the get-user cmdlet retrieves information about a user and remove-item deletes a specified item.
It’s a best practice to follow the same naming convention for the functions you create. Doing so will help you design your functions more effectively and make them easier to reuse.
Use approved verbs.
PowerShell provides a list of verbs that are recommended for use in functions. To view the list, simply run the following cmdlet:
Get-Verb
Choose descriptive nouns.
It’s vital to not give a function the same name as a built-in cmdlet. A good way to prevent this issue to avoid using generic nouns like user and data. Instead, choose nouns that clearly indicate the type of data the function is acting upon. For instance, it’s easy to understand how these functions differ: Get-EmployeeData, Get-UserInfo and Get-ServiceInfo.
Avoid using function names for versioning purposes.
Appending version numbers to function names, such as Get-UserInfoV2, is not recommended. It makes scripts harder to understand and increases the chances of using old versions of a function. Instead, handle versioning through your file versioning system.
Advanced Function Capabilities
Accepting Pipeline Input
You can design a function to accept input from another function or command via a pipeline by using the ValueFromPipeline attribute.
To illustrate, let’s create a function that processes a list of strings one by one, converts each string to uppercase, and outputs the results. For clarity, we will divide the string processing into three distinct blocks:
- BEGIN — This code runs once, before processing any pipeline input.
- PROCESS — This section runs for each item that is passed to the pipeline, processing the input objects one by one.
- END — This code runs after all the input has been processed to finalize the output.
function Convert-ToUppercase {
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline = $true, Mandatory = $true)]
[string]$InputString
)
BEGIN {
Write-Verbose "Starting the uppercase conversion process."
}
PROCESS {
if (-not [string]::IsNullOrWhiteSpace($InputString)) {
Write-Verbose "Processing string: $InputString"
$InputString.ToUpper()
} else {
Write-Verbose "Skipped empty or null string."
}
}
END {
Write-Verbose "Finished processing all strings."
}
}
Here’s how we could call our function to convert a list of strings provided via a pipeline:
"hello", "world", "this", "is", "powershell" | Convert-ToUppercase -Verbose
Note that lightweight functions called filters can provide a highly efficient way to process objects passed through a pipeline.
Enabling a Function to Behave like a Cmdlet
You can turn a basic function into an advanced function using the CmdletBinding attribute. Doing so enables the function to behave like a cmdlet, including supporting defined PowerShell parameters.
Here is the basic function we defined earlier to add two numbers:
function Add-Numbers {
param (
[int]$Number1,
[int]$Number2
)
$Sum = $Number1 + $Number2
Write-Output "The sum of $Number1 and $Number2 is $Sum."
}
We can turn it into an advanced function as follows:
function Add-Numbers {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[int]$Number1,
[Parameter(Mandatory)]
[int]$Number2
)
process {
# Verbose Output
Write-Verbose "Starting addition of $Number1 and $Number2"
# Debugging Output
Write-Debug "Debugging: Ensuring numbers are valid integers"
# Perform the Addition
try {
$Result = $Number1 + $Number2
Write-Output $Result
} catch {
# Handle errors with error stream
Write-Error "An error occurred while performing the addition."
}
# Verbose Completion Message
Write-Verbose "Addition completed successfully"
}
}
Building Function Modules
A function module is a file with a .psm1 extension that contains a collection of related functions. Organizing functions into modules facilitates reusability and maintenance.
To create a function module, simply create a text file, paste in your functions, and save the file with the extension .psm1 in the C:\Program Files\WindowsPowerShell\Modules directory. For example, we could create a module with arithmetic function such as the Add-Numbers function defined earlier.
To import the module into your PowerShell session, use the Import-Module command, as shown here:
Error Handling
PowerShell offers several error handling techniques to help you smoothly handle issues that could otherwise cause function execution to stop with an error, such as an attempt to divide by zero.
Try/Catch/Finally Blocks
When you know that a certain condition could cause a problem, you can use Try, Catch and Finally blocks in your function to handle that condition gracefully:
- Try — This block contains code that may produce an error (such as a division operation). If no error occurs, the Catch block will not be executed.
- Catch — If an error occurs in the Try block, the Catch block will handle it by capturing and processing the error details.
- Finally — This block is optional. If is present, it is executed after the Try and Catch blocks, regardless of whether an error occurred.
-ErrorAction Parameter
The -ErrorAction parameter allows you to specify how errors should be handled for a particular function. The possible values are:
- Continue — Display an error message and continue.
- Stop — Halts execution of the script when an error occurs.
- SilentlyContinue — Suppress the error message and continue execution.
- Inquire — Prompt the user for how to handle the error with options like Yes, No and Retry.
- Ignore — Ignore the error completely without even displaying a message.
Below are some examples of the -ErrorAction parameter:
Remove-Item "C:\path\to\nonexistentfile.txt" -ErrorAction SilentlyContinue
Get-Process -Name "Notepad" -ErrorAction Stop
$ErrorActionPreference Variable
The $ErrorActionPreference variable controls how errors are handled globally for all cmdlets in a session. This variable can be set to influence the behavior of error handling across all commands unless overridden by the -ErrorAction parameter.
Detailed Example
Below is an example that uses multiple methods of error handling:
- The $ErrorActionPreference variable sets the global error handling preference to stop, so when an error occurs that is not otherwise handled, execution will stop.
- The Try/Catch blocks gracefully handle the potential error of dividing by zero without halting execution. The Try block performs the mathematical operations. If it encounters division by zero, processing moves to the Catch block, which prints an error message.
- The Finally block will execute regardless of whether an error occurred, printing a message that operations are complete.
# Set global error action preference to 'Stop' to immediately halt on errors
$ErrorActionPreference = 'Stop'
function Perform-MathOperations {
param (
[int]$Num1,
[int]$Num2
)
try {
# Performing addition
Write-Host "Performing addition: $Num1 + $Num2"
$additionResult = $Num1 + $Num2
Write-Host "Addition Result: $additionResult"
# Checking for division by zero before performing division
if ($Num2 -eq 0) {
throw "Division by zero is not allowed."
}
# Performing division
Write-Host "Performing division: $Num1 / $Num2"
$divisionResult = $Num1 / $Num2
Write-Host "Division Result: $divisionResult"
}
catch {
# Catching any error that occurs in the try block
Write-Host "An error occurred: $_"
Write-Host "Error Type: $($_.Exception.GetType().Name)"
}
finally {
# This block always runs regardless of error occurrence
Write-Host "Finished performing math operations."
}
}
The screenshot below shows the result of calling this function in two ways. First, we call it with valid input:
Perform-MathOperations -Num1 10 -Num2 2
Then we call it with input that will result in an attempt to divide by zero:
Perform-MathOperations -Num1 10 -Num2 0
Troubleshooting Your Functions
You can display messages that provide various levels of detail during function execution using Write-output (or Write-host), Write-Verbose and Write-Debug. This information can help you trace the execution path step by step to pinpoint unexpected behavior.
For example, the following screenshot shows how we could revise our basic Add-Numbers function to provide troubleshooting details. Note that by default, the verbose message stream is not displayed, so we need to use -Verbose when invoking the function.
Add-Numbers -Number1 5 -Number2 10 -Verbose
Similarly, Write-Debug messages are not displayed in the console by default, but you can display them by including -Debug when you execute the function:
Add-Numbers -Number1 5 -Number2 10 -Debug
Best Practices
- Design each function to perform one task. This make them easier to test, use and maintain.
- Design functions in a modular way so that they can be reused in different scripts.
- When using parameters, specify their default values.
- Include detailed comments in your functions. This comment based help will facilitate debugging and function reuse.
- Have your functions return objects rather than formatted output to enhance reusability.
Additional Example
The following function illustrates many of the capabilities discussed earlier, including error handling:
# Function: Backup-Files
# Description: Backs up files from a source directory to a backup directory.
function Backup-Files {
param (
[Parameter(Mandatory = $true)]
[string]$SourcePath, # The directory containing the files to back up
[Parameter(Mandatory = $true)]
[string]$BackupPath, # The directory where files will be backed up
[string[]]$Extensions = @("*") # File extensions to back up (default: all files)
)
# Check if the source path exists
if (-not (Test-Path -Path $SourcePath)) {
Write-Error "Source path '$SourcePath' does not exist."
return
}
# Create the backup directory if it does not exist
if (-not (Test-Path -Path $BackupPath)) {
Write-Output "Creating backup directory at '$BackupPath'..."
New-Item -ItemType Directory -Path $BackupPath | Out-Null
}
# Get files matching the specified extensions
foreach ($extension in $Extensions) {
$files = Get-ChildItem -Path $SourcePath -Filter "*.$extension" -File -ErrorAction SilentlyContinue
foreach ($file in $files) {
$destination = Join-Path -Path $BackupPath -ChildPath $file.Name
Write-Output "Backing up file: $($file.FullName) -> $destination"
Copy-Item -Path $file.FullName -Destination $destination -Force
}
}
Write-Output "Backup completed successfully."
}
We can call this function to back up all files, or only select file extensions as shown here:
Backup-Files -SourcePath "D:\Office\project" -BackupPath "D:\Backup" -Extensions @("csv", "txt")
Conclusion
Functions in PowerShell provide a structured approach to scripting that improves modularity, reusability and maintainability. Feel free to experiment with all the sample code provided in this article. As you start creating your own functions, be sure to adopt the best practices for naming and using them.