Functions and Scripts
Learn to write PowerShell functions with parameters, create reusable scripts, understand scopes, and build professional-grade automation tools.
📖 6 min read📅 2026-02-10Scripting
Basic Functions
Functions let you package reusable code:
# Simple function
function Say-Hello {
Write-Host "Hello, World!"
}
# Call the function
Say-Hello
# Function with parameters
function Say-HelloTo {
param(
[string]$Name
)
Write-Host "Hello, $Name!"
}
Say-HelloTo -Name "Alice"Function Parameters
Parameter Block
function Get-Greeting {
param(
[string]$Name = "World",
[string]$Greeting = "Hello"
)
return "$Greeting, $Name!"
}
Get-Greeting # "Hello, World!"
Get-Greeting -Name "Alice" # "Hello, Alice!"
Get-Greeting -Name "Bob" -Greeting "Hi" # "Hi, Bob!"Mandatory Parameters
function New-User {
param(
[Parameter(Mandatory = $true)]
[string]$Username,
[Parameter(Mandatory = $true)]
[string]$Email,
[string]$Role = "User"
)
Write-Host "Creating user: $Username ($Email) with role: $Role"
}
# Will prompt for Username and Email if not provided
New-User -Username "john" -Email "[email protected]"Parameter Validation
function Set-ServerConfig {
param(
# Must not be empty
[ValidateNotNullOrEmpty()]
[string]$ServerName,
# Must be one of these values
[ValidateSet("Development", "Staging", "Production")]
[string]$Environment,
# Must be in range
[ValidateRange(1, 65535)]
[int]$Port = 8080,
# Must match pattern
[ValidatePattern("^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")]
[string]$IPAddress,
# Must pass custom validation
[ValidateScript({ Test-Path $_ })]
[string]$ConfigPath,
# Must have minimum length
[ValidateLength(3, 50)]
[string]$Description
)
Write-Host "Configuring $ServerName ($Environment) on ${IPAddress}:$Port"
}Switch Parameters
function Get-FileInfo {
param(
[string]$Path,
[switch]$IncludeHidden,
[switch]$Recurse
)
$params = @{ Path = $Path }
if ($IncludeHidden) { $params.Force = $true }
if ($Recurse) { $params.Recurse = $true }
Get-ChildItem @params
}
Get-FileInfo -Path "C:\Users" -Recurse -IncludeHiddenPipeline Input
function Get-FileSize {
param(
[Parameter(ValueFromPipeline = $true)]
[System.IO.FileInfo]$File
)
process {
[PSCustomObject]@{
Name = $File.Name
SizeMB = [math]::Round($File.Length / 1MB, 2)
}
}
}
# Use in pipeline
Get-ChildItem *.txt | Get-FileSizeReturn Values
# Return keyword
function Add-Numbers {
param([int]$A, [int]$B)
return $A + $B
}
$sum = Add-Numbers -A 5 -B 3 # $sum = 8
# Multiple return values (all output goes to pipeline)
function Get-ServerStatus {
param([string]$Server)
# CAREFUL: Everything that produces output is "returned"
[PSCustomObject]@{
Server = $Server
Status = "Online"
CPU = 45.2
Memory = 72.1
}
}
$status = Get-ServerStatus -Server "web01"
$status.CPU # 45.2Important: In PowerShell, ALL output in a function is returned, not just what follows
return. UseWrite-Hostfor display-only output or pipe toOut-Nullto suppress unwanted output.
Advanced Functions
CmdletBinding
Turn your function into a proper cmdlet with standard parameters:
function Get-SystemReport {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true, Position = 0)]
[string]$ComputerName,
[Parameter()]
[ValidateSet("Brief", "Detailed")]
[string]$ReportType = "Brief"
)
begin {
Write-Verbose "Starting system report for $ComputerName"
}
process {
$os = Get-CimInstance -ClassName Win32_OperatingSystem
$cpu = Get-CimInstance -ClassName Win32_Processor
[PSCustomObject]@{
Computer = $ComputerName
OS = $os.Caption
Memory = "$([math]::Round($os.TotalVisibleMemorySize / 1MB, 1)) GB"
CPU = $cpu.Name
ReportType = $ReportType
}
}
end {
Write-Verbose "Report complete"
}
}
# Now supports -Verbose, -Debug, -ErrorAction, etc.
Get-SystemReport -ComputerName "localhost" -VerboseBegin/Process/End Blocks
function ConvertTo-MB {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline = $true)]
[long]$Bytes
)
begin {
Write-Verbose "Starting conversion"
$count = 0
}
process {
$count++
[math]::Round($Bytes / 1MB, 2)
}
end {
Write-Verbose "Converted $count values"
}
}
# Each pipeline object hits the process block
1MB, 5MB, 100MB | ConvertTo-MBPowerShell Scripts (.ps1)
Creating Your First Script
Save this as Get-DiskReport.ps1:
#Requires -Version 5.1
<#
.SYNOPSIS
Generates a disk space report.
.DESCRIPTION
This script retrieves disk space information for all local drives
and generates a formatted report.
.PARAMETER ComputerName
The computer to check. Defaults to localhost.
.PARAMETER MinFreeGB
Minimum free space threshold in GB. Drives below this are flagged.
.EXAMPLE
.\Get-DiskReport.ps1
Gets disk report for the local computer.
.EXAMPLE
.\Get-DiskReport.ps1 -MinFreeGB 50
Gets disk report and flags drives with less than 50GB free.
#>
param(
[string]$ComputerName = $env:COMPUTERNAME,
[double]$MinFreeGB = 10
)
Write-Host "`nDisk Space Report for $ComputerName" -ForegroundColor Cyan
Write-Host ("=" * 50)
Get-PSDrive -PSProvider FileSystem | ForEach-Object {
$usedGB = [math]::Round($_.Used / 1GB, 2)
$freeGB = [math]::Round($_.Free / 1GB, 2)
$totalGB = $usedGB + $freeGB
if ($totalGB -gt 0) {
$percentUsed = [math]::Round(($usedGB / $totalGB) * 100, 1)
$status = if ($freeGB -lt $MinFreeGB) { "⚠️ LOW" } else { "✅ OK" }
[PSCustomObject]@{
Drive = "$($_.Name):"
"Total(GB)" = $totalGB
"Used(GB)" = $usedGB
"Free(GB)" = $freeGB
"Used%" = "$percentUsed%"
Status = $status
}
}
} | Format-Table -AutoSizeRunning Scripts
# Run a script
.\Get-DiskReport.ps1
# Run with parameters
.\Get-DiskReport.ps1 -MinFreeGB 50
# Execution Policy (first time setup)
# Check current policy
Get-ExecutionPolicy
# Allow local scripts to run
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUserScript Organization Best Practices
# 1. Always use comment-based help
# 2. Use #Requires for prerequisites
# 3. Declare parameters at the top
# 4. Use meaningful variable names
# 5. Handle errors with try/catch
# 6. Use Write-Verbose for debug info
# 7. Return objects, not formatted text
# Good script structure:
#Requires -Version 7.0
#Requires -Modules ActiveDirectory
<# .SYNOPSIS ... #>
[CmdletBinding()]
param(
# Parameters here
)
# Constants and configuration
$script:LogPath = Join-Path $PSScriptRoot "logs"
# Functions
function Initialize-Script { ... }
function Get-Data { ... }
function Export-Results { ... }
# Main execution
try {
Initialize-Script
$data = Get-Data
Export-Results -Data $data
Write-Host "Complete!" -ForegroundColor Green
}
catch {
Write-Error "Script failed: $_"
exit 1
}Scope
$globalVar = "I'm global"
function Test-Scope {
$localVar = "I'm local"
Write-Host "Inside function:"
Write-Host " Global: $globalVar" # Accessible
Write-Host " Local: $localVar" # Accessible
# Modify parent scope
$script:scriptVar = "Set from function"
}
Test-Scope
Write-Host "Outside function:"
Write-Host " Global: $globalVar" # Accessible
# Write-Host " Local: $localVar" # NOT accessible
Write-Host " Script: $scriptVar" # Accessible (set with $script:)Exercises
- Write a function
Get-RandomPasswordthat generates a random password of specified length - Create a script that takes a folder path and generates a file inventory (name, size, last modified)
- Write an advanced function with pipeline support that converts temperatures (C to F and back)
- Build a function with parameter validation that creates a project folder structure
- Create a script with comment-based help that monitors a log file for specific keywords
Next: Error Handling — learn to write robust, error-resilient scripts!