Organizing code into reusable functions: declaration with the Verb-Noun pattern, parameters with validation and default values, advanced functions with CmdletBinding and pipeline input, script blocks, scope coverage, and best practices for functions ready to use in real scripts.

In episode 6 you repeated processes with loops — but what if the same process is needed in many places? Copying code blocks everywhere is a recipe for disaster: one fix must be repeated in dozens of places. Episode 7 answers with functions & script blocks: packaging logic into named units that can be called, given parameters, and reused.
Imagine a kitchen recipe written on an index card. Every time you need fried rice, you don't rewrite the whole recipe — you grab the card and follow it. A function is that index card: a name that invokes a complete procedure.
A function declaration starts with the function keyword, followed by the name and a code block. The name follows the Verb-Noun pattern — a verb then an object — so the naming stays consistent like built-in cmdlets.
function Get-UptimeRingkas {
$up = (Get-Date) - (Get-CimInstance Win32_OperatingSystem).LastBootUpTime
Write-Output "$($up.Days) hari $($up.Hours) jam"
}The recommended verbs are available in the approved verb list — check with Get-Verb. New-, Get-, Set-, Remove-, Test-, Invoke-, and Convert- are common examples. Verbs outside the list are syntactically valid, but they keep your functions from being detected as "official" commands by tooling.
Calling a function is just like calling a cmdlet — name and arguments:
Get-UptimeRingkasEvery output the function produces becomes its return value. Important: everything written to the pipeline output becomes part of the result — whether explicit Write-Output or just an evaluated expression. Write-Host is different: it writes to the information stream for display, not returned.
function Get-LuasLingkaran {
param($JariJari)
[math]::PI * $JariJari * $JariJari
}
$luas = Get-LuasLingkaran -JariJari 7
Write-Host "Luas: $luas"The param block at the start of a function declares parameters. There are several kinds:
-Nama. Positional — filled by order if declared without special attributes.function New-UserEntry {
[CmdletBinding()]
param(
[Parameter(Mandatory, Position = 0)]
[ValidatePattern("^[a-z0-9_-]+$")]
[string]$Username,
[ValidateRange(1, 65535)]
[int]$Port = 8080,
[ValidateSet("aktif", "nonaktif")]
[string]$Status = "aktif"
)
Write-Output "Membuat $Username di port $Port status $Status"
}All the parameter attribute constructs above are written inside a code block. Outside of code, just remember the three most used parameter properties: Mandatory for requirements, default values for conciseness, and Validate attributes for input defense at the front door.
Tip
Parameter validation is the first line of defense. ValidateRange ensures numbers are reasonable, ValidateSet ensures a choice is one of a list, and ValidatePattern ensures a string meets a pattern. Better to reject bad input at the start than to handle it in the middle of the logic.
Adding the [CmdletBinding()] attribute turns a regular function into an advanced function — one that behaves like a real cmdlet:
[CmdletBinding()]
function Test-KoneksiServer {
param([string]$Hostname)
$ok = Test-NetConnection -ComputerName $Hostname -Port 443 -InformationLevel Quiet
Write-Output $ok
}The benefits are felt immediately: the function now supports the common parameters -Verbose, -Debug, -ErrorAction, -Confirm, and -WhatIf. That behavior comes without writing a single line — the attribute declares it.
For functions that change state, declare confirmation and simulation support:
[CmdletBinding(SupportsShouldProcess)]
function Remove-DataDir {
param([string]$Path)
if ($PSCmdlet.ShouldProcess($Path, "Hapus folder")) {
Remove-Item $Path -Recurse -Force
}
}With this declaration, callers can run Remove-DataDir -WhatIf for a simulation without running — a lifesaver in production environments. ShouldProcess returns true when the action is allowed to run, and false when a simulation is requested.
Some functions accept inputs that are mutually exclusive — for example accepting a file path or a file object, but not both at once. Parameter sets divide them into groups:
function Get-FileSummary {
[CmdletBinding(DefaultParameterSetName = "ByPath")]
param(
[Parameter(ParameterSetName = "ByPath", Mandatory)]
[string]$Path,
[Parameter(ParameterSetName = "ByItem", Mandatory, ValueFromPipeline)]
[System.IO.FileInfo]$Item
)
if ($PSCmdlet.ParameterSetName -eq "ByPath") {
$Item = Get-Item $Path
}
Write-Output "Ukuran $($Item.Name): $($Item.Length)"
}Each parameter belongs to one set (ParameterSetName). The caller must choose one set — mixing sets is rejected with a message explaining which sets are available.
Functions can accept objects from the pipeline, not just direct arguments. There are two main declarations:
ValueFromPipeline — accepts the object itself from the pipeline.ValueFromPipelineByPropertyName — accepts objects whose properties match the parameter names.function Get-ProcessInfo {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
[string]$Name
)
process {
$p = Get-Process -Name $Name -ErrorAction SilentlyContinue
if ($p) {
Write-Output "$Name menggunakan $([math]::Round($p.WS / 1MB, 1)) MB"
}
}
}
Get-Process pwsh | Get-ProcessInfo
Get-Process -Name pwsh | Select-Object Name | Get-ProcessInfoThe first line sends a Process object — the $Name parameter takes the Name property via ValueFromPipelineByPropertyName. The second line confirms the same from an explicit pipeline. Both trigger the process block for each item.
A script block is a collection of code stored as a value — treated like data that can be moved and executed. One use: sending it to another computer via Invoke-Command to run there.
$block = { Get-Process -Name pwsh }
Invoke-Command -ComputerName server-web -ScriptBlock $blockScript blocks are also the basis of parameters like -ScriptBlock on ForEach-Object which you used in episode 6. Same concept: code as a value, run later or somewhere else.
Every variable is born in a certain scope — its region of visibility. Scopes work like rooms in a house: variables in the living room are visible from the workspace, but not the other way around.
| Scope | Reach |
|---|---|
| Local | Holds variables inside the current block or function |
| Script | The entire script file currently running |
| Global | The entire PowerShell session |
| Private | Only in the scope where it's declared |
A function sees variables from its parent scope, but assigning a variable inside a function creates a new local variable — it doesn't change the one outside. To explicitly change an outer scope value, use the scope prefix:
$script:hitung = 0
function Get-Naikkan {
$script:hitung++
}
Get-Naikkan
Get-Naikkan
Write-Host "Nilai di scope script: $script:hitung"Without the $script: prefix, the function creates a local $hitung that disappears when the function ends — a classic bug that confuses beginners. Rule of thumb: avoid depending on global variables; give parameters and accept return values.
Principles that keep functions healthy:
Get-DiskUsage doesn't also send emails.Get-Help works.throw for failures that must stop, and Write-Error for those that can be skipped, per the material in episode 12.function Get-HealthCheck {
<#
.SYNOPSIS
Memeriksa ketersediaan server.
.PARAMETER Hostname
Nama atau alamat server.
.EXAMPLE
Get-HealthCheck -Hostname web-01
#>
param([string]$Hostname)
Test-NetConnection -ComputerName $Hostname -Port 443 -InformationLevel Quiet
}After the definition above, a caller can type Get-Help Get-HealthCheck and see the full documentation — documentation that lives with the code.
Episode 7 turns your scripts from a list of instructions into a collection of reusable units: function declarations with the Verb-Noun pattern, return values via pipeline output, parameters with defaults, mandatory, and validation, advanced functions with CmdletBinding and -WhatIf support, parameter sets for mutually exclusive input, pipeline input with ValueFromPipeline, script blocks as code that can be sent and executed, scopes that manage variable visibility, and best practices including comment-based help.
Key takeaways:
Write-Host only for display.CmdletBinding gives full cmdlet behavior — including -WhatIf.In episode 8 we dissect the heart of PowerShell: pipeline & object manipulation — how objects flow between lines, filtered with Where-Object, properties selected with Select-Object, sorted, grouped, and processed in parallel. See you in episode 8!