- devops
- l1
- topic-pack
- powershell --- Portal | Level: L1: Foundations | Topics: PowerShell | Domain: DevOps & Tooling
PowerShell Primer for Linux Ops Engineers¶
You run Linux. You think in bash. But your org has Windows servers, Active Directory, Azure, or Exchange -- and the GUI people are too slow. PowerShell is how you script that world.
Why You Need This¶
Who made it: PowerShell was created by Jeffrey Snover at Microsoft, first released in 2006 as "Windows PowerShell 1.0." Snover wrote the influential "Monad Manifesto" in 2002, arguing that Windows needed a command-line shell built on .NET objects rather than text streams. The name "Monad" came from Leibniz's philosophy; it was renamed to PowerShell before release. PowerShell Core (cross-platform, open-source) was released in 2016, and runs on Linux and macOS as
pwsh.
- Windows Server administration is PowerShell-native. No ssh + bash equivalent.
- Azure, M365, and Active Directory tooling is PowerShell-first.
pwshruns on Linux. You can automate Azure from your terminal.- Mixed environments are the norm. Refusing to learn PowerShell is a career limiter.
Mental Model: PowerShell vs Bash¶
| Concept | Bash | PowerShell |
|---|---|---|
| Commands | lowercase utilities (ls, grep) |
Verb-Noun cmdlets (Get-ChildItem, Select-String) |
| Pipeline | passes text (bytes) | passes .NET objects |
| Parsing output | awk, cut, sed |
.Property access, Select-Object |
| Typing | everything is a string | strong types (int, string, array, hashtable) |
Remember: The PowerShell naming convention: every cmdlet is Verb-Noun. Common verbs: Get (read), Set (modify), New (create), Remove (delete), Start/Stop (control), Invoke (execute). If you can guess the verb and noun, you can guess the cmdlet.
Get-Command *Service*confirms your guess.
The object pipeline is the single biggest difference. You don't parse text -- you access properties on structured objects.
Essential Discovery Cmdlets¶
Get-Command *Service* # find cmdlets by name pattern
Get-Help Get-Service -Full # full docs for a cmdlet
Get-Service | Get-Member # inspect what properties/methods an object has
Get-Member is your | head + man combined. Use it constantly.
Variables¶
$name = "web01" # simple variable
$env:PATH # environment variable
$_ # current pipeline object (like awk's $0)
$? # last command success (bool, not exit code)
$LASTEXITCODE # actual exit code from native commands
Pipeline and Filtering¶
Get-Process | Select-Object Name, CPU, Id # select properties
Get-Service | Where-Object { $_.Status -eq "Running" } # filter
Get-Process | ForEach-Object { "PID: $($_.Id) Name: $($_.Name)" } # iterate
Get-Process | Sort-Object CPU -Descending | Select-Object -First 10 # sort + limit
String Handling¶
"Connecting to $host" # double quotes interpolate
'Connecting to $host' # single quotes are literal
"CPU count: $($env:NUMBER_OF_PROCESSORS)" # subexpression inside double quotes
File Operations¶
Get-Content /etc/hosts # cat
Set-Content -Path ./out.txt -Value "data" # echo > file
Add-Content -Path ./out.txt -Value "more" # echo >> file
Get-ChildItem -Recurse -Filter *.log # find . -name '*.log'
Test-Path ./somefile # test -f / test -d
Process and Service Management¶
Get-Process -Name nginx # ps aux | grep nginx
Stop-Process -Name nginx -Force # kill -9 $(pgrep nginx)
Get-Service -Name sshd # systemctl status sshd
Restart-Service -Name W3SVC # systemctl restart apache2
Remote Execution¶
Invoke-Command -ComputerName web01 -ScriptBlock { Get-Service W3SVC } # ssh host 'cmd'
Enter-PSSession -ComputerName web01 # ssh
Invoke-Command -ComputerName web01,web02,web03 -ScriptBlock { hostname } # fan out
PSRemoting uses WinRM (port 5985/5986). Enable on target: Enable-PSRemoting -Force.
Gotcha: WinRM uses port 5985 (HTTP) and 5986 (HTTPS). In production, always use HTTPS (5986) with proper certificates. The HTTP transport sends credentials in a way that is vulnerable to replay attacks on untrusted networks. Also, PSRemoting defaults to a 5-connection limit per user per machine — fan-out to 50 servers from a single session requires raising
MaxShellsPerUserviawinrm set.
PowerShell on Linux (pwsh)¶
sudo apt-get install -y powershell # Ubuntu/Debian
sudo yum install -y powershell # RHEL/CentOS
pwsh # launch
Cross-platform modules work. Windows-specific cmdlets (Get-Service, Get-EventLog) do not. Azure, REST, and file cmdlets work fine on Linux.
REST APIs and JSON¶
# GET request (like curl -s | jq)
$resp = Invoke-RestMethod -Uri "https://api.github.com/repos/torvalds/linux"
$resp.stargazers_count
# POST request
$body = @{ name = "new-repo" } | ConvertTo-Json
Invoke-RestMethod -Uri "https://api.example.com" -Method Post -Body $body `
-ContentType "application/json"
# JSON parsing (like jq .)
$data = '{"name":"web01","cpu":4}' | ConvertFrom-Json
$data.name
# Emit JSON
@{ name = "web01"; cpu = 4 } | ConvertTo-Json
Active Directory Basics¶
Requires the ActiveDirectory module (RSAT on Windows, or run from a DC).
Get-ADUser -Identity jdoe -Properties * # look up a user
Get-ADUser -Filter {Enabled -eq $false} # find disabled accounts
Get-ADGroup -Identity "DevOps" | Get-ADGroupMember # group membership
Get-ADComputer -Filter * -SearchBase "OU=Servers,DC=corp,DC=com"
Unlock-ADAccount -Identity jdoe # unlock locked account
Azure PowerShell¶
Install-Module Az -Scope CurrentUser # install (once)
Connect-AzAccount # login
Get-AzResourceGroup # list resource groups
Get-AzVM | Select-Object Name, ResourceGroupName, Location
Get-AzStorageAccount | Select-Object StorageAccountName, Location
Works from pwsh on Linux. This is why you install PowerShell on your jumpbox.
Scripting Basics¶
function Get-DiskReport {
param(
[string[]]$ComputerName = @("localhost"),
[int]$ThresholdPercent = 90
)
foreach ($computer in $ComputerName) {
Invoke-Command -ComputerName $computer -ScriptBlock {
Get-PSDrive -PSProvider FileSystem |
Where-Object { ($_.Used / ($_.Used + $_.Free)) * 100 -gt $using:ThresholdPercent }
}
}
}
try {
Stop-Service -Name "CriticalApp" -ErrorAction Stop
} catch {
Write-Error "Failed to stop service: $_"
}
-ErrorAction Stop converts non-terminating errors into catchable exceptions.
Without it, try/catch won't catch most cmdlet failures.
Default trap: PowerShell has two kinds of errors: terminating (thrown exceptions) and non-terminating (written to the error stream but execution continues). Most cmdlet failures are non-terminating — meaning
try/catchsilently ignores them. Always use-ErrorAction Stopon cmdlets insidetryblocks. This is the #1 PowerShell scripting bug for bash users who expect errors to stop execution.One-liner: Quick Azure VM inventory from Linux:
pwsh -c 'Connect-AzAccount; Get-AzVM | Select Name, ResourceGroupName, Location | Format-Table'— run this from your jumpbox to get a fleet summary without touching the Azure portal.