The object-oriented shell — from Windows PowerShell 5.1 to cross-platform pwsh 7+
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.
-Online to open docs in browser.gcm *-Service.-Recurse and -Filter *.log.cd HKLM:).-Tail 20 for last N lines or -Wait to follow.Add-Content to append.$true or $false if a path exists. Works for files, dirs, and registry keys.-ItemType Directory for folders.$_ is the current object in the pipeline.-First / -Last.-Parallel for concurrent execution.-Descending to reverse.-Property Length for file sizes.Get-Process chrome.-Force if needed..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.
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.
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.
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 | 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 |
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
}
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.
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.
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.
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 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.
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.
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).
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.
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
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 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 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"
}
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 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
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" }
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 (==, >).
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" }
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
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.
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.
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
}
}
}
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.
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.
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
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"
}
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
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.
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.
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
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
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"
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.
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
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
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.
# 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
# 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
# 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"
# 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"
# 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.
# 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"
# 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"
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.
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.
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() }
}
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" }
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
}
}
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
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
}
}
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.
# 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
# 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"
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 |
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')
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
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.
# 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
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
}
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 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")
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.
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
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!"
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> "
}
Get-ChildItem -Recurse -File | Group-Object Length | Where-Object Count -gt 1Get-Content "app.log" -Tail 50 -Wait(Invoke-RestMethod ifconfig.me/ip).Trim()Get-NetTCPConnection -LocalPort 8080 | Stop-Process -Id { $_.OwningProcess }[Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes("secret"))$sw = [Diagnostics.Stopwatch]::StartNew(); <code>; $sw.ElapsedStart-Process pwsh -ArgumentList "-c", "python -m http.server 8080"Get-Clipboard · Set-Clipboard "text"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 |
Part of Tech Guides · HTML Style Guides
Windows 3.1 Design System · Self-Contained HTML · No Build Tools