Windows Fundamentals · beginner · ~11 min
- Read and write basic **PowerShell** cmdlets and use the **object pipeline** to filter and select data - Explain why the **Execution Policy is not a security boundary** - Enumerate a Windows host you own (services, users, ACLs, tasks, event logs) without extra tools - Turn on the **logging** that lets defenders see PowerShell activity (Script Block, Module, Transcription) - Name the defensive controls that actually contain PowerShell abuse (Constrained Language Mode, AMSI, remoting security)
PowerShell is the built-in Windows automation shell — roughly the Windows equivalent of bash, but it pipes objects instead of text. It is the primary tool for both administering and assessing Windows, which is exactly why understanding it (and its logging) matters for defenders.
This lesson builds on Users and groups: you'll now query that user and permission data programmatically. Everything here is meant to be practiced on a Windows host you own or are authorized to test — a local VM is ideal.
The security angle runs throughout: PowerShell is powerful and native, so both administrators and attackers use it. The defender's job is to use it well and see when someone else is using it.
Because PowerShell ships on every modern Windows host and can do almost anything an admin can, it is a favourite for "living off the land" — operating with built-in tools instead of dropped malware. That makes PowerShell logging and hardening one of the highest-value defensive controls on Windows: it turns an invisible action into an auditable event.
For a learner, it's also the fastest way to understand a Windows system: one object pipeline can answer "which services run as a privileged account?" in a single line.
Definition. A cmdlet is a command in Verb-Noun form: Get-Process, Get-Service, Get-LocalUser. Cmdlets emit objects, and the pipeline passes those objects (not text) to the next command, so you filter on real properties:
Get-Service | Where-Object { $_.Status -eq 'Running' } | Select-Object Name, StartType
When to use: almost all Windows automation and enumeration. Pitfall: thinking in text (grep-style). Filter on properties ($_.Status), not substrings.
This is the single most important correction in this lesson. Set-ExecutionPolicy controls whether scripts run by default — it is a safety catch against accidental double-clicks, not a security control. It is trivially bypassed by design (powershell -ExecutionPolicy Bypass, or piping a script to stdin). Microsoft says so explicitly.
Implication: never rely on Execution Policy to stop a determined actor. Real containment comes from the controls in concept 4.
Execution Policy = a seatbelt sign, not a locked door
Real controls = logging + Constrained Language Mode + AMSI + least privilege
Native cmdlets that answer common questions (run these on a host you own):
| Question | Cmdlet |
|---|---|
| What services exist, and how do they start? | Get-Service, Get-CimInstance Win32_Service |
| Who are the local users/admins? | Get-LocalUser, Get-LocalGroupMember Administrators |
| Who can access this file/key? | Get-Acl |
| What scheduled tasks run? | Get-ScheduledTask |
| What's in the event log? | Get-WinEvent |
Knowledge check:
The pipeline grammar to internalize:
Get-<Noun> # produce objects
| Where-Object { $_.<Prop> -op <value> } # filter (rows)
| Select-Object <Prop>, <Prop> # project (columns)
| Sort-Object <Prop> # order
| Format-Table / Export-Csv # present / save
Comparison operators are words: -eq -ne -gt -lt -like -match. $_ is "the current object" inside a script block { }.
PowerShell is the Windows automation shell. It is the primary tool for both administering and assessing Windows. Think of it as the rough equivalent of bash on Linux, but object-oriented.
Commands are cmdlets, written in Verb-Noun form: Get-Process, Get-Service, Get-LocalUser, Get-ChildItem.
Unlike bash, which pipes text, PowerShell pipes objects. This means you filter on properties:
Get-Service | Where-Object {$_.Status -eq 'Running'} | Select-Object Name, StartType
PowerShell is native and present on every modern Windows host. It is excellent for enumeration without dropping extra tools. You can list:
Get-AclGet-WinEvent reads event logs.
-ExecutionPolicy Bypass. Do not treat it as protection in a report.Constrained Language Mode, AMSI, and logging are the recommended hardening measures. Flag their absence.
A small, defensive enumeration script you can run on a Windows host you own — it lists services that run as a privileged account (a common misconfiguration to find and fix), then shows how a defender would enable Script Block Logging.
# audit-privileged-services.ps1 — run on a host you own/are authorized to assess.
# Lists services whose logon account is a privileged/system account.
Get-CimInstance Win32_Service |
Where-Object { $_.StartName -match 'LocalSystem|Administrator' } |
Select-Object Name, StartName, State, PathName |
Sort-Object Name |
Format-Table -AutoSize
# --- Defensive side: turn ON Script Block Logging (admin, on your own host) ---
# This makes event 4104 record the actual script text that runs.
$key = 'HKLM:\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging'
New-Item -Path $key -Force | Out-Null
Set-ItemProperty -Path $key -Name 'EnableScriptBlockLogging' -Value 1
What it does. The first pipeline finds services configured to log on as LocalSystem or an admin — over-privileged services are a real hardening finding, and this is how you inventory them defensively. The second block enables Script Block Logging so any future PowerShell (including an attacker's) is recorded in the event log.
Expected output. A table of service names with their logon account and binary path; after the registry change, subsequent PowerShell activity appears as event 4104 in Microsoft-Windows-PowerShell/Operational.
Reading the audit pipeline:
| Step | What happens | Why |
|---|---|---|
Get-CimInstance Win32_Service |
Emits one object per service | Structured data, not text |
Where-Object { $_.StartName -match … } |
Keeps only privileged logon accounts | The finding: over-privileged services |
Select-Object Name,StartName,State,PathName |
Picks the columns that matter | Readable, exportable |
Sort-Object Name |
Orders the results | Easier review |
Format-Table -AutoSize |
Presents it | Human-friendly output |
The logging block writes one registry value; from then on, event 4104 captures executed script text — the control that makes the next investigation possible.
Mistake 1 — trusting Execution Policy as security.
Wrong: "Execution Policy is Restricted, so scripts can't run."
Why wrong: it's bypassable by design (-ExecutionPolicy Bypass, stdin, encoded commands).
Fix: rely on logging + Constrained Language Mode + least privilege; treat Execution Policy as a convenience only.
Mistake 2 — text thinking on an object shell.
Wrong: Get-Service | Select-String "Running".
Why wrong: you're string-matching formatted text, which breaks on formatting changes.
Fix: filter on the property: Where-Object { $_.Status -eq 'Running' }.
Mistake 3 — enumerating hosts you don't own. Wrong: pointing remoting/enumeration at arbitrary machines. Why wrong: that's unauthorized access. Fix: only your own VM or an explicitly in-scope host.
Recognize it: if a "security" claim rests on Execution Policy, it's a mistake.
... cannot be loaded because running scripts is disabled — that's Execution Policy; for your own lab, Set-ExecutionPolicy -Scope Process RemoteSigned. (Remember it's not a security control.)Get-Member to see the real property names/types before filtering.Access is denied — you likely need an elevated (High-integrity) PowerShell; see the privilege-model lesson.Get-WinEvent returns nothing — the log/provider may be disabled; confirm the channel name and that logging is enabled.Questions to ask: Am I filtering on a real property (use Get-Member)? Do I have the rights I need? Is the relevant logging even turned on?
Security & safety — detection & logging. PowerShell is only "invisible" if logging is off:
-EncodedCommand), download-and-run patterns, calls to Invoke-Expression on downloaded content, and PowerShell spawned by Office apps.Beginner habits: filter on properties; run enumeration only on hosts you own; read Get-Help <cmdlet> -Examples.
Advanced habits: enforce Constrained Language Mode + application control (WDAC) for high-value hosts; centralize PowerShell logs; alert on encoded/obfuscated script blocks.
Beginner 1 — Pipeline basics. Objective: on your own VM, list all stopped services showing only Name and StartType. Concepts: pipeline, Where-Object, Select-Object.
Beginner 2 — Refute the boundary. Objective: in your own words (2–3 sentences), explain why setting Execution Policy to Restricted does not stop a determined user, and name the setting that would record what they ran. Concepts: Execution Policy, Script Block Logging.
Intermediate 1 — Privileged-service inventory. Objective: write a pipeline that lists services whose logon account is LocalSystem, sorted by name, exported to CSV. Concepts: Get-CimInstance, filtering, Export-Csv. Constraint: your own host only.
Intermediate 2 — Turn on the lights. Objective: enable Script Block Logging on your VM, run a harmless script, and describe where the recorded code appears (log/channel + event id). Concepts: detection, event 4104.
Challenge — Hardening checklist. Objective: produce a one-page PowerShell hardening checklist for a Windows host (logging, Constrained Language Mode, AMSI, least-privilege service accounts, remoting restrictions), with one sentence per item on why. Requirements: defensive only; no attacking anything. Concepts: everything in this lesson.
Verb-Noun cmdlets and an object pipeline — filter on properties with Where-Object, project with Select-Object.Next: RDP and SMB — the remote-access and file-sharing services you'll often find (and need to harden) on Windows.