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 "john@example.com"

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 -IncludeHidden

Pipeline 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-FileSize

Return 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.2

Important: In PowerShell, ALL output in a function is returned, not just what follows return. Use Write-Host for display-only output or pipe to Out-Null to 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" -Verbose

Begin/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-MB

PowerShell 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 -AutoSize

Running 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 CurrentUser

Script 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

  1. Write a function Get-RandomPassword that generates a random password of specified length
  2. Create a script that takes a folder path and generates a file inventory (name, size, last modified)
  3. Write an advanced function with pipeline support that converts temperatures (C to F and back)
  4. Build a function with parameter validation that creates a project folder structure
  5. 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!