Windows Fundamentals · beginner · ~11 min

PowerShell basics for operators

- 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)

Overview

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.

Why it matters

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.

Core concepts

1. Cmdlets and the object pipeline

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.

2. The Execution Policy is NOT a security boundary

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

3. Enumeration you can do on your own host

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

4. Controls that actually contain PowerShell

  • Script Block Logging (event 4104) records the actual code executed, even if obfuscated.
  • Module Logging (4103) records pipeline execution details.
  • Transcription writes a full session transcript to a protected location.
  • Constrained Language Mode limits the language to safe operations (blocks arbitrary .NET/API calls).
  • AMSI (Antimalware Scan Interface) lets AV inspect script content at runtime.

Knowledge check:

  1. Explain, in one sentence, why Execution Policy won't stop an attacker.
  2. Which single log setting reveals the actual code a script ran, even if obfuscated?
  3. You must let admins script but block arbitrary API calls — which control fits?

Syntax notes

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 { }.

Lesson

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.

Cmdlets and the object pipeline

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

Why testers use it

PowerShell is native and present on every modern Windows host. It is excellent for enumeration without dropping extra tools. You can list:

  • Services and users
  • ACLs, via Get-Acl
  • Scheduled tasks
  • Network configuration

Get-WinEvent reads event logs.

Security controls (both sides)

  • Execution Policy restricts which scripts can run. It is a safety feature, not a security boundary. It is trivially bypassed with -ExecutionPolicy Bypass. Do not treat it as protection in a report.
  • Script Block Logging, AMSI, and transcription let defenders log and scan PowerShell activity. This is exactly why "living off the land" via PowerShell is increasingly detectable.

Defensive note

Constrained Language Mode, AMSI, and logging are the recommended hardening measures. Flag their absence.

Code examples

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.

Line by line

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.

Common mistakes

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.

Debugging tips

  • ... 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.)
  • A property is empty/blank — pipe one object to 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?

Memory safety

Security & safety — detection & logging. PowerShell is only "invisible" if logging is off:

  • Log: Script Block Logging (event 4104 — the actual code), Module Logging (4103), and Transcription. Ship these to a central log store so they survive local tampering.
  • Watch for: encoded commands (-EncodedCommand), download-and-run patterns, calls to Invoke-Expression on downloaded content, and PowerShell spawned by Office apps.
  • Never log plaintext credentials or secrets that scripts handle — and be aware scripts that embed secrets will expose them in 4104; keep secrets out of scripts.
  • False positives: admins legitimately use these features; baseline normal activity so alerts focus on the unusual (encoded + network + off-hours).

Real-world uses

  • Blue teams / SOCs rely on Script Block Logging + Sysmon to detect "living off the land" PowerShell abuse.
  • System administrators automate builds, user management, and config with the same cmdlets.
  • Security assessments use native PowerShell to enumerate a host without dropping tools — which is exactly why logging matters.

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.

Practice tasks

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.

Summary

  • PowerShell uses Verb-Noun cmdlets and an object pipeline — filter on properties with Where-Object, project with Select-Object.
  • Execution Policy is not a security boundary; it's bypassable by design.
  • Real containment = Script Block Logging (4104) + Module Logging + Transcription + Constrained Language Mode + AMSI + least privilege.
  • Enumerate only hosts you own or are authorized to test.
  • Defenders make PowerShell auditable; that's what turns an invisible action into a detectable event.

Next: RDP and SMB — the remote-access and file-sharing services you'll often find (and need to harden) on Windows.