Skip to main content

PowerShell Functions: Complete Guide with Parameters and Examples [2024]

7 min read
powershell functions scripting parameters reusable code function parameters

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

  1. What are PowerShell Functions?
  2. Why Use Functions?
  3. Basic Function Syntax
  4. Simple Functions
  5. Function Parameters
  6. Mandatory vs Optional Parameters
  7. Default Parameter Values
  8. Parameter Validation
  9. Pipeline Input
  10. Advanced Functions
  11. Return Values
  12. Variable Scope
  13. Error Handling
  14. Function Documentation
  15. Performance Considerations
  16. Common Mistakes
  17. Best Practices
  18. Troubleshooting
  19. FAQs
  20. Real-World Examples
  21. 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

TypeDescriptionExample
[string]Text value"Hello"
[int]Whole number42
[double]Decimal number3.14
[boolean]True/False$true
[array]Collection@(1, 2, 3)
[PSObject]PowerShell objectCustom 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.


Core Function Concepts

  • Complete PowerShell Guide - Comprehensive PowerShell reference with functions section
  • PowerShell Complete Guide - Full PowerShell tutorial including advanced functions

Parameter Handling & Validation

Control Flow & Logic

Data Processing & Output

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

Data Conversion & Formatting

Active Directory & System Integration

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