Cross-Platform Scripting
Detect the actual runtime before assuming what syntax/tools are available —
don't write PS7 syntax for a PS5.1 host, or GNU sed flags for macOS.
1. Detect the OS
- PowerShell 6+/7+:
$IsWindows,$IsMacOS,$IsLinux(booleans). - PowerShell 5.1: these variables don't exist — PS5.1 only runs on
Windows, so its mere presence (
$PSVersionTable.PSEdition -eq 'Desktop') implies Windows. - From a POSIX shell:
uname -s→Linux,Darwin(macOS), orMINGW*/MSYS*/CYGWIN*(Git Bash / WSL-adjacent on Windows). - If a project ships parallel scripts (
setup.ps1+setup.sh, or*.cmd+*.ps1), that's a signal contributors use multiple OSes — keep both in sync when changing behavior.
2. Detect PowerShell version before writing/editing .ps1
$PSVersionTable.PSVersion # e.g. 5.1.19041.x or 7.4.x
$PSVersionTable.PSEdition # "Desktop" (5.1) or "Core" (7+)
Check the project's existing scripts for a stated minimum (a comment like
"PS5-compatible" or #Requires -Version 7.0) — match that, don't assume
the latest syntax is fine.
3. PS7+-only syntax to avoid on PS5.1
| Feature | PS7+ | PS5.1-safe equivalent |
|---|---|---|
| Pipeline chain operators | cmd1 && cmd2, cmd1 || cmd2 |
cmd1; if ($?) { cmd2 } |
| Ternary | $x ? $a : $b |
if ($x) { $a } else { $b } |
| Null-coalescing | $a ?? $b, $a ??= $b |
if ($null -eq $a) { $b } else { $a } |
ForEach-Object -Parallel |
available | sequential foreach/ForEach-Object, or Start-Job/runspaces |
Get-Error |
available | inspect $Error[0] / $_.Exception |
&&/|| for native exe stderr capture |
works as expected | redirecting a native command's stderr inside PS5.1 wraps it as a NativeCommandError and can flip $? to $false even on exit 0 — avoid 2>&1 on native exes; capture stdout/stderr separately if needed |
When a script must support both, default to PS5.1-safe syntax and add
#Requires -Version 7.0 only if a PS7+-only feature is genuinely needed —
then document that requirement at the top of the script and in any README
that lists prerequisites.
4. Cross-OS path and tool differences
- Use
Join-Path(PowerShell) or path-library functions instead of hardcoded\or/separators. sed -idiffers: GNUsed -i 's/x/y/' filevs BSD/macOSsed -i '' 's/x/y/' file(macOS requires the empty extension argument). Preferperl -i -peor a small Python/Node one-liner when a script must run on both.- Line endings: scripts checked out on Windows may get CRLF;
.shscripts with CRLF line endings fail withbad interpreteron Linux/macOS — check.gitattributesfor* text=autoor expliciteol=lfrules on shell scripts. Test-Path/New-Item -ItemType Directory -Force(PowerShell) vs[ -d "$dir" ]/mkdir -p(POSIX) — don't mix syntaxes within one script.
5. When generating a new script
- Detect (or ask) which environment(s) it must run on.
- If the project already has a script for the same task in another shell
(
.ps1next to.sh), match its behavior and keep both updated. - Default to the most restrictive compatible syntax (PS5.1 / POSIX sh)
unless the task genuinely needs a newer feature — then state the
requirement explicitly (
#Requires -Version 7.0,#!/usr/bin/env bashwith a documented bash-version minimum). - Test the script (or at least syntax-check it:
pwsh -NoProfile -Command "& { . ./script.ps1 -WhatIf }"style dry runs, orbash -n script.sh) before declaring it done.