-
Notifications
You must be signed in to change notification settings - Fork 2
Pro Workflows
Copy-paste recipes for Jamf Pro administration. For flag details, see Jamf Pro Commands. For shared patterns (apply, scaffold, pagination), see CLI Patterns. For output format options, see Output Formats.
# Full instance dashboard: version, inventory counts, feature flags, cert expiry, alerts
jamf-cli pro overview
# Machine-readable overview for alerting pipelines
jamf-cli pro overview -o json
# Quick reachability check across all configured profiles
jamf-cli config list --status# List all computers (auto-paginates by default)
jamf-cli pro computers list
# Export full computer inventory to CSV
jamf-cli pro computers list -o csv --out-file computers.csv
# Export mobile devices to CSV
jamf-cli pro mobile-devices list -o csv --out-file mobile-devices.csv
# Extract just serial numbers (one per line)
jamf-cli pro computers list --field serialNumber
# Multi-field extraction via jq
jamf-cli pro computers list -o json \
| jq -r '.[] | [.id, .name, .serialNumber] | @csv' > inventory.csv
# Single page only (disable auto-pagination)
jamf-cli pro computers list --all=false --page-size 50
# Wide table with all columns
jamf-cli pro computers list -o table -w# Computers that haven't checked in for 30+ days
# Uses epoch comparison — works regardless of locale or date format
jamf-cli pro computers list -o json | jq --argjson cutoff "$(date -v-30d +%s)" '
[ .[]
| select(.lastContactTime != null)
| select((.lastContactTime | sub("\\.[0-9]+.*";"") | strptime("%Y-%m-%dT%H:%M:%S") | mktime) < $cutoff)
]'
# Just the count
jamf-cli pro computers list -o json | jq --argjson cutoff "$(date -v-30d +%s)" '
[ .[]
| select(.lastContactTime != null)
| select((.lastContactTime | sub("\\.[0-9]+.*";"") | strptime("%Y-%m-%dT%H:%M:%S") | mktime) < $cutoff)
] | length'
# Stale device names only
jamf-cli pro computers list -o json | jq -r --argjson cutoff "$(date -v-30d +%s)" '
.[]
| select(.lastContactTime != null)
| select((.lastContactTime | sub("\\.[0-9]+.*";"") | strptime("%Y-%m-%dT%H:%M:%S") | mktime) < $cutoff)
| .name'
# Linux-compatible cutoff (replace -v-30d with GNU date)
# cutoff=$(date -d '30 days ago' +%s)# List all Classic API policies
jamf-cli pro classic-policies list
# Count policies
jamf-cli pro classic-policies list -o json | jq length
# List macOS configuration profiles
jamf-cli pro classic-macos-config-profiles list
# List mobile device configuration profiles
jamf-cli pro classic-mobile-config-profiles list
# Count all config management objects at once
echo "Policies: $(jamf-cli pro classic-policies list -o json --no-input | jq length)"
echo "macOS Profiles: $(jamf-cli pro classic-macos-config-profiles list -o json --no-input | jq length)"
echo "Mobile Profiles: $(jamf-cli pro classic-mobile-config-profiles list -o json --no-input | jq length)"
echo "Scripts: $(jamf-cli pro scripts list -o json --no-input | jq length)"
echo "Packages: $(jamf-cli pro packages list -o json --no-input | jq length)"
# Find policies by name pattern
jamf-cli pro classic-policies list -o json | jq '.[] | select(.name | test("patch"; "i"))'
# Export a specific policy's XML for review
jamf-cli pro classic-policies get --name "Install Chrome"View and modify scope on policies, configuration profiles, and other Classic API resources — without editing raw XML. Scope is a subcommand on each resource that supports it.
# Check what's in scope for a policy
jamf-cli pro classic-policies scope get "Deploy Chrome" -o tableRESULTS (4 total)
SECTION TYPE NAME
───────────────────────────────────────────
target all_computers true
target computer_group All Managed Clients
limitation network_segment Corporate
exclusion computer_group Test Machines
# Add a computer group to targets
jamf-cli pro classic-policies scope add "Deploy Chrome" --computer-group "Marketing Macs"
# Add a building to exclusions
jamf-cli pro classic-policies scope add "Deploy Chrome" --section exclusion --building "London"
# Add a user group to limitations
jamf-cli pro classic-policies scope add "Deploy Chrome" --section limitation --user-group "Staff"
# Scope a macOS config profile
jamf-cli pro classic-macos-config-profiles scope add "Wi-Fi Settings" --computer-group "All Managed Clients"
jamf-cli pro classic-macos-config-profiles scope add "Wi-Fi Settings" --section exclusion --department "IT"# Remove a test group from targets
jamf-cli pro classic-policies scope remove "Deploy Chrome" --computer-group "Test Group"
# Remove a building from exclusions
jamf-cli pro classic-policies scope remove "Deploy Chrome" --section exclusion --building "London"# Add the same exclusion to multiple policies
for policy in "Deploy Chrome" "Deploy Firefox" "Deploy Slack"; do
jamf-cli pro classic-policies scope add "$policy" \
--section exclusion --computer-group "Test Machines"
done
# Audit scope across policies — export as JSON for processing
for policy in "Deploy Chrome" "Install Updates" "Security Baseline"; do
echo "=== $policy ==="
jamf-cli pro classic-policies scope get "$policy" -o json
doneNote: All add/remove operations are idempotent. Adding a group that already exists or removing one that isn't there is a silent no-op — safe to run in loops.
apply is a name-based upsert — creates the resource if it doesn't exist, replaces it (with confirmation) if it does. For flag details and collision handling see CLI Patterns#Apply (Upsert).
# Apply from stdin or file — creates or replaces by name
echo '{"name":"HQ","city":"Cupertino"}' | jamf-cli pro buildings apply --yes
jamf-cli pro buildings apply --from-file building.json --yes
# Classic API — same pattern with XML
cat policy.xml | jamf-cli pro classic-policies apply --yes
# Dry-run to preview without executing
jamf-cli pro buildings apply --from-file building.json --dry-run# Safe to run repeatedly — creates only if missing, replaces if present
for name in "Security" "Productivity" "Development" "Utilities"; do
echo "{\"name\":\"$name\",\"priority\":0}" | jamf-cli pro categories apply --yes
done# Export, modify a field, re-apply
jamf-cli pro buildings get --name "HQ" -o json \
| jq '.city = "San Francisco"' \
| jamf-cli pro buildings apply --yes
# Clone with a new name
jamf-cli pro buildings get 1 -o json \
| jq '.name = "Branch Office Copy"' \
| jamf-cli pro buildings apply
# Sync a resource from prod to staging
jamf-cli pro buildings get --name "HQ" -p prod -o json \
| jamf-cli pro buildings apply -p staging --yesjamf-cli pro buildings delete --name "Old Office" --yes
jamf-cli pro classic-policies delete --name "Legacy Policy" --yes
jamf-cli pro classic-printers delete --name "Broken Printer" --dry-runpatch changes only the fields you specify, leaving everything else untouched. Use it when you need to update a field on an existing resource without touching the rest of its configuration.
# Update a single field by name
jamf-cli pro buildings patch --name "HQ" --set city="Cupertino" --yes
# Update a computer by serial number
jamf-cli pro computers patch --serial C02X1234 --set general.assetTag=CORP-101
# Update multiple fields at once
jamf-cli pro computers patch --serial C02X1234 \
--set general.managed=true \
--set general.assetTag=CORP-101
# Preview the patchable fields for a resource
jamf-cli pro buildings patch --scaffold
# Patch from a JSON merge-patch file
jamf-cli pro scripts patch 15 --from-file changes.jsonUse --scaffold to see the full list of patchable fields. Only the keys you send are touched by the API.
Always preview with --dry-run before executing destructive operations.
# Delete scripts by ID — always dry-run first
jamf-cli pro scripts list --field id | while read id; do
jamf-cli pro scripts delete "$id" --dry-run
done
# Execute deletes (--yes skips per-item confirmation)
jamf-cli pro scripts list --field id | while read id; do
jamf-cli pro scripts delete "$id" --yes
done
# Or delete by name — no ID lookup needed
jamf-cli pro scripts delete --name "Old Script" --yes
# Scaffold a building JSON template, edit it, then apply
jamf-cli pro buildings apply --scaffold > building.json
# ... edit building.json ...
cat building.json | jamf-cli pro buildings apply --yes
# Clone a resource: get, modify, apply with a new name
jamf-cli pro buildings get 1 -o json | jq '.name = "Branch Office Copy"' | jamf-cli pro buildings applyAll generated delete commands accept --from-file (a file of IDs/names/serials) or --group (computers and mobile devices).
# Build a list of decommissioned computers and delete them all
jamf-cli pro computers-inventory delete --from-file decommissioned.txt --dry-run
jamf-cli pro computers-inventory delete --from-file decommissioned.txt --yes
# Delete all members of a computer group
jamf-cli pro computers-inventory delete --group "Decommissioned Macs" --yes
# Delete retired mobile devices by group
jamf-cli pro mobile-devices delete --group "Retired iPads" --yes
# Bulk delete stale config profiles by name
jamf-cli pro classic-macos-config-profiles list -o json \
| jq -r '.[] | select(.name | test("^LEGACY")) | .name' \
> legacy-profiles.txt
jamf-cli pro classic-macos-config-profiles delete --from-file legacy-profiles.txt --dry-run
jamf-cli pro classic-macos-config-profiles delete --from-file legacy-profiles.txt --yesFile format accepts IDs, names, serial numbers, UDIDs, or any supported lookup identifier — one per line, # comments ignored:
# decommissioned.txt
C02X1234
C02Y5678
42
Mirror JCDS to a local file-share distribution point using packages sync. Runs unattended; SHA3-512 checksums detect remote updates so only changed files are re-downloaded.
# One-shot sync (safe — no deletion)
jamf-cli pro packages sync --dir /Volumes/Packages
# Full mirror: download new/changed files and delete orphans
jamf-cli pro packages sync --dir /Volumes/Packages --delete
# Preview before committing
jamf-cli pro packages sync --dir /Volumes/Packages --delete --dry-run \
| jq '.summary'Launch daemons run without a user session, so Keychain is unavailable. Store the client secret in a root-only readable file instead, and reference it with file: in the config profile.
# 1. Store the client secret in a file only root can read
sudo mkdir -p /private/etc/jamf-cli
printf '%s' 'your-client-secret' | sudo tee /private/etc/jamf-cli/jcds-sync-secret > /dev/null
sudo chmod 600 /private/etc/jamf-cli/jcds-sync-secret
# 2. Create the profile as root (prompts for client ID and secret)
sudo jamf-cli config add-profile jcds-sync \
--url https://your-instance.jamfcloud.com --auth-method oauth2After setup, edit root's config (sudo $EDITOR /var/root/.config/jamf-cli/config.yaml) and replace the generated keychain:… secret value with the file reference:
client-secret: "file:/private/etc/jamf-cli/jcds-sync-secret"Then reference the profile with -p in the launch daemon — no credentials in the plist:
<!-- /Library/LaunchDaemons/com.example.jcds-sync.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.example.jcds-sync</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/jamf-cli</string>
<string>-p</string>
<string>jcds-sync</string>
<string>pro</string>
<string>packages</string>
<string>sync</string>
<string>--dir</string>
<string>/Volumes/Packages</string>
<string>--delete</string>
<string>--no-input</string>
<string>--no-color</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>2</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
<key>StandardOutPath</key>
<string>/var/log/jcds-sync.log</string>
<key>StandardErrorPath</key>
<string>/var/log/jcds-sync.log</string>
</dict>
</plist>Same file: secret approach. Run as a dedicated service account rather than root.
Headless servers have no D-Bus keychain daemon, so create the config directly rather than using config add-profile (which would try to store credentials in a keychain that doesn't exist).
# Create service account with home directory
sudo useradd -r -m -s /usr/sbin/nologin jcds-sync
# Store client secret — readable only by the service account
sudo mkdir -p /etc/jamf-cli
printf '%s' 'your-client-secret' | sudo tee /etc/jamf-cli/jcds-sync-secret > /dev/null
sudo chmod 600 /etc/jamf-cli/jcds-sync-secret
sudo chown jcds-sync:jcds-sync /etc/jamf-cli/jcds-sync-secret
# Write config directly — client-id is not a secret, client-secret uses file: ref
sudo -u jcds-sync mkdir -p /home/jcds-sync/.config/jamf-cli
sudo tee /home/jcds-sync/.config/jamf-cli/config.yaml > /dev/null <<'EOF'
default-profile: jcds-sync
profiles:
jcds-sync:
url: https://your-instance.jamfcloud.com
auth-method: oauth2
client-id: your-client-id
client-secret: "file:/etc/jamf-cli/jcds-sync-secret"
EOF
sudo chmod 600 /home/jcds-sync/.config/jamf-cli/config.yaml
sudo chown jcds-sync:jcds-sync /home/jcds-sync/.config/jamf-cli/config.yaml/etc/systemd/system/jcds-sync.service:
[Unit]
Description=JCDS package sync
After=network-online.target
Wants=network-online.target
ConditionPathIsMountPoint=/mnt/packages
[Service]
Type=oneshot
User=jcds-sync
ExecStart=/usr/local/bin/jamf-cli -p jcds-sync pro packages sync \
--dir /mnt/packages --delete --no-input --no-color/etc/systemd/system/jcds-sync.timer:
[Unit]
Description=Run JCDS sync daily at 02:00
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
[Install]
WantedBy=timers.targetsudo systemctl daemon-reload
sudo systemctl enable --now jcds-sync.timer
# View logs
journalctl -u jcds-syncNotable differences from launchd: ConditionPathIsMountPoint= skips the run if the volume isn't mounted (launchd has no equivalent); Persistent=true catches up on missed runs if the server was down at 02:00; logs go to journald with no log file to rotate.
result=$(jamf-cli pro packages sync --dir /Volumes/Packages --no-input --no-color)
failed=$(echo "$result" | jq '.summary.failed')
if [ "$failed" -gt 0 ]; then
echo "JCDS sync: $failed file(s) failed" >&2
echo "$result" | jq '[.files[] | select(.status == "failed")]' >&2
exit 1
fiTarget devices by serial number, name, ID, group, or file. Destructive operations have safety gates.
# Send a blank push to trigger check-in
jamf-cli pro computers blank-push --serial C02X1234
# Redeploy the Jamf management framework
jamf-cli pro computers redeploy-framework --name "Neil's MacBook"
# Force a DDM sync
jamf-cli pro computers ddm-sync --id 42
# Renew MDM profile
jamf-cli pro computers renew-mdm --serial C02X1234
# Erase a computer (requires --yes)
jamf-cli pro computers erase --serial C02X1234 --yes
# Unmanage a mobile device (requires --yes)
jamf-cli pro mobile-devices unmanage --serial F4K3SER1AL --yesWhen devices have stuck or failed MDM commands, flush-commands clears them without a device wipe.
# Flush failed commands from a computer (safe default)
jamf-cli pro computers flush-commands --serial C02X1234 --yes
# Flush both pending and failed (clears entire queue — use with care)
jamf-cli pro computers flush-commands --serial C02X1234 --status both --yes
# Flush failed commands from an entire group (one API call)
jamf-cli pro computers flush-commands --group "Problem Devices" --yes
# Dry-run to preview what would be flushed
jamf-cli pro computers flush-commands --serial C02X1234 --dry-run# Blank push to all members of a group
jamf-cli pro computers blank-push --group "All Macs" --yes
# Redeploy framework on all listed devices from a file
jamf-cli pro computers redeploy-framework --from-file devices.txt --yes
# Destructive bulk operations require both --yes and --confirm-destructive
jamf-cli pro computers erase --group "Decommissioned" --dry-run
jamf-cli pro computers erase --group "Decommissioned" --yes --confirm-destructive# By serial number, Jamf ID, or name
jamf-cli pro device C02X1234
jamf-cli pro device 42
jamf-cli pro device "Neil's MacBook" -o jsonShows identity, hardware, OS, security posture (FileVault, SIP, Gatekeeper, firewall), user info, MDM command history, and policy execution logs. Data is fetched in parallel; partial failures appear as stderr warnings.
# Fleet security posture
jamf-cli pro report security -o table
# Machine-readable for alerting
jamf-cli pro report security -o jsonThe multi command runs any command against multiple profiles in one shot. Profiles can be selected by glob pattern, explicit list, file, or interactively.
# Interactive profile selection (prompts with numbered list)
jamf-cli multi pro overview
# Run against all pro- profiles (glob pattern)
jamf-cli multi --filter 'pro-*' -- pro computers list -o table
# Explicit profile list
jamf-cli multi --profiles pro-school1,pro-school2 -- pro overview
# From a file (profile names or instance URLs — same file used for setup)
jamf-cli multi --from-file instances.txt -- pro buildings apply --from-file building.json --yes
# Check version across all instances
jamf-cli multi --filter 'pro-*' -- pro jamf-pro-versions list# Aggregate patch compliance across all managed instances
jamf-cli multi --filter 'pro-*' -- pro report patch-status
# Fleet-wide security posture
jamf-cli multi --filter 'pro-*' -- pro report security
# See each instance's output separately
jamf-cli multi --filter 'pro-*' --sequential -- pro report patch-status# Compare device counts across environments
for env in prod staging dev; do
count=$(jamf-cli pro computers list -p "$env" -o json --no-input 2>/dev/null | jq length)
echo "$env: $count computers"
done
# Side-by-side category comparison
diff <(jamf-cli pro categories list -p prod --field name | sort) \
<(jamf-cli pro categories list -p staging --field name | sort)Recommended flags for unattended scripts: --no-input --no-color -o json
# Exit code checking
jamf-cli pro computers list -p prod -o json --no-input > /dev/null 2>&1
rc=$?
case $rc in
0) echo "OK" ;;
3) echo "Auth failed — check credentials" ;;
4) echo "Not found" ;;
*) echo "Error (exit code $rc)" ;;
esacSee Error Handling & Exit Codes for the full exit code table and CI/CD & Scripting for script templates, GitHub Actions examples, and Docker patterns.
Repository · Issues · Releases
jamf-cli Wiki
- Home
- Community
- Getting Started
- CLI Reference
- Product Commands
- Workflows
- Configuration
- Reference
Products
- Jamf Pro —
jamf-cli pro - Jamf Platform API —
jamf-cli pro(platform commands) - Jamf Protect —
jamf-cli protect - Jamf School —
jamf-cli school