To create a function in PowerShell, write the function keyword, a name, and the code in curly braces. Then call it by name, like any cmdlet:
function Get-Greeting {
'Hello from a function'
}
Get-Greeting
Output:
Hello from a function
A function is a named block of code you can run again and again. Once you add parameters, it behaves like a small cmdlet, with named arguments, defaults and validation.
I ran every example in PowerShell 7.6 and Windows PowerShell 5.1, and they behaved the same, apart from the wording of one error message.
Add parameters to a function
Declare parameters in a param() block at the top of the function. Give them types and default values, and call them by name:
function Get-Greeting {
param(
[string]$Name = 'there',
[int]$Times = 1
)
for ($i = 1; $i -le $Times; $i++) {
"Hello, $Name!"
}
}
Get-Greeting
Get-Greeting -Name Maria -Times 2
Output:
Hello, there!
Hello, Maria!
Hello, Maria!
Name your function with an approved verb and a singular noun, like Get-Greeting. PowerShell naming conventions covers the rules.
Make a parameter required and validate it
[Parameter(Mandatory)] makes PowerShell prompt for a missing value, and validation attributes reject bad values before your code runs:
function Set-Discount {
param(
[Parameter(Mandatory)]
[ValidateRange(1, 50)]
[int]$Percent
)
"Discount set to $Percent%"
}
Set-Discount -Percent 15
try {
Set-Discount -Percent 75
}
catch {
$_.Exception.Message
}
Output:
Discount set to 15%
Cannot validate argument on parameter 'Percent'. The 75 argument is greater than the maximum allowed range of 50. Supply an argument that is less than or equal to 50 and then try the command again.
The error explains exactly what was wrong, so you don’t need to write your own checks for simple limits. For lists of values, see PowerShell array parameters.
What does a PowerShell function return?
Everything it outputs, not just what follows return. This is the biggest surprise for people coming from other languages. Here, ArrayList.Add() outputs an index each time, and those numbers end up in the result:
function Get-OrderTotal {
param([int[]]$Prices)
$log = [System.Collections.ArrayList]::new()
foreach ($price in $Prices) {
$log.Add("Added $price")
}
return ($Prices | Measure-Object -Sum).Sum
}
$total = Get-OrderTotal -Prices 10, 20, 30
"Total: $total"
"Items returned: $($total.Count)"
Output:
Total: 0 1 2 60
Items returned: 4

The function returned four values: 0, 1 and 2 from Add(), then 60. Send output you don’t want to [void] or $null:
function Get-OrderTotal {
param([int[]]$Prices)
$log = [System.Collections.ArrayList]::new()
foreach ($price in $Prices) {
[void]$log.Add("Added $price")
}
return ($Prices | Measure-Object -Sum).Sum
}
"Total: $(Get-OrderTotal -Prices 10, 20, 30)"
Output:
Total: 60
return just ends the function early. Any line that produces output, like a bare variable or a method that returns something, adds to the result.
Variable scope in functions
A function can read variables from the script, but assigning a variable inside it creates a new local one. The script’s variable doesn’t change:
$count = 0
function Add-One {
$count = $count + 1
"Inside Add-One: $count"
}
Add-One
"Outside: $count"
function Add-OneToScript {
$script:count = $script:count + 1
"Inside Add-OneToScript: $script:count"
}
Add-OneToScript
"Outside: $count"
Output:
Inside Add-One: 1
Outside: 0
Inside Add-OneToScript: 1
Outside: 1

The $script: prefix changes the script’s variable. Use it sparingly, though. Returning a value and assigning it is easier to follow.
Define a function before you call it
PowerShell runs a script from top to bottom, so a function doesn’t exist until its definition has run:
try {
Get-Report
}
catch {
"Error: $($_.Exception.Message)"
}
function Get-Report { 'Report ready' }
Get-Report
Output in PowerShell 7:
Error: The term 'Get-Report' is not recognized as a name of a cmdlet, function, script file, or executable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
Report ready
Windows PowerShell 5.1 said the same with slightly different wording, “not recognized as the name of a cmdlet“. Put your functions at the top of the script, or in a separate file that you load first.
Add -WhatIf support
[CmdletBinding(SupportsShouldProcess)] gives your function -WhatIf and -Confirm, just like built-in cmdlets. Wrap the risky part in $PSCmdlet.ShouldProcess():
function Remove-OldLog {
[CmdletBinding(SupportsShouldProcess)]
param([string]$Path)
if ($PSCmdlet.ShouldProcess($Path, 'Delete log file')) {
"Deleted $Path"
}
}
Remove-OldLog -Path C:\Logs\app-2026-08.log -WhatIf
Remove-OldLog -Path C:\Logs\app-2026-08.log
Output:
What if: Performing the operation "Delete log file" on target "C:\Logs\app-2026-08.log".
Deleted C:\Logs\app-2026-08.log
With -WhatIf, the function only describes what it would do. Without it, the code runs. This example only prints a message, so nothing was actually deleted.
Load functions from another file
To reuse functions across scripts, keep them in a file and dot-source it: a dot, a space, then the path. Running the file normally doesn’t keep its functions:
& C:\psfaqs\tools.ps1
"After running it: $([bool](Get-Command Get-ServerList -ErrorAction SilentlyContinue))"
. C:\psfaqs\tools.ps1
"After dot-sourcing it: $([bool](Get-Command Get-ServerList -ErrorAction SilentlyContinue))"
(Get-ServerList) -join ', '
Output:
After running it: False
After dot-sourcing it: True
WEB01, WEB02, SQL01
