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
Create a script that:
- Stores your full name in a variable
- Converts it to uppercase
- Gets the length
- Extracts first name (before the space)
- 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
Create an array of 3 PSCustomObjects representing employees with Name, Department, and Salary properties. Then:
- Display all employees
- Find employees in "IT" department
- Calculate average salary
- 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
Create a hashtable representing a server configuration with properties: ServerName, IPAddress, Port, IsActive. Then:
- Display the configuration
- Update the Port to 8080
- Add a new property "Location"
- Remove IsActive property
- 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:
- Variables start with
$and are case-insensitive - Types are automatic but can be explicitly specified with
[type] - Strings use double quotes for expansion, single quotes for literals
- Numbers support multiple types: int, long, double, decimal
- Booleans are
$trueand$false; various values are "truthy" or "falsy" - Arrays store multiple values; use ArrayList or Generic List for dynamic sizing
- Hashtables store key-value pairs for lookups
- PSCustomObject creates custom objects that work seamlessly with PowerShell cmdlets
- Automatic variables provide system information
- Scope determines variable visibility
Master these fundamentals, and you have the foundation for all PowerShell scripting.
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 →