Understanding PowerShell Functions
Functions are reusable blocks of code that perform specific tasks in PowerShell. They help you organize code, reduce repetition, and create maintainable scripts. A well-designed function accepts inputs, processes them, and returns results while managing variable scope appropriately.
PowerShell functions follow a structured syntax that includes the function keyword, a name, parameters, and a script block containing the function’s logic.
Basic Function Syntax
The simplest function in PowerShell contains just a name and a script block:
function Get-Greeting {
"Hello, World!"
}
# Call the function
Get-Greeting
Output:
Hello, World!
PowerShell convention recommends using Verb-Noun naming for functions, following approved verbs from Get-Verb. This makes your functions consistent with native cmdlets.
Using the Param Block
The param block defines input parameters for your function. It must be the first executable statement inside the function body.
Simple Parameters
function Get-PersonInfo {
param(
$FirstName,
$LastName,
$Age
)
"Name: $FirstName $LastName"
"Age: $Age"
}
# Call with parameters
Get-PersonInfo -FirstName "John" -LastName "Doe" -Age 30
Output:
Name: John Doe
Age: 30
Typed Parameters
Adding type constraints ensures parameters receive the correct data type and provides automatic validation:
function Add-Numbers {
param(
[int]$Number1,
[int]$Number2
)
$Number1 + $Number2
}
# Valid call
Add-Numbers -Number1 10 -Number2 20
# This would throw an error
# Add-Numbers -Number1 "abc" -Number2 20
Output:
30
Mandatory Parameters
Use the [Parameter(Mandatory=$true)] attribute to require specific parameters:
function Get-FileSize {
param(
[Parameter(Mandatory=$true)]
[string]$Path
)
if (Test-Path $Path) {
$file = Get-Item $Path
"File size: $($file.Length) bytes"
} else {
"File not found: $Path"
}
}
# PowerShell will prompt for Path if not provided
Get-FileSize -Path "C:\Windows\System32\notepad.exe"
Default Parameter Values
Assign default values to make parameters optional:
function New-LogEntry {
param(
[string]$Message,
[string]$Level = "INFO",
[string]$Timestamp = (Get-Date -Format "yyyy-MM-dd HH:mm:ss")
)
"[$Timestamp] [$Level] $Message"
}
# Using defaults
New-LogEntry -Message "Application started"
# Overriding defaults
New-LogEntry -Message "Critical error occurred" -Level "ERROR"
Output:
[2025-10-22 15:01:30] [INFO] Application started
[2025-10-22 15:01:30] [ERROR] Critical error occurred
Parameter Validation
PowerShell provides validation attributes to ensure parameters meet specific criteria:
function Set-ServerPort {
param(
[Parameter(Mandatory=$true)]
[ValidateRange(1024, 65535)]
[int]$Port,
[ValidateSet("HTTP", "HTTPS", "FTP")]
[string]$Protocol = "HTTP",
[ValidateNotNullOrEmpty()]
[string]$ServerName
)
"Configuring $ServerName on port $Port for $Protocol"
}
# Valid call
Set-ServerPort -Port 8080 -Protocol "HTTPS" -ServerName "webserver01"
# Invalid - port out of range
# Set-ServerPort -Port 80 -ServerName "webserver01"
Output:
Configuring webserver01 on port 8080 for HTTPS
Return Values and Output
PowerShell functions can return values in multiple ways, each with different behaviors and use cases.
Pipeline Output
Any unassigned output in a function automatically goes to the pipeline:
function Get-MultipleValues {
"First line"
"Second line"
"Third line"
}
$results = Get-MultipleValues
$results
Output:
First line
Second line
Third line
Using Return Statement
The return keyword exits the function immediately and sends a value to the pipeline:
function Test-NumberRange {
param([int]$Number)
if ($Number -lt 0) {
return "Negative"
}
if ($Number -eq 0) {
return "Zero"
}
return "Positive"
}
Test-NumberRange -Number -5
Test-NumberRange -Number 0
Test-NumberRange -Number 42
Output:
Negative
Zero
Positive
Returning Objects
Functions can return custom objects with multiple properties:
function Get-SystemInfo {
[PSCustomObject]@{
ComputerName = $env:COMPUTERNAME
OSVersion = [System.Environment]::OSVersion.Version
ProcessorCount = $env:NUMBER_OF_PROCESSORS
UserName = $env:USERNAME
}
}
$info = Get-SystemInfo
$info | Format-List
Output:
ComputerName : DESKTOP-ABC123
OSVersion : 10.0.19045.0
ProcessorCount : 8
UserName : JohnDoe
Controlling Output with Write Cmdlets
Different Write cmdlets send output to specific streams:
function Invoke-ProcessWithLogging {
param([string]$ProcessName)
Write-Verbose "Starting process: $ProcessName" -Verbose
Write-Host "Processing..." -ForegroundColor Cyan
$result = [PSCustomObject]@{
ProcessName = $ProcessName
Status = "Completed"
Timestamp = Get-Date
}
Write-Verbose "Process completed successfully" -Verbose
# Only this goes to the pipeline
$result
}
$output = Invoke-ProcessWithLogging -ProcessName "DataSync"
$output
Variable Scope in Functions
PowerShell uses different scopes to determine variable visibility and lifetime. Understanding scope is crucial for writing predictable functions.
Local Scope
Variables created inside a function are local by default and don’t affect outside variables:
$globalVar = "I'm global"
function Test-LocalScope {
$localVar = "I'm local"
$globalVar = "I'm modified locally"
"Inside function - localVar: $localVar"
"Inside function - globalVar: $globalVar"
}
Test-LocalScope
"Outside function - globalVar: $globalVar"
# This would error - $localVar doesn't exist here
# "Outside function - localVar: $localVar"
Output:
Inside function - localVar: I'm local
Inside function - globalVar: I'm modified locally
Outside function - globalVar: I'm global
Script Scope
The $script: modifier accesses variables at the script level:
$script:counter = 0
function Increment-Counter {
$script:counter++
"Counter is now: $script:counter"
}
Increment-Counter
Increment-Counter
Increment-Counter
"Final counter value: $script:counter"
Output:
Counter is now: 1
Counter is now: 2
Counter is now: 3
Final counter value: 3
Global Scope
The $global: modifier creates or modifies variables accessible everywhere:
function Set-GlobalConfig {
param([string]$ConfigValue)
$global:AppConfig = $ConfigValue
"Global configuration set"
}
Set-GlobalConfig -ConfigValue "Production"
# Accessible from anywhere
"Config value: $global:AppConfig"
Using Scope Modifiers
$global:environment = "Development"
$script:version = "1.0"
function Show-ScopeExample {
$local:temp = "temporary"
"Local variable: $temp"
"Script variable: $script:version"
"Global variable: $global:environment"
# Modify parent scope
$script:version = "1.1"
}
Show-ScopeExample
"Version after function: $script:version"
Output:
Local variable: temporary
Script variable: 1.0
Global variable: Development
Version after function: 1.1
Advanced Function Features
CmdletBinding Attribute
Adding [CmdletBinding()] transforms your function into an advanced function with additional capabilities:
function Get-ProcessMemory {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$true)]
[string]$ProcessName = "*"
)
begin {
Write-Verbose "Starting process memory check"
}
process {
Get-Process -Name $ProcessName -ErrorAction SilentlyContinue |
Select-Object Name,
@{Name='MemoryMB';Expression={[math]::Round($_.WorkingSet / 1MB, 2)}}
}
end {
Write-Verbose "Process memory check completed"
}
}
Get-ProcessMemory -ProcessName "powershell" -Verbose
Parameter Sets
Parameter sets allow different combinations of parameters:
function Get-UserData {
[CmdletBinding(DefaultParameterSetName='ByName')]
param(
[Parameter(ParameterSetName='ByName', Mandatory=$true)]
[string]$Name,
[Parameter(ParameterSetName='ById', Mandatory=$true)]
[int]$Id,
[Parameter(ParameterSetName='All')]
[switch]$All
)
switch ($PSCmdlet.ParameterSetName) {
'ByName' { "Retrieving user: $Name" }
'ById' { "Retrieving user ID: $Id" }
'All' { "Retrieving all users" }
}
}
Get-UserData -Name "JohnDoe"
Get-UserData -Id 12345
Get-UserData -All
Output:
Retrieving user: JohnDoe
Retrieving user ID: 12345
Retrieving all users
Pipeline Input
Functions can accept input from the pipeline using ValueFromPipeline:
function Convert-ToUpperCase {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$true, Mandatory=$true)]
[string]$InputString
)
process {
$InputString.ToUpper()
}
}
"hello", "world", "powershell" | Convert-ToUpperCase
Output:
HELLO
WORLD
POWERSHELL
Best Practices
Function Naming
Follow PowerShell naming conventions with approved verbs and singular nouns. Use Get-Verb to see approved verbs.
# Good
function Get-ServerStatus { }
function Set-Configuration { }
function Test-Connection { }
# Avoid
function ServerStatus { }
function ConfigSet { }
function CheckConnection { }
Comment-Based Help
Add help documentation directly in your functions:
function Get-DiskSpace {
<#
.SYNOPSIS
Retrieves disk space information for specified drives.
.DESCRIPTION
This function queries the file system and returns available
and used disk space for one or more drives.
.PARAMETER DriveLetter
The drive letter to check (without colon). Defaults to C.
.EXAMPLE
Get-DiskSpace -DriveLetter "C"
Gets disk space for C: drive.
.EXAMPLE
"C", "D", "E" | Get-DiskSpace
Gets disk space for multiple drives via pipeline.
#>
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$true)]
[string]$DriveLetter = "C"
)
process {
$drive = Get-PSDrive -Name $DriveLetter -PSProvider FileSystem
[PSCustomObject]@{
Drive = $DriveLetter
UsedGB = [math]::Round($drive.Used / 1GB, 2)
FreeGB = [math]::Round($drive.Free / 1GB, 2)
TotalGB = [math]::Round(($drive.Used + $drive.Free) / 1GB, 2)
}
}
}
# View help
Get-Help Get-DiskSpace -Full
Error Handling
Implement proper error handling with try-catch blocks:
function Get-FileContent {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$Path
)
try {
if (-not (Test-Path $Path)) {
throw "File not found: $Path"
}
Get-Content -Path $Path -ErrorAction Stop
}
catch {
Write-Error "Failed to read file: $_"
return $null
}
}
Output Type Declaration
Declare the output type for better IntelliSense and validation:
function Get-ComputerDetails {
[CmdletBinding()]
[OutputType([PSCustomObject])]
param()
[PSCustomObject]@{
Name = $env:COMPUTERNAME
Domain = $env:USERDOMAIN
OS = [System.Environment]::OSVersion.VersionString
PowerShellVersion = $PSVersionTable.PSVersion.ToString()
}
}
Practical Examples
File Processing Function
function Process-LogFiles {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$Path,
[Parameter()]
[string]$Pattern = "ERROR",
[Parameter()]
[int]$MaxResults = 100
)
begin {
$results = @()
Write-Verbose "Searching for pattern: $Pattern"
}
process {
try {
$files = Get-ChildItem -Path $Path -Filter "*.log" -File
foreach ($file in $files) {
$matches = Select-String -Path $file.FullName -Pattern $Pattern
foreach ($match in $matches) {
$results += [PSCustomObject]@{
FileName = $file.Name
LineNumber = $match.LineNumber
Content = $match.Line.Trim()
}
if ($results.Count -ge $MaxResults) {
break
}
}
}
}
catch {
Write-Error "Error processing logs: $_"
}
}
end {
Write-Verbose "Found $($results.Count) matches"
$results
}
}
REST API Wrapper Function
function Invoke-ApiRequest {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$Endpoint,
[Parameter()]
[ValidateSet('GET', 'POST', 'PUT', 'DELETE')]
[string]$Method = 'GET',
[Parameter()]
[hashtable]$Headers = @{},
[Parameter()]
[object]$Body
)
$script:apiCallCount++
try {
$params = @{
Uri = $Endpoint
Method = $Method
Headers = $Headers
ContentType = 'application/json'
}
if ($Body) {
$params.Body = $Body | ConvertTo-Json
}
$response = Invoke-RestMethod @params
[PSCustomObject]@{
Success = $true
Data = $response
StatusCode = 200
Timestamp = Get-Date
}
}
catch {
[PSCustomObject]@{
Success = $false
Data = $null
StatusCode = $_.Exception.Response.StatusCode.Value__
ErrorMessage = $_.Exception.Message
Timestamp = Get-Date
}
}
}
Summary
Writing effective PowerShell functions requires understanding parameters, return mechanisms, and variable scope. Use the param block with appropriate validation attributes to create robust parameter definitions. Control output carefully using return statements and pipeline behavior. Manage variable scope with local, script, and global modifiers to prevent unintended side effects.
Advanced features like [CmdletBinding()], parameter sets, and pipeline input enable you to create professional-grade functions that integrate seamlessly with PowerShell’s ecosystem. Always include comment-based help, implement error handling, and follow naming conventions to make your functions maintainable and user-friendly.
Practice these concepts by building real-world functions that solve actual problems in your environment. Start simple with basic parameters and gradually incorporate advanced features as your requirements grow.








