Lesson 2 • Beginner
Variables and Data Types
By the end of this lesson you'll be able to store text, numbers, true/false values, arrays, and hashtables in PowerShell variables, convert between types with casting, and build clean output with string interpolation and here-strings — the foundation of every script you'll write.
What You'll Learn
- Create variables with the $ prefix and read their value back
- Recognise the common types: String, Int32, Double, Boolean and $null
- Build arrays with @() and hashtables with @{ } and read items from them
- Convert between types with casts like [int] and [double]
- Interpolate variables in "double quotes" vs keep text literal in 'single quotes'
- Write multi-line text cleanly with here-strings
Write-Output. Every example below is real PowerShell — run it in the free online terminal linked under each block, or open PowerShell on your system, and the listed output is what you should see.$), and the box holds one value. Unlike stricter languages, PowerShell doesn't make you say what kind of thing goes in the box up front — you drop a value in and PowerShell reads the label on the contents to work out the type. That flexibility is convenient, but it's also why a few "wrong type" surprises show up later in this lesson.1. Declaring Variables & Common Types
To make a variable, pick a name starting with $ and assign a value with =: $name = "Alice". You never declare the type — PowerShell is dynamically typed, so it figures the type out from the value. The everyday types are String (text), Int32 (whole numbers), Double (decimals) and Boolean ($true / $false). There's also $null, which means "no value at all". Read this worked example, run it, then you'll write your own.
# A variable is a named box that holds a value. In PowerShell every
# variable name starts with a $ sign — the $ is part of the name.
$name = "Alice" # text -> String (use quotes)
$age = 30 # whole number -> Int32
$price = 19.99 # decimal number -> Double
$isAdmin = $true # true/false -> Boolean ($true or $false)
$nothing = $null # "no value at all" -> Null
# Every value is a .NET object, so you can ask it for its type name.
Write-Output "name is a $($name.GetType().Name)"
Write-Output "age is an $($age.GetType().Name)"
Write-Output "price is a $($price.GetType().Name)"
Write-Output "admin is a $($isAdmin.GetType().Name)"
# $null is genuinely empty. Comparing to it is how you test "is it set?".
Write-Output "nothing is null: $($null -eq $nothing)"name is a String
age is an Int32
price is a Double
admin is a Boolean
nothing is null: True2. Strings: Interpolation, Quotes & Here-Strings
The quote you choose changes what happens. Double quotes expand — a $variable inside the text is replaced by its value, and $(...) runs a small expression and drops the result in. Single quotes are literal — what you type is exactly what you get, so '$name' stays as the text $name. For multi-line text, a here-string (@" ... "@) keeps your layout intact, and the double-quoted form still expands variables inside it.
$user = "Sam"
$count = 3
# Double quotes EXPAND: variables and $(...) subexpressions are replaced.
Write-Output "Hello, $user!" # Hello, Sam!
Write-Output "You have $count messages" # You have 3 messages
Write-Output "2 + 2 = $(2 + 2)" # 2 + 2 = 4 (subexpression)
# Single quotes are LITERAL: nothing is expanded, what you see is what you get.
Write-Output 'Raw text: $user is not expanded' # Raw text: $user is not expanded
# A here-string holds multi-line text exactly as written.
# Double-quoted @" ... "@ still expands variables inside it.
$report = @"
User: $user
Messages: $count
Status: OK
"@
Write-Output $reportHello, Sam!
You have 3 messages
2 + 2 = 4
Raw text: $user is not expanded
User: Sam
Messages: 3
Status: OKYour turn. The script below is almost complete — fill in the three blanks marked ___ using the hints in the comments, then run it.
# 🎯 YOUR TURN — replace each ___ then run the script.
# 1) Make a string called $city set to a city name
$city = ___ # 👉 text in "double quotes", e.g. "London"
# 2) Make an int called $year set to the current year
$year = ___ # 👉 a whole number, e.g. 2026
# 3) Make a bool called $isOpen set to true
$isOpen = ___ # 👉 use $true (not "true")
# These already work once your variables exist (note: double quotes expand):
Write-Output "In $year, the office in $city is open: $isOpen"
# ✅ Expected output (example):
# In 2026, the office in London is open: TrueIn 2026, the office in London is open: True3. Collections: Arrays & Hashtables
When you need more than one value, reach for a collection. An array holds values in order — build it with @(...) and read items by position, where [0] is the first and [-1] is the last. A hashtable holds key = value pairs (a lookup table) — build it with @{ ... } and read a value by its key with $h.Key or $h['Key']. Inside a string, wrap property access in $(...) so it's evaluated.
# Arrays hold many values in order. Build one with @( ).
$fruits = @("Apple", "Banana", "Cherry")
Write-Output "First: $($fruits[0])" # index 0 is the first item
Write-Output "Last: $($fruits[-1])" # -1 counts from the end
Write-Output "Count: $($fruits.Count)" # how many items
# += appends — but it builds a brand-new array each time (slow in big loops).
$fruits += "Date"
Write-Output "Joined: $($fruits -join ', ')"
# Hashtables hold key -> value pairs (a lookup table). Build one with @{ }.
$server = @{
Name = "WebServer01"
IP = "192.168.1.10"
Status = "Running"
}
Write-Output "Name: $($server.Name)" # dot access by key
Write-Output "IP: $($server['IP'])" # or bracket access by keyFirst: Apple
Last: Cherry
Count: 3
Joined: Apple, Banana, Cherry, Date
Name: WebServer01
IP: 192.168.1.104. Type Casting
Sometimes a value is the wrong type — most often you have text that needs to be a number. Put the target type in square brackets in front of the value to cast it: [int]$typed, [double]$priceText, [string]$age. This matters because PowerShell decides what + does from the left-hand value: a string on the left glues text together, while a number on the left adds. Cast first and the maths behaves.
# Text typed by a user is always a String — even if it looks like a number.
$typed = "42"
Write-Output "Without casting: $($typed + 8)" # "428" — string glued together!
# Cast with [int] to force a real number, then maths works.
$number = [int]$typed
Write-Output "With [int] cast: $($number + 8)" # 50
# Cast the other way too: [string], [double], [bool] all work.
$rounded = [int]3.9 # 4 -> [int] ROUNDS to nearest, it does NOT truncate
Write-Output "[int]3.9 = $rounded"
# Surprise: exact .5 ties round to the EVEN number (banker's rounding).
Write-Output "[int]2.5 = $([int]2.5)" # 2 (not 3!)
# You can pin a variable's type by casting on the left-hand side.
[int]$score = "100"
Write-Output "score is now a $($score.GetType().Name) = $score"Without casting: 428
With [int] cast: 50
[int]3.9 = 4
[int]2.5 = 2
score is now a Int32 = 100Now you try. Input from a user always arrives as text, so before you can do maths you must cast it. Fill in the two blanks:
# 🎯 YOUR TURN — input always arrives as TEXT, so cast before doing maths.
$quantityText = "3"
$priceText = "4.50"
# 1) Cast $quantityText to a whole number
$quantity = ___ # 👉 use [int]$quantityText
# 2) Cast $priceText to a decimal number
$unitPrice = ___ # 👉 use [double]$priceText
$total = $quantity * $unitPrice
Write-Output "$quantity items at $unitPrice each, total $total"
# ✅ Expected output:
# 3 items at 4.5 each, total 13.53 items at 4.5 each, total 13.5Pro Tip
For large collections that grow in a loop, avoid $arr += item — it copies the whole array every time, which is O(n). Use a typed list instead: $list = [System.Collections.Generic.List[string]]::new() and $list.Add("x"), which is O(1). For ordered key/value data where insertion order matters, use [ordered]@{ ... } — a plain hashtable does not guarantee order.
Common Errors (and the fix)
- Single quotes when you wanted interpolation:
'User: $name'prints the literal text$name. Switch to double quotes —"User: $name"— for the value to appear. - Property access not expanding in a string:
"$server.Name"prints the whole object then the literal.Name. Wrap it in a subexpression:"$($server.Name)". - Adding text instead of numbers:
"5" + 3gives53, not8, because the left value is a string. Cast first:[int]"5" + 3→8. - Backwards null test: writing
$x -eq $nullcan misbehave when$xis an array. Always put$nullon the left:$null -eq $x. - Cannot convert value to type "System.Int32": you cast something that isn't a number, e.g.
[int]"abc". Make sure the text is numeric before casting.
📋 Quick Reference
| Task | Syntax | Result |
|---|---|---|
| String | $name = "Alice" | Alice |
| Integer | $count = 42 | 42 |
| Boolean | $ok = $true | True |
| Array | $a = @(1,2,3) | 1 2 3 |
| Hashtable | $h = @{A=1} | $h.A → 1 |
| Interpolate | "Hi $name" | Hi Alice |
| Subexpression | "$($h.A + 1)" | 2 |
| Cast to int | [int]"42" | 42 |
| Null test | $null -eq $x | True / False |
Frequently Asked Questions
Q: Why does my variable print as literal $name instead of the value?
You wrapped the text in single quotes. Single-quoted strings are literal in PowerShell — nothing inside them is expanded. Use double quotes ("Hello $name") whenever you want a variable's value substituted in.
Q: How do I put an object's property inside a string, like $server.Name?
Wrap it in a subexpression: $(...). Inside double quotes, "$server.Name" prints the whole object followed by the literal .Name, because PowerShell only expands the bare variable. Write "$($server.Name)" so the .Name part is evaluated first.
Q: Why did $typed + 8 give me 428 instead of 50?
$typed held the string "42", and with a string on the left the + operator concatenates, gluing "8" on the end. Cast it to a number first: [int]$typed + 8 gives 50. This automatic-coercion-by-left-operand surprise is the classic PowerShell trap.
Q: What's the difference between $null, "" and 0?
$null means 'no value assigned at all'. An empty string "" is a real String of length 0, and 0 is a real number. They are different things — test for unset with the idiom $null -eq $yourVar (put $null on the left).
Q: When should I use a here-string instead of normal quotes?
Reach for a here-string (@" ... "@ or @' ... '@) when your text spans multiple lines or contains lots of quotes you'd otherwise have to escape — banners, config blocks, SQL, HTML. The double-quoted form still expands variables; the single-quoted form keeps everything literal.
Mini-Challenge: Server Status Report
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. This combines a hashtable, an array, casting a bool, and a here-string — exactly the kind of small script real automation is made of.
# 🎯 MINI-CHALLENGE: Server status report
# 1. Make a hashtable $server with keys: Name, IP, and Online (a bool).
# 2. Make an array $services with 3 service names, e.g. "DNS","Web","SQL".
# 3. Use a double-quoted here-string (@" ... "@) to print a tidy report
# that shows the name, IP, whether it's online, the service count,
# and the services joined with ", " (use $($services -join ', ')).
#
# ✅ Example output:
# Server: WebServer01
# IP: 192.168.1.10
# Online: True
# Services: 3 (DNS, Web, SQL)
# your code here🎉 Lesson Complete!
- ✅ Variables start with
$and are dynamically typed: String, Int32, Double, Boolean, plus$null - ✅ Double quotes expand variables and
$(...); single quotes are literal - ✅ Wrap property/expression access in a
$(...)subexpression inside strings - ✅ Build collections with
@()(arrays) and@{ }(hashtables) - ✅ Cast with
[int]/[double]; remember+follows the left-hand type - ✅ Here-strings (
@" ... "@) keep multi-line text tidy - ✅ Next lesson: Working with Objects — pipe data between cmdlets and inspect their properties
Sign up for free to track which lessons you've completed and get learning reminders.