The Windows side of the same story: Select-String and Invoke-RestMethod
PowerShell pipes objects, not text. For a code model that is a real advantage.
The PowerShell cmdlets worth knowing cold
Same idea as the Unix toolkit, one big difference: you filter on properties instead of parsing columns.
Select-String "TODO" -Path src\* -Recurse sls "api_key" . -Recurse -List sls "error|warn" app.log (sls "GET /" access.log).Count sls "^#" config.ini -NotMatch
irm https://api.example.com/v1
(iwr https://example.com).Headers
iwr -Uri https://host/a.zip -OutFile a.zip
irm -Method Post -ContentType "application/json" `
-Body '{"q":"hi"}' https://api.example.com
Why these two
Select-String (alias sls) is PowerShell's grep. Regex by default, recurses with -Recurse, inverts with -NotMatch. When an AI coding agent is working on a Windows box and needs to find every reference to a function, this is what it runs.
Invoke-RestMethod (alias irm) is PowerShell's curl — except it doesn't hand you a wall of JSON. It parses the response into a live object. irm $url then .items[0].id, and you're done. No jq required.
The twist: objects, not text
In Bash you pipe strings and parse columns with awk. In PowerShell you pipe [FileInfo] or [Process] objects and filter on properties: Where-Object Status -eq "Running". For a code model that is a real advantage — it reasons about fields by name instead of guessing which column holds what.
- Discoverable.
Get-Command *-Service*tells the agent what exists before it guesses. - Consistent. Every cmdlet is Verb-Noun. Learn one, predict the rest.
- Composable. One pipeline, five tiny steps, no script file.
- Honest about failure. Typed errors, not just "exit 1."
Different philosophy, same lesson: small tools with clear contracts are the interface language models were waiting for.
The full reference
PowerShell first, then the Bash equivalents so you can translate in either direction.
Select-String
| command | what it does |
|---|---|
| sls "pattern" -Path src\* -Recurse | recursive search; output shows file:line:text |
| sls "pattern" . -Recurse -List | first match per file only — filenames, effectively |
| sls "pattern" file -CaseSensitive | case-sensitive (default is insensitive) |
| sls "pattern" file -SimpleMatch | literal string, no regex |
| sls "pattern" file -NotMatch | invert |
| sls "pattern" file -Context 1,3 | 1 line before, 3 after |
| (sls "pattern" file).Count | count matching lines |
| sls "pattern" file | select -Expand Line | just the matching text |
| (sls "(\d+)" file).Matches.Groups[1].Value | capture-group extraction |
| gci -Recurse -Include *.py | sls "def " | restrict by file type via the pipeline |
Invoke-RestMethod and Invoke-WebRequest
| command | what it does |
|---|---|
| irm $url | GET, JSON auto-parsed into objects |
| (iwr $url).StatusCode | status code (iwr = raw response object) |
| (iwr $url -Method Head).Headers | headers only |
| iwr $url -OutFile a.zip | download to a file |
| irm $url -Method Post -Body ($obj | ConvertTo-Json) -ContentType "application/json" | JSON POST from a PowerShell object |
| irm $url -Headers @{Authorization="Bearer $t"} | auth header |
| irm $url -Credential (Get-Credential) | prompted basic auth |
| irm $url -Form @{file=Get-Item report.pdf} | multipart upload (PS 7+) |
| irm $url -MaximumRetryCount 3 -TimeoutSec 10 | retries and timeout (PS 7+) |
| irm $url -SkipHttpErrorCheck | don't throw on 4xx/5xx; inspect instead (PS 7+) |
| iwr $url -Verbose | see the request/response details |
Files and folders
| command | what it does |
|---|---|
| gci -Recurse -Filter *.log | find by name (Get-ChildItem; alias ls, dir) |
| gci -Recurse | ? LastWriteTime -gt (Get-Date).AddDays(-1) | modified in the last day |
| gci -Recurse | ? Length -gt 100MB | larger than 100 MB |
| gci *.tmp -Recurse | Remove-Item -WhatIf | preview a delete; drop -WhatIf to do it |
| gc file -Head 20 / gc file -Tail 20 | first / last 20 lines (Get-Content) |
| gc app.log -Wait -Tail 20 | follow a growing file |
| (gc file).Count | line count |
| gc a, b | sc c | concatenate (Set-Content) |
| Test-Path file | does it exist |
| Resolve-Path .\x | absolute path |
Transforming data
| command | what it does |
|---|---|
| (gc f) -replace 'old','new' | sc f | replace in a file (regex) |
| gc f | select -Skip 9 -First 11 | lines 10–20 |
| … | % { ($_ -split '\s+')[0] } | first whitespace column |
| Import-Csv f.csv | select col3 | CSV column by header name |
| Import-Csv f.csv | ? { [int]$_.qty -gt 100 } | rows where a column exceeds 100 |
| … | sort col2 | Export-Csv out.csv -NoTypeInformation | sort and write CSV |
| "abc".ToUpper() | string methods are right there |
| … | group | sort Count -Desc | frequency count (Group-Object) |
| gc d.json | ConvertFrom-Json | % items | select id, name | reshape JSON |
| $obj | ConvertTo-Json -Depth 5 | object back to JSON |
| … | % { cmd $_ } | run cmd once per item (ForEach-Object) |
| … | Tee-Object out.log | write to file and still print |
| … | Format-Table -Auto / Format-List | render as table / list (display only — keep it last) |
Processes, ports, disk, services
| command | what it does |
|---|---|
| Get-Process node | find a process (alias ps) |
| Get-Process | ? CommandLine -match "app.py" | match on full command line (PS 7+) |
| Stop-Process -Id PID -Force | kill |
| Get-NetTCPConnection -LocalPort 3000 | what's on port 3000 (Windows) |
| Get-NetTCPConnection -State Listen | select LocalPort, OwningProcess | all listeners with PIDs |
| Get-Service | ? Status -eq Running | running services |
| Restart-Service name | restart one |
| Get-PSDrive -PSProvider FileSystem | disk free per drive |
| gci | % { "{0,-30} {1,10:N1} MB" -f $_.Name, ((gci $_ -Recurse -File | measure Length -Sum).Sum/1MB) } | size of each folder here |
| Start-Job { cmd } / Receive-Job | run in the background |
| cmd *>&1 | Tee-Object run.log | capture every stream together |
Archives, help, history
| command | what it does |
|---|---|
| Compress-Archive dir\ out.zip | zip a folder |
| Expand-Archive in.zip -DestinationPath out\ | unzip |
| Get-Command *-Service* | discover cmdlets by name |
| Get-Help cmd -Examples | worked examples for any cmdlet |
| cmd | Get-Member | what properties and methods does this object have — the single most useful habit |
| Get-Alias sls | what an alias expands to |
| Get-History | ? CommandLine -match ssh | find that command you ran last week |
| ctrl-r | reverse history search (PSReadLine) |
| $PSVersionTable | which PowerShell am I on |
Bash ↔ PowerShell translation table
| bash | powershell |
|---|---|
| grep -rn x . | sls x . -Recurse |
| curl -s url | jq .a | (irm url).a |
| find . -name "*.log" | gci -Recurse -Filter *.log |
| sed -i 's/a/b/g' f | (gc f) -replace 'a','b' | sc f |
| awk '{print $1}' | % { ($_ -split '\s+')[0] } |
| tail -f f | gc f -Wait -Tail 10 |
| sort | uniq -c | sort -rn | group | sort Count -Desc |
| xargs -n1 cmd | % { cmd $_ } |
| ps aux | grep x | Get-Process x |
| lsof -i :3000 | Get-NetTCPConnection -LocalPort 3000 |
| du -sh * | gci | measure Length -Sum (per folder, see above) |
| tar -czf o.tgz d/ | Compress-Archive d\ o.zip |
| chmod +x s.sh | (not needed — execution policy instead: Set-ExecutionPolicy RemoteSigned) |
| which cmd | Get-Command cmd |
| man cmd | Get-Help cmd |
| 2>&1 | tee f | *>&1 | Tee-Object f |
Get-NetTCPConnection, Get-Service, and Get-PSDrive details are Windows-specific. On PowerShell 7 for macOS/Linux, the file, text, JSON, and web cmdlets all work the same; for ports and services, fall back to the native tools (lsof, systemctl).