Skip to content

Hands On With Jamf Concepts

Neil Martin edited this page Oct 9, 2026 · 5 revisions

A guided walk through jamf-cli, run against a shared sandbox, then a buffet to pick from. No prior jamf-cli experience needed.

The machine in front of you already holds jamf-cli and a saved sandbox profile. We checked every command below against that sandbox on v1.31.1, and the bulk-operation and profile-conversion commands again on v1.33.0. Each snippet is one command — use the copy button and paste into Terminal.

Already know the CLI? Skip to Buffet and pick what interests you. Ask Keaton or Neil anything.


Start here

Self Service holds the username and password for the sandbox. Open it before anything else:

open "jamfselfservice://content?entity=policy&id=20&action=view"

Keep that window handy. You need those credentials when a command sends you to the Jamf Pro web interface. The CLI itself is already signed in on this Mac.


1. Orientation (1 minute)

Which products does this CLI cover?

jamf-cli

One screen for the whole instance — version, fleet counts, certificate expiry, feature flags, object counts:

jamf-cli pro overview

jamf-cli pro --help lists every Jamf Pro command. The sandbox credential covers Jamf Pro, blueprints and compliance benchmarks.

You do not have to memorise any of it. Press Tab while you type and this Mac completes command names and flags for you. Try it — type jamf-cli pro comp and press Tab to see every command that starts that way.


2. Your whole fleet as a web page (1 minute)

jamf-cli dashboard --out-file ~/Desktop/fleet.html && open ~/Desktop/fleet.html

It takes about three seconds. Self-contained HTML, light and dark themes, no external assets, nothing to host. Fleet counts, security posture, OS spread, audit findings and blueprint state, from one command.

It prints five Jamf Security Cloud errors to stderr and omits that section, because this credential does not reach Security Cloud.


3. Fleet data, shaped (1 minute)

jamf-cli pro computers list

A table, although you did not ask for one. The format follows the destination: a terminal gets a table, a pipe or a file gets JSON. Name the format with -o when you need a particular one.

One field, one value per line. Pipe this into anything:

jamf-cli pro computers list --field serial

A spreadsheet:

jamf-cli pro computers list -o csv --out-file ~/Desktop/fleet.csv

--field and --select name fields in whichever shape your format prints. In a table those names are the column labels: id, name, serial, model, osVersion, lastCheckIn. -o json prints the record's dot paths instead, like hardware.serialNumber. Add -o json and the dot paths work in a terminal and through a pipe alike.


4. Change something, then put it back (1 minute)

JNUC Lab - Playground is a disabled policy that exists for you to edit. Look at its scope:

jamf-cli pro classic-policies scope get --name "JNUC Lab - Playground" -o table

Add a smart group to its targets, by name:

jamf-cli pro classic-policies scope add --name "JNUC Lab - Playground" --computer-group "JNUC Lab - macOS 26" -o table

The command sends only the <scope> element, so nothing else about the policy changes. Remove it again:

jamf-cli pro classic-policies scope remove --name "JNUC Lab - Playground" --computer-group "JNUC Lab - macOS 26" -o table

Other people in the room may edit the same policy. scope get always shows the live scope.


5. Pipe it to jq (1 minute)

Through a pipe the output is JSON, which is the only reason jq can read it:

jamf-cli pro computers list | jq length

OS version breakdown, most common first:

jamf-cli pro computers list --section OPERATING_SYSTEM -o json | jq -r 'group_by(.operatingSystem.version) | map({v: .[0].operatingSystem.version, n: length}) | sort_by(-.n)[] | "\(.n)\t\(.v)"'

That is the five minutes. Everything below is optional — pick what interests you, or ask Keaton or Neil.


Buffet

Pick anything. We checked all of them against this sandbox.

Some of these create objects. Set your initials first so your objects stay separate from your neighbour's:

export ME=<your initials>

More ways to shape the output

Name your own fields. --select takes dot paths under -o json:

jamf-cli pro computers list --select general.name,hardware.model,operatingSystem.version -o json

Or the table's own column labels:

jamf-cli pro computers list --select name,serial,osVersion

An RSQL filter, run server-side:

jamf-cli pro computers list --filter 'operatingSystem.version=="26.6.0"'

Sort and cap:

jamf-cli pro computers list --sort general.lastContact:desc --limit 5

Find one record without knowing its ID:

jamf-cli pro computers get --serial FWWT058JG5 --select general.name,hardware.model,operatingSystem.version,operatingSystem.fileVault2Status -o json

Some commands have no summary row and return the API's own wide rows. --compact drops the bulky columns:

jamf-cli pro compliance-benchmarks list
jamf-cli pro compliance-benchmarks list --compact

See the HTTP calls. The first goes to /pro/v1/categories, the second to /proclassic/policies — one credential, two APIs, both through the Platform gateway:

jamf-cli pro categories list -v
jamf-cli pro classic-policies list -v

Health checks and reports

Cross-resource health checks, ranked by severity. It reads many endpoints and takes about 15 seconds:

jamf-cli pro audit

FileVault, firewall, Gatekeeper and SIP percentages:

jamf-cli pro report security -o table

Policy health, including scope problems:

jamf-cli pro report policy-status -o table

There are 15 reports in all:

jamf-cli pro report --help

Back up the configuration, then diff it

What can be backed up:

jamf-cli pro backup list-resources -o plain

Export every policy as its own YAML file:

jamf-cli pro backup --output ~/Desktop/backup-before --resources policies

Change one policy's scope:

jamf-cli pro classic-policies scope add --name "JNUC Lab - Playground" --computer-group "JNUC Lab - macOS 26" -o table

Export again:

jamf-cli pro backup --output ~/Desktop/backup-after --resources policies

Compare the two directories:

jamf-cli pro diff --source ~/Desktop/backup-before --target ~/Desktop/backup-after -o json | jq -r '.[] | "\(.resource)\t\(.name)\t\(.change)\t\(.field)"'

Remove it from the scope again:

jamf-cli pro classic-policies scope remove --name "JNUC Lab - Playground" --computer-group "JNUC Lab - macOS 26" -o table

pro diff also compares two live instances. Pass profile names instead of directories.

Create, change and delete a resource

What does the body look like?

jamf-cli pro categories create --scaffold

Create one and keep its id:

CAT=$(echo "{\"name\":\"JNUC Lab $ME\",\"priority\":9}" | jamf-cli pro categories create --field id) && echo "created category $CAT"

Read it back:

jamf-cli pro categories get $CAT -o table

Read it, change one field and write it back, in one line:

jamf-cli pro categories get $CAT -o json | jq '.priority = 1' | jamf-cli pro categories update $CAT

Delete it:

jamf-cli pro categories delete $CAT --yes

Write a policy from XML

Classic API resources take XML. --scaffold prints a complete template:

jamf-cli pro classic-policies create --scaffold

Create a disabled policy of your own:

POLICY_ID=$(jamf-cli pro classic-policies create --field policy.id --quiet <<EOF
<policy>
  <general>
    <name>JNUC Lab - $ME</name>
    <enabled>false</enabled>
    <frequency>Ongoing</frequency>
  </general>
</policy>
EOF
) && echo "created policy $POLICY_ID"

Exclusions work the same way as targets:

jamf-cli pro classic-policies scope add $POLICY_ID --section exclusion --computer-group "JNUC Lab - Rotation" -o table

--set builds a Classic body in dot notation, and -n previews any write without sending it:

jamf-cli pro classic-policies update $POLICY_ID --set general.name="JNUC Lab - $ME v2" -n

--set refuses a value outside the enum. The Classic API accepts one and silently substitutes its default, so this refusal is the only warning you get:

jamf-cli pro classic-policies update $POLICY_ID --set general.frequency="Twice per fortnight" -n

Delete the policy:

jamf-cli pro classic-policies delete $POLICY_ID --yes

Ten policies at once

You can run any command in a loop. Make ten policies:

for i in $(seq 1 10); do
  jamf-cli pro classic-policies create --field policy.id --quiet <<EOF
<policy><general><name>JNUC Lab - $ME $i</name><enabled>false</enabled><frequency>Ongoing</frequency></general></policy>
EOF
done

Collect their IDs by name:

jamf-cli pro classic-policies list -o json | jq -r ".[] | select(.name | startswith(\"JNUC Lab - $ME \")) | .id"

Scope all ten to one group in one pass. Each scope add writes the new scope to stdout and its confirmation to stderr, so >/dev/null leaves you one line per policy:

for id in $(jamf-cli pro classic-policies list -o json | jq -r ".[] | select(.name | startswith(\"JNUC Lab - $ME \")) | .id"); do
  jamf-cli pro classic-policies scope add $id --computer-group "JNUC Lab - macOS 26" --quiet >/dev/null
done

Ten scoped policies in about five seconds. One report command now finds what you just made. Each policy carries a scope and no enabled flag, and pro report policy-status names every one:

jamf-cli pro report policy-status -o table

Delete them again:

for id in $(jamf-cli pro classic-policies list -o json | jq -r ".[] | select(.name | startswith(\"JNUC Lab - $ME \")) | .id"); do
  jamf-cli pro classic-policies delete $id --yes --quiet
done

Groups

Every group, with its membership count and its Platform UUID:

jamf-cli pro groups list -o table

Largest groups first:

jamf-cli pro groups list -o json | jq -r '.[] | select(.groupType=="COMPUTER") | "\(.membershipCount)\t\(.groupName)"' | sort -rn

A smart group's criteria and member count:

jamf-cli pro classic-computer-groups get 7101 -o json | jq '{name, criteria, members: (.computers | length)}'

The body for a new smart group:

jamf-cli pro classic-computer-groups create --scaffold

Bulk operations

A list, a group or a filter prints a preview first and changes nothing until you pass --yes.

Preview adding one group's members to the static group:

jamf-cli pro classic-computer-groups add-members --name "JNUC Lab - Rotation" --from-group "JNUC Lab - macOS 26"

Now add them:

jamf-cli pro classic-computer-groups add-members --name "JNUC Lab - Rotation" --from-group "JNUC Lab - macOS 26" --yes

Remove them again:

jamf-cli pro classic-computer-groups remove-members --name "JNUC Lab - Rotation" --from-group "JNUC Lab - macOS 26" --yes

Preview an MDM command to 12 devices:

jamf-cli pro computer-inventory blank-push --group "JNUC Lab - macOS 26" --dry-run

A destructive command needs two flags, not one:

jamf-cli pro computer-inventory erase --group "JNUC Lab - macOS 26" --yes --confirm-destructive

Blueprints (Platform API)

What blueprints exist:

jamf-cli pro blueprints list -o table

The full definition:

jamf-cli pro blueprints get --name "JNUC Lab - Math Settings" -o json

Portable JSON, ready to apply to another tenant:

jamf-cli pro blueprints export --name "JNUC Lab - Math Settings" -o json > ~/Desktop/bp.json

The 17 component types a blueprint can hold:

jamf-cli pro blueprints components list -o table

Example configuration for one component:

jamf-cli pro blueprints components scaffold com.jamf.ddm.math-settings

Which device groups it targets:

jamf-cli pro blueprints scope list --name "JNUC Lab - Math Settings" -o table

Clone it:

jamf-cli pro blueprints clone "JNUC Lab - Math Settings" "zz-$ME-clone"

Delete the clone:

jamf-cli pro blueprints delete --name "zz-$ME-clone" --yes

Convert a Classic profile to DDM

Pick a profile:

jamf-cli pro classic-macos-config-profiles list -o table

Convert it, with your initials in the name:

jamf-cli pro blueprints import-profile 9650 --convert --blueprint-name "zz-$ME-ddm"

The output names each payload, says which native DDM component it maps to, and warns about the keys that have no DDM equivalent.

Read the blueprint the conversion built. One Software Update payload became a native DDM component, and the rest stayed in a configuration-profile wrapper:

jamf-cli pro blueprints get --name "zz-$ME-ddm" -o json | jq '{name, state: .deploymentState.state, components: [.steps[].components[].identifier]}'

Then delete it:

jamf-cli pro blueprints delete --name "zz-$ME-ddm" --yes

Compliance benchmarks (Platform API)

The 15 baselines Jamf ships:

jamf-cli pro baselines list --select baselineId,title,ruleCount -o json

Benchmarks on this tenant:

jamf-cli pro compliance-benchmarks list --compact -o table

Per-rule pass and fail counts:

jamf-cli pro benchmark-reports rules --name "JNUC Lab - CIS Level 1 (Monitor)" -o json | jq -r '.[] | [.ruleNumber, .passed, .failed, .ruleTitle] | @tsv' | head -20

Overall percentage:

jamf-cli pro benchmark-reports compliance-percentage --name "JNUC Lab - CIS Level 1 (Monitor)" -o table

A full 104-rule benchmark body, pre-populated from a baseline:

jamf-cli pro compliance-benchmarks apply --scaffold-from-baseline cis_lvl1 | jq '{title, sourceBaselineId, enforcementMode, rules: (.rules|length)}'

Creating a benchmark builds Jamf Pro objects for you. Three smart groups:

jamf-cli pro classic-computer-groups list -o json | jq -r '.[] | select(.name | test("CIS Level 1")) | .name'

One policy:

jamf-cli pro classic-policies list -o json | jq -r '.[] | select(.name | test("CIS Level 1")) | .name'

One script:

jamf-cli pro scripts list -o json | jq -r '.[] | select(.name | test("compliance_benchmark")) | .name'

App installers

The full catalog Jamf offers:

jamf-cli pro app-installers-titles list --select titleName,publisher,version -o json | jq -r '.[] | [.publisher, .titleName, .version] | @tsv' | head -20

Deployments configured here:

jamf-cli pro app-installers-deployments list --select name,app.bundleId,app.deployedVersion,smartGroup.name -o json

Jump to the interface

These open a browser, which asks you to sign in. The credentials are in Self Service — see Start here.

Section names the CLI knows:

jamf-cli pro open --list -o json | jq -r '.[].name'

Open one:

jamf-cli pro open smart-computer-groups

Print the URL instead of opening it:

jamf-cli pro open blueprints --print

Drive it from an AI client

Operating guidance for an agent — auth, exit codes, flag conventions:

jamf-cli agent-context

Serve the whole command tree over MCP:

jamf-cli mcp --help

The MCP server gives a connected model three tools: list_commands, run_command and generate_report. Credentials never cross the protocol — child commands inherit the profile the server started with.


Sandbox contents

Object Detail
45 computers, 60 mobile devices, 192 users simulated inventory
JNUC Lab - MacBook Fleet smart group, 30 members, criterion Model like MacBook
JNUC Lab - macOS 26 smart group, 12 members, criterion Operating System Version like 26.
JNUC Lab - Rotation static group, empty: the target for add-members
JNUC Lab - Playground disabled policy, scoped to JNUC Lab - MacBook Fleet
JNUC Lab - Math Settings blueprint with one DDM math-settings component
JNUC Lab - CIS Level 1 (Monitor) compliance benchmark, 104 rules, monitor mode

Write freely. Anything named JNUC Lab - <your initials> is yours. We rebuild the fixtures above between sessions.


Two things worth knowing

No command writes without your consent. -n previews any write. Mutating commands need --yes. Destructive MDM commands need --yes and --confirm-destructive. Bulk commands print a preview table and exit.

Exit codes are stable. 0 success, 2 usage, 4 not found, 8 refused by the gateway. Full table: Error Handling & Exit Codes.


Next

jamf-cli Wiki


Products

  • Jamf Pro: jamf-cli pro
  • Jamf Platform API: jamf-cli pro (blueprints, benchmarks, DDM reports)
  • Jamf Platform: jamf-cli platform (AI Governance, Jamf Account, audit)
  • Jamf Protect: jamf-cli protect
  • Jamf School: jamf-cli school
  • Jamf Security Cloud: jamf-cli security

Clone this wiki locally