Structures#
PowerShell variables are typed .NET objects under the hood,
even when the operator never names a type. The everyday types
are scalars (string, int, double, bool, datetime), arrays
(@()), hashtables (@{}), the dynamic
``PSCustomObject`` for structured records, and full classes
when the work earns one. The underlying .NET types make
PowerShell objects interoperate cleanly with every cmdlet in the
ecosystem.
Types#
A variable picks up its type from the value bound to it. Cast
with [Type] to lock it in.
Type |
Declaration |
When to reach for it |
|---|---|---|
String |
|
Text. Backed by |
Int |
|
32-bit integer. |
Double / Decimal |
|
Floating-point. |
Bool |
|
|
DateTime |
|
Time and date. |
Array |
|
Ordered list, 0-based, fixed size after creation. |
List |
|
Resizable, typed list. |
Hashtable |
|
Key / value map. Iteration order preserved. |
OrderedDictionary |
|
Hashtable with guaranteed insertion order. |
PSCustomObject |
|
The “structured record”; what the pipeline carries. |
Class |
|
First-class types with methods and inheritance. |
String#
Backed by System.String. Methods come from .NET; immutable.
$name = 'operator'
$name.ToUpper() # OPERATOR
$name.Substring(0, 3) # ope
$name.Length # 8
Int / Double / Decimal#
PowerShell parses numeric literals as the narrowest type that fits. Cast for control.
$n = 42 # int
$r = 3.14 # double
[decimal]$money = 19.95 # decimal (money math)
$big = 1KB + 2MB # numeric type suffixes
Bool#
$true and $false are the canonical booleans. Truthiness:
$null, 0, '', empty arrays, and $false are all
falsy.
if ($items.Count) { 'have items' } # truthy if non-zero
Array#
Ordered list, 0-based, fixed length after creation. Append
with += returns a new array; use [List[T]] for hot loops.
$hosts = @('web01', 'web02', 'db01')
$hosts += 'cache01' # creates a new array
$hosts[0] # web01
$hosts[-1] # cache01 (negative indexing)
$hosts[1..2] # slice: web02, db01
$hosts.Count # 4
For high-volume appends, use a generic list:
$buf = [System.Collections.Generic.List[string]]::new()
foreach ($h in $hosts) { $buf.Add($h) }
$buf.ToArray()
The single-element-array trap: @(...) forces array semantics
even on a one-item result. Without it, Get-ChildItem of a
folder with one file returns the bare FileInfo, not an array.
$files = @(Get-ChildItem *.log) # always an array
Hashtable#
Key / value map with O(1) lookup. Literal form @{...}.
$region = @{
web01 = 'us-east-1'
db01 = 'eu-west-1'
cache01 = 'us-west-2'
}
$region['web01'] # us-east-1
$region.web01 # same; dot notation
$region.Keys
$region.ContainsKey('web01')
foreach ($k in $region.Keys) {
"$k -> $($region[$k])"
}
Use [ordered]@{...} when iteration order matters. Standard
hashtables preserve insertion order in modern PowerShell, but
[ordered] makes the intent explicit and works in older
versions.
PSCustomObject#
The “structured record” that pipelines carry. Looks like a hashtable
literal with [PSCustomObject] cast in front. Property access
is dotted and case-insensitive. The standard return structure
for advanced functions that need to emit multi-field rows.
$user = [PSCustomObject]@{
Name = 'operator'
Role = 'analyst'
Port = 22
}
$user.Name
# Build a table:
$rows = $hosts | ForEach-Object {
[PSCustomObject]@{
Host = $_
Region = $region[$_]
}
}
$rows | Format-Table
Class#
PowerShell 5+ has real classes with constructors, methods, properties, static members, and inheritance.
class Host {
[string]$Name
[string]$Region
[int] $Port
Host([string]$n, [string]$r) {
$this.Name = $n
$this.Region = $r
$this.Port = 22
}
[string] ToString() {
return "$($this.Name)@$($this.Region)"
}
}
$h = [Host]::new('web01', 'us-east-1')
$h.ToString()
Reach for a class when behavior travels with the data; a
PSCustomObject plus a function usually suffices.
Scope#
Variables live in a scope: Global, Script,
Private, Local (the default). Subordinate scopes can read
parent values but not write them; $script:counter or
$global:logger reaches across.
$script:counter = 0
function Tick { $script:counter++ }
Tick; Tick; Tick
$script:counter # 3
Picking the right type#
Type |
Reach for it when… |
Watch out for |
|---|---|---|
String |
Single value, text. |
It is immutable; build long strings with
|
Array |
Fixed-size ordered list, small. |
|
|
Hot-loop appends, typed elements. |
.NET-flavored; less ergonomic than |
Hashtable |
String → anything map. |
Default iteration order is insertion order in
modern PowerShell; use |
|
Structured records flowing through the pipeline. |
Property access is by name; no schema enforced. |
Class |
Behavior + data; the same type used many places. |
More ceremony than |
Hosts#
The everyday array structure.
$hosts = @('web01', 'web02', 'db01', 'cache01')
foreach ($h in $hosts) {
ssh -o ConnectTimeout=3 $h 'uptime'
}
Drive the list from a file or a command.
$hosts = Get-Content hosts.txt
$hosts = (Get-Content inventory.txt) -match '^prod-' -replace '\s.*'
Host → metadata#
Hashtable for the lookup; the operator types the host name they already know.
$region = @{
web01 = 'us-east-1'
db01 = 'eu-west-1'
cache01 = 'us-west-2'
}
Connect-AzAccount -Region $region[$h]
Record per host#
PSCustomObject per row; Format-Table to render. The same
structure feeds Export-Csv, ConvertTo-Json, and any cmdlet
that wants a table.
$rows = $hosts | ForEach-Object {
[PSCustomObject]@{
Host = $_
Region = $region[$_]
Port = 22
}
}
$rows | Format-Table
$rows | Export-Csv hosts.csv -NoTypeInformation
$rows | ConvertTo-Json
Sets via maps#
There is no set type. Use a hashtable with a sentinel value, or
System.Collections.Generic.HashSet[T] for the .NET version.
$seen = @{}
foreach ($ip in (Get-Content access.log | ForEach-Object { ($_ -split ' ')[0] })) {
if (-not $seen.ContainsKey($ip)) {
$seen[$ip] = $true
$ip # first sighting
}
}
$set = [System.Collections.Generic.HashSet[string]]::new()
$null = $set.Add('a'); $null = $set.Add('a')
$set.Count # 1
References#
Get-Help about_Variables,Get-Help about_Hash_Tables,Get-Help about_Arrays,Get-Help about_PSCustomObject,Get-Help about_Classes.Overview for the language-wide patterns these structures plug into.
I/O and Pipelines for how these structures flow through the pipeline.
Algorithms for
Sort-Object/Group-Object/Compare-Objectover these structures.