← BACK TO HOME

The Shell Crossover, Part 1: PowerShell on macOS

A practical guide to using PowerShell 7+ on macOS for Windows administrators crossing into Apple fleet management.

PowerShell 7+ on macOS runs as pwsh. For a Windows administrator, that distinction matters. powershell.exe refers to legacy Windows PowerShell 5.1, which is tied to the Windows-only .NET Framework. pwsh is the cross-platform PowerShell executable used on macOS, Linux, and modern Windows automation hosts.

The useful mental model is not “PowerShell turns a Mac into Windows.” It does not. PowerShell 7+ gives you the object pipeline, structured data handling, REST API ergonomics, module packaging, and familiar syntax you already know from Windows administration. The macOS operating system underneath still follows Unix conventions: forward-slash paths, executable permissions, case-sensitive behavior in many locations, process ownership, launch services, property lists, MDM profiles, and native shell tooling.

That makes pwsh a bridge, not a replacement for the Apple management stack. Use PowerShell 7+ when the task is API-heavy, object-heavy, or cross-platform. Use zsh, Jamf Pro policies, configuration profiles, declarative device management, and native macOS binaries when the task depends on local macOS behavior.

Windows-to-macOS translation map

  • Windows PowerShell 5.1: Windows-only automation based on the .NET Framework. The executable is powershell.exe.
  • PowerShell 7+: Cross-platform automation based on modern .NET. The executable is pwsh.
  • Command Prompt / batch files: Closest macOS operational equivalent is usually zsh scripting, not PowerShell.
  • Group Policy: Closest Apple management equivalent is MDM-delivered configuration profiles, not PowerShell scripts.
  • SCCM/MECM package logic: Closest Jamf equivalent is usually packages, policies, smart groups, extension attributes, and scripts.
  • PowerShell remoting over WinRM: Do not assume the Windows remoting model exists on macOS. Favor SSH, MDM commands, Jamf policy execution, or API-driven orchestration.

Architecture overview

PowerShell 7+ is a cross-platform task automation environment made up of a command-line shell, a scripting language, and a configuration management framework. On macOS, it runs on the modern .NET runtime and interacts with the system like other Unix-aware command-line tools.

The core components are:

  • PowerShell engine: The runtime that parses commands, executes scripts, and manages the object pipeline.
  • Cmdlets: Compiled or script-based commands that emit .NET objects instead of plain text whenever possible.
  • Modules: Reusable units that package commands, classes, formats, and supporting files. Examples include Az, Microsoft.Graph, and custom modules such as an internal Jamf Pro API wrapper.
  • .NET runtime: The runtime layer that allows PowerShell 7+ to run across Windows, macOS, and Linux.
  • Native command interoperability: The ability to invoke macOS commands such as sw_vers, scutil, profiles, defaults, plutil, and jamf from inside a PowerShell session.

Why Windows administrators should care

The most valuable feature PowerShell brings to macOS administration is not local Mac control. The value is structured automation. Jamf Pro, Microsoft Graph, Entra ID, AWS, GitHub, and most modern management systems expose APIs that return JSON. PowerShell 7+ is a strong fit for collecting that data, reshaping it, validating it, and sending it to another system.

For example, a Jamf Pro workflow may start by querying computer inventory through the Jamf Pro API, compare that data against Entra ID or asset records, and then produce an exception report. That is a natural PowerShell problem. Installing Rosetta, reading a local preference file, or triggering a local Jamf policy may be better handled by zsh or a native Jamf script payload.

Strengths and limitations on macOS

Strengths

  • Object pipeline: PowerShell passes objects between commands, which reduces fragile text parsing when working with structured data.
  • API automation: Invoke-RestMethod, JSON conversion, hashtables, and custom objects make PowerShell 7+ a practical language for Jamf Pro API work.
  • Cross-platform reuse: The same script can often run from a Windows administration workstation, a Mac administration workstation, a Linux runner, or a CI/CD job with only path and credential handling changes.
  • Cloud modules: Microsoft and AWS management modules make PowerShell useful for identity, cloud, and endpoint-adjacent workflows.
  • Readable administrative syntax: Administrators with an SCCM, GPO, or server automation background can usually understand PowerShell intent faster than dense shell pipelines.

Limitations

  • It is not native macOS configuration management: PowerShell does not replace Apple MDM, configuration profiles, declarative device management, PPPC payloads, FileVault payloads, or Jamf Pro policy mechanics.
  • Some Windows cmdlets do not exist on macOS: Several management and remoting cmdlets are Windows-only or behave differently on non-Windows platforms.
  • Execution policy is not a macOS security boundary: On Linux and macOS, PowerShell reports execution policy as unrestricted and Set-ExecutionPolicy does not enforce script trust.
  • Case and path behavior can break Windows-era assumptions: macOS and Unix-style tooling care more about path casing and executable permissions than many Windows administrators expect.
  • Performance overhead exists: For a short local task, native zsh may start faster and depend on fewer moving parts.
  • DSC is not legacy Windows DSC on a Mac: DSC v3 is cross-platform, but it is invoked as a command and does not include the old Local Configuration Manager service model.

Installation methods

Microsoft documents multiple ways to install PowerShell 7 on macOS. For a managed admin workstation, the Microsoft package installer is the most direct vendor-supported path. Homebrew is convenient and common among Mac administrators, but the Homebrew formula is maintained by the Homebrew community and builds PowerShell from source rather than installing a Microsoft-built package.

Option A: Install with Homebrew

Use this method when Homebrew is already part of your admin workstation standard. On Apple Silicon, Homebrew commonly lives under /opt/homebrew. On Intel Macs, it commonly lives under /usr/local.

# Ask Homebrew for its install prefix so the script does not assume Intel or Apple Silicon.
brew --prefix

# Update Homebrew metadata before installing packages.
brew update

# Install the community-maintained PowerShell formula.
brew install powershell

# Confirm that the pwsh executable is discoverable through PATH.
which pwsh

# Confirm the installed PowerShell version.
pwsh --version

Option B: Install with Microsoft’s macOS package

Use this method when you want the direct package published for a specific PowerShell release. Microsoft currently documents supported macOS packages for PowerShell 7.6 LTS, 7.5, and 7.4 LTS, with separate builds for Arm64 and x64 systems.

# Report the Mac CPU architecture.
uname -m

# Report the macOS version.
sw_vers

# After installing the correct Microsoft package, confirm the executable path.
which pwsh

# Confirm the installed PowerShell version.
pwsh --version

First-run validation

After installation, start PowerShell from Terminal:

# Start an interactive PowerShell 7+ session.
pwsh

Inside the PowerShell session, inspect the runtime and platform details:

# Display the PowerShell version, edition, OS description, and platform.
$PSVersionTable

# Show where the current PowerShell engine is installed.
$PSHOME

# Show the current user's PowerShell profile path.
$PROFILE

# Display the module lookup paths used by this PowerShell session.
$env:PSModulePath -split [IO.Path]::PathSeparator

On macOS, user profiles are read from ~/.config/powershell/profile.ps1, user modules are read from ~/.local/share/powershell/Modules, and shared modules are read from /usr/local/share/powershell/Modules. That layout is different from Windows profile and module locations, so avoid hardcoding Windows paths in cross-platform scripts.

Module installation and trust boundaries

PowerShell modules are useful on macOS, but the same supply-chain rules apply that you would use for NuGet packages, Python packages, or vendor scripts. Install modules from known repositories, pin versions where repeatability matters, and avoid turning PSGallery into a blind trust source on production systems.

# Inspect currently registered PowerShell repositories.
Get-PSRepository

# Install the Azure PowerShell rollup module from PSGallery.
Install-Module -Name Az -Repository PSGallery -Scope CurrentUser

# Import the module into the current session.
Import-Module Az

# Confirm that the module loaded and report its installed version.
Get-Module Az -ListAvailable | Select-Object Name, Version, Path

A practical Jamf helper module would follow the same pattern. The module should hide authentication boilerplate, return structured objects, and keep article examples focused on the administrative workflow rather than repeated OAuth plumbing.

macOS behaviors that break Windows assumptions

Execution policy

Do not use execution policy as a macOS script-control strategy. On non-Windows platforms, PowerShell ignores execution policies. Get-ExecutionPolicy reports Unrestricted, and Set-ExecutionPolicy does not enforce script execution rules.

# On macOS, this reports Unrestricted.
Get-ExecutionPolicy

# On macOS, this does not create a Windows-style enforcement boundary.
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser

For managed Macs, use MDM controls, configuration profiles, PPPC payloads, Gatekeeper, notarization expectations, Jamf policy controls, file permissions, and source control review to create enforcement boundaries. Execution policy is not the macOS equivalent of an enterprise script enforcement control and should not be thought of as such.

Case sensitivity and paths

PowerShell itself is mostly case-insensitive, but the underlying macOS file system and Unix tooling can expose case-sensitive behavior. Module names, file names, and paths should be cased consistently.

# Prefer Join-Path over hardcoded string concatenation.
$ScriptRoot = $HOME
$TargetPath = Join-Path -Path $ScriptRoot -ChildPath 'AdminCrossover/Test.json'

# Create parent folders explicitly.
New-Item -Path (Split-Path $TargetPath) -ItemType Directory -Force

# Write structured data as JSON.
[pscustomobject]@{
    Platform = 'macOS'
    Shell    = 'pwsh'
} | ConvertTo-Json | Set-Content -Path $TargetPath

Native commands return text

PowerShell cmdlets usually emit objects. Native macOS commands usually emit text. Treat native command output as untrusted text until you parse or validate it.

# sw_vers is a native macOS command, so its output is text.
$MacVersionText = sw_vers

# Convert selected lines into a hashtable-like object for safer downstream use.
$MacInfo = @{}
foreach ($Line in $MacVersionText) {
    $Name, $Value = $Line -split ':', 2
    $MacInfo[$Name.Trim()] = $Value.Trim()
}

# Emit a structured object after parsing the native text output.
[pscustomobject]@{
    ProductName    = $MacInfo['ProductName']
    ProductVersion = $MacInfo['ProductVersion']
    BuildVersion   = $MacInfo['BuildVersion']
}

When to use pwsh, zsh, or Jamf

The goal is not to force every Mac task into PowerShell. The goal is to select the tool that matches the control plane.

  • Use PowerShell 7+: Jamf Pro API automation, Microsoft Graph workflows, AWS automation, report generation, JSON transformations, CSV exports, and cross-platform orchestration.
  • Use zsh: Local Mac tasks that call native binaries, manipulate local files, run during Jamf policy execution, or need minimal runtime dependencies.
  • Use Jamf Pro and MDM: Settings enforcement, PPPC, FileVault, certificates, configuration profiles, software deployment, inventory, scoping, Self Service, and declarative management workflows.

A practical rule: if the script spends most of its time talking to APIs, start with pwsh. If the script spends most of its time changing local Mac state, start with zsh. If the desired outcome is ongoing configuration enforcement, use MDM and Jamf Pro rather than a recurring shell script.

Troubleshooting

pwsh: command not found

  • Restart Terminal after installing PowerShell.
  • Run brew --prefix and confirm the related bin path is in your shell profile.
  • On Apple Silicon, check /opt/homebrew/bin. On Intel Macs, check /usr/local/bin.
  • Run which pwsh from zsh to confirm path resolution.

Module installation fails

  • Confirm network access to PowerShell Gallery or the internal repository you use.
  • Use -Scope CurrentUser when you do not need a machine-wide module install.
  • Check proxy and TLS inspection behavior if the Mac is on a managed enterprise network.
  • Use Get-PSRepository to verify the repository registration.

A Windows script runs differently on macOS

  • Check for hardcoded backslash paths such as C:\Temp.
  • Check for Windows-only cmdlets such as Get-Service, Set-Service, or Enable-PSRemoting.
  • Check file and module name casing.
  • Check assumptions about execution policy, ACLs, registry access, COM objects, WMI, and Windows event logs.

The operating rule

Use PowerShell 7+ on macOS when the work is object-heavy, API-heavy, or cross-platform. Use zsh and native macOS tools when the work changes local Mac state. Use Jamf Pro and MDM when the goal is ongoing enforcement rather than one-time command execution.