invoke-expression — Execute a string as PowerShell — eval equivalent across all 5 shells
Equivalents in every shell
eval "$cmd_string"Builtin. Parses the argument as a shell command and runs it in the CURRENT shell — it sees and can modify the shell's variables and functions. `eval "$user_input"` is the canonical injection vector and an OWASP regular.
eval "$cmd_string"Same `eval` builtin. Zsh adds `noglob eval "$x"` to suppress globbing during parsing — useful when the string contains literal `*` you do not want expanded.
eval "$cmd_string"Builtin in fish 3+ (replaced the earlier `function eval` shim). Same injection-risk profile as bash. Fish style guides recommend `string` builtins plus `command` invocation over `eval` for parsed input.
Invoke-Expression $cmdStringAliased as `iex`. Parses the input string as PowerShell script and executes it in the CURRENT scope — it sees and modifies session variables and functions. Microsoft explicitly recommends AVOIDING it for any input you do not fully control.
call %cmdVar%`call %var%` re-parses variable substitution to allow nested-variable expansion (limited eval-like behaviour). For full dynamic execution, write the string to a `.bat` file and invoke it. cmd has no general `eval` builtin — by design.
Worked examples
Execute a dynamically constructed command
cmd="ls -la /tmp"; eval "$cmd"$cmd = "Get-ChildItem -Force C:\Windows"; Invoke-Expression $cmdRun the output of another command as code (typical bootstrapper anti-pattern)
eval "$(ssh-agent -s)"Invoke-Expression (Invoke-WebRequest https://example.com/install.ps1).ContentSafer alternative: use the call operator with arguments as an array (no parsing)
cmd=(ls -la /tmp); "${cmd[@]}"$exe = "ls"; $args = @("-la", "/tmp"); & $exe @argsGotchas
- `Invoke-Expression $userInput` is the canonical PowerShell code-injection vector. Microsoft explicitly recommends against passing untrusted input to it. Safer alternatives for almost every use case: `&` (call operator) with separate args, splatting (`& $exe @paramHashtable`), `[scriptblock]::Create($x).Invoke()` (slightly safer because invocation is explicit), or `Get-Command` resolution.
- Scope: `Invoke-Expression` runs in the CALLER'S scope — assignments persist after it returns. `Invoke-Expression '$x = 42'` sets `$x` in your current scope. This makes it dangerous AND useful (e.g. for sourcing config files that set variables).
- Quoting is fragile: `Invoke-Expression "Get-Process -Name $name"` interpolates `$name` BEFORE invocation, so a `$name` of `'foo; Remove-Item *'` runs the destructive part. Single-quote and concat to defer expansion: `Invoke-Expression ('Get-Process -Name ' + $name)` — still dangerous if `$name` is untrusted, but at least the surrounding text is fixed.
- PSScriptAnalyzer flags every `Invoke-Expression` usage as `PSAvoidUsingInvokeExpression` — a code-review smell signal. Legitimate uses (REPL-like tools, package bootstrappers) should suppress with `[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingInvokeExpression','')]` AND document the justification in a comment immediately above the call.
- Performance: `Invoke-Expression` parses the input string on EVERY call — far slower than calling a cmdlet or function directly. Inside loops, parse once via `$sb = [scriptblock]::Create($code); 1..1000 | ForEach-Object { & $sb }` to amortise parsing.