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 file works but cat -n file doesn't.
  • Profiles~/.config/powershell/Microsoft.PowerShell_profile.ps1 is 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.