Variables, Data Types & Operators

PowerShell Fundamentals

Chapter 5 · Variables, Data Types & Operators

Every object pipelined through Get-Member in Chapter 3, and every property tested with -eq/-gt/-like in Chapter 4, was a real, typed .NET object the entire time — that's exactly what made those chapters work. Variables are no different, even though declaring one looks deceptively casual. This chapter is about that duality: PowerShell lets you create a variable with zero type declaration, the same relaxed feel as Bash or Python, while every value it ever holds stays a genuine, strongly-typed object underneath — not the loose, everything-is-basically-text world Bash actually lives in.

Declaring a Variable

Every variable name starts with $ — on both assignment and read, which is one real, easy-to-trip-on difference from Bash, where $ only appears when reading a variable (name="Philip" to assign, $name to read):

$name = "Philip" $age = 42 Write-Output $name

No type was declared anywhere in that example — and yet neither variable is untyped. Ask any variable what it actually is with .GetType():

$name.GetType().Name # String $age.GetType().Name # Int32
The central fact this chapter is built on
"Dynamically typed" here means the variable isn't locked to one type — $age could hold a string tomorrow if you reassign it. It does not mean the value is typeless the way Bash treats everything as fundamentally text until proven otherwise. Every value in PowerShell is a real .NET object with a real, discoverable type, the whole time — which is exactly why Get-Member (Chapter 3) always has something concrete to report, and why -gt/-lt (Chapter 4) can compare numbers as numbers instead of comparing digit characters as text.

Casting & Type Accelerators

A short bracketed name before a value — a type accelerator — converts it explicitly:

[int]"42" + 8 # 50 — real addition, because "42" was cast to a real number first "42" + 8 # "428" — no cast, so + falls back to string concatenation # A type-constrained variable rejects a value that can't convert [int]$count = 5 $count = "hello" # throws — "hello" can't become an Int32
Accelerator.NET typeHolds
[string]System.StringText
[int]System.Int32Whole numbers
[double]System.DoubleDecimal numbers
[bool]System.Boolean$true / $false
[datetime]System.DateTimeDates and times
[array]System.ArrayAn ordered collection
[hashtable]System.Collections.HashtableKey-value pairs

Arithmetic — and a Genuinely Surprising Rounding Gotcha

10 + 3 # 13 10 % 3 # 1 — modulo, the remainder 7 / 2 # 3.5 — division auto-widens to a double, it does not truncate like some languages
Casting a .5 value doesn't always round the way you'd expect
Casting a double back to [int] uses banker's rounding (round-half-to-even), not the "always round .5 up" rule most people assume by default: [int]2.5 is 2, but [int]3.5 is 4 — both rounded to the nearest even number, not simply upward. If you specifically want traditional round-half-up behavior, reach for [math]::Round($value, 0, [MidpointRounding]::AwayFromZero) instead of a bare cast.

Strings: Quoting, Interpolation & Joining

# Single quotes — literal, no interpolation at all 'Hello, $name' # Hello, $name (printed exactly as typed) # Double quotes — variables and $(...) expressions are evaluated inline "Hello, $name" # Hello, Philip "Next year you'll be $($age + 1)" # Next year you'll be 43 # -join / -split move between an array and a single string "a,b,c" -split "," # @('a', 'b', 'c') @("a", "b", "c") -join "-" # "a-b-c"

That single-vs-double distinction is a real, common trip-up: reach for single quotes when you actually want $ printed literally, and double quotes any time a variable or expression should be substituted in.

Arrays: @()

$fruits = @("apple", "banana", "cherry") $fruits[0] # apple — zero-indexed $fruits[-1] # cherry — negative indexing counts from the end $fruits[0..1] # apple, banana — the .. range operator $fruits.Count # 3 # Arrays can freely mix types — nothing enforces a single element type $mixed = @(1, "two", 3.0, $true)

Hashtables: @{}

$person = @{ Name = "Alice"; Age = 30 } $person['Age'] # 30 — bracket access $person.Age # 30 — dot access, same result # A plain @{} hashtable does NOT guarantee its keys stay in insertion order # [ordered]@{} does — reach for it whenever display order actually matters $ordered = [ordered]@{ First = 1; Second = 2; Third = 3 }

Looping over every key or value in a hashtable properly belongs to Chapter 6's own control-flow material — for now, know that @{} and [ordered]@{} are genuinely different types with a real behavioral difference, not just a stylistic choice.

Automatic Variables Worth Knowing Now

VariableHolds
$nullPowerShell's explicit "no value" — comparing something to $null uses -eq/-ne like any other value
$true / $falseThe two [bool] literals
$_ / $PSItemThe current pipeline object inside a script block (Chapter 4)
$PSVersionTableThe running PowerShell version details (Chapter 1)
$ErrorA list of recent errors — covered properly in Chapter 9
$argsPositional arguments passed to a script or function — covered properly in Chapter 7
A first practical habit
Whenever a value's behavior surprises you — a comparison that doesn't match, arithmetic that doesn't look right — reach for .GetType() before assuming PowerShell is wrong. Nine times out of ten, the type wasn't what you assumed it was, and .GetType() tells you immediately rather than leaving you to guess.

Hands-On Exercises

Exercise 1

Predict what .GetType().Name reports for $x = "42" versus $x = 42, and predict what $x + 8 does in each case. Explain the reasoning behind both predictions using this chapter's own casting example.

📄 View solution
Exercise 2

Explain why [int]2.5 evaluates to 2 while [int]3.5 evaluates to 4. What should you use instead if you specifically want traditional round-half-up behavior?

📄 View solution
Exercise 3

Given $person = @{ Name = "Alice"; Age = 30 }, show two different ways to read the Age value. Then explain why a plain @{} hashtable's key order isn't guaranteed the way an array's order is, and what fixes that.

📄 View solution

Chapter 5 Quick Reference

  • $name = value$ is used on both assignment and read, unlike Bash
  • .GetType() — reveals a variable's real underlying .NET type
  • Type accelerators[string] [int] [double] [bool] [datetime] [array] [hashtable], for explicit casting or constraining a variable
  • Division auto-widens7 / 2 is 3.5, a double, not a truncated integer
  • Casting to [int] rounds half-to-even[int]2.5 is 2; [int]3.5 is 4
  • Single vs. double quotes — single is literal; double interpolates variables and $(...) expressions
  • @() arrays — zero-indexed, negative indexing, .. range operator, freely mixed types
  • @{} vs. [ordered]@{} — a plain hashtable doesn't guarantee key order; [ordered]@{} does
  • Next chapter: Control Flow: Conditionals, Loops & Switch