← Tech Guides
PowerShell — Quick Reference Guide
File Edit View Help

PowerShell

The object-oriented shell — from Windows PowerShell 5.1 to cross-platform pwsh 7+

Ready v7.5 Cross-Platform
01. Cheat Sheet

The most essential PowerShell cmdlets at a glance. Each card shows the canonical cmdlet name, common aliases, and a one-line description. Pin this section and reach for it daily.

Getting Help
Get-Help <cmd> -Examples
Alias: help, man
Show usage examples for any cmdlet. Add -Online to open docs in browser.
Get-Command -Verb Get
Alias: gcm
Find cmdlets by verb, noun, or wildcard pattern. Try gcm *-Service.
Get-Member
Alias: gm
Inspect the properties and methods of any object. Pipe anything into it.
Update-Help
No alias
Download the latest help files from Microsoft. Run as admin once after installing.
Navigation & Files
Get-ChildItem
Alias: ls, dir, gci
List files and directories. Use -Recurse and -Filter *.log.
Set-Location
Alias: cd, sl
Change the current working directory. Works with PSDrives too (e.g., cd HKLM:).
Get-Content
Alias: cat, gc, type
Read a file. Use -Tail 20 for last N lines or -Wait to follow.
Set-Content
Alias: sc
Write text to a file (overwrites). See also Add-Content to append.
Test-Path
No alias
Returns $true or $false if a path exists. Works for files, dirs, and registry keys.
New-Item
Alias: ni
Create a file or directory. Use -ItemType Directory for folders.
Pipeline Essentials
Where-Object { }
Alias: where, ?
Filter objects by condition. $_ is the current object in the pipeline.
Select-Object
Alias: select
Pick specific properties or first/last N items with -First / -Last.
ForEach-Object { }
Alias: foreach, %
Process each pipeline item. In PS7+, add -Parallel for concurrent execution.
Sort-Object
Alias: sort
Sort by one or more properties. Add -Descending to reverse.
Measure-Object
Alias: measure
Calculate count, sum, average, min, max. Use -Property Length for file sizes.
Group-Object
Alias: group
Group results by a property. Great for quick frequency counts.
System & Process
Get-Process
Alias: ps, gps
List running processes. Filter by name: Get-Process chrome.
Stop-Process
Alias: kill, spps
Terminate a process by name or ID. Add -Force if needed.
Get-Service
Alias: gsv
List Windows services and their status (Running, Stopped).
Invoke-WebRequest
Alias: iwr, curl, wget
Make HTTP requests. Access .Content, .StatusCode, .Headers on the response.

Tip: You can discover any cmdlet's aliases with Get-Alias -Definition Get-ChildItem. Aliases are handy at the prompt, but always use full cmdlet names in scripts for readability.

02. PowerShell 5.1 vs 7+

Many developers don't realize there are two very different versions of PowerShell. Windows PowerShell 5.1 (powershell.exe) ships built-in with every modern Windows installation but is frozen — no new features since 2017. PowerShell 7+ (pwsh.exe) is the actively developed, cross-platform successor built on .NET 8+. It is significantly faster, has modern language features, and runs on Windows, macOS, and Linux.

They install side by side. Installing pwsh does not remove or replace powershell.exe. You can run both at the same time, test scripts in both, and migrate at your own pace.

Performance

PowerShell 7 is built on .NET 8+ (formerly .NET Core) instead of the legacy .NET Framework 4.x. This gives it substantial performance advantages across the board.

Metric Windows PS 5.1 PowerShell 7+
Runtime .NET Framework 4.x .NET 8+ (AOT-capable)
Startup time ~600-800ms ~300-400ms
Cmdlet execution Baseline Up to 2x faster (JIT improvements)
Pipeline throughput Single-threaded only ForEach-Object -Parallel
JSON handling JavaScriptSerializer (slow) System.Text.Json (fast)

Note: The ForEach-Object -Parallel parameter alone can cut processing time dramatically for I/O-bound tasks like batch API calls or file processing across thousands of files.

Installation

PowerShell 7+ installs alongside Windows PowerShell 5.1 — both can coexist without conflict. The executable is pwsh.exe (not powershell.exe).

# Windows (winget — recommended)
winget install Microsoft.PowerShell

# macOS (Homebrew)
brew install powershell/tap/powershell

# Ubuntu / Debian
sudo apt-get update
sudo apt-get install -y powershell

# Fedora / RHEL
sudo dnf install powershell

# .NET global tool (any platform with .NET SDK)
dotnet tool install --global PowerShell

# Verify installation
pwsh --version

After installation, powershell.exe (5.1) remains untouched. You now have pwsh.exe (or pwsh on Linux/macOS) available as a separate shell.

Feature Comparison
Feature Windows PS 5.1 PowerShell 7+
Executable powershell.exe pwsh.exe
.NET Runtime .NET Framework 4.x .NET 8+
Cross-Platform Windows only Win / Mac / Linux
Ternary operator ? : No Yes
Null-coalescing ?? No Yes
Null-conditional ?. No Yes
Pipeline chains && || No Yes
ForEach-Object -Parallel No Yes
SSH Remoting No Yes
ISE Support Yes No (use VS Code)
COM / WMI Objects Full support Limited (Windows only)
Default Encoding UTF-16 LE (BOM) UTF-8 (no BOM)
JSON Default Depth 2 100
Profile Locations

Each version uses a separate profile directory. Changes to one do not affect the other.

Version $PROFILE Path
PS 5.1 Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
PS 7+ Documents\PowerShell\Microsoft.PowerShell_profile.ps1
PS 7+ (Linux/macOS) ~/.config/powershell/Microsoft.PowerShell_profile.ps1
# Check your current profile path
$PROFILE

# See all profile locations
$PROFILE | Get-Member -MemberType NoteProperty | Select-Object Name, @{
    N='Path'; E={ $PROFILE.($_.Name) }
}

# Create your profile if it doesn't exist
if (-not (Test-Path $PROFILE)) {
    New-Item -Path $PROFILE -ItemType File -Force
}
Migration Tips

Moving from 5.1 to 7+ is straightforward, but watch for these common gotchas:

Area What Changed Action
Encoding Default changed from UTF-16 LE to UTF-8 (no BOM) Review scripts that parse or write text files
JSON depth ConvertTo-Json default depth went from 2 to 100 Remove explicit -Depth params if you set them to work around 5.1
Legacy modules Some 5.1-only modules won't load natively Use Import-Module -UseWindowsPowerShell
COM / WMI COM objects limited; Get-WmiObject removed Use Get-CimInstance instead (works in both)
ISE Not supported in PS 7+ Install VS Code + PowerShell extension
# Test your scripts in both versions during migration
powershell.exe -File .\myscript.ps1    # Run in 5.1
pwsh.exe -File .\myscript.ps1          # Run in 7+

# Load a legacy 5.1-only module from PS 7
Import-Module ActiveDirectory -UseWindowsPowerShell

Switch today: winget install Microsoft.PowerShell — then set pwsh as your default in Windows Terminal under Settings > Startup. You get modern syntax, parallel pipelines, and cross-platform scripts while powershell.exe stays available for any legacy needs.

03. Core Concepts

PowerShell looks like a command-line shell, but underneath it is a full .NET scripting environment. The five concepts in this section are the foundation for everything else. Master them and the rest of PowerShell follows naturally.

Everything is an Object

This is the single most important concept in PowerShell. Unlike bash, zsh, or cmd, PowerShell pipelines do not pass text — they pass live .NET objects. When you run Get-Process, each item in the output is a System.Diagnostics.Process object with typed properties (.Name, .Id, .CPU, .WorkingSet64) and methods (.Kill(), .Refresh()).

This means you never need to parse text with grep, awk, or sed. You access structured data directly.

# Bash: text parsing required — fragile and error-prone
ps aux | grep nginx | awk '{print $2}'

# PowerShell: direct property access — always reliable
Get-Process nginx | Select-Object Id
# Explore what an object contains
Get-Process | Select-Object -First 1 | Get-Member

# Access properties directly
$proc = Get-Process -Name pwsh
$proc.Id              # Process ID (Int32)
$proc.CPU             # CPU time (Double)
$proc.WorkingSet64    # Memory in bytes (Int64)
$proc.StartTime       # DateTime object

# Call methods on objects
$date = Get-Date
$date.AddDays(30)     # Returns a new DateTime 30 days from now
$date.ToString("yyyy-MM-dd")

Why it matters: Object-based pipelines eliminate an entire class of bugs. No broken parsing when a filename has spaces, no locale-dependent date formats, no column alignment shifts. The data is always structured and typed.

Verb-Noun Naming Convention

Every cmdlet follows a strict Verb-Noun naming pattern. The verb describes the action; the noun describes what you act on. This makes PowerShell remarkably discoverable — once you know the pattern, you can guess cmdlet names before you look them up.

Verb Meaning Example
Get Retrieve / read data Get-Process, Get-Content
Set Modify / update Set-Content, Set-Location
New Create something New-Item, New-Object
Remove Delete / destroy Remove-Item, Remove-Module
Start Begin an operation Start-Process, Start-Job
Stop End an operation Stop-Process, Stop-Service
Invoke Execute / call Invoke-WebRequest, Invoke-Command
Test Check / validate Test-Path, Test-Connection
Import Bring data in Import-Module, Import-Csv
Export Send data out Export-Csv, Export-Clixml
ConvertTo Transform format ConvertTo-Json, ConvertTo-Html
ConvertFrom Parse format ConvertFrom-Json, ConvertFrom-Csv
# Find all cmdlets that use a specific verb
Get-Command -Verb "Test"

# Find all cmdlets that work with a specific noun
Get-Command -Noun "*Service*"

# List all approved verbs (for writing your own cmdlets)
Get-Verb | Sort-Object Verb
The Pipeline

The pipe operator | passes objects from one cmdlet to the next. Unlike Unix pipes that pass byte streams, PowerShell pipes carry full .NET objects with all their properties and methods intact. You chain cmdlets together to build multi-stage data processing workflows without intermediate variables.

# Multi-stage pipeline: find large files, sort, and format
Get-ChildItem -Recurse -File |
    Where-Object { $_.Length -gt 1MB } |
    Sort-Object Length -Descending |
    Select-Object Name, @{N='SizeMB';E={[math]::Round($_.Length/1MB,2)}} |
    Format-Table -AutoSize

Each stage in the pipeline receives objects from the previous stage. The $_ automatic variable (or $PSItem) represents the current object being processed.

# Pipeline stage breakdown:
#   1. Get-ChildItem  — emits FileInfo objects
#   2. Where-Object   — filters, passes only files > 1MB
#   3. Sort-Object    — reorders by Length descending
#   4. Select-Object  — creates new objects with Name + calculated SizeMB
#   5. Format-Table   — renders output as a table

# Real-world example: find services that failed to start
Get-Service |
    Where-Object { $_.StartType -eq 'Automatic' -and $_.Status -ne 'Running' } |
    Select-Object Name, Status, StartType |
    Sort-Object Name

Caution: Format-* cmdlets (Format-Table, Format-List) produce formatting objects, not data objects. Always place them at the end of a pipeline. Never pipe Format output into Where-Object or Export-Csv — you'll get garbled results.

The Help System

PowerShell has the most comprehensive built-in help system of any shell. Three discovery cmdlets form the backbone of self-directed learning:

Cmdlet Purpose When to Use
Get-Help Read documentation "How do I use this cmdlet?"
Get-Command Find cmdlets "Is there a cmdlet that does X?"
Get-Member Inspect objects "What properties does this object have?"
# Get-Help: read the manual for a cmdlet
Get-Help Get-Process                # Synopsis + syntax
Get-Help Get-Process -Examples      # Practical examples
Get-Help Get-Process -Full          # Everything: params, notes, examples
Get-Help Get-Process -Online        # Open Microsoft docs in browser
Get-Help about_Operators            # Conceptual "about_" topics

# Get-Command: find cmdlets by pattern
Get-Command *-Service*               # Wildcards in name
Get-Command -Verb Get -Noun *Net*   # Filter by verb + noun
Get-Command -Module Microsoft.PowerShell.Management  # By module

# Get-Member: discover what an object can do
Get-Process | Get-Member                # All members
Get-Process | Get-Member -MemberType Property  # Properties only
Get-Process | Get-Member -MemberType Method    # Methods only
"hello" | Get-Member                    # Works on any object

Tip: Run Update-Help once (as admin) after installing PowerShell. Without it, Get-Help returns minimal stubs. After updating, you get complete documentation with examples for every cmdlet.

Providers & Drives

PowerShell providers expose different data stores as file-system-like drives. You can use the same cmdlets (Get-ChildItem, Set-Location, New-Item, Remove-Item) to navigate and manipulate registries, environment variables, certificates, and more — exactly like you browse files.

Provider Drive(s) What It Exposes
FileSystem C:, D:, etc. Files and directories on disk
Registry HKLM:, HKCU: Windows Registry hives
Environment Env: Environment variables
Alias Alias: Command aliases
Function Function: Loaded functions
Variable Variable: All session variables
Certificate Cert: X.509 certificates (Windows)
# Browse the Windows Registry like a file system
Set-Location HKLM:\SOFTWARE\Microsoft
Get-ChildItem

# List all environment variables
Get-ChildItem Env:

# Access a specific env var (two ways)
$env:PATH
(Get-Item Env:PATH).Value

# See all loaded aliases
Get-ChildItem Alias: | Where-Object { $_.Name -like "g*" }

# Create a new environment variable
New-Item -Path Env: -Name MY_VAR -Value "hello"

# List all available PSDrives
Get-PSDrive

# Create a custom drive mapping
New-PSDrive -Name Projects -PSProvider FileSystem -Root "C:\Users\me\Projects"
Set-Location Projects:

Note: The Registry and Certificate providers are Windows-only. On Linux and macOS, you have FileSystem, Environment, Alias, Function, and Variable providers. Third-party modules can add custom providers (e.g., cloud storage, databases).

04. Variables & Data Types

PowerShell variables are prefixed with $, are case-insensitive, and do not require declaration — just assign a value and the variable is created with the appropriate .NET type. Because PowerShell is built on .NET, every value is a typed object: strings are System.String, numbers are System.Int32 or System.Double, and so on.

Variable Basics

Variables start with $ and are case-insensitive. No declaration keyword is needed — assign with = and the type is inferred automatically.

$name = "PowerShell"        # String
$count = 42                 # Int32
$pi = 3.14159              # Double
$active = $true            # Boolean
$nothing = $null           # Null

# Check a variable's type
$name.GetType().Name       # String
$count.GetType().Name      # Int32

# Variables are case-insensitive
$Name -eq $NAME            # True — same variable

# Remove a variable
Remove-Variable -Name name
# Or use the Variable: drive
Remove-Item Variable:\count
Strings

Single-quoted strings are literal — no variable expansion. Double-quoted strings expand variables and subexpressions. Here-strings handle multiline content.

$name = "World"
'Hello $name'              # Hello $name (literal)
"Hello $name"              # Hello World (expanded)
"Files: $((Get-ChildItem).Count)"  # Subexpression

# Here-strings (multiline)
$multiline = @"
Line 1: $name
Line 2: $(Get-Date)
"@

# Literal here-string (no expansion)
$template = @'
No $variables expanded here.
Use this for regex patterns or templates.
'@

Common string methods — every string is a [System.String] object:

$s = "  Hello, World!  "
$s.ToUpper()               # "  HELLO, WORLD!  "
$s.ToLower()               # "  hello, world!  "
$s.Trim()                  # "Hello, World!"
$s.Replace("World", "PS")  # "  Hello, PS!  "
$s.Split(",")              # @("  Hello", " World!  ")
$s.Contains("World")       # True
$s.StartsWith("  H")      # True
$s.Substring(2, 5)        # "Hello"

Tip: Use the subexpression operator $() inside double-quoted strings for anything beyond a simple variable — property access, method calls, and expressions all need it: "Today is $((Get-Date).DayOfWeek)".

Arrays

Arrays are created with @() or comma-separated values. They are zero-indexed, support negative indexing from the end, and can hold mixed types.

$colors = @("Red", "Green", "Blue")
$numbers = 1, 2, 3, 4, 5
$empty = @()
$range = 1..100

$colors[0]          # Red
$colors[-1]         # Blue (last item)
$colors[0..1]      # Red, Green (slice)
$colors += "Yellow" # Add element (creates new array)
$colors.Count       # 4

# Check if array contains a value
$colors -contains "Red"     # True
"Red" -in $colors            # True (PS 3.0+)

# Strongly typed array
[int[]] $ids = 1, 2, 3
$ids += "not a number"      # Error — type enforced

Caution: PowerShell arrays are fixed-size. The += operator creates an entirely new array each time, copying every element. For loops that add thousands of items, use a [System.Collections.Generic.List[object]] instead and call .Add().

Hashtables

Hashtables are key-value stores created with @{}. Keys are accessed with dot notation or bracket syntax. By default, key order is not preserved — use [ordered] if order matters.

$server = @{
    Name     = "web01"
    IP       = "192.168.1.10"
    Port     = 443
    Enabled  = $true
}
$server.Name              # web01
$server['IP']             # 192.168.1.10
$server.Keys              # Name, IP, Port, Enabled
$server.Values            # web01, 192.168.1.10, 443, True
$server.ContainsKey('IP') # True

# Add and remove keys
$server.OS = "Windows"    # Add new key
$server.Remove('Enabled') # Remove key

# Ordered hashtable (preserves insertion order)
$config = [ordered]@{
    Host     = "localhost"
    Port     = 5432
    Database = "myapp"
}
PSCustomObject

The modern way to create structured data objects (PS 3.0+). Faster than New-Object, maintains property order, and is the idiomatic way to produce function output.

$user = [PSCustomObject]@{
    Name     = "jsmith"
    Email    = "john@example.com"
    Role     = "Admin"
    Created  = (Get-Date)
}
$user.Name    # jsmith
$user | Format-Table

# Add a property after creation
$user | Add-Member -NotePropertyName "Department" -NotePropertyValue "IT"

# Create multiple objects (common in scripts)
$report = foreach ($svc in Get-Service) {
    [PSCustomObject]@{
        Name   = $svc.DisplayName
        Status = $svc.Status
        Type   = $svc.StartType
    }
}
$report | Export-Csv -Path services.csv -NoTypeInformation
Type Accelerators

Type accelerators are shorthand aliases for common .NET types. Use them for casting, variable constraints, and parameter declarations.

Accelerator .NET Type Example
[int] System.Int32 [int]"42"42
[string] System.String [string]42"42"
[bool] System.Boolean [bool]1$true
[datetime] System.DateTime [datetime]"2025-01-15"
[array] System.Object[] [array]$single → wraps in array
[hashtable] System.Collections.Hashtable Type-check a parameter
[regex] System.Text.RegularExpressions.Regex [regex]::Match($s, '\d+')
[pscustomobject] System.Management.Automation.PSObject Structured data objects
[xml] System.Xml.XmlDocument [xml]$doc = Get-Content file.xml
[ipaddress] System.Net.IPAddress [ipaddress]"10.0.0.1"
# Casting examples
[int]"42"                  # String to integer
[datetime]"2025-03-15"     # String to DateTime
[string]3.14              # Double to string

# Constrain a variable to a type
[int] $port = 8080
$port = "not a number"    # Error — type constraint enforced
Automatic Variables

PowerShell defines many automatic variables that provide session state, pipeline context, and execution information. These are read-only (with a few exceptions).

Variable Description
$_ / $PSItem Current pipeline object
$PSVersionTable PowerShell version info (PSVersion, PSEdition, OS)
$PROFILE Path to the current user's profile script
$HOME User home directory path
$PWD Current working directory (PathInfo object)
$null Null value — used to discard output or test emptiness
$true / $false Boolean constants
$Error Collection of recent errors (most recent first)
$LASTEXITCODE Exit code of the last native command (e.g., git)
$? Boolean — $true if last command succeeded
$args Array of unbound arguments passed to a function
$Matches Hashtable of regex capture groups from -match
$env:VAR Access environment variables (e.g., $env:PATH)
# Check your PowerShell version
$PSVersionTable.PSVersion

# Use $_ in pipeline filters
Get-Service | Where-Object { $_.Status -eq 'Running' }

# Capture regex matches
"Error 404: Not Found" -match '(\d{3})'
$Matches[1]              # 404

# Check if last native command succeeded
git status
if ($LASTEXITCODE -ne 0) { "Git command failed" }
05. Control Flow

PowerShell's control flow uses familiar structures — if/elseif/else, switch, foreach, while — but with its own comparison operators and some unique twists. The biggest difference from most languages: PowerShell uses word-based operators (-eq, -gt, -like) instead of symbols (==, >).

Comparison Operators

PowerShell comparison operators are case-insensitive by default. Prefix with c for case-sensitive variants (-ceq, -clike, -cmatch).

Operator Meaning Example
-eq Equal to 5 -eq 5$true
-ne Not equal to "a" -ne "b"$true
-gt Greater than 10 -gt 5$true
-ge Greater or equal 5 -ge 5$true
-lt Less than 3 -lt 5$true
-le Less or equal 5 -le 5$true
-like Wildcard match "hello" -like "hel*"$true
-notlike Wildcard non-match "hello" -notlike "bye*"$true
-match Regex match "abc123" -match '\d+'$true
-notmatch Regex non-match "hello" -notmatch '\d+'$true
-contains Collection contains value @(1,2,3) -contains 2$true
-notcontains Collection missing value @(1,2,3) -notcontains 5$true
-in Value in collection 2 -in @(1,2,3)$true
-notin Value not in collection 5 -notin @(1,2,3)$true

Logical operators: PowerShell uses -and, -or, -not (or !), and -xor for boolean logic. Example: if ($age -ge 18 -and $hasID) { "Allowed" }

If / ElseIf / Else

Standard conditional branching. Conditions are enclosed in parentheses and bodies in braces. PowerShell 7+ adds a ternary operator for simple inline conditionals.

$score = 85
if ($score -ge 90) {
    "Grade: A"
} elseif ($score -ge 80) {
    "Grade: B"
} elseif ($score -ge 70) {
    "Grade: C"
} else {
    "Grade: F"
}

# Ternary operator (PS 7+ only)
$grade = ($score -ge 90) ? "A" : "B-or-lower"

# Null-coalescing operator (PS 7+ only)
$name = $inputName ?? "default"

# Null-conditional member access (PS 7+ only)
$length = ${myString}?.Length
Switch Statement

PowerShell's switch is far more powerful than most languages. It supports wildcards, regex, script block conditions, and can process arrays — matching each element against all clauses.

# Basic switch
switch ($status) {
    "Running" { "Active"; break }
    "Stopped" { "Inactive"; break }
    default   { "Unknown" }
}

# With -Wildcard
switch -Wildcard ($filename) {
    "*.txt"  { "Text file" }
    "*.log"  { "Log file" }
    "*.json" { "JSON file" }
}

# With -Regex
switch -Regex ($line) {
    '^\d+$'       { "Number" }
    '^[a-z]+$'    { "Word" }
    '^#'          { "Comment" }
}

# Process array (each element tested against all clauses)
switch (1, 2, 3, 4, 5) {
    {$_ -gt 3} { "$_ is greater than 3" }
    {$_ % 2}   { "$_ is odd" }
}

Tip: Without break, switch falls through and tests all remaining clauses. This lets a single value match multiple conditions — useful when you want cumulative matches. Use break for exclusive matching.

Loops

PowerShell provides five loop constructs. The foreach statement is the most common for iterating collections, while while and do-until are ideal for polling and retry logic.

# ForEach (statement form — loads entire collection first)
foreach ($file in Get-ChildItem *.txt) {
    "Processing: $($file.Name)"
}

# For loop (classic counter-based)
for ($i = 0; $i -lt 10; $i++) {
    "Iteration $i"
}

# While (test condition first)
$attempts = 0
while ($attempts -lt 3) {
    try { Connect-Server; break }
    catch { $attempts++ }
}

# Do-While (runs at least once, then tests)
do {
    $input = Read-Host "Enter 'quit' to exit"
} while ($input -ne 'quit')

# Do-Until (runs at least once, stops when condition is true)
do {
    Start-Sleep -Seconds 5
    $status = Get-ServiceStatus
} until ($status -eq 'Ready')

foreach statement vs ForEach-Object cmdlet: The foreach ($x in $collection) statement loads the full collection into memory first — faster, but higher memory use. The ForEach-Object cmdlet (%) streams one object at a time through the pipeline — slower, but memory-efficient for large datasets.

Break, Continue, Return

Three keywords for controlling execution flow within loops and functions.

# break — exit the current loop entirely
foreach ($n in 1..100) {
    if ($n -eq 5) { break }   # Stops at 5, exits loop
    $n
}

# continue — skip rest of current iteration, go to next
foreach ($n in 1..10) {
    if ($n % 2 -eq 0) { continue }  # Skip even numbers
    $n                          # Outputs: 1, 3, 5, 7, 9
}

# return — exit the current function (and emit a value)
function Find-First {
    param([string[]]$Items, [string]$Pattern)
    foreach ($item in $Items) {
        if ($item -match $Pattern) {
            return $item     # Exits function, returns match
        }
    }
}
06. Functions

Functions in PowerShell range from simple one-liners to full-featured cmdlets with parameter validation, pipeline support, and built-in -WhatIf / -Confirm safety. The key insight: adding [CmdletBinding()] transforms a script function into an "advanced function" that behaves exactly like a compiled cmdlet.

Basic Functions

Functions use the function keyword. Parameters go in a param() block. Return values are implicit — the last expression output is the return value.

# Simple function with a default parameter
function Get-Greeting {
    param([string]$Name = "World")
    "Hello, $Name!"
}
Get-Greeting -Name "PowerShell"  # Hello, PowerShell!
Get-Greeting                      # Hello, World!

# Return values are implicit — last expression is output
function Add-Numbers {
    param([int]$A, [int]$B)
    $A + $B    # This is the return value
}
$sum = Add-Numbers -A 5 -B 3  # 8

Caution: Every uncaptured expression in a function is part of the output. A stray $variable on its own line, a Write-Output, or even some method calls will pollute your return value. Use $null = expression or pipe to Out-Null to suppress unwanted output.

Advanced Functions (CmdletBinding)

Adding [CmdletBinding()] before param() transforms a simple function into an advanced function. It automatically gains common parameters like -Verbose, -ErrorAction, -Debug, -WarningAction, and more — the same parameters every compiled cmdlet has.

function Get-DiskSpace {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true, Position=0)]
        [string]$ComputerName,

        [Parameter()]
        [ValidateSet("GB", "MB", "TB")]
        [string]$Unit = "GB",

        [Parameter()]
        [switch]$IncludeRemovable
    )

    Write-Verbose "Querying $ComputerName..."
    # Function body here
}

# Call with -Verbose to see debug messages
Get-DiskSpace -ComputerName "web01" -Verbose
# Positional param: ComputerName is Position=0
Get-DiskSpace "web01" -Unit MB
Parameter Validation

Validation attributes enforce constraints before your function body runs. Invalid input produces clear error messages automatically.

Attribute Purpose Example
[ValidateNotNullOrEmpty()] Reject null/empty values Required string params
[ValidateSet()] Restrict to listed values [ValidateSet("Dev","Prod")]
[ValidateRange()] Numeric min/max range [ValidateRange(1, 65535)]
[ValidatePattern()] Regex pattern match [ValidatePattern('^\d{3}-\d{4}$')]
[ValidateLength()] String length min/max [ValidateLength(1, 50)]
[ValidateCount()] Array element count [ValidateCount(1, 10)]
[ValidateScript()] Custom validation logic [ValidateScript({Test-Path $_})]
function New-Deployment {
    [CmdletBinding()]
    param(
        [ValidateNotNullOrEmpty()]
        [string]$AppName,

        [ValidateSet("Dev", "Staging", "Production")]
        [string]$Environment = "Dev",

        [ValidateRange(1, 65535)]
        [int]$Port = 8080,

        [ValidateScript({ Test-Path $_ })]
        [string]$ConfigPath
    )
    # All params are validated before this line executes
    "Deploying $AppName to $Environment on port $Port"
}
Pipeline Input

Advanced functions can accept pipeline input using the begin / process / end blocks. The process block runs once per pipeline object, while begin and end run once each.

function Convert-ToUpperCase {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline=$true)]
        [string]$Text
    )
    begin   { $results = @() }
    process { $results += $Text.ToUpper() }
    end     { $results }
}

"hello", "world" | Convert-ToUpperCase
# HELLO
# WORLD

# Accept objects by property name
function Get-FileAge {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipelineByPropertyName=$true)]
        [string]$FullName
    )
    process {
        $file = Get-Item $FullName
        [PSCustomObject]@{
            Name = $file.Name
            Age  = (Get-Date) - $file.LastWriteTime
        }
    }
}
# Works because FileInfo objects have a FullName property
Get-ChildItem *.log | Get-FileAge
ShouldProcess (WhatIf / Confirm)

For functions that modify or delete data, SupportsShouldProcess adds automatic -WhatIf (preview mode) and -Confirm (prompt before each action) parameters. This is expected for any function that follows PowerShell conventions.

function Remove-OldLogs {
    [CmdletBinding(SupportsShouldProcess=$true)]
    param(
        [string]$Path,
        [int]$DaysOld = 30
    )

    $old = Get-ChildItem $Path -Filter *.log |
        Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$DaysOld) }

    foreach ($file in $old) {
        if ($PSCmdlet.ShouldProcess($file.Name, "Delete")) {
            Remove-Item $file.FullName
        }
    }
}

# Preview what would be deleted (no actual deletion)
Remove-OldLogs -Path "C:\Logs" -WhatIf

# Ask for confirmation before each deletion
Remove-OldLogs -Path "C:\Logs" -Confirm

Best practice: Every function that creates, modifies, or deletes data should include SupportsShouldProcess. The -WhatIf flag is invaluable for testing scripts in production — you can see exactly what would happen without any risk.

07. Objects & Pipeline

The pipeline is PowerShell's superpower. Objects flow from one command to the next, preserving their structure. Mastering Select-Object, Where-Object, ForEach-Object, Sort-Object, and Group-Object lets you slice, filter, transform, and summarize data without writing loops or parsing text.

Understanding Objects

Everything in PowerShell is a .NET object. Use Get-Member to inspect any object's type, properties, and methods.

Get-Process | Get-Member
# Shows TypeName: System.Diagnostics.Process
# Lists Properties: Name, Id, CPU, WorkingSet64...
# Lists Methods: Kill(), Refresh(), ToString()...

# Access properties directly
(Get-Process pwsh).Id
(Get-Date).DayOfWeek
Select-Object

Pick specific properties, create calculated properties, or grab the first/last items from a collection.

# Pick specific properties
Get-Process | Select-Object Name, Id, CPU

# Calculated properties (rename/transform)
Get-ChildItem | Select-Object Name,
    @{Name='SizeKB'; Expression={[math]::Round($_.Length/1KB, 2)}},
    @{Name='Modified'; Expression={$_.LastWriteTime.ToString('yyyy-MM-dd')}}

# ExpandProperty (unwrap single property)
Get-Process | Select-Object -ExpandProperty Name

# First / Last / Skip
Get-Process | Sort-Object CPU -Descending | Select-Object -First 5
Get-EventLog -LogName System -Newest 100 | Select-Object -Last 10
Where-Object

Filter objects with script blocks or simplified syntax. Only objects that match the condition pass through.

# Script block syntax
Get-Service | Where-Object { $_.Status -eq 'Running' }

# Simplified syntax (PS 3.0+)
Get-Service | Where-Object Status -eq Running

# Multiple conditions
Get-Process | Where-Object {
    $_.CPU -gt 10 -and
    $_.WorkingSet64 -gt 100MB
}

# Comparison with wildcards
Get-ChildItem | Where-Object Name -like "*.log"
ForEach-Object

Process each pipeline item with a script block, method shorthand, or parallel execution.

# Script block
Get-ChildItem *.txt | ForEach-Object {
    "File: $($_.Name) — Size: $($_.Length) bytes"
}

# Method shorthand (PS 4.0+)
(Get-Service).Name

# begin/process/end blocks
1..10 | ForEach-Object -Begin { $sum = 0 } -Process { $sum += $_ } -End { "Total: $sum" }

# Parallel execution (PS 7+ ONLY)
1..50 | ForEach-Object -Parallel {
    Start-Sleep -Milliseconds 500
    "Processed item $_"
} -ThrottleLimit 10

Game-changer: ForEach-Object -Parallel (PowerShell 7+ only) runs script blocks concurrently in separate runspaces. Use -ThrottleLimit to control concurrency. Variables from the parent scope must be passed via $using: — e.g., $using:myVar.

Sort-Object, Group-Object, Measure-Object

The trio for ordering, grouping, and summarizing pipeline data.

# Sort by one or more properties
Get-Process | Sort-Object CPU -Descending
Get-ChildItem | Sort-Object Extension, Name

# Group by property
Get-Service | Group-Object Status
# Output: Count Name      Group
#         76    Running   {AarSvc_2c8db, ...}
#         86    Stopped   {AJRouter, ...}

# Measure (count, sum, average, min, max)
Get-ChildItem -File | Measure-Object -Property Length -Sum -Average -Maximum
# Output: Count: 42, Sum: 1234567, Average: 29394.2, Maximum: 512000

# Compare two collections
$before = Get-Process
# ... do something ...
$after = Get-Process
Compare-Object $before $after -Property Name
Formatting vs Filtering

Format cmdlets produce display objects, not data. They must always be the last command in any pipeline.

Critical rule: Format-Table, Format-List, Format-Wide, and Format-Custom destroy structured data. Never pipe their output to another cmdlet — the objects become formatting instructions, not usable data.

# WRONG — formatting destroys data
Get-Process | Format-Table | Where-Object { $_.CPU -gt 10 }

# RIGHT — filter first, format last
Get-Process | Where-Object CPU -gt 10 | Format-Table Name, CPU, Id
08. File System & I/O

PowerShell treats the file system as one of many "drives" accessible through a unified provider model. The same Get-ChildItem, Get-Content, and Set-Content cmdlets work on files, registry keys, certificates, and more. This section focuses on everyday file operations and data format conversions.

Reading Files
# Read file line by line (returns string array)
$lines = Get-Content -Path "log.txt"

# Read entire file as single string
$raw = Get-Content -Path "config.json" -Raw

# Read first/last N lines
Get-Content "log.txt" -Head 10
Get-Content "log.txt" -Tail 20

# Follow a log file (like tail -f)
Get-Content "app.log" -Tail 10 -Wait
Writing Files
# Overwrite file
"Hello World" | Set-Content -Path "output.txt"

# Append to file
"New entry" | Add-Content -Path "log.txt"

# Pipe command output to file
Get-Process | Out-File -Path "processes.txt"

# With encoding control
Set-Content -Path "utf8.txt" -Value "Content" -Encoding UTF8
Path Operations
# Test if path exists
Test-Path "C:\Projects\file.txt"

# Join path segments (cross-platform safe)
Join-Path -Path $HOME -ChildPath "Documents" "scripts"

# Split path components
Split-Path "C:\Users\John\file.txt" -Parent      # C:\Users\John
Split-Path "C:\Users\John\file.txt" -Leaf         # file.txt
Split-Path "C:\Users\John\file.txt" -Extension    # .txt (PS 6+)

# Resolve relative to absolute
Resolve-Path ".\relative\path"
CSV Operations
# Import CSV (auto-creates objects with column headers as properties)
$users = Import-Csv "users.csv"
$users | Where-Object Department -eq "Engineering"

# Export to CSV
Get-Process | Select-Object Name, CPU, Id |
    Export-Csv "processes.csv" -NoTypeInformation

# Import with custom delimiter
Import-Csv "data.tsv" -Delimiter "`t"
JSON Operations
# Read JSON file
$config = Get-Content "config.json" -Raw | ConvertFrom-Json
$config.database.host

# Create and write JSON
$data = @{
    server = "localhost"
    port = 8080
    features = @("auth", "logging", "cache")
} | ConvertTo-Json -Depth 10

$data | Set-Content "config.json"

# Convert objects to JSON (great for APIs)
Get-Process | Select-Object Name, Id, CPU |
    ConvertTo-Json | Set-Content "processes.json"

Watch out: In Windows PowerShell 5.1, ConvertTo-Json defaults to -Depth 2 — any nested objects beyond that are silently converted to their .ToString() representation. PowerShell 7+ raised the default to 100, but for cross-version safety, always specify -Depth 10 (or higher) explicitly.

XML Operations
# Read XML
[xml]$xml = Get-Content "data.xml"
$xml.configuration.appSettings.add

# XPath queries
$xml.SelectNodes("//setting[@name='timeout']")

# Create XML from objects
$data | Export-Clixml "backup.xml"
$restored = Import-Clixml "backup.xml"
File Search
# Recursive file search
Get-ChildItem -Path "C:\Projects" -Recurse -Filter "*.ps1"

# Find large files
Get-ChildItem -Recurse -File | Where-Object Length -gt 100MB |
    Sort-Object Length -Descending |
    Select-Object FullName, @{N='SizeMB';E={[math]::Round($_.Length/1MB)}}

# Search file contents (like grep)
Get-ChildItem -Recurse -Filter "*.ps1" |
    Select-String -Pattern "Write-Error"
09. Error Handling

PowerShell has two distinct error types and a rich set of mechanisms for catching, suppressing, and recovering from them. Understanding the difference between terminating and non-terminating errors is the key to writing robust scripts that fail gracefully instead of silently continuing with bad data.

Error Types

PowerShell distinguishes between errors that halt execution and errors that report a problem but keep going.

Type Behavior Examples Caught by try/catch?
Terminating Stops execution immediately throw, command not found, type conversion errors, -ErrorAction Stop Yes
Non-terminating Reports error, continues to next item File not found, permission denied, network timeout No (unless -ErrorAction Stop)

Key insight: Most cmdlet errors are non-terminating by default. This means try/catch will not catch them unless you add -ErrorAction Stop to the command. This is the most common PowerShell error-handling mistake.

Try / Catch / Finally

The structured error-handling pattern. Catch specific exception types first, then use a general catch-all. The finally block always executes — ideal for cleanup.

try {
    $content = Get-Content "missing.txt" -ErrorAction Stop
    $data = $content | ConvertFrom-Json
}
catch [System.IO.FileNotFoundException] {
    Write-Warning "File not found: $($_.Exception.Message)"
}
catch [System.ArgumentException] {
    Write-Warning "Invalid JSON: $($_.Exception.Message)"
}
catch {
    # Catch-all for any other error
    Write-Error "Unexpected error: $($_.Exception.Message)"
    Write-Error "At line: $($_.InvocationInfo.ScriptLineNumber)"
}
finally {
    # Always executes — cleanup code
    if ($connection) { $connection.Close() }
}
ErrorAction Parameter

The -ErrorAction common parameter controls how a single command responds to non-terminating errors.

Value Behavior
Stop Convert to terminating error (caught by try/catch)
Continue Display error, keep running (default)
SilentlyContinue Suppress display, add to $Error, keep running
Ignore Suppress completely (not added to $Error)
Inquire Prompt user to choose action

Best practice: use -ErrorAction per command, not $ErrorActionPreference globally.

# Convert non-terminating to terminating (catches in try/catch)
Get-Item "C:\missing" -ErrorAction Stop

# Suppress errors silently
$result = Get-Item "C:\maybe" -ErrorAction SilentlyContinue

# Store error in variable without displaying
Get-Item "C:\maybe" -ErrorAction SilentlyContinue -ErrorVariable err
if ($err) { Write-Warning "Item not found" }
throw vs Write-Error

Two ways to signal errors from your own code, with very different outcomes.

Command Error Type Use When
throw Terminating Critical failure — cannot continue
Write-Error Non-terminating Recoverable issue — process remaining items
# Use throw for critical failures
function Connect-Database {
    param([string]$ConnectionString)
    if (-not $ConnectionString) {
        throw "ConnectionString is required"
    }
    # ...
}

# Use Write-Error for recoverable issues
function Get-UserData {
    [CmdletBinding()]
    param([string[]]$UserNames)
    foreach ($name in $UserNames) {
        $user = Find-User $name
        if (-not $user) {
            Write-Error "User not found: $name"
            continue  # Process remaining users
        }
        $user
    }
}
The $Error Collection

PowerShell maintains an automatic $Error ArrayList that stores every error from the current session, most recent first.

# View recent errors
$Error[0]                            # Most recent error
$Error[0].Exception.Message          # Just the message
$Error[0].InvocationInfo             # Where it happened
$Error[0].ScriptStackTrace           # Call stack
$Error.Count                         # Number of errors in session
$Error.Clear()                       # Reset error collection

# Automatic variables
$?                                   # $true if last command succeeded
$LASTEXITCODE                        # Exit code of last native command
Real-World Error Pattern

A complete, production-ready error-handling template combining typed exceptions, scoped preferences, and re-throw for callers.

function Invoke-SafeOperation {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Path
    )

    $ErrorActionPreference = 'Stop'  # OK inside function scope

    try {
        Write-Verbose "Processing: $Path"
        if (-not (Test-Path $Path)) {
            throw [System.IO.FileNotFoundException]::new("Path not found: $Path")
        }

        $data = Get-Content $Path -Raw | ConvertFrom-Json
        return $data
    }
    catch [System.IO.FileNotFoundException] {
        Write-Warning $_.Exception.Message
        return $null
    }
    catch [System.Text.Json.JsonException] {
        Write-Error "Invalid JSON in $Path"
        return $null
    }
    catch {
        Write-Error "Unexpected: $_"
        throw  # Re-throw to caller
    }
}
10. Modules & Package Management

Modules are the primary way to extend PowerShell. The PowerShell Gallery (PSGallery) is the central repository — think npm for PowerShell. Every module is a self-contained package of cmdlets, functions, and resources that you can install, import, and share with a single command.

Finding Modules
# Search the PowerShell Gallery
Find-Module -Name "*Azure*"
Find-Module -Tag "DevOps" | Select-Object Name, Description

# Get module details
Find-Module PSScriptAnalyzer | Select-Object Name, Version, Description, Author
Installing & Managing
# Install for current user (no admin needed)
Install-Module -Name Pester -Scope CurrentUser

# Install specific version
Install-Module -Name Az -RequiredVersion 12.0.0

# Update modules
Update-Module -Name Pester

# List installed modules
Get-InstalledModule
Get-Module -ListAvailable    # All available (including built-in)

# Import into current session
Import-Module -Name Pester

# Remove module
Uninstall-Module -Name OldModule

# Save for inspection (download without installing)
Save-Module -Name UnknownModule -Path "C:\Inspect"
Essential Modules

Must-know modules from the PowerShell Gallery and built-in library.

Module Purpose Install
PSReadLine Command-line editing, prediction Built-in (PS 7)
Pester Testing framework Install-Module Pester
PSScriptAnalyzer Static code analysis (linter) Install-Module PSScriptAnalyzer
Az Azure management Install-Module Az
AWS.Tools AWS management Install-Module AWS.Tools.Common
Microsoft.Graph Microsoft 365 / Entra ID Install-Module Microsoft.Graph
ImportExcel Excel without Office Install-Module ImportExcel
Plaster Project scaffolding Install-Module Plaster
Creating Modules

A module is a .psm1 file containing functions, paired with an optional .psd1 manifest that declares metadata and exports.

# File: MyModule/MyModule.psm1
function Get-Greeting {
    param([string]$Name = "World")
    "Hello, $Name!"
}

function Get-SystemReport {
    [PSCustomObject]@{
        Hostname = $env:COMPUTERNAME
        OS       = (Get-CimInstance Win32_OperatingSystem).Caption
        Uptime   = (Get-Date) - (Get-CimInstance Win32_OperatingSystem).LastBootUpTime
        Memory   = "{0:N2} GB" -f ((Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory / 1GB)
    }
}

Export-ModuleMember -Function Get-Greeting, Get-SystemReport

# Create module manifest
New-ModuleManifest -Path "MyModule.psd1" `
    -RootModule "MyModule.psm1" `
    -ModuleVersion "1.0.0" `
    -Author "Your Name" `
    -Description "My custom tools" `
    -FunctionsToExport @('Get-Greeting', 'Get-SystemReport')
Script Analysis

PSScriptAnalyzer is the official PowerShell linter. Use it to catch common mistakes, enforce best practices, and auto-fix formatting issues.

# Analyze a script for issues
Invoke-ScriptAnalyzer -Path "MyScript.ps1"

# Fix auto-fixable issues
Invoke-ScriptAnalyzer -Path "MyScript.ps1" -Fix

# Check specific rules
Invoke-ScriptAnalyzer -Path "." -IncludeRule PSAvoidUsingCmdletAliases
11. Remoting

PowerShell Remoting lets you run commands on remote machines as if you were sitting at their console. Windows uses WinRM (WS-Management) by default, while PowerShell 7+ adds SSH-based remoting for true cross-platform management — Windows, Linux, and macOS.

WinRM Remoting (Windows)
# Enable remoting (run as Administrator)
Enable-PSRemoting -Force

# Interactive session (one-to-one)
Enter-PSSession -ComputerName Server01
hostname          # Shows "Server01"
Get-Process       # Runs on Server01
Exit-PSSession

# Run command on remote machines (one-to-many)
Invoke-Command -ComputerName Server01, Server02, Server03 -ScriptBlock {
    Get-Service W3SVC | Select-Object Status, Name
}

# With credentials
$cred = Get-Credential
Invoke-Command -ComputerName Server01 -Credential $cred -ScriptBlock {
    Restart-Service W3SVC
}

# Persistent session (reusable connection)
$session = New-PSSession -ComputerName Server01
Invoke-Command -Session $session -ScriptBlock { Get-Process }
Invoke-Command -Session $session -ScriptBlock { Get-Service }
Remove-PSSession $session
SSH Remoting (PS 7+ Cross-Platform)

Tip: SSH remoting is a PS 7+ feature that enables true cross-platform management — connect from Windows to Linux or vice versa. No Enable-PSRemoting needed on the target; just an SSH server with the PowerShell subsystem configured.

# Connect via SSH (no Enable-PSRemoting needed!)
Enter-PSSession -HostName user@linux-server.com

# With key-based auth
Enter-PSSession -HostName server.com -UserName admin -KeyFilePath ~/.ssh/id_rsa

# Run commands on Linux from Windows
Invoke-Command -HostName ubuntu@192.168.1.50 -ScriptBlock {
    uname -a
    Get-Process pwsh
}

# Multi-machine SSH
Invoke-Command -HostName admin@web1, admin@web2, admin@web3 -ScriptBlock {
    systemctl status nginx
}
CIM/WMI Queries

Use Get-CimInstance (not Get-WmiObject). CIM uses WS-Man, is more secure, cross-platform compatible, and Get-WmiObject is deprecated in PowerShell 7.

# System information
Get-CimInstance Win32_OperatingSystem | Select-Object Caption, Version, LastBootUpTime

# Hardware info
Get-CimInstance Win32_Processor | Select-Object Name, NumberOfCores, MaxClockSpeed
Get-CimInstance Win32_PhysicalMemory | Measure-Object Capacity -Sum

# Disk space
Get-CimInstance Win32_LogicalDisk -Filter "DriveType=3" |
    Select-Object DeviceID,
        @{N='SizeGB';E={[math]::Round($_.Size/1GB)}},
        @{N='FreeGB';E={[math]::Round($_.FreeSpace/1GB)}}

# Services
Get-CimInstance Win32_Service | Where-Object StartMode -eq 'Auto' |
    Select-Object Name, State, StartMode

# CIM Sessions (efficient for multiple queries)
$session = New-CimSession -ComputerName Server01
Get-CimInstance Win32_Process -CimSession $session
Get-CimInstance Win32_Service -CimSession $session
Remove-CimSession $session
Background Jobs

Background jobs run commands asynchronously in a separate process. Useful for long-running operations that should not block your session.

# Start background job
$job = Start-Job -ScriptBlock {
    Get-ChildItem -Recurse "C:\LargeDirectory"
}

# Check job status
Get-Job

# Get results (waits for completion)
$results = Receive-Job -Job $job -Wait

# Remove completed jobs
Get-Job -State Completed | Remove-Job

# Scheduled job
Register-ScheduledJob -Name "NightlyBackup" -ScriptBlock {
    Copy-Item "C:\Data" "D:\Backup" -Recurse
} -Trigger (New-JobTrigger -Daily -At "2:00 AM")
12. Tips & Tricks

Power-user techniques, modern operators, and quality-of-life improvements that make daily PowerShell use faster and more enjoyable. If you only take away one thing from this section, let it be splatting — it transforms unreadable one-liners into clean, maintainable code.

Splatting

Pass parameters as a hashtable with @ instead of $. Keeps long commands readable and parameters reusable.

# Without splatting (long, hard to read)
Send-MailMessage -From "admin@company.com" -To "team@company.com" -Subject "Report" -Body $body -SmtpServer "smtp.company.com" -Port 587 -UseSsl

# With splatting (clean and reusable)
$mailParams = @{
    From       = "admin@company.com"
    To         = "team@company.com"
    Subject    = "Report"
    Body       = $body
    SmtpServer = "smtp.company.com"
    Port       = 587
    UseSsl     = $true
}
Send-MailMessage @mailParams
PowerShell 7+ Operators

Modern operators added in PowerShell 7 that bring the language closer to C# and JavaScript conventions.

Operator Name Example
? : Ternary $x -gt 5 ? "big" : "small"
?? Null-coalescing $val ?? "default"
??= Null-coalescing assign $x ??= "fallback"
&& Pipeline chain (and) cmd1 && cmd2
|| Pipeline chain (or) cmd1 || cmd2
?. Null-conditional member $obj?.Property
?[] Null-conditional index $arr?[0]
# Ternary — replaces if/else for simple assignments
$status = (Test-Connection -Quiet server01) ? "Online" : "Offline"

# Null-coalescing — default values without if
$config = (Get-Content "config.json" -Raw -ErrorAction SilentlyContinue) ?? '{}'
$settings = $config | ConvertFrom-Json

# Null-coalescing assignment — set only if null
$env:LOG_LEVEL ??= "INFO"

# Pipeline chain — conditional execution
Test-Path "./build" && Remove-Item "./build" -Recurse
dotnet build || Write-Error "Build failed!"
Profile Customization

Your PowerShell profile is a script that runs at startup. Use it to load modules, set aliases, configure PSReadLine, and customize your prompt.

# Find your profile path
$PROFILE

# Create profile if it doesn't exist
if (-not (Test-Path $PROFILE)) {
    New-Item -Path $PROFILE -ItemType File -Force
}

# Example profile content:
# Import modules
Import-Module PSReadLine

# PSReadLine configuration
Set-PSReadLineOption -PredictionSource History
Set-PSReadLineOption -PredictionViewStyle ListView
Set-PSReadLineKeyHandler -Key UpArrow -Function HistorySearchBackward
Set-PSReadLineKeyHandler -Key DownArrow -Function HistorySearchForward
Set-PSReadLineKeyHandler -Key Tab -Function MenuComplete

# Custom aliases
Set-Alias -Name g -Value git
Set-Alias -Name k -Value kubectl
Set-Alias -Name c -Value code

# Custom prompt
function prompt {
    $loc = (Get-Location).Path.Replace($HOME, '~')
    $branch = git branch --show-current 2>$null
    $prefix = "PS"
    if ($branch) { $prefix = "PS [$branch]" }
    "$prefix $loc> "
}
Useful One-Liners
Find duplicate files
Get-ChildItem -Recurse -File | Group-Object Length | Where-Object Count -gt 1
Monitor a log file
Get-Content "app.log" -Tail 50 -Wait
Get public IP
(Invoke-RestMethod ifconfig.me/ip).Trim()
Kill process by port
Get-NetTCPConnection -LocalPort 8080 | Stop-Process -Id { $_.OwningProcess }
Base64 encode
[Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes("secret"))
Stopwatch
$sw = [Diagnostics.Stopwatch]::StartNew(); <code>; $sw.Elapsed
Quick HTTP server
Start-Process pwsh -ArgumentList "-c", "python -m http.server 8080"
Clipboard operations
Get-Clipboard · Set-Clipboard "text"
Bash to PowerShell Rosetta

Essential equivalents for developers coming from Linux/macOS. PowerShell aliases many of these (ls, cat, cd) but the native cmdlets give you object output instead of text.

Bash PowerShell Notes
ls -la Get-ChildItem -Force Shows hidden files
grep -r "pattern" . Get-ChildItem -Recurse | Select-String "pattern" Or sls alias
cat file.txt Get-Content file.txt Or gc alias
find . -name "*.txt" Get-ChildItem -Recurse -Filter "*.txt"
wc -l file (Get-Content file).Count
sed 's/old/new/g' file (Get-Content file) -replace 'old','new' | Set-Content file
curl -s url Invoke-RestMethod url Returns parsed JSON
ps aux | grep nginx Get-Process nginx Direct name parameter
kill -9 PID Stop-Process -Id PID -Force
export VAR=val $env:VAR = "val" Session-scoped
echo $? $? or $LASTEXITCODE
command || fallback command || fallback PS 7+ only
tail -f log Get-Content log -Tail 10 -Wait
chmod +x script Set-ExecutionPolicy RemoteSigned Different model
source ~/.bashrc . $PROFILE Dot-source operator
About PowerShell Tech Guide

Part of Tech Guides · HTML Style Guides

Windows 3.1 Design System · Self-Contained HTML · No Build Tools

PowerShell 5.1 — 7.5+
12 Sections
Zero Dependencies