Skip to main content
  1. Posts/

PowerShell Basics: Concepts and Syntax

··1506 words·8 mins·
Table of Contents
Port scanning, password cracking, and crawling systems you don’t own is illegal. Everything here is for authorized work: your own lab, a client engagement with a signed scope, or a CTF. Keep it inside the rules of engagement.

PowerShell is the language Windows hands you for free. Every modern Windows box has it, it reaches straight into .NET, WMI, and the Windows API, and it runs in memory without dropping a binary to disk. That combination is exactly why operators reach for it, and exactly why defenders watch it so closely.

This is the working-operator’s version of a PowerShell primer: enough syntax to read and write scripts, three examples you’ll recognize from an engagement (a port scanner, a hash-cracking loop, and a web crawler), and an honest look at where PowerShell helps and where it gets you caught.

Basic concepts and syntax
#

PowerShell is a command-line shell and scripting language Microsoft built for Windows. It gives administrators and developers a way to automate tasks and manage Windows environments, and it ships on every current version of the OS.

Variables and data types
#

Like most programming languages, PowerShell has variables, which can store data of various types. The most commonly used data types in PowerShell are:

  • Strings: A sequence of characters enclosed in double or single quotes. For example, “Hello, world!” or ‘12345’.
  • Integers: A whole number without a fractional component. For example, 42 or -99.
  • Floats: A number with a fractional component. For example, 3.14 or -0.01.
  • Booleans: A value that can be either True or False.

To declare a variable in PowerShell, use the $ symbol followed by the variable name:

$myString = "Hello, world!"
$myInt = 42
$myFloat = 3.14
$myBool = $true

Operators
#

PowerShell supports a variety of operators, including arithmetic, comparison, and logical operators. Some of the most commonly used operators include:

  • Arithmetic Operators: + (addition), - (subtraction), * (multiplication), / (division), % (modulus).
  • Comparison Operators: -eq (equal), -ne (not equal), -gt (greater than), -lt (less than), -ge (greater than or equal to), -le (less than or equal to).
  • Logical Operators: -and (logical and), -or (logical or), -not (logical not).

Control structures
#

PowerShell supports various control structures, such as If-Else statements, For loops, and While loops. These structures can be used to conditionally execute code and control the flow of the script.

For example:

if ($myInt -gt 0) {
    Write-Host "The value of myInt is positive."
} else {
    Write-Host "The value of myInt is negative or zero."
}

for ($i = 1; $i -le 10; $i++) {
    Write-Host $i
}

while ($myBool -eq $true) {
    # do something
}

Functions
#

Functions are a fundamental building block of PowerShell scripts. They allow you to define a block of code that can be called multiple times within the script. Here’s an example of a function that takes two arguments and returns their sum:

function Add-Numbers {
    param($num1, $num2)
    $sum = $num1 + $num2
    return $sum
}

$result = Add-Numbers 3 4
Write-Host "The sum is $result"

Pen testing and red teaming with PowerShell
#

The same reach into .NET and the Windows API that makes PowerShell useful to admins makes it useful on an engagement. Network scanning, hash cracking, pulling apart a web app: none of it needs a compiled tool when the interpreter is already on the box. Three examples that show the shape of the work.

Port scanner
#

Port scanning is table stakes. The example below walks a small range of hosts and the first 1024 ports, and reports which ones accept a TCP connection.

$subnet = "192.168.1"
$hosts  = 1..10
$ports  = 1..1024

foreach ($h in $hosts) {
    $target = "$subnet.$h"
    foreach ($port in $ports) {
        $client  = New-Object Net.Sockets.TcpClient
        $connect = $client.BeginConnect($target, $port, $null, $null)
        # WaitOne returns once the handshake completes or the timeout expires;
        # check Connected to tell an open port from one that just timed out.
        if ($connect.AsyncWaitHandle.WaitOne(200, $false) -and $client.Connected) {
            Write-Host "$target`:$port open"
            $client.EndConnect($connect)
        }
        $client.Close()
    }
}

The original range string ("192.168.1.1-10") is a common trap: PowerShell treats it as a single string, so a foreach over it runs exactly once against that literal. Build the addresses instead, as above. TcpClient opens a connection to each host and port; WaitOne bounds how long you wait, and Connected confirms the port actually answered rather than the timeout firing.

This is synchronous and slow, one socket at a time, and a full connect scan is loud (every attempt completes a handshake the target can log). It’s fine for reading and for a handful of hosts. For anything real, reach for runspaces to parallelize, or just use nmap.

Password cracker
#

A dictionary attack is just a loop: hash each candidate from a wordlist with the same algorithm the target used, and compare. The example below cracks an MD5 digest.

$targetHash = "5f4dcc3b5aa765d61d8327deb882cf99"   # md5("password")
$md5 = [System.Security.Cryptography.MD5]::Create()

foreach ($candidate in Get-Content "C:\wordlist.txt") {
    $bytes = [System.Text.Encoding]::UTF8.GetBytes($candidate)
    $hash  = [System.BitConverter]::ToString($md5.ComputeHash($bytes)).Replace("-", "").ToLower()
    if ($hash -eq $targetHash) {
        Write-Host "Found: $candidate"
        break
    }
}

One thing to be clear about, because a lot of copied-around scripts get it wrong: base64 is not a hash. Encoding a string with [Convert]::ToBase64String is reversible and provides no security, so comparing base64 output against a “hash” cracks nothing. The loop above uses a real one-way function (System.Security.Cryptography.MD5) and does the comparison as hex.

MD5 is here because it’s short and recognizable. On a real Windows engagement you’re usually cracking NTLM or NetNTLMv2 out of a hashdump or a Responder capture, and you’d feed those to hashcat or John rather than a PowerShell loop, which is orders of magnitude slower. The value of writing it out by hand is understanding what those tools do underneath.

Web crawler
#

Mapping a site’s links is a common early step against a web target. The crawler below stays on one domain and visits each page once.

$start = "https://example.com"
$queue = [System.Collections.Generic.Queue[string]]::new()
$seen  = [System.Collections.Generic.HashSet[string]]::new()
$queue.Enqueue($start); [void]$seen.Add($start)

while ($queue.Count -gt 0) {
    $current = $queue.Dequeue()
    try { $response = Invoke-WebRequest -Uri $current -UseBasicParsing } catch { continue }

    foreach ($href in $response.Links.href) {
        if (-not $href) { continue }
        # Resolve relative links ("/about") against the current page to an absolute URL.
        $abs = [System.Uri]::new([System.Uri]$current, $href).AbsoluteUri
        if ($abs.StartsWith($start) -and $seen.Add($abs)) {
            $queue.Enqueue($abs)
            Write-Host $abs
        }
    }
}

Two details the quick-and-dirty version usually gets wrong. First, a Queue plus a HashSet beats slicing an array by hand: $links[1..($links.Count - 1)] looks like “drop the first element,” but when the array has one item left it becomes $links[1..0], which PowerShell reads as the range 1,0 and hands back the elements in reverse. HashSet.Add also returns $false when the item is already present, which is your dedup check for free. Second, most href values are relative (/login, ../admin); resolving them against the current page with System.Uri before you compare or enqueue is what keeps the crawl on the target and pointed at real URLs.

Strengths and trade-offs on an engagement
#

The reason PowerShell is the default on Windows is that it’s already there. It’s native to the OS, it reaches every Windows subsystem through .NET, and the syntax is forgiving enough to pick up quickly. There’s also a deep well of existing tooling to build on: PowerView, PowerUp, the wider PowerSploit and Empire lineage, and a large community that has already written most of what you need.

Cross-platform used to be the obvious weakness, and it mostly isn’t anymore. PowerShell 7, the open-source .NET-based successor to Windows PowerShell 5.1, runs on Linux and macOS. The Windows-specific pieces still assume a Windows target (WMI, the ActiveDirectory module, most living-off-the-land tricks), so on engagement it’s still a Windows tool in practice. Performance is fine for glue and recon but not for anything CPU-bound; a real crack or a fast scan belongs in a purpose-built tool.

The trade-off that actually shapes how you use it is that PowerShell is loud. Defenders instrument it heavily. AMSI scans script content before it runs, script-block logging records what you executed even if you obfuscated it, and Constrained Language Mode plus execution policies can box you in. The same visibility that makes PowerShell convenient for you makes it convenient for the blue team, which is why mature environments watch it closely and why “just run it in PowerShell” is often the fastest way to get caught.

Where this leaves you
#

This is enough syntax to read almost any PowerShell you’ll run into and to write your own for recon and glue work. The three examples aren’t valuable as tools in their own right; nobody port-scans with a for-loop when nmap exists. They’re valuable because they show how little friction there is between having a shell on a Windows box and doing real work, since the interpreter is already sitting there. Just remember the other half of that bargain: the same box is very likely logging what you run, so treat every script you drop into a session as something the blue team may read back later.

UncleSp1d3r
Author
UncleSp1d3r
As a computer security professional, I’m passionate about building secure systems and exploring new technologies to enhance threat detection and response capabilities. My experience with Rails development has enabled me to create efficient and scalable web applications. At the same time, my passion for learning Rust has allowed me to develop more secure and high-performance software. I’m also interested in Nim and love creating custom security tools.