Collections and Custom Objects

Summary: in this tutorial, you will learn master powershell arrays, hashtables, pscustomobject, automatic variables, and variable scope for effective scripting.

Collections and Custom Objects

Individual variables store single values, but real-world scripts need to work with collections of data. PowerShell provides arrays for ordered lists, hashtables for key-value pairs, and PSCustomObject for creating structured data. Understanding these collection types is essential for pipeline processing and data manipulation.

Arrays: Working with Multiple Values

Arrays store multiple values in a single variable. PowerShell arrays are flexible and easy to work with.

Creating Arrays

# Explicit array syntax
$numbers = @(1, 2, 3, 4, 5)
 
# Implicit (comma-separated values)
$colors = "red", "green", "blue"
 
# Range operator
$oneToTen = 1..10
$letters = 'a'..'z'
 
# Empty array
$empty = @()
 
# Single-item array (forces array type)
$single = @(42)
 
# Mixed types
$mixed = 1, "hello", $true, (Get-Date)
 

Accessing Array Elements

$fruits = "apple", "banana", "cherry", "date"
 
# Index from 0
$fruits[0]                    # apple
$fruits[1]                    # banana
 
# Negative indices count from end
$fruits[-1]                   # date (last item)
$fruits[-2]                   # cherry
 
# Ranges
$fruits[0..2]                 # apple, banana, cherry
$fruits[1,3]                  # banana, date
 
# Length
$fruits.Count                 # 4
$fruits.Length                # 4 (same thing)
 

Modifying Arrays

Regular PowerShell arrays are fixed size. To modify, you create a new array:

$colors = "red", "green"
 
# Add item (creates new array)
$colors += "blue"
# $colors is now: red, green, blue
 
# Remove item (filter to new array)
$colors = $colors | Where-Object { $_ -ne "green" }
# $colors is now: red, blue
 
# Replace item
$colors[0] = "yellow"
# $colors is now: yellow, blue
 

Adding items with += is inefficient for large arrays because it creates a new array each time. For frequent modifications, use ArrayList or Generic List instead.

ArrayList: Dynamic Arrays

For arrays that grow frequently:

# Create ArrayList
$list = [System.Collections.ArrayList]::new()
 
# Add items (returns index, suppress with $null =)
$null = $list.Add("first")
$null = $list.Add("second")
$null = $list.Add("third")
 
# Remove items
$list.Remove("second")
$list.RemoveAt(0)             # Remove by index
 
# Check contents
$list.Contains("third")       # True
$list.Count                   # 2
 

Generic Lists: Best Performance

For best performance, use typed generic lists:

# Create a list of strings
$list = [System.Collections.Generic.List[string]]::new()
 
# Add items (no return value)
$list.Add("apple")
$list.Add("banana")
$list.Add("cherry")
 
# Access like arrays
$list[0]                      # apple
$list[-1]                     # cherry
 
# Modify
$list[1] = "blueberry"
$list.Remove("cherry")
$list.RemoveAt(0)
 
# List methods
$list.Contains("banana")      # True
$list.IndexOf("banana")       # 1
$list.Count                   # 2
 
# Why use Generic List?
# - Faster than ArrayList
# - Type-safe (only holds specified type)
# - No output when adding items
 

Hashtables: Key-Value Pairs

Hashtables store data as key-value pairs. They're like dictionaries in Python or objects in JavaScript.

Creating Hashtables

# Basic hashtable
$person = @{
    Name = "Alice"
    Age = 30
    City = "Seattle"
}
 
# Empty hashtable
$empty = @{}
 
# Ordered hashtable (preserves insertion order)
$ordered = [ordered]@{
    First = 1
    Second = 2
    Third = 3
}
 

Accessing Values

$person = @{
    Name = "Alice"
    Age = 30
    City = "Seattle"
}
 
# Dot notation
$person.Name                  # Alice
$person.Age                   # 30
 
# Bracket notation
$person["Name"]               # Alice
$person["Age"]                # 30
 
# Dynamic keys
$key = "City"
$person[$key]                 # Seattle
 

Modifying Hashtables

# Add new key-value pair
$person.Country = "USA"
$person["State"] = "WA"
 
# Modify existing value
$person.Age = 31
$person["City"] = "Portland"
 
# Remove a key
$person.Remove("State")
 
# Check if key exists
$person.ContainsKey("Name")   # True
$person.ContainsKey("Email")  # False
 
# Get all keys
$person.Keys
 
# Get all values
$person.Values
 
# Count items
$person.Count                 # 4
 

Iterating Hashtables

$person = @{
    Name = "Alice"
    Age = 30
    City = "Seattle"
}
 
# Iterate with foreach
foreach ($key in $person.Keys) {
    Write-Host "$key : $($person[$key])"
}
 
# Using GetEnumerator
$person.GetEnumerator() | ForEach-Object {
    "$($_.Key) : $($_.Value)"
}
 

Nested Hashtables

$config = @{
    Server = @{
        Name = "WebServer01"
        IP = "192.168.1.10"
        Port = 8080
    }
    Database = @{
        Name = "ProductionDB"
        Server = "sql.example.com"
    }
}
 
# Access nested values
$config.Server.Name           # WebServer01
$config.Database.Server       # sql.example.com
 

PSCustomObject: Creating Custom Objects

PSCustomObject is PowerShell's way of creating custom objects with named properties.

Creating PSCustomObjects

# Basic PSCustomObject
$user = [PSCustomObject]@{
    Name = "Alice"
    Email = "alice@example.com"
    Age = 30
}
 
# Access properties
$user.Name                    # Alice
$user.Email                   # alice@example.com
 
# Properties display nicely
$user
# Name  Email              Age
# ----  -----              ---
# Alice alice@example.com  30
 

Why Use PSCustomObject?

PSCustomObjects work better with PowerShell cmdlets than hashtables:

# Create multiple objects
$users = @(
    [PSCustomObject]@{Name="Alice"; Age=30}
    [PSCustomObject]@{Name="Bob"; Age=25}
    [PSCustomObject]@{Name="Charlie"; Age=35}
)
 
# Works perfectly with Select-Object
$users | Select-Object Name
 
# Works with Where-Object
$users | Where-Object Age -gt 28
 
# Works with Sort-Object
$users | Sort-Object Age
 
# Exports nicely to CSV
$users | Export-Csv users.csv
 
# Displays as table
$users | Format-Table
 

Adding Members Dynamically

$obj = [PSCustomObject]@{
    Name = "Test"
}
 
# Add property
$obj | Add-Member -NotePropertyName "Value" -NotePropertyValue 42
 
# Add method
$obj | Add-Member -MemberType ScriptMethod -Name "Display" -Value {
    "Name: $($this.Name), Value: $($this.Value)"
}
 
$obj.Display()                # Name: Test, Value: 42
 

Automatic Variables

PowerShell has several built-in variables you didn't create:

$_             # Current pipeline object
$PSVersionTable # PowerShell version info
$PSHome        # PowerShell installation directory
$Home          # User's home directory
$PWD           # Current directory
$PID           # Current process ID
$Host          # Host program info
$Error         # Array of recent errors
$?             # Success of last command ($true or $false)
$LASTEXITCODE  # Exit code of last native command
$Args          # Array of arguments passed to script/function
$Input         # Pipeline input (in functions)
$PSItem        # Same as $_ (PowerShell 3+)
$true          # Boolean true
$false         # Boolean false
$null          # Null value
 

Variable Scope

Variables exist in different scopes:

# Global scope (entire session)
$Global:myVar = "global"
 
# Script scope (current script file)
$Script:myVar = "script level"
 
# Local scope (current context)
$Local:myVar = "local"
 
# Default is local
$myVar = "implicit local"
 
# Functions create child scope
function Test {
    $functionVar = "inside function"  # Local to function
    $Global:globalVar = "accessible anywhere"
}
 

Exercises

🏋️ Exercise 1: String Manipulation

Create a script that:

  1. Stores your full name in a variable
  2. Converts it to uppercase
  3. Gets the length
  4. Extracts first name (before the space)
  5. Creates an email address: firstname.lastname@example.com (lowercase)
Show Solution
$fullName = "Alice Johnson"
 
# Uppercase
$upper = $fullName.ToUpper()
Write-Host "Uppercase: $upper"
 
# Length
$length = $fullName.Length
Write-Host "Length: $length"
 
# First name
$firstName = $fullName.Split(" ")[0]
Write-Host "First name: $firstName"
 
# Email
$parts = $fullName.Split(" ")
$email = "$($parts[0]).$($parts[1])@example.com".ToLower()
Write-Host "Email: $email"
 
# Complete output:
# Uppercase: ALICE JOHNSON
# Length: 13
# First name: Alice
# Email: alice.johnson@example.com
 
🏋️ Exercise 2: Building Data Structures

Create an array of 3 PSCustomObjects representing employees with Name, Department, and Salary properties. Then:

  1. Display all employees
  2. Find employees in "IT" department
  3. Calculate average salary
  4. Sort by salary descending
Show Solution
# Create employees
$employees = @(
    [PSCustomObject]@{
        Name = "Alice"
        Department = "IT"
        Salary = 75000
    }
    [PSCustomObject]@{
        Name = "Bob"
        Department = "Sales"
        Salary = 65000
    }
    [PSCustomObject]@{
        Name = "Charlie"
        Department = "IT"
        Salary = 85000
    }
)
 
# Display all
Write-Host "All Employees:"
$employees | Format-Table
 
# IT department
Write-Host "`nIT Department:"
$employees | Where-Object Department -eq "IT" | Format-Table
 
# Average salary
$avgSalary = ($employees | Measure-Object Salary -Average).Average
Write-Host "`nAverage Salary: `$$avgSalary"
 
# Sort by salary
Write-Host "`nSorted by Salary:"
$employees | Sort-Object Salary -Descending | Format-Table
 
🏋️ Exercise 3: Hashtable Manipulation

Create a hashtable representing a server configuration with properties: ServerName, IPAddress, Port, IsActive. Then:

  1. Display the configuration
  2. Update the Port to 8080
  3. Add a new property "Location"
  4. Remove IsActive property
  5. Display final configuration
Show Solution
# Create configuration
$config = @{
    ServerName = "WebServer01"
    IPAddress = "192.168.1.10"
    Port = 80
    IsActive = $true
}
 
# Display initial
Write-Host "Initial Configuration:"
$config | Format-Table
 
# Update port
$config.Port = 8080
Write-Host "`nPort updated to 8080"
 
# Add location
$config.Location = "DataCenter-West"
Write-Host "Added Location property"
 
# Remove IsActive
$config.Remove("IsActive")
Write-Host "Removed IsActive property"
 
# Display final
Write-Host "`nFinal Configuration:"
$config.GetEnumerator() | ForEach-Object {
    "$($_.Key): $($_.Value)"
}
 

Summary

Understanding variables and types in PowerShell:

  1. Variables start with $ and are case-insensitive
  2. Types are automatic but can be explicitly specified with [type]
  3. Strings use double quotes for expansion, single quotes for literals
  4. Numbers support multiple types: int, long, double, decimal
  5. Booleans are $true and $false; various values are "truthy" or "falsy"
  6. Arrays store multiple values; use ArrayList or Generic List for dynamic sizing
  7. Hashtables store key-value pairs for lookups
  8. PSCustomObject creates custom objects that work seamlessly with PowerShell cmdlets
  9. Automatic variables provide system information
  10. Scope determines variable visibility

Master these fundamentals, and you have the foundation for all PowerShell scripting.

Was this page helpful?
SR

Written by the ShellRAG Team

The ShellRAG editorial team writes practical, beginner-friendly PowerShell tutorials with tested code examples and real-world use cases. Every article is technically reviewed for accuracy and updated regularly.

Learn more about us →