PowerShell Basics

PowerShell fundamentals and file operations.

Variables and Types

# Variable declaration
$name = "value"
$number = 42
$float = 3.14
$bool = $true
$null_var = $null

# Strong typing
[string]$str = "text"
[int]$num = 100
[datetime]$date = Get-Date
[array]$arr = @()
[hashtable]$hash = @{}

# Type conversion
[int]"42"           # String to int
[string]42          # Int to string
[datetime]"2026-02-27"  # String to datetime

# Check type
$var.GetType().Name
$var -is [string]   # Returns $true or $false

# Arrays
$array = @("one", "two", "three")
$array = 1, 2, 3
$array += 4                   # Append (creates new array!)
$array[0]                     # First element
$array[-1]                    # Last element
$array[1..3]                  # Slice (elements 1, 2, 3)
$array.Count                  # Length

# ArrayList (better for appending)
$list = [System.Collections.ArrayList]@()
$list.Add("item") | Out-Null  # Out-Null suppresses return value
$list.Remove("item")

# Hashtables (dictionaries)
$hash = @{
    Name = "vault-01"
    IP = "10.50.1.60"
    Port = 8200
}
$hash["Name"]                 # Access by key
$hash.Name                    # Dot notation
$hash.Keys                    # All keys
$hash.Values                  # All values
$hash.ContainsKey("Name")     # Check if key exists
$hash += @{Role = "PKI"}      # Add key-value

# Ordered hashtable (preserves insertion order)
$ordered = [ordered]@{
    First = 1
    Second = 2
    Third = 3
}

# Environment variables
$env:PATH
$env:USERNAME
$env:COMPUTERNAME
$env:CUSTOM_VAR = "value"     # Set for current session

TIP: Use ArrayList instead of @() arrays when building collections in loops - arrays create new copies on each append.

Operators

# Comparison (case-insensitive by default)
$a -eq $b        # Equal
$a -ne $b        # Not equal
$a -gt $b        # Greater than
$a -ge $b        # Greater than or equal
$a -lt $b        # Less than
$a -le $b        # Less than or equal

# Case-sensitive versions
$a -ceq $b       # Case-sensitive equal
$a -cne $b       # Case-sensitive not equal

# String matching
"hello" -like "*ell*"         # Wildcard match
"hello" -notlike "world*"     # Negation
"hello" -match "^h.*o$"       # Regex match
$matches[0]                   # Capture group 0
"hello" -replace "l", "x"     # Regex replace: hexxo

# Collection operators
$array -contains "value"      # Check if array contains value
$array -notcontains "value"
"value" -in $array           # Same but reversed operands
"value" -notin $array

# Logical operators
$true -and $false
$true -or $false
-not $false
!$false                       # Same as -not

# Bitwise operators
$a -band $b      # AND
$a -bor $b       # OR
$a -bxor $b      # XOR
-bnot $a         # NOT

# String operators
$s = "hello " + "world"       # Concatenation
$s = "hello" * 3              # Repeat: hellohellohello
$s = "value: $variable"       # Variable expansion
$s = "path: $($hash.Name)"    # Expression expansion

# Range operator
1..10            # Array of 1 through 10
'a'..'z'         # Array of a through z

# Split and join
"a,b,c" -split ","           # @("a", "b", "c")
@("a", "b", "c") -join ","   # "a,b,c"

# Ternary (PowerShell 7+)
$result = $condition ? "yes" : "no"

# Null-coalescing (PowerShell 7+)
$value = $null ?? "default"
$value ??= "default"          # Assign if null

Control Flow

# If/ElseIf/Else
if ($value -gt 10) {
    Write-Host "Greater"
} elseif ($value -eq 10) {
    Write-Host "Equal"
} else {
    Write-Host "Less"
}

# Switch
switch ($status) {
    "Running" { Write-Host "Service is running" }
    "Stopped" { Write-Host "Service is stopped" }
    default { Write-Host "Unknown status: $status" }
}

# Switch with regex
switch -Regex ($input) {
    "^Error" { Write-Host "Error found" }
    "^Warning" { Write-Host "Warning found" }
}

# Switch with wildcard
switch -Wildcard ($filename) {
    "*.txt" { Write-Host "Text file" }
    "*.log" { Write-Host "Log file" }
}

# ForEach loop
foreach ($item in $collection) {
    Write-Host $item
}

# For loop
for ($i = 0; $i -lt 10; $i++) {
    Write-Host $i
}

# While loop
$i = 0
while ($i -lt 10) {
    Write-Host $i
    $i++
}

# Do-While (always runs at least once)
do {
    $input = Read-Host "Enter value"
} while ($input -ne "quit")

# Do-Until
do {
    $result = Get-Service -Name "MyService"
} until ($result.Status -eq "Running")

# Break and Continue
foreach ($item in $collection) {
    if ($item -eq "skip") { continue }
    if ($item -eq "stop") { break }
    Write-Host $item
}

Functions

# Basic function
function Get-Greeting {
    return "Hello, World!"
}

# Function with parameters
function Get-Greeting {
    param (
        [string]$Name = "World"
    )
    return "Hello, $Name!"
}

# Advanced function with validation
function Get-ServerInfo {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string]$ComputerName,

        [Parameter()]
        [ValidateSet("CPU", "Memory", "Disk")]
        [string]$Resource = "CPU",

        [Parameter()]
        [switch]$Detailed
    )

    begin {
        Write-Verbose "Starting Get-ServerInfo for $ComputerName"
    }

    process {
        # Main logic here
        $info = @{
            ComputerName = $ComputerName
            Resource = $Resource
            Timestamp = Get-Date
        }

        if ($Detailed) {
            $info.Add("Extra", "Details")
        }

        return [PSCustomObject]$info
    }

    end {
        Write-Verbose "Completed Get-ServerInfo"
    }
}

# Pipeline input
function Process-Items {
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline = $true)]
        [string]$Item
    )

    process {
        Write-Host "Processing: $Item"
    }
}

# Usage: "a", "b", "c" | Process-Items

# Splatting (pass hashtable as parameters)
$params = @{
    Path = "C:\logs"
    Filter = "*.log"
    Recurse = $true
}
Get-ChildItem @params

# Filter function (single-item pipeline)
filter Get-Large {
    if ($_.Length -gt 1MB) { $_ }
}
Get-ChildItem | Get-Large

Modules

# List installed modules
Get-Module -ListAvailable
Get-Module                           # Currently loaded

# Import module
Import-Module ActiveDirectory
Import-Module -Name "C:\path\to\module.psm1"

# Install from PSGallery
Install-Module -Name Az -Scope CurrentUser
Install-Module -Name Pester -Force   # Update if exists

# Find modules
Find-Module -Name "*Azure*"
Find-Module -Tag "Security"

# Update modules
Update-Module -Name Az

# Remove module
Remove-Module ActiveDirectory        # Unload from session
Uninstall-Module -Name OldModule     # Remove from system

# Create simple module
# File: MyModule.psm1
function Get-MyData {
    # Function code
}

Export-ModuleMember -Function Get-MyData

# Module manifest
# File: MyModule.psd1
@{
    ModuleVersion = '1.0.0'
    RootModule = 'MyModule.psm1'
    FunctionsToExport = @('Get-MyData')
    Author = 'Your Name'
    Description = 'Module description'
}

# Common modules
Import-Module ActiveDirectory        # AD management
Import-Module DnsServer              # DNS management
Import-Module DHCPServer             # DHCP management
Import-Module GroupPolicy            # GPO management
Import-Module ServerManager          # Server roles/features
Import-Module NetSecurity            # Firewall rules
Import-Module NetTCPIP               # Network configuration
Import-Module Storage                # Disk/volume management

Pipeline and Filtering

# Basic pipeline
Get-Process | Where-Object CPU -gt 10 | Sort-Object CPU -Descending

# Simplified Where-Object syntax
Get-Process | Where-Object {$_.CPU -gt 10 -and $_.Name -like "chrome*"}

# Simplified syntax (PowerShell 3+)
Get-Process | Where-Object CPU -gt 10
Get-Service | Where-Object Status -eq "Running"

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

# Select first/last N
Get-Process | Select-Object -First 10
Get-Process | Select-Object -Last 5

# Unique values
Get-Process | Select-Object -Unique Name

# Calculated properties
Get-Process | Select-Object Name, @{
    Name = "MemoryMB"
    Expression = { [math]::Round($_.WorkingSet / 1MB, 2) }
}

# ForEach-Object
1..10 | ForEach-Object { $_ * 2 }

# Simplified ForEach syntax
Get-Process | ForEach-Object Name

# Method calling in ForEach
"hello", "world" | ForEach-Object ToUpper

# Sort
Get-Process | Sort-Object CPU -Descending
Get-Process | Sort-Object Name, CPU

# Group
Get-Service | Group-Object Status

# Measure
Get-Process | Measure-Object CPU -Sum -Average -Maximum

# Compare objects
Compare-Object $array1 $array2

# Tee (save and pass through)
Get-Process | Tee-Object -Variable procs | Select-Object -First 5

# Output to multiple places
Get-Process | Tee-Object -FilePath procs.txt | Where-Object CPU -gt 10

# Pipeline variable
Get-Process | Where-Object {$_.CPU -gt 10} -PipelineVariable proc |
    ForEach-Object { "$($proc.Name): $($proc.CPU)" }

Output and Formatting

# Output methods
Write-Host "Message"                    # Console only (not pipeline)
Write-Output $object                    # Pipeline output (default)
Write-Information "Info"                # Information stream
Write-Warning "Warning"                 # Warning stream
Write-Error "Error"                     # Error stream
Write-Verbose "Verbose"                 # Requires -Verbose
Write-Debug "Debug"                     # Requires -Debug

# Write-Host with colors
Write-Host "Success" -ForegroundColor Green
Write-Host "Error" -ForegroundColor Red -BackgroundColor Yellow
Write-Host "No newline" -NoNewline

# Format output
Get-Process | Format-Table              # Table format
Get-Process | Format-Table -AutoSize    # Adjust column widths
Get-Process | Format-List               # List format (all properties)
Get-Process | Format-Wide Name          # Wide format (single property)

# Custom table columns
Get-Process | Format-Table Name, @{
    Label = "Memory (MB)"
    Expression = { [math]::Round($_.WorkingSet / 1MB, 2) }
    Alignment = "Right"
} -AutoSize

# Export data
Get-Process | Export-Csv -Path procs.csv -NoTypeInformation
Get-Process | Export-Clixml -Path procs.xml
Get-Process | ConvertTo-Json | Out-File procs.json
Get-Process | ConvertTo-Html | Out-File procs.html

# Out-* cmdlets
Get-Process | Out-File -FilePath procs.txt
Get-Process | Out-GridView                # GUI table (Windows only)
Get-Process | Out-String                  # Convert to string
Get-Process | Out-Null                    # Discard output

# Redirect streams
Get-Process 2>&1 | Out-File all.txt      # Errors to output
Get-Process *>&1 | Out-File all.txt      # All streams to output

# Suppress output
[void](New-Item -Path "file.txt")
New-Item -Path "file.txt" | Out-Null
$null = New-Item -Path "file.txt"

Error Handling

# Try/Catch/Finally
try {
    $result = Get-Content -Path "nonexistent.txt" -ErrorAction Stop
} catch {
    Write-Error "Failed to read file: $_"
} finally {
    # Cleanup code (always runs)
    Write-Host "Cleanup complete"
}

# Catch specific exception types
try {
    $null.Method()
} catch [System.Management.Automation.RuntimeException] {
    Write-Error "Runtime error: $_"
} catch [System.IO.FileNotFoundException] {
    Write-Error "File not found: $_"
} catch {
    Write-Error "General error: $_"
}

# ErrorAction parameter
Get-Content -Path "file.txt" -ErrorAction SilentlyContinue
Get-Content -Path "file.txt" -ErrorAction Stop      # Throws terminating error
Get-Content -Path "file.txt" -ErrorAction Continue  # Default behavior
Get-Content -Path "file.txt" -ErrorAction Ignore    # Suppress completely

# $ErrorActionPreference (global setting)
$ErrorActionPreference = "Stop"     # All errors become terminating

# Check if error occurred
if (!$?) {
    Write-Host "Previous command failed"
}

# $Error automatic variable
$Error[0]                    # Most recent error
$Error.Clear()               # Clear error history

# Throw custom errors
throw "Something went wrong"
throw [System.ArgumentException]::new("Invalid argument", "paramName")

# Write-Error vs Throw
Write-Error "Non-terminating error"    # Continues execution
throw "Terminating error"              # Stops execution

# ErrorVariable parameter
Get-ChildItem -Path "C:\fake" -ErrorVariable myError -ErrorAction SilentlyContinue
if ($myError) {
    Write-Host "Errors: $($myError.Count)"
}

TIP: Use -ErrorAction Stop when you want to catch errors with try/catch - most cmdlet errors are non-terminating by default.

JSON and XML

# JSON - Read
$json = Get-Content -Path "config.json" -Raw | ConvertFrom-Json
$json.server.host
$json.users[0].name

# JSON - Write
$data = @{
    server = @{
        host = "vault-01"
        port = 8200
    }
    users = @("admin", "operator")
}
$data | ConvertTo-Json -Depth 10 | Out-File "config.json"

# JSON from API
$response = Invoke-RestMethod -Uri "https://api.example.com/data"
$response.items | ForEach-Object { $_.name }

# Modify JSON
$json = Get-Content "config.json" -Raw | ConvertFrom-Json
$json.server.port = 8201
$json | ConvertTo-Json -Depth 10 | Set-Content "config.json"

# XML - Read
[xml]$xml = Get-Content -Path "config.xml"
$xml.configuration.appSettings.add
$xml.SelectNodes("//add[@key='server']")

# XML - Modify
$node = $xml.SelectSingleNode("//add[@key='server']")
$node.value = "new-server"
$xml.Save("config.xml")

# Create XML
$xml = New-Object System.Xml.XmlDocument
$root = $xml.CreateElement("configuration")
$xml.AppendChild($root)
$setting = $xml.CreateElement("setting")
$setting.SetAttribute("name", "value")
$root.AppendChild($setting)
$xml.Save("new-config.xml")

# CSV
$csv = Import-Csv -Path "data.csv"
$csv | Where-Object Status -eq "Active"

$data | Export-Csv -Path "output.csv" -NoTypeInformation

# CLI XML (PowerShell object serialization)
Get-Process | Export-Clixml -Path "procs.xml"
$procs = Import-Clixml -Path "procs.xml"

Web Requests and APIs

# Simple GET
$response = Invoke-WebRequest -Uri "https://example.com"
$response.Content
$response.StatusCode
$response.Headers

# REST API (returns parsed JSON)
$data = Invoke-RestMethod -Uri "https://api.example.com/users"

# POST with body
$body = @{
    name = "test"
    value = 123
} | ConvertTo-Json

Invoke-RestMethod -Uri "https://api.example.com/items" `
    -Method Post `
    -ContentType "application/json" `
    -Body $body

# Authentication - Basic
$cred = Get-Credential
Invoke-RestMethod -Uri "https://api.example.com" -Credential $cred

# Authentication - Bearer token
$headers = @{
    Authorization = "Bearer $token"
    "Content-Type" = "application/json"
}
Invoke-RestMethod -Uri "https://api.example.com" -Headers $headers

# Authentication - API key
$headers = @{
    "X-API-Key" = $apiKey
}
Invoke-RestMethod -Uri "https://api.example.com" -Headers $headers

# Ignore SSL errors (for self-signed certs)
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
# Or in PowerShell 7+:
Invoke-RestMethod -Uri "https://..." -SkipCertificateCheck

# Download file
Invoke-WebRequest -Uri "https://example.com/file.zip" -OutFile "file.zip"

# Upload file
Invoke-RestMethod -Uri "https://api.example.com/upload" `
    -Method Post `
    -InFile "file.txt" `
    -ContentType "text/plain"

# Form data
$form = @{
    file = Get-Item -Path "file.txt"
    name = "test"
}
Invoke-RestMethod -Uri "https://api.example.com/upload" `
    -Method Post `
    -Form $form

Common Cmdlets

# Information
Get-Help Get-Process -Full           # Help documentation
Get-Command -Noun "Process"          # Find commands
Get-Member -InputObject $object      # Object properties/methods
Get-Alias                            # List aliases

# String operations
"Hello".ToUpper()
"Hello".ToLower()
"Hello".Contains("ell")
"Hello".Replace("l", "x")
"  Hello  ".Trim()
"Hello".Substring(0, 3)              # "Hel"
"Hello".Split("l")                   # @("He", "", "o")

# Date/Time
Get-Date
Get-Date -Format "yyyy-MM-dd"
Get-Date -Format "yyyy-MM-dd HH:mm:ss"
(Get-Date).AddDays(7)
(Get-Date).AddHours(-24)
[datetime]::Now
[datetime]::UtcNow

# Math
[math]::Round(3.14159, 2)
[math]::Ceiling(3.2)
[math]::Floor(3.8)
[math]::Pow(2, 10)
[math]::Sqrt(16)
[math]::Abs(-5)

# Random
Get-Random
Get-Random -Minimum 1 -Maximum 100
Get-Random -InputObject @("a", "b", "c")
1..10 | Get-Random -Count 3          # 3 random items

# Sleep/Wait
Start-Sleep -Seconds 5
Start-Sleep -Milliseconds 500

# Object creation
[PSCustomObject]@{
    Name = "vault-01"
    IP = "10.50.1.60"
    Status = "Active"
}

# Clipboard (Windows)
Get-Clipboard
Set-Clipboard -Value "text"
Get-Process | Set-Clipboard

# History
Get-History
Invoke-History 42                     # Run command #42
Clear-History

Common Gotchas

# WRONG: Using = instead of -eq in comparisons
if ($value = 10) { }  # This ASSIGNS 10 to $value!

# CORRECT: Use comparison operator
if ($value -eq 10) { }

# WRONG: Forgetting $_ in Where-Object script block
Get-Process | Where-Object {CPU -gt 10}  # Error!

# CORRECT: Use $_ or simplified syntax
Get-Process | Where-Object {$_.CPU -gt 10}
Get-Process | Where-Object CPU -gt 10

# WRONG: Expecting case-sensitive comparison
"Hello" -eq "HELLO"  # Returns $true!

# CORRECT: Use case-sensitive operator
"Hello" -ceq "HELLO"  # Returns $false

# WRONG: String expansion in single quotes
$name = "World"
'Hello, $name'  # Outputs: Hello, $name

# CORRECT: Use double quotes for expansion
"Hello, $name"  # Outputs: Hello, World

# WRONG: Array appending in loops (slow!)
$results = @()
foreach ($item in $collection) {
    $results += $item  # Creates new array each time!
}

# CORRECT: Use ArrayList or collect from pipeline
$results = [System.Collections.ArrayList]@()
foreach ($item in $collection) {
    $results.Add($item) | Out-Null
}
# Or:
$results = foreach ($item in $collection) { $item }

# WRONG: Assuming cmdlets throw on error
Get-ChildItem -Path "C:\fake"  # Continues execution!

# CORRECT: Use -ErrorAction Stop
try {
    Get-ChildItem -Path "C:\fake" -ErrorAction Stop
} catch {
    Write-Error "Path not found"
}

# WRONG: Forgetting -Raw for file content
$json = Get-Content "file.json" | ConvertFrom-Json  # Fails on multi-line!

# CORRECT: Use -Raw for single string
$json = Get-Content "file.json" -Raw | ConvertFrom-Json

# WRONG: Return statement behavior
function Get-Data {
    return 1
    return 2  # Never executed - return exits immediately
}

# CORRECT: Output to pipeline for multiple values
function Get-Data {
    1
    2  # Both values output
}

# WRONG: Forgetting ConvertTo-Json depth limit
$deep | ConvertTo-Json  # Default depth is 2!

# CORRECT: Specify depth
$deep | ConvertTo-Json -Depth 10

File System Navigation

# Get current location
Get-Location
$PWD
(Get-Location).Path

# Change directory
Set-Location C:\Windows
cd C:\Windows           # Alias
Push-Location C:\temp   # Save current, go to new
Pop-Location            # Return to saved

# List directory contents
Get-ChildItem
Get-ChildItem -Path C:\Windows
ls                      # Alias
dir                     # Alias

# List with filters
Get-ChildItem -Filter *.txt
Get-ChildItem -Include *.txt, *.log
Get-ChildItem -Exclude *.tmp

# Recursive listing
Get-ChildItem -Recurse
Get-ChildItem -Recurse -Depth 2   # Limit depth

# Hidden and system files
Get-ChildItem -Force              # Include hidden
Get-ChildItem -Hidden             # Only hidden
Get-ChildItem -System             # Only system

# Files only or directories only
Get-ChildItem -File
Get-ChildItem -Directory

# List with properties
Get-ChildItem | Select-Object Name, Length, LastWriteTime
Get-ChildItem | Format-Table Name, @{
    Name = "SizeMB"
    Expression = { [math]::Round($_.Length / 1MB, 2) }
} -AutoSize

# Sort by size (largest first)
Get-ChildItem -File | Sort-Object Length -Descending | Select-Object -First 10

# Find large files
Get-ChildItem -Recurse -File | Where-Object Length -gt 100MB

# Find recent files
Get-ChildItem -Recurse -File |
    Where-Object LastWriteTime -gt (Get-Date).AddDays(-7)

Path Operations

# Path manipulation
$path = "C:\Users\Admin\Documents\file.txt"

Split-Path -Path $path -Parent        # C:\Users\Admin\Documents
Split-Path -Path $path -Leaf          # file.txt
Split-Path -Path $path -Extension     # .txt (PowerShell 7+)
Split-Path -Path $path -Qualifier     # C:

# Join paths (safe concatenation)
Join-Path -Path "C:\Users" -ChildPath "Admin"
Join-Path "C:\Users" "Admin" "Documents"   # Multiple segments (PS 7+)

# Resolve relative paths
Resolve-Path -Path "..\file.txt"
Resolve-Path -Path "C:\Win*"          # Wildcards

# Test path validity (doesn't check existence)
Test-Path -Path "C:\file.txt" -IsValid

# Convert to absolute path
[System.IO.Path]::GetFullPath(".\file.txt")

# Get file extension
[System.IO.Path]::GetExtension("file.txt")    # .txt

# Change extension
[System.IO.Path]::ChangeExtension("file.txt", ".bak")  # file.bak

# Get filename without extension
[System.IO.Path]::GetFileNameWithoutExtension("file.txt")  # file

# Get temp path
[System.IO.Path]::GetTempPath()

# Create temp file
$tempFile = [System.IO.Path]::GetTempFileName()
# Or:
$tempFile = New-TemporaryFile

# Normalize path separators
$path -replace '/', '\'

# Environment paths
$env:TEMP
$env:APPDATA
$env:LOCALAPPDATA
$env:USERPROFILE
$env:ProgramFiles
$env:ProgramData

File Operations (Copy, Move, Delete)

# Copy file
Copy-Item -Path source.txt -Destination dest.txt
Copy-Item source.txt dest.txt         # Positional

# Copy with overwrite
Copy-Item -Path source.txt -Destination dest.txt -Force

# Copy directory
Copy-Item -Path C:\Source -Destination C:\Dest -Recurse

# Copy multiple files
Copy-Item -Path *.txt -Destination C:\Backup

# Move file
Move-Item -Path source.txt -Destination dest.txt
Move-Item source.txt C:\Archive\

# Move with rename
Move-Item -Path file.txt -Destination newname.txt

# Rename file
Rename-Item -Path oldname.txt -NewName newname.txt

# Delete file
Remove-Item -Path file.txt
Remove-Item file.txt -Force           # Ignore read-only
Remove-Item file.txt -WhatIf          # Preview

# Delete directory
Remove-Item -Path C:\Folder -Recurse -Force

# Delete by pattern
Remove-Item -Path C:\Temp\*.tmp

# Create file
New-Item -Path file.txt -ItemType File
New-Item -Path file.txt -ItemType File -Value "content"

# Create directory
New-Item -Path C:\NewFolder -ItemType Directory
mkdir C:\NewFolder                     # Alias

# Create parent directories if needed
New-Item -Path C:\A\B\C\file.txt -ItemType File -Force

# Test if exists
Test-Path -Path C:\file.txt
Test-Path -Path C:\folder -PathType Container
Test-Path -Path C:\file.txt -PathType Leaf

# Create if not exists
if (-not (Test-Path C:\folder)) {
    New-Item -Path C:\folder -ItemType Directory
}

File Content Operations

# Read entire file
Get-Content -Path file.txt
Get-Content -Path file.txt -Raw       # As single string (important for JSON!)

# Read with encoding
Get-Content -Path file.txt -Encoding UTF8

# Read specific lines
Get-Content -Path file.txt -First 10  # First 10 lines
Get-Content -Path file.txt -Last 5    # Last 5 lines
Get-Content -Path file.txt -Tail 5    # Same as -Last

# Read by line number
(Get-Content file.txt)[0]             # First line (0-indexed)
(Get-Content file.txt)[5..10]         # Lines 6-11

# Write file (overwrite)
Set-Content -Path file.txt -Value "content"
"content" | Set-Content file.txt

# Write multiple lines
$lines = "line1", "line2", "line3"
Set-Content -Path file.txt -Value $lines

# Append to file
Add-Content -Path file.txt -Value "new line"
"new line" | Add-Content file.txt

# Write with encoding
Set-Content -Path file.txt -Value "content" -Encoding UTF8

# Clear file contents
Clear-Content -Path file.txt

# Out-File (alternative, preserves formatting)
Get-Process | Out-File -FilePath procs.txt
Get-Process | Out-File -FilePath procs.txt -Append

# Stream reader for large files (memory efficient)
$reader = [System.IO.StreamReader]::new("C:\large.log")
while ($null -ne ($line = $reader.ReadLine())) {
    # Process each line
    if ($line -match "error") {
        Write-Host $line
    }
}
$reader.Close()

# Read binary file
[System.IO.File]::ReadAllBytes("file.bin")
$bytes = Get-Content -Path "file.bin" -AsByteStream -Raw

# Write binary file
[System.IO.File]::WriteAllBytes("file.bin", $bytes)
$bytes | Set-Content -Path "file.bin" -AsByteStream

Searching File Contents

# Basic search (grep equivalent)
Select-String -Pattern "error" -Path *.log

# Recursive search
Get-ChildItem -Recurse -Filter *.log | Select-String -Pattern "error"

# Case-sensitive search
Select-String -Pattern "Error" -Path *.log -CaseSensitive

# Regex search
Select-String -Pattern "error|warning|critical" -Path *.log
Select-String -Pattern "^\d{4}-\d{2}-\d{2}" -Path *.log    # Date at start

# Context (lines before/after match)
Select-String -Pattern "error" -Path *.log -Context 2, 2

# Count matches
(Select-String -Pattern "error" -Path *.log).Count

# Get just matching values
(Select-String -Pattern "\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" -Path *.log).Matches.Value

# Search with Where-Object (more control)
Get-ChildItem -Recurse -File | ForEach-Object {
    $content = Get-Content $_.FullName -Raw -ErrorAction SilentlyContinue
    if ($content -match "password") {
        Write-Host "Found in: $($_.FullName)"
    }
}

# Find files containing text
Get-ChildItem -Recurse -Filter *.ps1 |
    Select-String -Pattern "Get-ADUser" |
    Select-Object -Unique Path

# Infrastructure pattern: Search logs for errors
function Search-ErrorLogs {
    param (
        [string]$Path = "C:\Logs",
        [int]$Hours = 24
    )

    $cutoff = (Get-Date).AddHours(-$Hours)

    Get-ChildItem -Path $Path -Filter *.log -Recurse |
        Where-Object LastWriteTime -gt $cutoff |
        Select-String -Pattern "error|exception|failed" -AllMatches |
        Group-Object Path |
        Select-Object @{N='File'; E={$_.Name}}, Count |
        Sort-Object Count -Descending
}

File Attributes and Properties

# Get file info
$file = Get-Item -Path C:\file.txt
$file.Name
$file.FullName
$file.Extension
$file.Length                          # Size in bytes
$file.CreationTime
$file.LastWriteTime
$file.LastAccessTime
$file.Attributes

# Format file size
function Get-FileSize {
    param ([long]$Bytes)
    switch ($Bytes) {
        {$_ -ge 1TB} { "{0:N2} TB" -f ($_ / 1TB); break }
        {$_ -ge 1GB} { "{0:N2} GB" -f ($_ / 1GB); break }
        {$_ -ge 1MB} { "{0:N2} MB" -f ($_ / 1MB); break }
        {$_ -ge 1KB} { "{0:N2} KB" -f ($_ / 1KB); break }
        default { "$_ Bytes" }
    }
}

# Set file attributes
$file = Get-Item C:\file.txt
$file.Attributes = "ReadOnly"
$file.Attributes = "Hidden, System"

# Remove read-only
$file.Attributes = $file.Attributes -band (-bnot [System.IO.FileAttributes]::ReadOnly)

# Set timestamps
$file = Get-Item C:\file.txt
$file.CreationTime = Get-Date
$file.LastWriteTime = "2026-01-01 12:00:00"

# Get ACL (permissions)
Get-Acl -Path C:\file.txt
(Get-Acl C:\file.txt).Access          # Detailed permissions

# Set ACL
$acl = Get-Acl C:\file.txt
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
    "DOMAIN\User", "FullControl", "Allow"
)
$acl.SetAccessRule($rule)
Set-Acl -Path C:\file.txt -AclObject $acl

# Take ownership
takeown /f C:\file.txt /a             # To Administrators group

# Directory size
function Get-DirectorySize {
    param ([string]$Path)

    $size = (Get-ChildItem -Path $Path -Recurse -File -ErrorAction SilentlyContinue |
        Measure-Object -Property Length -Sum).Sum

    [PSCustomObject]@{
        Path = $Path
        SizeBytes = $size
        SizeMB = [math]::Round($size / 1MB, 2)
        SizeGB = [math]::Round($size / 1GB, 2)
    }
}

Compression and Archives

# Create ZIP archive
Compress-Archive -Path C:\Folder\* -DestinationPath C:\archive.zip

# Create ZIP from multiple sources
Compress-Archive -Path file1.txt, file2.txt, C:\Folder -DestinationPath archive.zip

# Update existing archive
Compress-Archive -Path newfile.txt -DestinationPath archive.zip -Update

# Overwrite existing archive
Compress-Archive -Path C:\Folder\* -DestinationPath archive.zip -Force

# Set compression level
Compress-Archive -Path C:\Folder\* -DestinationPath archive.zip -CompressionLevel Optimal
# Options: Optimal, Fastest, NoCompression

# Extract ZIP
Expand-Archive -Path archive.zip -DestinationPath C:\Extracted

# Extract with overwrite
Expand-Archive -Path archive.zip -DestinationPath C:\Extracted -Force

# List ZIP contents
$zip = [System.IO.Compression.ZipFile]::OpenRead("archive.zip")
$zip.Entries | Select-Object FullName, Length, CompressedLength
$zip.Dispose()

# Extract single file from ZIP
$zip = [System.IO.Compression.ZipFile]::OpenRead("archive.zip")
$entry = $zip.Entries | Where-Object FullName -eq "specific-file.txt"
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, "output.txt", $true)
$zip.Dispose()

# Infrastructure pattern: Backup with timestamp
function New-Backup {
    param (
        [string]$SourcePath,
        [string]$BackupFolder = "C:\Backups"
    )

    $timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
    $name = Split-Path -Path $SourcePath -Leaf
    $archiveName = "${name}_${timestamp}.zip"
    $archivePath = Join-Path $BackupFolder $archiveName

    Compress-Archive -Path $SourcePath -DestinationPath $archivePath -CompressionLevel Optimal

    return $archivePath
}

# Backup with rotation
function New-RotatingBackup {
    param (
        [string]$SourcePath,
        [string]$BackupFolder,
        [int]$KeepCount = 7
    )

    $archivePath = New-Backup -SourcePath $SourcePath -BackupFolder $BackupFolder

    # Remove old backups
    $name = Split-Path -Path $SourcePath -Leaf
    Get-ChildItem -Path $BackupFolder -Filter "${name}_*.zip" |
        Sort-Object CreationTime -Descending |
        Select-Object -Skip $KeepCount |
        Remove-Item -Force

    return $archivePath
}

Configuration File Handling

# JSON config
$config = Get-Content -Path "config.json" -Raw | ConvertFrom-Json
$config.server.host = "new-server"
$config | ConvertTo-Json -Depth 10 | Set-Content "config.json"

# XML config
[xml]$config = Get-Content "app.config"
$appSettings = $config.configuration.appSettings
$setting = $appSettings.add | Where-Object key -eq "ServerName"
$setting.value = "new-server"
$config.Save("app.config")

# INI file parser
function Get-IniContent {
    param ([string]$Path)

    $ini = @{}
    $section = ""

    Get-Content $Path | ForEach-Object {
        $line = $_.Trim()

        if ($line -match '^\[(.+)\]$') {
            $section = $Matches[1]
            $ini[$section] = @{}
        } elseif ($line -match '^([^=]+)=(.*)$') {
            $key = $Matches[1].Trim()
            $value = $Matches[2].Trim()
            if ($section) {
                $ini[$section][$key] = $value
            }
        }
    }

    return $ini
}

# Usage
$config = Get-IniContent "settings.ini"
$config["Database"]["Server"]

# Write INI file
function Set-IniContent {
    param (
        [hashtable]$Content,
        [string]$Path
    )

    $lines = foreach ($section in $Content.Keys) {
        "[$section]"
        foreach ($key in $Content[$section].Keys) {
            "$key=$($Content[$section][$key])"
        }
        ""
    }

    $lines | Set-Content $Path
}

# Environment file (.env)
function Import-EnvFile {
    param ([string]$Path)

    Get-Content $Path | ForEach-Object {
        if ($_ -match '^([^#][^=]+)=(.*)$') {
            $name = $Matches[1].Trim()
            $value = $Matches[2].Trim().Trim('"').Trim("'")
            Set-Item -Path "Env:$name" -Value $value
        }
    }
}

# YAML (requires powershell-yaml module)
# Install-Module powershell-yaml
Import-Module powershell-yaml
$yaml = Get-Content "config.yaml" -Raw | ConvertFrom-Yaml
$yaml.settings.value = "new"
$yaml | ConvertTo-Yaml | Set-Content "config.yaml"

Registry Operations

# Navigate registry as drive
Set-Location HKLM:\SOFTWARE
Get-ChildItem HKCU:\SOFTWARE\Microsoft

# Read registry value
Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion" -Name "ProgramFilesDir"

# Get all values in key
Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion"

# Read single value
(Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion").ProgramFilesDir

# Test if key exists
Test-Path "HKLM:\SOFTWARE\MyApp"

# Test if value exists
(Get-ItemProperty "HKLM:\SOFTWARE\MyApp" -ErrorAction SilentlyContinue).PSObject.Properties.Name -contains "Setting"

# Create registry key
New-Item -Path "HKLM:\SOFTWARE\MyApp"

# Create registry value
New-ItemProperty -Path "HKLM:\SOFTWARE\MyApp" -Name "Setting" -Value "data" -PropertyType String

# Property types: String, ExpandString, Binary, DWord, MultiString, QWord

# Set registry value
Set-ItemProperty -Path "HKLM:\SOFTWARE\MyApp" -Name "Setting" -Value "new value"

# Delete registry value
Remove-ItemProperty -Path "HKLM:\SOFTWARE\MyApp" -Name "Setting"

# Delete registry key
Remove-Item -Path "HKLM:\SOFTWARE\MyApp" -Recurse

# Export registry key
reg export "HKLM\SOFTWARE\MyApp" C:\backup.reg

# Import registry key
reg import C:\backup.reg

# Search registry
Get-ChildItem -Path HKLM:\SOFTWARE -Recurse -ErrorAction SilentlyContinue |
    Where-Object { $_.GetValueNames() -contains "TargetSetting" }

# Common registry locations
# HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run     # Startup programs
# HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run     # User startup
# HKLM:\SYSTEM\CurrentControlSet\Services                 # Services
# HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion      # OS info

Log File Parsing

# Parse structured log (space-delimited)
Get-Content app.log | ForEach-Object {
    $parts = $_ -split '\s+'
    [PSCustomObject]@{
        Timestamp = $parts[0] + " " + $parts[1]
        Level = $parts[2]
        Message = $parts[3..($parts.Length-1)] -join ' '
    }
}

# Parse CSV-like log
Import-Csv -Path app.log -Delimiter "|" -Header "Time", "Level", "Message"

# Parse JSON log (NDJSON)
Get-Content app.log | ForEach-Object {
    $_ | ConvertFrom-Json
} | Where-Object level -eq "error"

# Parse IIS log
Get-Content "C:\inetpub\logs\LogFiles\W3SVC1\u_ex*.log" |
    Where-Object { $_ -notmatch "^#" } |
    ForEach-Object {
        $fields = $_ -split ' '
        [PSCustomObject]@{
            Date = $fields[0]
            Time = $fields[1]
            ClientIP = $fields[2]
            Method = $fields[3]
            UriStem = $fields[4]
            Status = $fields[5]
        }
    }

# Parse Windows Event Log format
Get-WinEvent -FilterHashtable @{
    LogName = 'Security'
    ID = 4624
    StartTime = (Get-Date).AddHours(-24)
} | Select-Object TimeCreated, @{
    N='User'
    E={$_.Properties[5].Value}
}, @{
    N='LogonType'
    E={$_.Properties[8].Value}
}

# Real-time log monitoring (tail -f equivalent)
Get-Content -Path C:\app.log -Wait -Tail 10

# With filtering
Get-Content -Path C:\app.log -Wait -Tail 0 | Where-Object { $_ -match "error" }

# Infrastructure pattern: Parse auth failures
function Get-AuthFailures {
    param (
        [string]$LogPath,
        [int]$Hours = 24
    )

    $cutoff = (Get-Date).AddHours(-$Hours)

    Get-Content $LogPath | ForEach-Object {
        if ($_ -match "(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}).*Failed login.*user[=:]\s*(\S+).*from[=:]\s*([\d.]+)") {
            $time = [datetime]$Matches[1]
            if ($time -gt $cutoff) {
                [PSCustomObject]@{
                    Time = $time
                    User = $Matches[2]
                    SourceIP = $Matches[3]
                }
            }
        }
    }
}

Common Gotchas

# WRONG: Get-Content returns array of lines
$json = Get-Content "file.json" | ConvertFrom-Json  # Fails!

# CORRECT: Use -Raw for single string
$json = Get-Content "file.json" -Raw | ConvertFrom-Json

# WRONG: Forgetting about encoding
Set-Content -Path file.txt -Value "text"  # Default encoding varies by PS version

# CORRECT: Specify encoding
Set-Content -Path file.txt -Value "text" -Encoding UTF8

# WRONG: Using > or >> (lose encoding control)
Get-Process > procs.txt  # Uses system default encoding

# CORRECT: Use Out-File or Set-Content
Get-Process | Out-File procs.txt -Encoding UTF8

# WRONG: Path with spaces
Copy-Item C:\My Folder\file.txt D:\

# CORRECT: Quote paths with spaces
Copy-Item "C:\My Folder\file.txt" "D:\"

# WRONG: Assuming path exists
Get-ChildItem C:\NonExistent\*  # Error

# CORRECT: Check first
if (Test-Path "C:\NonExistent") {
    Get-ChildItem "C:\NonExistent\*"
}

# WRONG: Using Remove-Item -Recurse on locked files
Remove-Item C:\Folder -Recurse  # Fails if files locked

# CORRECT: Handle errors
Remove-Item C:\Folder -Recurse -Force -ErrorAction SilentlyContinue

# WRONG: Assuming Select-String returns strings
$result = Select-String -Pattern "error" -Path *.log
$result  # Returns MatchInfo objects!

# CORRECT: Access the matched text
$result.Line        # The full line
$result.Matches.Value  # Just the matched part

# WRONG: Registry path format
Get-ItemProperty -Path HKEY_LOCAL_MACHINE\SOFTWARE\...  # Error!

# CORRECT: Use PSDrive format
Get-ItemProperty -Path HKLM:\SOFTWARE\...