PowerShell Functions: Complete Guide with Parameters and Examples [2024]
Master PowerShell functions—the foundation of reusable, maintainable code. This comprehensive guide covers everything from basic functions to advanced parameter handling, error handling, and production-ready patterns.
Table of Contents
- What are PowerShell Functions?
- Why Use Functions?
- Basic Function Syntax
- Simple Functions
- Function Parameters
- Mandatory vs Optional Parameters
- Default Parameter Values
- Parameter Validation
- Pipeline Input
- Advanced Functions
- Return Values
- Variable Scope
- Error Handling
- Function Documentation
- Performance Considerations
- Common Mistakes
- Best Practices
- Troubleshooting
- FAQs
- Real-World Examples
- Conclusion
What are PowerShell Functions? {#what-are-functions}
A PowerShell function is a reusable block of code that performs a specific task. Functions encapsulate logic into discrete units, making code more organized, maintainable, and testable.
Key Benefits:
- ✅ Code Reusability: Write once, use multiple times
- ✅ Modularity: Break complex scripts into manageable pieces
- ✅ Maintainability: Changes in one place affect all uses
- ✅ Testing: Easy to test individual components
- ✅ Readability: Makes scripts easier to understand
- ✅ Debugging: Isolate problems to specific functions
Why Use Functions? {#why-functions}
Example: Without Functions (Repetitive)
# Check if service is running - written 3 times
if ((Get-Service -Name "BITS").Status -eq "Running") {
Write-Output "BITS is running"
} else {
Write-Output "BITS is stopped"
}
if ((Get-Service -Name "wuauserv").Status -eq "Running") {
Write-Output "wuauserv is running"
} else {
Write-Output "wuauserv is stopped"
}
if ((Get-Service -Name "WinDefend").Status -eq "Running") {
Write-Output "WinDefend is running"
} else {
Write-Output "WinDefend is stopped"
}
Example: With Functions (Reusable)
function Get-ServiceStatus {
param([string]$ServiceName)
$service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
if ($service.Status -eq "Running") {
return "$ServiceName is running"
} else {
return "$ServiceName is stopped"
}
}
# Call once, reuse everywhere
Get-ServiceStatus -ServiceName "BITS"
Get-ServiceStatus -ServiceName "wuauserv"
Get-ServiceStatus -ServiceName "WinDefend"
The function version is more maintainable and scalable.
Basic Function Syntax {#basic-syntax}
Simple Function
function Get-Greeting {
Write-Output "Hello, World!"
}
# Call the function
Get-Greeting
Function with Parameters
function Get-Greeting {
param(
[string]$Name
)
Write-Output "Hello, $Name!"
}
# Call with parameter
Get-Greeting -Name "John"
Function with Return Value
function Add-Numbers {
param(
[int]$Number1,
[int]$Number2
)
return $Number1 + $Number2
}
$result = Add-Numbers -Number1 5 -Number2 3
Write-Output "Sum: $result" # Output: Sum: 8
Simple Functions {#simple-functions}
Function Without Parameters
function Get-SystemInfo {
$osInfo = Get-CimInstance -ClassName Win32_OperatingSystem
Write-Output "OS: $($osInfo.Caption)"
Write-Output "Version: $($osInfo.Version)"
}
Get-SystemInfo
Output:
OS: Microsoft Windows 10 Pro
Version: 10.0.19045
Function That Performs an Action
function Clear-TempFolder {
$tempPath = "C:\Temp"
$itemCount = (Get-ChildItem -Path $tempPath -ErrorAction SilentlyContinue | Measure-Object).Count
Remove-Item -Path "$tempPath\*" -Force -ErrorAction SilentlyContinue
Write-Output "Cleaned $itemCount items from $tempPath"
}
Clear-TempFolder
Output:
Cleaned 42 items from C:\Temp
Function Parameters {#parameters}
Basic Parameter Syntax
function Test-Parameters {
param(
[string]$Param1,
[int]$Param2,
[string[]]$Param3
)
Write-Output "String param: $Param1"
Write-Output "Int param: $Param2"
Write-Output "Array param: $Param3"
}
Test-Parameters -Param1 "Hello" -Param2 42 -Param3 "One", "Two", "Three"
Output:
String param: Hello
Int param: 42
Array param: One Two Three
Multiple Parameter Types
| Type | Description | Example |
|---|---|---|
[string] | Text value | "Hello" |
[int] | Whole number | 42 |
[double] | Decimal number | 3.14 |
[boolean] | True/False | $true |
[array] | Collection | @(1, 2, 3) |
[PSObject] | PowerShell object | Custom objects |
[switch] | Flag parameter | -Verbose |
Using Parameter Attributes
function Get-UserInfo {
param(
[Parameter(Mandatory=$true, HelpMessage="Enter username")]
[string]$UserName,
[Parameter(Mandatory=$false)]
[string]$Domain = "CONTOSO"
)
$adUser = Get-ADUser -Identity $UserName -Server $Domain
Write-Output $adUser
}
Mandatory vs Optional Parameters {#mandatory-optional}
Mandatory Parameter
function Get-FileSize {
param(
[Parameter(Mandatory=$true)]
[string]$FilePath
)
$file = Get-Item -Path $FilePath
$sizeMB = $file.Length / 1MB
Write-Output "File: $($file.Name), Size: $([Math]::Round($sizeMB, 2)) MB"
}
# This will prompt for FilePath if not provided
Get-FileSize -FilePath "C:\LargeFile.iso"
Optional Parameter with Default
function Export-ServiceList {
param(
[string]$ComputerName = "localhost",
[string]$OutputPath = "C:\Temp"
)
$services = Get-Service -ComputerName $ComputerName
$services | Export-Csv -Path "$OutputPath\services.csv" -NoTypeInformation
Write-Output "Exported $(($services | Measure-Object).Count) services"
}
# Both calls work
Export-ServiceList # Uses defaults
Export-ServiceList -ComputerName "Server1" -OutputPath "C:\Reports"
Using Switch Parameters
function Get-AdvancedProcessInfo {
param(
[Parameter(Mandatory=$true)]
[string]$ProcessName,
[switch]$IncludeThreads,
[switch]$IncludeModules
)
$process = Get-Process -Name $ProcessName
Write-Output "Process: $($process.Name), PID: $($process.Id)"
if ($IncludeThreads) {
Write-Output "Threads: $($process.Threads.Count)"
}
if ($IncludeModules) {
Write-Output "Modules: $($process.Modules.Count)"
}
}
Get-AdvancedProcessInfo -ProcessName "powershell" -IncludeThreads -IncludeModules
Output:
Process: powershell, PID: 5432
Threads: 12
Modules: 28
Default Parameter Values {#default-values}
Setting Defaults in param Block
function Send-Report {
param(
[string]$ReportName = "DefaultReport",
[string]$Recipient = "admin@contoso.com",
[int]$Priority = 1
)
Write-Output "Sending $ReportName to $Recipient with priority $Priority"
}
# Use defaults
Send-Report
# Override one parameter
Send-Report -ReportName "QuarterlyReport"
# Override multiple
Send-Report -ReportName "SecurityAudit" -Priority 5
Dynamic Defaults
$defaultServer = "Server1"
function Get-RemoteInfo {
param(
[string]$ComputerName = $defaultServer
)
Get-CimInstance -ClassName Win32_ComputerSystem -ComputerName $ComputerName
}
Get-RemoteInfo # Uses $defaultServer
Parameter Validation {#parameter-validation}
ValidateSet (Only specific values allowed)
function Set-ReportFormat {
param(
[Parameter(Mandatory=$true)]
[ValidateSet("CSV", "JSON", "XML", "HTML")]
[string]$Format
)
Write-Output "Report format set to: $Format"
}
Set-ReportFormat -Format "CSV" # Works
# Set-ReportFormat -Format "PDF" # Error: PDF not in ValidateSet
ValidateRange (Numeric range)
function Set-LogLevel {
param(
[ValidateRange(0, 5)]
[int]$Level = 1
)
Write-Output "Log level set to: $Level"
}
Set-LogLevel -Level 3 # Works
# Set-LogLevel -Level 10 # Error: Outside range
ValidateLength (String length)
function New-Password {
param(
[Parameter(Mandatory=$true)]
[ValidateLength(8, 50)]
[string]$Password
)
Write-Output "Password created (length: $($Password.Length))"
}
New-Password -Password "MySecurePassword123!" # Works (20 chars)
# New-Password -Password "Short" # Error: Too short
ValidatePattern (Regex validation)
function Set-Email {
param(
[ValidatePattern("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")]
[string]$Email
)
Write-Output "Email set to: $Email"
}
Set-Email -Email "user@example.com" # Works
# Set-Email -Email "invalid.email" # Error: Invalid format
Pipeline Input {#pipeline-input}
Accepting Pipeline Input
function Show-FileInfo {
param(
[Parameter(ValueFromPipeline=$true)]
[string[]]$FilePath
)
process {
foreach ($file in $FilePath) {
$item = Get-Item -Path $file -ErrorAction SilentlyContinue
if ($item) {
[PSCustomObject]@{
Name = $item.Name
Size = "$([Math]::Round($item.Length / 1KB, 2)) KB"
Modified = $item.LastWriteTime
}
}
}
}
}
# Usage: Pipe file paths
Get-ChildItem -Path "C:\Temp" -Filter "*.txt" -File |
Select-Object -ExpandProperty FullName |
Show-FileInfo
ValueFromPipelineByPropertyName
function Get-UserByName {
param(
[Parameter(ValueFromPipelineByPropertyName=$true)]
[string]$Name
)
process {
Get-ADUser -Filter { Name -like $Name }
}
}
# Pipe objects with a Name property
Get-ADUser -Filter * | Select-Object -First 5 | Get-UserByName
Advanced Functions {#advanced-functions}
Using CmdletBinding
function Get-FileReport {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$Path,
[switch]$Recurse,
[switch]$Verbose
)
begin {
Write-Verbose "Starting file report for: $Path"
}
process {
$params = @{ Path = $Path; File = $true }
if ($Recurse) { $params.Recurse = $true }
$files = Get-ChildItem @params
Write-Output "Found $($files.Count) files"
}
end {
Write-Verbose "Report completed"
}
}
Get-FileReport -Path "C:\Temp" -Verbose
Using SupportsShouldProcess
function Remove-OldFiles {
[CmdletBinding(SupportsShouldProcess=$true)]
param(
[Parameter(Mandatory=$true)]
[string]$Path,
[ValidateRange(1, 365)]
[int]$DaysOld = 30
)
$cutoffDate = (Get-Date).AddDays(-$DaysOld)
$files = Get-ChildItem -Path $Path -File |
Where-Object { $_.LastWriteTime -lt $cutoffDate }
foreach ($file in $files) {
if ($PSCmdlet.ShouldProcess($file.FullPath, "Remove")) {
Remove-Item -Path $file.FullPath
Write-Output "Deleted: $($file.Name)"
}
}
}
# Supports -WhatIf and -Confirm
Remove-OldFiles -Path "C:\Logs" -Verbose -WhatIf
Return Values {#return-values}
Explicit Return
function Get-Sum {
param(
[int]$A,
[int]$B
)
return $A + $B
}
$result = Get-Sum -A 10 -B 20
Write-Output "Sum: $result" # Output: Sum: 30
Implicit Return (Last value output)
function Get-Product {
param(
[int]$A,
[int]$B
)
$A * $B # Automatically returned
}
$result = Get-Product -A 5 -B 4
Write-Output "Product: $result" # Output: Product: 20
Multiple Return Values as Object
function Get-FileStatistics {
param(
[string]$Path
)
$files = Get-ChildItem -Path $Path -File
return [PSCustomObject]@{
TotalFiles = $files.Count
TotalSize = "{0:N2} MB" -f ($files.Length | Measure-Object -Sum | Select-Object -ExpandProperty Sum) / 1MB
Timestamp = Get-Date
}
}
$stats = Get-FileStatistics -Path "C:\Temp"
Write-Output $stats
Output:
TotalFiles : 42
TotalSize : 523.45 MB
Timestamp : 2/5/2024 2:30:45 PM
Variable Scope {#variable-scope}
Function Scope
$globalVar = "I'm global"
function Test-Scope {
$localVar = "I'm local"
$globalVar = "Modified global"
Write-Output "Inside function:"
Write-Output " Global var: $globalVar"
Write-Output " Local var: $localVar"
}
Test-Scope
Write-Output "Outside function:"
Write-Output " Global var: $globalVar"
# Write-Output " Local var: $localVar" # Error: doesn't exist here
Output:
Inside function:
Global var: Modified global
Local var: I'm local
Outside function:
Global var: I'm global
Using Script Scope
function Modify-ScriptVar {
$script:sharedVar = "Modified"
}
$sharedVar = "Original"
Modify-ScriptVar
Write-Output $sharedVar # Output: Modified
Error Handling {#error-handling}
Try-Catch in Functions
function Get-FileContent {
param(
[Parameter(Mandatory=$true)]
[string]$FilePath
)
try {
Get-Content -Path $FilePath -ErrorAction Stop
} catch {
Write-Error "Failed to read file: $_"
}
}
Get-FileContent -FilePath "C:\nonexistent.txt"
ErrorAction Parameter
function Find-Process {
param(
[Parameter(Mandatory=$true)]
[string]$ProcessName,
[ValidateSet("Stop", "Continue", "SilentlyContinue", "Inquire")]
[string]$OnError = "Stop"
)
Get-Process -Name $ProcessName -ErrorAction $OnError
}
Find-Process -ProcessName "NotExist" -OnError "SilentlyContinue" # No error
Function Documentation {#documentation}
Using Comment-Based Help
<#
.SYNOPSIS
Gets the status of a Windows service.
.DESCRIPTION
Retrieves detailed information about a Windows service including
its current status, startup type, and display name.
.PARAMETER ServiceName
The name of the service to retrieve. Mandatory parameter.
.PARAMETER ComputerName
The name of the computer to query. Defaults to localhost.
.EXAMPLE
Get-ServiceStatus -ServiceName "BITS"
.EXAMPLE
Get-ServiceStatus -ServiceName "wuauserv" -ComputerName "Server1"
.NOTES
Requires administrator privileges to run.
.LINK
https://activedirectorytools.net/powershell-functions
#>
function Get-ServiceStatus {
param(
[Parameter(Mandatory=$true, HelpMessage="Enter service name")]
[string]$ServiceName,
[string]$ComputerName = "localhost"
)
Get-Service -Name $ServiceName -ComputerName $ComputerName
}
# View help
Get-Help Get-ServiceStatus -Full
Performance Considerations {#performance}
Efficient Parameter Handling
# SLOW: Calling function multiple times in pipeline
1..10000 | ForEach-Object { Get-Service -Name BITS }
# BETTER: Call once, process results
$service = Get-Service -Name BITS
1..10000 | ForEach-Object { $service }
# Function to optimize
function Test-ServiceHealth {
param([string[]]$ServiceNames)
foreach ($name in $ServiceNames) {
Get-Service -Name $name
}
}
# Usage
Test-ServiceHealth -ServiceNames "BITS", "wuauserv", "WinDefend"
Benchmarking Functions
function Measure-FunctionPerformance {
param(
[scriptblock]$ScriptBlock,
[int]$Iterations = 1000
)
$measure = Measure-Command {
for ($i = 0; $i -lt $Iterations; $i++) {
& $ScriptBlock
}
}
Write-Output "Total time: $($measure.TotalMilliseconds) ms"
Write-Output "Per iteration: $($measure.TotalMilliseconds / $Iterations) ms"
}
# Test a function's performance
Measure-FunctionPerformance -ScriptBlock { Get-Process } -Iterations 100
Common Mistakes {#common-mistakes}
❌ Mistake 1: Not Validating Parameters
# WRONG: No validation
function Divide {
param([int]$A, [int]$B)
return $A / $B
}
Divide -A 10 -B 0 # Runtime error: DivideByZeroException
# RIGHT: Add validation
function Divide {
param(
[int]$A,
[ValidateScript({ $_ -ne 0 })]
[int]$B
)
return $A / $B
}
❌ Mistake 2: Mixing Output with Write-Host
# WRONG: Can't capture output
function Get-Info {
Write-Host "Processing..." # Not capturable
return "Result"
}
# RIGHT: Use Write-Output for pipeline
function Get-Info {
Write-Verbose "Processing..." # For logging, use -Verbose
return "Result"
}
$result = Get-Info -Verbose
❌ Mistake 3: Not Using Pipeline Parameters
# WRONG: Requires explicit parameter
function Show-Names {
param([string[]]$Names)
foreach ($name in $Names) { Write-Output $name }
}
$names | Show-Names -Names $_ # Awkward
# RIGHT: Use ValueFromPipeline
function Show-Names {
param([Parameter(ValueFromPipeline=$true)][string[]]$Names)
process { foreach ($name in $Names) { Write-Output $name } }
}
$names | Show-Names # Natural pipeline
❌ Mistake 4: Global Variable Pollution
# WRONG: Creates/modifies global variable
function Process-Data {
$globalResults = @() # Creates global var implicitly
$globalResults += "Item"
}
# RIGHT: Use local variables and return
function Process-Data {
[PSCustomObject[]]$localResults = @()
$localResults += "Item"
return $localResults
}
$results = Process-Data
Best Practices {#best-practices}
✅ Function Naming:
- Use Verb-Noun convention:
Get-,Set-,Remove-,New-, etc. - Be descriptive and consistent
- Avoid aliases in function names
✅ Parameters:
- Define mandatory parameters explicitly
- Provide default values where sensible
- Add parameter validation attributes
- Document each parameter
✅ Error Handling:
- Use try-catch for critical operations
- Provide meaningful error messages
- Use Write-Error with Stop action
✅ Code Organization:
- Keep functions focused and single-responsibility
- Use comment blocks for documentation
- Use regions for function groups (in larger scripts)
✅ Performance:
- Avoid unnecessary nested loops
- Cache results when called multiple times
- Use -ErrorAction SilentlyContinue judiciously
✅ Testing:
- Test with edge cases (null, empty, very large)
- Use Pester framework for unit testing
- Document expected inputs and outputs
Troubleshooting {#troubleshooting}
Issue: “The term ‘function-name’ is not recognized”
- Cause: Function defined in different scope or not loaded
- Solution: Ensure function is defined before calling, source script properly
Issue: Parameters not accepting values
- Cause: Type mismatch or validation failure
- Solution: Check parameter type and validation rules
Issue: Function returns multiple values unexpectedly
- Cause: Multiple Write-Output statements
- Solution: Collect results in array, return once at end
Issue: “Cannot find a parameter that matches”
- Cause: Typo in parameter name or wrong parameter type
- Solution: Use Get-Help function-name -Full to verify parameter names
FAQs {#faqs}
Q: What’s the difference between function and cmdlet?
A: Functions are PowerShell scripts, cmdlets are .NET compiled objects. Functions are easier to write; cmdlets are more performant.
Q: Can I create functions in the console?
A: Yes, type in the console or ISE, but they’re lost when you close. Save in .ps1 files for reuse.
Q: How do I make a function available in all sessions?
A: Add it to your PowerShell profile ($PROFILE)
Q: What’s the maximum number of parameters?
A: No hard limit, but keep functions focused—if you have 10+ parameters, consider object parameter.
Q: How do I require administrator privileges in a function?
A: Add validation: [System.Management.Automation.PSObject[]]$PSBoundParameters | Get-ADUser
Q: Can functions recursively call themselves?
A: Yes, PowerShell supports recursion. Be careful with infinite loops.
Real-World Examples {#real-world-examples}
Example 1: AD User Management
function New-ADUserBatch {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
[string[]]$UserList,
[string]$OUPath = "OU=Users,DC=contoso,DC=com",
[string]$Department = "IT"
)
process {
foreach ($username in $UserList) {
try {
$newUser = New-ADUser -Name $username -Path $OUPath `
-AccountPassword (ConvertTo-SecureString -String "TempPassword123!" -AsPlainText -Force) `
-Department $Department -Enabled $true -ErrorAction Stop
Write-Output "Created: $username"
} catch {
Write-Error "Failed to create $username: $_"
}
}
}
}
# Usage
"user1", "user2", "user3" | New-ADUserBatch -Department "Engineering"
Example 2: Server Health Check
function Test-ServerHealth {
param(
[Parameter(Mandatory=$true)]
[string[]]$ComputerNames
)
$results = @()
foreach ($computer in $ComputerNames) {
$uptime = try {
int - (Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName $computer).InstallDate).TotalDays
} catch { "N/A" }
$results += [PSCustomObject]@{
ComputerName = $computer
Status = if (Test-Connection -ComputerName $computer -Count 1 -Quiet) { "Online" } else { "Offline" }
Uptime = $uptime
Timestamp = Get-Date
}
}
return $results
}
Test-ServerHealth -ComputerNames "Server1", "Server2", "Server3" | Format-Table
Conclusion
PowerShell functions are fundamental to writing clean, maintainable, professional scripts. Mastering function creation—from basic syntax to advanced parameter handling—enables you to build robust automation solutions.
Key Takeaways:
- Functions improve code reusability and maintainability
- Use proper parameter validation and error handling
- Follow Verb-Noun naming conventions
- Document functions with comment-based help
- Return structured objects for pipeline compatibility
- Test functions with edge cases
For more PowerShell scripting patterns, explore the Complete PowerShell Guide and learn about error handling, arrays, and loops.
Related Articles
Core Function Concepts
- Complete PowerShell Guide - Comprehensive PowerShell reference with functions section
- PowerShell Complete Guide - Full PowerShell tutorial including advanced functions
Parameter Handling & Validation
- PowerShell Variables - Master variable handling in functions
- PowerShell Hashtables - Use hashtables as parameter values
- PowerShell Arrays - Pass array parameters to functions
Control Flow & Logic
- PowerShell If-Else Statements - Conditional logic within functions
- PowerShell Switch Statement - Switch-based function logic
- PowerShell For Loops - Loop constructs in functions
- PowerShell While Loops - Conditional looping in functions
Data Processing & Output
- PowerShell Where-Object - Filter data in function pipelines
- PowerShell Select-Object - Transform output objects
- PowerShell ForEach-Object - Iterate collections in functions
- PowerShell Strings - String manipulation in functions
- PowerShell Output to File - Write function results to files
- PowerShell Export CSV - Export function results to CSV
Error Handling & Robustness
- PowerShell Try-Catch - Exception handling in functions
- PowerShell Error Handling - Comprehensive error management
- PowerShell Get-ChildItem Filter - Filtering with error handling
File Operations in Functions
- PowerShell Delete Files - File deletion functions
- PowerShell Rename Files - Batch rename operations
- PowerShell List Files in Directory - File listing functions
- PowerShell Get-Content - Read file contents in functions
- PowerShell File Properties - Access file metadata
- PowerShell Get Folder Size - Folder analysis functions
Data Conversion & Formatting
- PowerShell Data Types - Type handling in function parameters
- PowerShell Format Table - Format function output
- PowerShell DateTime Format - Date handling in functions
Active Directory & System Integration
- PowerShell AD User Management - AD functions with Get-ADUser
- PowerShell AD Groups - Work with AD groups
- PowerShell Get-Process - Process management functions
- PowerShell Get CPU Usage - System monitoring functions
- PowerShell Get Memory Usage - Memory analysis functions
Advanced Function Patterns
- PowerShell Recursion - Recursive function patterns
- PowerShell Scope - Variable and function scoping
- PowerShell Lambda Functions - Anonymous function expressions
- PowerShell Closures - Function closures and state
Performance & Optimization
- PowerShell Performance Tuning - Optimize function performance
- PowerShell Benchmarking - Measure function speed
Comprehensive Guides
- PowerShell Tutorial Complete - Full PowerShell course
- Active Directory PowerShell - AD automation with functions