Classes and Advanced Functions

Reviewed & published by Brayan K

By the end of this lesson you'll define your own types with the class keyword — properties, constructors, methods, validation, inheritance, static members, and enums — and build advanced functions with [CmdletBinding()], mandatory and pipeline parameters, begin/process/end blocks, and comment-based help, so your scripts become genuine, professional tools.

Part of the free PowerShell course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn

💡 Real-World Analogy

A class is a cookie cutter; each object you stamp out is a cookie. The cutter's shape (properties) and the icing instructions (methods) are defined once, so every cookie comes out consistent. A constructor is the rule for how a cookie must be made; validation rejects bad dough before it ruins a batch; inheritance is a fancier cutter built from a basic one; and a static member is the shared tally on the kitchen wall counting how many cookies the whole batch produced. An advanced function, meanwhile, is the professional-grade tool that uses these cookies on an assembly line — handling them one at a time as they come down the belt.

1. Defining a class

The class keyword (PowerShell 5+) defines a new type with properties (the data, optionally guarded by validation attributes like [ValidateNotNullOrEmpty()]), a constructor (a method named the same as the class that runs when you create an instance), and methods (behaviour, with an optional return type). You create instances with [ClassName]::new(...) and refer to the current instance inside the class as $this. Read this worked example, then run it.

# 'class' defines a blueprint with PROPERTIES, a CONSTRUCTOR, and METHODS.
class Server {
    # Properties (with optional validation attributes):
    [ValidateNotNullOrEmpty()][string]$Name
    [int]$Port = 443                       # default value

    # Constructor: same name as the class; runs on [Server]::new(...)
    Server([string]$name, [int]$port) {
        $this.Name = $name
        $this.Port = $port
    }

    # A method returns text; [string] before the name is the return type.
    [string] Describe() {
        return "$($this.Name) listening on port $($this.Port)"
    }
}

$web = [Server]::new('web01', 8080)
$web.Describe()
$web.Port

2. Inheritance, Static Members & Enums

A class can inherit from another with the colon syntax (class Derived : Base), gaining all its members and optionally overriding them. A static member belongs to the class itself rather than any instance — declared with static and accessed via [Class]::Member — which makes it ideal for shared counters or constants. An enum defines a fixed set of named values, giving readable names to a limited list of options.

# An ENUM is a fixed set of named values.
enum Role { Guest; User; Admin }

# Base class:
class Account {
    [string]$Login
    [Role]$Role = [Role]::User
    [string] Greet() { return "Hi $($this.Login)" }
}

# DerivedClass : BaseClass  -> inherits everything, can add/override.
class AdminAccount : Account {
    # STATIC member belongs to the class itself, not an instance:
    static [int] $Count = 0
    AdminAccount() { $this.Role = [Role]::Admin; [AdminAccount]::Count++ }
    [string] Greet() { return "Admin $($this.Login)" }   # override
}

$a = [AdminAccount]::new(); $a.Login = 'root'
$a.Greet()                       # overridden method
$a.Role                          # Admin
[AdminAccount]::Count            # static, shared by all instances

Now you try. Fill in the three blanks using the hints in the comments, then run it.

# 🎯 YOUR TURN — replace each ___ then run it.
# Goal: a Point class with X, Y and a method that sums them.

___ Point {                       # 👉 the keyword is: class
    [int]$X
    [int]$Y

    # Constructor sets both coordinates:
    Point([int]$x, [int]$y) {
        $___.X = $x                # 👉 the instance variable is: this
        $this.Y = $y
    }

    [int] Sum() { return $this.X + $this.Y }
}

$p = [Point]::___(3, 4)           # 👉 the constructor call is: new
$p.Sum()

# ✅ Expected output:
# 7

3. Advanced Functions: [CmdletBinding()] & begin/process/end

Adding [CmdletBinding()] turns a plain function into an advanced function that behaves like a compiled cmdlet, gaining common parameters such as -Verbose and -ErrorAction. Mark a parameter [Parameter(Mandatory)] to require it and ValueFromPipeline to accept piped input. The body splits into three blocks: begin (once, before items), process (once per item), and end (once, after items).

# An ADVANCED FUNCTION behaves like a compiled cmdlet.
function Get-Doubled {
    [CmdletBinding()]                    # adds -Verbose, -ErrorAction, etc.
    param(
        [Parameter(Mandatory, ValueFromPipeline)]   # required + pipeline
        [int]$Number
    )
    begin   { Write-Verbose 'Starting'; $total = 0 }   # once, before items
    process {                                          # once PER item
        $doubled = $Number * 2
        $total  += $doubled
        $doubled                                       # emit per item
    }
    end     { Write-Verbose "Total doubled: $total" }  # once, after items
}

1, 2, 3 | Get-Doubled
1, 2, 3 | Get-Doubled -Verbose

Pro Tip

If your function takes pipeline input, the per-item logic must live in the process block. Put it directly in the body (no blocks) and the function behaves like one big end block — it runs once and only ever sees the last piped item. This is the single most common advanced-function bug.

Putting It Together: comment-based help

A specially formatted comment block at the top of a function — with .SYNOPSIS, .DESCRIPTION, .PARAMETER, and .EXAMPLE keywords — makes Get-Help work for your function exactly as it does for built-in cmdlets. This is what turns a script into a discoverable, professional tool.

function Get-Cube {
<#
.SYNOPSIS
    Returns the cube of a number.
.DESCRIPTION
    Multiplies the input by itself three times. Accepts pipeline input.
.PARAMETER Number
    The integer to cube.
.EXAMPLE
    Get-Cube -Number 3
    Returns 27.
#>
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [int]$Number
    )
    process { $Number * $Number * $Number }
}

# Comment-based help works with Get-Help, just like a real cmdlet:
Get-Help Get-Cube -Examples
2, 3 | Get-Cube

Run Get-Help Get-Cube -Full to see synopsis, description, parameters, and examples all rendered from your comment block.

Common Errors (and the fix)

📋 Quick Reference — Classes & Advanced Functions

ConceptSyntax
Define a classclass Server { ... }
Create an instance[Server]::new('web01', 8080)
Inheritclass Admin : Account { }
Static memberstatic [int]$Count; [Class]::Count
Enumenum Role { Guest; User; Admin }
Advanced function[CmdletBinding()] param( ... )
Pipeline + per-itemValueFromPipeline + process { }

Mini-Challenge: a BankAccount class + tool

No blanks this time — just a brief and an outline to keep you on track. Build it, run it, and check your output against the example in the comments. Combining a class with an advanced function is exactly how reusable, professional PowerShell tools are built.

# 🎯 MINI-CHALLENGE: a BankAccount class + a tool to use it
# No code is filled in — just an outline. Build it and check the output.
#
# 1. enum Currency { USD; EUR; GBP }
# 2. class BankAccount with:
#       [ValidateNotNullOrEmpty()][string]$Owner
#       [decimal]$Balance = 0
#       [Currency]$Currency = [Currency]::USD
#       a constructor taking ($owner, $currency)
#       methods Deposit([decimal]$amount) and Withdraw([decimal]$amount)
#         (Withdraw should throw if amount > Balance)
#       a static [int]$AccountCount incremented in the constructor.
# 3. An advanced function New-Account with [CmdletBinding()], a Mandatory
#    -Owner parameter, comment-based help, and a process block that returns
#    a new BankAccount.
# 4. Create two accounts, deposit/withdraw, and print balances.
#
# ✅ Example output:
#    Ada balance: 150
#    Accounts created: 2

# your code here

🎉 Lesson Complete

Practice quiz

Which keyword defines a class in PowerShell?

  • class
  • type
  • object
  • struct

Answer: class. PowerShell 5+ uses the class keyword to define a class with properties and methods.

What is a constructor in a PowerShell class?

  • An inherited method
  • A property that holds data
  • A method that runs when an instance is created
  • A static field

Answer: A method that runs when an instance is created. A constructor is a special method named the same as the class that runs when you create an instance with [Class]::new().

How does one class inherit from another?

  • class Dog extends Animal
  • class Dog : Animal
  • class Dog from Animal
  • class Dog inherits Animal

Answer: class Dog : Animal. PowerShell uses a colon: 'class Dog : Animal' makes Dog inherit Animal's members.

What does a static member belong to?

  • The parent only
  • The pipeline
  • A single instance
  • The class itself, not any instance

Answer: The class itself, not any instance. Static members belong to the class itself and are accessed with [Class]::Member, not through an instance.

What does the [ValidateNotNull()] attribute do on a property?

  • Rejects a null value assigned to it
  • Sets a default
  • Hides the property
  • Makes it static

Answer: Rejects a null value assigned to it. Validation attributes like [ValidateNotNull()] enforce rules when a value is assigned to the property.

What does [CmdletBinding()] add to a function?

  • A constructor
  • Common parameters like -Verbose and -ErrorAction
  • A return type
  • Automatic inheritance

Answer: Common parameters like -Verbose and -ErrorAction. [CmdletBinding()] promotes a function to an advanced function with the common parameters of a real cmdlet.

How do you make a parameter required in an advanced function?

  • [Parameter(Mandatory)]
  • [Required]
  • [NotNull]
  • param(force)

Answer: [Parameter(Mandatory)]. [Parameter(Mandatory)] forces the caller to supply a value, prompting if it is omitted.

Which block of an advanced function runs once for EACH pipeline item?

  • begin
  • end
  • init
  • process

Answer: process. The process block runs once per item that arrives from the pipeline; begin and end run once each, before and after.

Which attribute lets a parameter accept values from the pipeline?

  • PipelineInput
  • AcceptPipe
  • ValueFromPipeline
  • FromPipe

Answer: ValueFromPipeline. Marking a parameter [Parameter(ValueFromPipeline)] lets values arrive from the pipeline into a process block.

What is an enum used for?

  • Storing files
  • Defining a fixed set of named constant values
  • Catching errors
  • Joining strings

Answer: Defining a fixed set of named constant values. An enum defines a named set of constant values, giving readable names to a fixed list of options.

Continue this course

Frequently asked questions

When should I use a class instead of a [pscustomobject]?

Reach for a [pscustomobject] (or a hashtable) when you just need a lightweight bag of named properties to pass around — most scripting needs stop here. Use a class when you need real structure: a defined TYPE you can validate against, methods that operate on the data, a constructor that enforces how instances are built, inheritance to share behaviour, or static members shared across instances. Classes shine for modelling domain objects (a Server, an Account) and for code that other scripts will build on; pscustomobjects shine for quick, ad-hoc results.

How do I create an instance of a class?

Use the static ::new() method: $s = [Server]::new('web01', 8080). This calls the matching constructor. PowerShell also allows the older $s = New-Object Server -ArgumentList 'web01', 8080 syntax, but ::new() is faster, clearer, and the recommended form. If a class has several constructors with different parameter lists, ::new() picks the one whose argument count and types match. A parameterless ::new() works only if the class has a default (parameterless) constructor.

What is the difference between a static member and an instance member?

An instance member belongs to one specific object: each $account has its own $Login. A static member belongs to the CLASS itself and is shared by every instance — there is exactly one copy. You declare it with the static keyword and access it through the type, [AdminAccount]::Count, never through an instance. Statics are perfect for things like a shared counter, a constant, or a factory method that doesn't need an existing instance to run.

Why split an advanced function into begin, process, and end blocks?

When a function accepts pipeline input, those three blocks control timing. begin runs ONCE before any items arrive — ideal for setup like opening a connection or initialising a counter. process runs ONCE PER ITEM coming down the pipeline, which is where the real per-item work goes (your ValueFromPipeline parameter holds the current item here). end runs ONCE after all items — perfect for summaries or cleanup. Skip the blocks and your whole body acts like an end block and only sees the LAST piped item, a classic bug.

What is comment-based help and why bother?

Comment-based help is a specially formatted comment block (with .SYNOPSIS, .DESCRIPTION, .PARAMETER, .EXAMPLE keywords) placed at the top of a function. PowerShell parses it so that Get-Help YourFunction, Get-Help YourFunction -Examples, and tab-completion all work exactly as they do for built-in cmdlets. It costs a few lines but makes your tools self-documenting and discoverable for teammates (and for future-you), which is the difference between a throwaway script and a reusable, professional tool.