← ./articles

PowerShell Where-Object Fails from Bash? It Is Usually Variable Expansion

PowerShell commands that work in PowerShell can fail when you launch them through Bash.

The failure is especially confusing with Where-Object, because the command looks correct:

powershell -Command "Get-Process | Where-Object { $_.Id -eq $target_pid }"

The problem is that Bash sees $target_pid and $_ before PowerShell ever receives the command. By the time PowerShell runs, the script block may already be corrupted.

The symptom

You may see:

  • PowerShell parser errors
  • empty values inside Where-Object
  • filters that return nothing even though the process exists
  • errors mentioning unexpected \ or missing expressions

The command works when typed directly in PowerShell, but fails when wrapped in bash, sh, Git Bash, or a tool that launches commands through a POSIX shell.

Root cause

There are two shells involved:

Bash parses the command string first
PowerShell receives the already-expanded result

In this string:

powershell -Command "Get-Process | Where-Object { $_.Id -eq $target_pid }"

Bash tries to expand $target_pid. It may also treat $_ as a shell variable. If those variables are not defined in Bash, they become empty.

PowerShell then receives something like:

Get-Process | Where-Object { .Id -eq }

That is not the command you thought you sent.

Do not fix it with backslashes

This usually makes things worse:

powershell -Command "Get-Process | Where-Object { \$_.Id -eq \$target_pid }"

Backslash escaping is a Bash convention. PowerShell does not treat \$ as a normal escaped dollar in a script block. It may receive a literal backslash and then fail parsing.

Best fix: keep the logic inside PowerShell

If the logic can be self-contained, write it entirely as PowerShell:

powershell -Command "if (Get-Process -Name node -ErrorAction SilentlyContinue) { Write-Output 'running' } else { Write-Output 'stopped' }"

No Bash variables cross into the PowerShell string, so there is less to corrupt.

If a value must cross, use parameters

When you must pass a value from Bash to PowerShell, pass it as an argument:

target_pid=1234
powershell -Command "param([int]$pid) if (Get-Process -Id $pid -ErrorAction SilentlyContinue) { Write-Output 'found' }" -ArgumentList "$target_pid"

Now Bash passes one argument, and PowerShell receives it through param().

Practical checklist

When a PowerShell command fails only when launched from Bash:

  • look for $ inside the -Command string
  • avoid Where-Object { $_ ... } unless quoting is fully controlled
  • prefer pure PowerShell logic
  • pass values through param() and -ArgumentList
  • test the PowerShell body directly in PowerShell first

Summary

This is not a PowerShell bug. It is a shell boundary bug.

Bash expands $... before PowerShell gets the command. If you need reliable automation, either keep the logic entirely inside PowerShell or pass values through -ArgumentList.

References