By now, you’ve written linear scripts with logic and variables. But eventually, you’ll want to reuse code, reduce repetition, and make your scripts easier to test and maintain. That’s where functions come in.

This post shows you how to write clean, reusable functions using PowerShell best practices, with parameter validation, output handling, and real-world context.


Why Functions Matter

Functions let you encapsulate logic into a named block that:

  • Can be reused multiple times
  • Accepts input (parameters)
  • Returns output (via Write-Output)
  • Keeps your script clean and testable

Think of functions as power tools for scripting: they do one job, and do it well.


Basic Function Syntax

Here’s a simple example:

function Get-Greeting {
    param (
        [string]$Name
    )

    Write-Output "Hello, $Name!"
}

Call it like this:

Get-Greeting -Name 'Menno'

Use [Parameter()] for Clarity

Decorating your parameters helps PowerShell understand how to parse them—and helps you enforce clean usage.

function Restart-ServiceSafe {
    param (
        [Parameter(Mandatory)]
        [string]$ServiceName
    )

    $service = Get-Service -Name $ServiceName -ErrorAction Stop
    if ($service.Status -eq 'Running') {
        Restart-Service -Name $ServiceName -Force
        Write-Output "$ServiceName restarted successfully."
    }
    else {
        Write-Output "$ServiceName is not running. No restart needed."
    }
}

Use -ErrorAction Stop to trap errors, especially inside functions.


Input Validation = Fewer Headaches

Use attributes like ValidateSet, ValidatePattern, and ValidateNotNullOrEmpty:

function Set-LogLevel {
    param (
        [Parameter(Mandatory)]
        [ValidateSet('Info', 'Warning', 'Error')]
        [string]$Level
    )

    Write-Output "Log level set to: $Level"
}

This ensures the function only accepts known-good input—no surprises, no garbage data.


Return Values: Write-Output or Explicit return

PowerShell returns anything not captured or redirected. You can use return, but prefer Write-Output to make behavior consistent:

function Get-ComputerUptime {
    $uptime = (Get-CimInstance Win32_OperatingSystem).LastBootUpTime
    Write-Output $uptime
}

⚠️ Avoid using Write-Host to return values—it just writes to the screen and vanishes.


Real Example: Create an Azure Resource Group

This shows everything so far in practice:

function New-AzResourceGroupSafe {
    param (
        [Parameter(Mandatory)]
        [string]$ResourceGroupName,

        [Parameter(Mandatory)]
        [string]$Location
    )

    if (-not (Get-AzResourceGroup -Name $ResourceGroupName -ErrorAction SilentlyContinue)) {
        New-AzResourceGroup -Name $ResourceGroupName -Location $Location | Out-Null
        Write-Output "Resource group '$ResourceGroupName' created in $Location."
    }
    else {
        Write-Output "Resource group '$ResourceGroupName' already exists."
    }
}

Usage:

New-AzResourceGroupSafe -ResourceGroupName 'rg-demo' -Location 'westeurope'

Summary

You now know how to:

  • Write functions with parameters and validation
  • Return data cleanly with Write-Output
  • Reuse logic for maintainable scripts

Functions are where PowerShell scripting stops being “glue” and becomes “code.” Embrace them.


Next Up

In Part 4, we’ll tackle error handling and logging. The stuff that turns fragile scripts into production-ready tools.

Until then: Write small. Write reusable. Write functions.