Install on Debian / Ubuntu
sudo apt update
sudo apt install -y wget apt-transport-https software-properties-common
# Microsoft package repo
source /etc/os-release
wget -q "https://packages.microsoft.com/config/${ID}/${VERSION_ID}/packages-microsoft-prod.deb"
sudo dpkg -i packages-microsoft-prod.deb
rm packages-microsoft-prod.deb
sudo apt update
sudo apt install -y powershell
# Run it
pwsh
# PowerShell 7.x.x
# PS /home/amir>
Also available via brew (brew install powershell) on macOS, dnf on Fedora/RHEL, and as a portable tarball.
The object pipeline (the actual selling point)
bash pipelines are text. Every tool re-parses the text its predecessor produced; field-position bugs are constant. PowerShell pipelines pass typed objects:
# Get processes, filter by CPU, sort
Get-Process | Where-Object { $_.CPU -gt 50 } | Sort-Object CPU -Descending | Select-Object -First 5
# Equivalent in bash requires awk + ps parsing
ps aux --sort=-%cpu | awk 'NR>1 && $3 > 50' | head -5
The PowerShell version is type-safe; $_.CPU is a number, not a substring. Refactoring is easier; less prone to "the field shifted when we added a column" bugs.
Real cmdlet examples on Linux
# List files with size + last write
Get-ChildItem -Path /etc -File | Select-Object Name, Length, LastWriteTime
# Find files larger than 100 MB
Get-ChildItem -Path /var/log -Recurse -File | Where-Object Length -gt 100MB
# JSON in / out
Get-Content config.json | ConvertFrom-Json | Select-Object -ExpandProperty database |
Where-Object port -eq 5432 | ConvertTo-Json
# CSV in / out
Import-Csv users.csv | Where-Object active -eq true |
ForEach-Object { "$($_.email) is active" }
# Built-in HTTP
Invoke-RestMethod https://api.github.com/repos/cli/cli/issues |
Where-Object state -eq open |
Select-Object number, title, user
# Aliases for common bash commands
ls # alias for Get-ChildItem
cd # alias for Set-Location
pwd # alias for Get-Location
cat # alias for Get-Content
The killer use case: managing Windows from Linux
For mixed environments where some hosts are Windows and you're on Linux:
# Connect to a Windows host over PowerShell Remoting via SSH
Enter-PSSession -HostName win-server.lab -UserName administrator
# Or one-shot
Invoke-Command -HostName win-server.lab -ScriptBlock {
Get-Service | Where-Object Status -eq Running
}
# Run against many Windows hosts in parallel
$hosts = "web-01", "web-02", "web-03"
Invoke-Command -HostName $hosts -ScriptBlock {
Get-WmiObject Win32_OperatingSystem | Select-Object Caption, Version
}
The Linux host runs pwsh; SSH to Windows targets running OpenSSH (default since Windows 10/Server 2019) with a sshd_config that has PowerShell as a subsystem. Manage entire Windows fleets from a Linux admin VM.
Azure CLI in PowerShell
# Install the Az module
Install-Module -Name Az -Scope CurrentUser -Repository PSGallery -Force
# Login
Connect-AzAccount
# List VMs across subscriptions, find ones over a size threshold
Get-AzVM | Where-Object HardwareProfile.VmSize -like "Standard_D*" |
Select-Object Name, Location, @{N='VmSize';E={$_.HardwareProfile.VmSize}}
The Az PowerShell module is more idiomatic for complex Azure operations than the az CLI, particularly when chaining queries.
Scripts for cross-platform use
# build.ps1 (works on Linux / macOS / Windows)
#!/usr/bin/env pwsh
[CmdletBinding()]
param(
[string]$Target = "all",
[switch]$Clean
)
if ($Clean) {
Write-Host "Cleaning..." -ForegroundColor Yellow
Remove-Item -Recurse -Force ./dist -ErrorAction SilentlyContinue
}
Write-Host "Building target: $Target" -ForegroundColor Green
& dotnet build -c Release
if ($LASTEXITCODE -ne 0) { throw "Build failed" }
Write-Host "Done" -ForegroundColor Green
Mark executable (chmod +x build.ps1) and run via the shebang — same script works on every platform that has pwsh.
Modules + the PowerShell Gallery
Find-Module -Tag aws
Install-Module AWS.Tools.S3 -Scope CurrentUser
Import-Module AWS.Tools.S3
Get-S3Bucket | Select-Object BucketName, CreationDate
The PSGallery (similar to PyPI / npm) has thousands of modules: cloud providers, monitoring, parsers, format converters. Most are vendor-maintained (AWS, Azure, Google, VMware, Cisco, Microsoft).
Where bash beats PowerShell
- Startup time — pwsh takes ~300ms to start; bash is <10ms. For shell scripts called from cron / hot paths, bash wins.
- Universality — bash is everywhere; pwsh isn't installed by default on most distros.
- Integration with classic Unix tools — awk / sed / find idioms are deeply ingrained in *nix workflows; PowerShell on Linux is a bit of an alien import.
- Idiomatic Unix — PowerShell sometimes papers over actual Linux concepts (Set-Location vs cd; Get-Content vs cat). Use the bash idioms where they fit.
Where PowerShell beats bash
- Anything that involves structured data (JSON, CSV, XML) — ConvertFrom-Json / Import-Csv + object pipelines are dramatically cleaner than jq + awk chains.
- Anything that touches Windows or Azure — the native tooling is PowerShell-shaped.
- Long, complex scripts with multiple data structures — types catch bugs that bash's stringly-typed everything doesn't.
- Cross-platform admin scripting where you can't assume bash idioms work.
Worth knowing
- Aliases — PowerShell ships with bash-friendly aliases (
ls,cd,pwd,cat,cp,mv,rm). Quality varies;cat fileworks butcat -n filedoesn't. - Profiles —
~/.config/powershell/Microsoft.PowerShell_profile.ps1is the equivalent of~/.bashrc. Customize prompts, aliases, function libraries. - Module auto-load — modules referenced in a script auto-load on first use; no need to manually
Import-Module.