|
| 1 | +# PowerShell Syntax Reference |
| 2 | + |
| 3 | +PowerShell is **object-oriented**, not text-oriented: pipelines pass .NET |
| 4 | +objects, not lines of text. It is not POSIX and does not accept Bash syntax. |
| 5 | +Target it for `.ps1` / `.psm1` / `.psd1` files, Windows-first tooling, and |
| 6 | +cross-platform automation where `pwsh` (PowerShell 7+) is the runtime. Prefer |
| 7 | +`pwsh` (cross-platform, MIT-licensed) over Windows PowerShell 5.1 for anything |
| 8 | +new. |
| 9 | + |
| 10 | +Docs: <https://learn.microsoft.com/powershell/scripting/> |
| 11 | + |
| 12 | +## Variables |
| 13 | + |
| 14 | +```powershell |
| 15 | +$name = "mohamed" # spaces around = are fine and conventional |
| 16 | +[int]$n = 42 # typed |
| 17 | +[string[]]$files = 'a.rs','b.rs' |
| 18 | +$env:PATH = "$env:PATH:/extra" # environment variables live under $env: |
| 19 | +Remove-Variable name # unset |
| 20 | +``` |
| 21 | + |
| 22 | +- Sigil `$` is part of the name (`$name`), not just expansion — assignment uses |
| 23 | + it too: `$x = 1`, never `x=1`. |
| 24 | +- **No word-splitting.** `$files` stays one array; `"$name"` stays one string. |
| 25 | + This is the biggest mental shift from POSIX — you almost never need defensive |
| 26 | + quoting against splitting. |
| 27 | +- Environment variables are a drive: `$env:HOME`, `$env:USERPROFILE`. |
| 28 | +- `$null`, `$true`, `$false` are the literals. |
| 29 | + |
| 30 | +## Everything is an object |
| 31 | + |
| 32 | +```powershell |
| 33 | +Get-ChildItem *.rs | Where-Object Length -gt 1kb | Sort-Object Name |
| 34 | +Get-Process | Select-Object Name, Id | Format-Table |
| 35 | +``` |
| 36 | + |
| 37 | +- Cmdlets are `Verb-Noun` (`Get-ChildItem`, `Set-Content`, `Where-Object`). |
| 38 | +- The pipeline carries objects; `$_` (or `$PSItem`) is the current object inside |
| 39 | + `ForEach-Object` / `Where-Object`. |
| 40 | +- Reach for cmdlets over external binaries when a native one exists — you keep |
| 41 | + the objects instead of re-parsing text. |
| 42 | + |
| 43 | +## Comparison operators (NOT `[[ ]]` / `-eq` is a word) |
| 44 | + |
| 45 | +```powershell |
| 46 | +if ($n -eq 5) { } # equal |
| 47 | +if ($n -ne 5) { } # not equal |
| 48 | +if ($n -lt 5) { } # less than (-le -gt -ge similarly) |
| 49 | +if ($s -like '*.rs') { } # wildcard match |
| 50 | +if ($s -match '\.rs$'){ } # regex match, sets $matches |
| 51 | +if ($arr -contains 1){ } # membership |
| 52 | +``` |
| 53 | + |
| 54 | +- **No `<`, `>`, `==` for comparison** — those are redirection/assignment-ish. |
| 55 | + Use the dashed word operators. |
| 56 | +- Case-insensitive by default; prefix `c` for case-sensitive (`-ceq`, `-cmatch`) |
| 57 | + or `i` to force insensitive (`-ieq`). |
| 58 | +- Against an array, comparison operators **filter**: `1,2,3 -gt 1` returns |
| 59 | + `2,3`, not a boolean. Wrap in `@(...)`/check `.Count` when you mean a test. |
| 60 | + |
| 61 | +## Conditionals |
| 62 | + |
| 63 | +```powershell |
| 64 | +if ($x -gt 10) { |
| 65 | + "big" |
| 66 | +} elseif ($x -eq 10) { |
| 67 | + "ten" |
| 68 | +} else { |
| 69 | + "small" |
| 70 | +} |
| 71 | +
|
| 72 | +switch ($name) { |
| 73 | + 'a.rs' { 'rust' } |
| 74 | + { $_ -like '*.toml' } { 'config' } |
| 75 | + default { 'other' } |
| 76 | +} |
| 77 | +``` |
| 78 | + |
| 79 | +## Loops |
| 80 | + |
| 81 | +```powershell |
| 82 | +foreach ($f in $files) { $f } |
| 83 | +for ($i = 0; $i -lt 10; $i++) { $i } |
| 84 | +while ($x -lt 100) { $x++ } |
| 85 | +do { $x++ } while ($x -lt 100) |
| 86 | +
|
| 87 | +Get-ChildItem *.rs | ForEach-Object { $_.Name } # pipeline form |
| 88 | +1..10 | ForEach-Object { $_ } # range operator |
| 89 | +``` |
| 90 | + |
| 91 | +- `1..10` is an inclusive range. |
| 92 | +- `foreach` (statement) and `ForEach-Object` (pipeline cmdlet) are different — |
| 93 | + the statement is faster for in-memory collections; the cmdlet streams. |
| 94 | + |
| 95 | +## Functions |
| 96 | + |
| 97 | +```powershell |
| 98 | +function Get-Greeting { |
| 99 | + param( |
| 100 | + [Parameter(Mandatory)][string]$Name, |
| 101 | + [int]$Times = 1 |
| 102 | + ) |
| 103 | + 1..$Times | ForEach-Object { "hello $Name" } |
| 104 | +} |
| 105 | +
|
| 106 | +Get-Greeting -Name mohamed -Times 2 |
| 107 | +``` |
| 108 | + |
| 109 | +- Name functions `Verb-Noun` with an approved verb (`Get`, `Set`, `New`, |
| 110 | + `Remove`, …; see `Get-Verb`). |
| 111 | +- Parameters go in a `param( )` block, typed and attributed. Add |
| 112 | + `[CmdletBinding()]` above `param` for an advanced function (gets `-Verbose`, |
| 113 | + `-ErrorAction`, etc. for free). |
| 114 | +- Output is whatever expression values fall out — no `return` needed (and |
| 115 | + `return $x` just emits `$x` then exits). |
| 116 | + |
| 117 | +## Arrays, hashtables, splatting |
| 118 | + |
| 119 | +```powershell |
| 120 | +$arr = 1, 2, 3 # or @(1,2,3) |
| 121 | +$arr += 4 # arrays are immutable; this rebuilds |
| 122 | +$arr.Count |
| 123 | +$map = @{ name = 'mj'; n = 42 } |
| 124 | +$map['name']; $map.name |
| 125 | +
|
| 126 | +$params = @{ Path = 'x.txt'; Encoding = 'utf8' } |
| 127 | +Set-Content @params # splatting: @params, not $params |
| 128 | +``` |
| 129 | + |
| 130 | +## Strings |
| 131 | + |
| 132 | +```powershell |
| 133 | +"interpolated $name and $($n + 1)" # double quotes expand; $() for expressions |
| 134 | +'literal $name' # single quotes do NOT expand |
| 135 | +@" |
| 136 | +multi-line $name |
| 137 | +"@ # expanding here-string |
| 138 | +@' |
| 139 | +multi-line literal $name |
| 140 | +'@ # literal here-string |
| 141 | +``` |
| 142 | + |
| 143 | +- Sub-expression `$( … )` is required to interpolate anything beyond a bare |
| 144 | + variable (property access, arithmetic, method calls). |
| 145 | + |
| 146 | +## Command substitution / external commands |
| 147 | + |
| 148 | +```powershell |
| 149 | +$branch = (git rev-parse --abbrev-ref HEAD) # capture stdout as string(s) |
| 150 | +$count = (Get-Content notes.txt | Measure-Object -Line).Lines |
| 151 | +& $exe --flag # call operator for a path/var |
| 152 | +git status; $LASTEXITCODE # exit code of last native exe |
| 153 | +$? # success of last operation (bool) |
| 154 | +``` |
| 155 | + |
| 156 | +- `$(...)` is a sub-expression; `( )` captures pipeline output. Native command |
| 157 | + stdout comes back as an array of strings (split on newlines). |
| 158 | +- Use `&` (call operator) to run a command whose name is in a variable or has a |
| 159 | + space in its path. |
| 160 | +- Native-exe success is `$LASTEXITCODE`; cmdlet success is `$?`. They are not |
| 161 | + the same thing. |
| 162 | + |
| 163 | +## Redirection |
| 164 | + |
| 165 | +```powershell |
| 166 | +cmd > out.txt # success stream (1) to file |
| 167 | +cmd >> out.txt # append |
| 168 | +cmd 2> err.txt # error stream |
| 169 | +cmd *> all.txt # all streams |
| 170 | +cmd 2>&1 # merge error into success |
| 171 | +cmd | Out-File -Encoding utf8 out.txt |
| 172 | +$null = cmd # discard output (faster than | Out-Null) |
| 173 | +``` |
| 174 | + |
| 175 | +PowerShell has numbered streams beyond 1/2: `3` warning, `4` verbose, `5` debug, |
| 176 | +`6` information. `*>` captures them all. |
| 177 | + |
| 178 | +## Error handling |
| 179 | + |
| 180 | +```powershell |
| 181 | +$ErrorActionPreference = 'Stop' # make errors terminating (script-wide) |
| 182 | +
|
| 183 | +try { |
| 184 | + Get-Content missing.txt -ErrorAction Stop |
| 185 | +} catch { |
| 186 | + Write-Error "failed: $($_.Exception.Message)" |
| 187 | +} finally { |
| 188 | + # cleanup |
| 189 | +} |
| 190 | +
|
| 191 | +throw "fatal" |
| 192 | +``` |
| 193 | + |
| 194 | +- Many cmdlet errors are **non-terminating** by default and won't trigger |
| 195 | + `catch` unless you set `-ErrorAction Stop` (or `$ErrorActionPreference`). |
| 196 | +- The current error in `catch` is `$_`; the error collection is `$Error`. |
| 197 | + |
| 198 | +## Gotchas |
| 199 | + |
| 200 | +- **Not Bash.** `[[ ]]`, `$(( ))`, `${var^^}`, `for ((;;))`, `arr=(...)`, `&&` |
| 201 | + in older Windows PowerShell — none are PowerShell. (`&&`/`||` *do* work in |
| 202 | + pwsh 7+ as pipeline-chain operators, but not in 5.1.) |
| 203 | +- **Output is objects.** Piping to a text tool (`findstr`, `grep`) forces a |
| 204 | + string conversion and loses structure — prefer `Where-Object`/`Select-Object`. |
| 205 | +- **Execution policy** (Windows): scripts may be blocked. `pwsh -File x.ps1` or |
| 206 | + `Set-ExecutionPolicy -Scope Process RemoteSigned` for a one-off. |
| 207 | +- **Paths:** use `/` for cross-platform; `\` is Windows-only and is also the |
| 208 | + escape-ish char in some contexts. Prefer `Join-Path`. |
| 209 | +- **Case-insensitive** by default for comparisons, variable names, and cmdlet |
| 210 | + names — do not rely on case to disambiguate. |
| 211 | +- Pin the runtime: target `pwsh` 7+ for cross-platform scripts; note when a |
| 212 | + script requires Windows PowerShell 5.1 (e.g. uses a Windows-only module). |
| 213 | + |
| 214 | +## Quick translation targets |
| 215 | + |
| 216 | +| Bash / POSIX | PowerShell | |
| 217 | +|--------------|------------| |
| 218 | +| `foo=bar` | `$foo = 'bar'` | |
| 219 | +| `export FOO=bar` | `$env:FOO = 'bar'` | |
| 220 | +| `$(cmd)` | `(cmd)` or `$(cmd)` | |
| 221 | +| `if [ "$a" = "$b" ]` | `if ($a -eq $b)` | |
| 222 | +| `if [ -f x ]` | `if (Test-Path x)` | |
| 223 | +| `for f in *.rs; do …; done` | `foreach ($f in Get-ChildItem *.rs) { … }` | |
| 224 | +| `cmd > out 2>&1` | `cmd *> out` | |
| 225 | +| `grep pat file` | `Select-String pat file` | |
| 226 | +| `cat file` | `Get-Content file` | |
| 227 | +| `arr=(a b c); "${arr[0]}"` | `$arr = 'a','b','c'; $arr[0]` | |
| 228 | +| `${#arr[@]}` | `$arr.Count` | |
| 229 | +| `function foo { … }` | `function Foo { … }` | |
| 230 | +| `set -e` | `$ErrorActionPreference = 'Stop'` | |
0 commit comments