WindowsIntermediate

PowerShell Basics for Developers Coming from Bash

PowerShell isn't cmd.exe with extra steps — it's an object-oriented shell with a genuinely different model. A practical translation guide for developers who already know bash.

DevFieldGuideJune 15, 2026 (updated July 29, 2026)6 min read
Share:

The biggest adjustment moving from bash to PowerShell isn't syntax — it's that PowerShell pipes objects between commands, not plain text, which changes how filtering and processing actually work.

The core difference: objects, not text

bash
# bash — everything is text; you parse it with grep/awk/cut
ps aux | grep node | awk '{print $2}'
powershell
# PowerShell — Get-Process returns real objects with real properties
Get-Process node | Select-Object Id

There's no text-parsing step in the PowerShell version — Get-Process returns actual process objects with typed properties (Id, CPU, Name), and Select-Object picks the property you want directly, rather than counting whitespace-separated columns in a text stream.

Command translation table

bashPowerShellWhat it does
ls -laGet-ChildItem (alias ls, dir)List directory contents
cd /pathSet-Location (alias cd)Change directory
cat file.txtGet-Content file.txt (alias cat)Print file contents
grep "text" fileSelect-String "text" fileSearch for a pattern
rm fileRemove-Item file (alias rm)Delete a file
export VAR=value$env:VAR = "value"Set an environment variable
which nodeGet-Command nodeFind where a command resolves to
curl https://api...Invoke-WebRequest / Invoke-RestMethodMake an HTTP request

Most common bash commands have a PowerShell alias (ls, cat, rm, cd all work) — these are convenience aliases pointing at the real Verb-Noun cmdlet, not separate implementations, which is why behavior can differ subtly from bash's actual ls/cat.

The pipe, with real filtering

powershell
Get-Process | Where-Object { $_.CPU -gt 100 } | Sort-Object CPU -Descending

Where-Object filters based on a real property comparison ($_.CPU -gt 100 — "CPU greater than 100"), not a text pattern match — this is the direct payoff of piping objects instead of text: filtering and sorting by an actual typed field, not a guessed column position.

Variables and environment variables

powershell
$name = "world"
Write-Output "Hello, $name"
 
$env:PATH                    # read an environment variable
$env:NODE_ENV = "production" # set one, for this session

$env: is the PowerShell equivalent of $ for environment variables specifically — a plain $variable is a regular PowerShell variable, scoped to the session, not automatically exported to child processes the way an exported bash variable is.

Scripts: .ps1 files and execution policy

PowerShell scripts (.ps1) don't run by default on a fresh Windows install — an execution policy blocks unsigned scripts as a security default:

powershell
Get-ExecutionPolicy
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser

RemoteSigned (a common practical choice) allows locally-created scripts to run while still requiring downloaded scripts to be signed — a reasonable middle ground between the very restrictive default and disabling the protection entirely.

Real-world example: bulk-renaming files

powershell
Get-ChildItem *.txt | Rename-Item -NewName { $_.Name -replace '\.txt$', '.md' }

This renames every .txt file to .md in the current directory — Get-ChildItem returns real file objects, Rename-Item operates on each one via the pipeline, and the script block computes the new name per file using the object's actual .Name property.

Functions and modules

powershell
function Get-Greeting {
    param([string]$Name)
    return "Hello, $Name"
}
 
Get-Greeting -Name "world"

PowerShell functions use named parameters by convention (-Name) rather than positional arguments as the default style — this is idiomatic PowerShell, even though positional arguments also work, because named parameters make a call site self-documenting without needing to check the function definition.

Working with JSON, the way most modern tooling expects

powershell
$response = Invoke-RestMethod -Uri "https://api.example.com/data"
$response.items | Where-Object { $_.status -eq "active" }
 
# Convert an object to JSON, or parse JSON into an object
$data | ConvertTo-Json
'{"name": "Ada"}' | ConvertFrom-Json

Invoke-RestMethod (unlike Invoke-WebRequest) automatically parses a JSON response into a real PowerShell object — no separate parsing step, and the result immediately supports Where-Object/Select-Object the same as any other PowerShell object, which is a genuinely convenient default for anyone working with JSON APIs regularly.

Profile scripts: PowerShell's equivalent of .bashrc

Persistent customization (aliases, prompt configuration, environment variables set on every session) lives in a profile script, analogous to .bashrc/.zshrc:

powershell
$PROFILE  # shows the path to your current profile script
notepad $PROFILE  # or code $PROFILE, to edit it
powershell
# Inside the profile script
function ll { Get-ChildItem -Force }
$env:EDITOR = "code"

Unlike bash, PowerShell has multiple possible profile scopes (current user/current host, current user/all hosts, all users) — $PROFILE alone points at the most specific one (current user, current host), which is the right one to edit for personal customization in most cases.

Common mistakes

Common mistakes
  • Treating PowerShell output as plain text and reaching for string parsing (-split, regex) when the actual object already has the property you need — Select-Object -Property or .PropertyName is almost always the more correct, more robust approach.
  • Assuming an aliased command (ls, cat, rm) behaves identically to its bash namesake in every edge case. They're aliases for PowerShell cmdlets with PowerShell's own semantics — subtle differences (flag names, output format) can surface in less common usage.
  • Running scripts with an overly permissive execution policy (Unrestricted) instead of a more scoped one (RemoteSigned, -Scope CurrentUser) — the scoped, less-permissive option is usually sufficient and meaningfully safer.
  • Forgetting that $env:VAR = "value" only sets the variable for the current PowerShell session, the same way export in bash is session-scoped — persisting it across sessions needs a profile script or a system-level environment variable setting.

Frequently Asked Questions

DevFieldGuide
DevFieldGuide

Editorial Team

Practical tutorials and developer tools, written and maintained by the DevFieldGuide team.

Enjoyed this article?

Get the next one straight to your inbox, along with the best of what we publish each week.

Related Articles

More in Windows

View all