Functions & Script Files (.ps1)
PowerShell Fundamentals
Chapter 7 · Functions & Script Files (.ps1)
Everything so far has run one line, or one pipeline, at a time in an interactive session. This chapter is where PowerShell starts looking like a real programming language rather than a shell: packaging logic into reusable functions, giving them real typed parameters, and saving them into .ps1 script files — plus one rule that catches almost everyone coming from a C-family language off guard, because it means return doesn't work quite the way you'd expect.
Basic Function Syntax
Your own functions aren't required to follow Chapter 3's Verb-Noun convention, but it's worth adopting anyway — it's the difference between a function that reads clearly next to every built-in cmdlet, and one that stands out as obviously home-grown. Once functions start shipping inside a real module (Chapter 11), PowerShell will actually warn you if a function name uses an unapproved verb.
Parameters: Types, Defaults & Mandatory
[Parameter(Mandatory)] doesn't throw immediately if you omit the value — it interactively prompts for it, which is a genuinely different failure mode from most languages simply erroring on a missing argument.
$args: Every Extra Positional Argument, With No param() at All
A function with no param() block isn't a function that takes no arguments — every positional argument you pass still lands somewhere, in the $args automatic variable flagged back in Chapter 5:
Return Values — The Single Biggest Habit to Unlearn
In most languages, only what follows an explicit return comes back from a function — everything else is just "stuff that ran." PowerShell doesn't work that way:
return. Write-Output "about to double $n" was meant as a progress message, but because it was never captured, assigned, or redirected, it became a real, permanent part of what Get-Doubled actually returned. This is exactly the same object-pipeline behavior Chapter 1 built the whole course around, applied to a function body instead of a command line: anything that reaches the end of a statement uncaptured flows onward, whether that "onward" is the next pipeline stage or the function's own caller.
Write-Host writes straight to the console and never enters the output stream at all — unlike Write-Output (or a bare, uncaptured expression, which behaves identically to Write-Output), it can never accidentally become part of what a function returns. Reach for Write-Host specifically for progress/status messages a human should see but a caller should never receive as data. (Write-Verbose, suppressed by default unless -Verbose is passed, is another safe option — covered properly alongside error handling in Chapter 9.)
return: Exits Early, Optionally With One More Value
return's real job is stopping execution at that point — the value after it is just one more thing added to the output stream, exactly like any other uncaptured expression. It doesn't erase or override anything that was already output earlier in the function.
Script Files: Running vs. Dot-Sourcing
Save a function into a .ps1 file, and how you invoke that file changes whether its contents are actually usable afterward:
Dot-sourcing is how you'd load a personal library of reusable functions into your current session — Chapter 11's module system is the properly packaged, shareable version of the exact same idea.
.Count the way this chapter's $result.Count example did. If it's more than 1 and you only expected one value back, an uncaptured Write-Output call (or a bare expression) is almost always the cause.
Hands-On Exercises
A function does Write-Output "Starting..." and then return 42. Explain exactly what $x = MyFunc assigns to $x — is it just 42? Use this chapter's own Get-Doubled example to support your answer.
Explain the difference between running .\script.ps1 normally and dot-sourcing it with . .\script.ps1. Specifically, why is a function defined inside the script only callable afterward in the dot-sourced case?
Write a function Get-Square that takes a mandatory [int] parameter and returns its square, with zero extra output polluting the return value. Explain how you'd actually verify — not just assume — that your function returns exactly one clean value.
Chapter 7 Quick Reference
function Verb-Noun { param(...) ... }— basic function shape; Verb-Noun is a convention here, not enforced[Parameter(Mandatory)]— prompts interactively for a missing value, rather than erroring immediately$args— captures every positional argument when there's noparam()block at all- Every uncaptured line joins the return value — not just what follows
return; this is the single biggest habit to unlearn Write-Host— console-only, never enters the output stream, safe for status messagesreturn— exits early; the value after it is just one more thing added to the output, not a replacement for earlier output.\script.ps1— runs in a child scope; functions/variables don't persist afterward. .\script.ps1(dot-sourcing) — runs in the current scope; functions/variables persist in your session- Next chapter: Working with Files, Text & Formatting Output