Generates a comprehensive, self-contained HTML report for a Jamf Pro instance
using jamf-cli. All data is
collected via the Jamf Pro API through jamf-cli; no credentials are stored by
this script.
| Section | Details |
|---|---|
| Instance overview | Server URL, Jamf Pro version, health status, active alerts, managed / unmanaged device counts, check-in frequency, DEP token expiry, built-in CA expiry |
| Summary cards | Policies, macOS profiles, iOS profiles, scripts, packages, categories, smart groups, sites, buildings, departments, ADE instances, VPP locations, app installers, webhooks, patch titles |
| Security posture | Animated gauge charts for FileVault, Gatekeeper, SIP and Firewall compliance percentages across all managed computers |
| macOS version distribution | Interactive donut chart showing device counts per OS version, newest-first, with .0 patch variants normalised (e.g. 15.4.0 and 15.4 are merged) |
| macOS adoption timeline | Line chart showing how each macOS version's device count has grown or shrunk across multiple report runs over time. Only visible when 2 or more historical snapshots exist for this instance. Opt-in via --track-history. |
| Flagged devices | Searchable table of every device that fails at least one security check, showing name, serial, OS version and exactly which checks failed |
| Deployment hierarchy | Expandable tree grouped by category for policies, macOS config profiles, iOS config profiles and scripts |
| Cleanup analysis | Separate tab listing disabled policies, enabled policies with no scope, macOS profiles with no scope, unused packages and unused scripts. Opt-in via --cleanup. |
| MDM profile failures | Profiles that have generated InstallProfile command failures in the last N days, with per-profile device count, error count and top error message. Opt-in via --profile-status. Look-back window configurable via --profile-days (default: 30 days). |
| MDM app deployment failures | Apps that have generated InstallApplication / InstallEnterpriseApplication MDM command failures, with per-app device count, error count and top error. Highlights high-failure devices. Opt-in via --app-status. Look-back window configurable via --app-days (default: 30 days). |
| Managed software update status | Plan-state breakdown (Failed, Completed, Exception, Waiting for DDM Update, …) across all managed software update plans. Shown as a visual pill grid with percentages. Opt-in via --update-status. |
| Device check-in compliance | All managed devices with days since last check-in, stale status, OS version and last contact timestamp. Sortable/filterable table with a "Stale only" toggle. Opt-in via --device-compliance. Stale threshold configurable via --checkin-days (default: 90 days). |
| Organisational structure | Expandable dropdowns for smart groups, sites, buildings, departments and categories |
| Self Service icon | Automatically extracted from the local Self Service app (or supplied via environment variable) and embedded in the report header |
| Dark-mode toggle | One-click switch between light and dark themes, persisted to localStorage |
| Self-contained output | A single .html file — no external CSS, JavaScript or image dependencies |
| Tool | Version | Install |
|---|---|---|
jamf-cli |
≥ 1.9.0 | brew install Jamf-Concepts/tap/jamf-cli |
jq |
≥ 1.6 | brew install jq |
bash |
≥ 3.2 | Ships with macOS |
base64 |
any | Ships with macOS |
sips |
any | Ships with macOS (used for icon extraction) |
swiftDialog |
≥ 2.5 | brew install swiftDialog — required only for report-ui.zsh |
See requirements.txt for the full dependency list.
Jamf Pro permissions — the API account used by
jamf-clineeds at minimum read access to: Computers, Policies, Configuration Profiles, Scripts, Packages, Smart Computer Groups, Categories, Sites, Buildings, Departments, and the Security Report.
./report.sh [OPTIONS]| Option | Description |
|---|---|
-p, --profile <name> |
jamf-cli profile to use (default: active profile) |
-o, --output <file> |
Output HTML file path (default: jamf-report-TIMESTAMP.html) |
-n, --no-open |
Do not auto-open the report in the browser after generation |
-t, --track-history |
Opt in to history tracking — saves a snapshot of the macOS version distribution to the history file after each run |
--history-file <path> |
Override the history file location (default: ~/.jamf-report.history.json, or set JAMF_REPORT_HISTORY_FILE) |
-c, --cleanup |
Opt in to cleanup analysis — fetches full details for every policy and macOS profile to identify disabled policies, unscoped policies/profiles, unused packages and unused scripts. Adds one extra step to the run. |
--patch-status |
Opt in to patch title compliance data — adds a Patch Compliance section showing per-title compliance percentages, device counts and latest version. |
--profile-status |
Opt in to MDM profile failure reporting — adds a Profile Failures section showing every profile that generated an InstallProfile error in the look-back window. |
--profile-days <n> |
Look-back window for profile failures in days (default: 30). Only used when --profile-status is passed. |
--app-status |
Opt in to MDM app deployment failure reporting — adds an App Status section showing apps with MDM install failures, device counts and top error messages. |
--app-days <n> |
Look-back window for app failures in days (default: 30). Only used when --app-status is passed. |
--update-status |
Opt in to managed software update plan reporting — adds an Update Status section with a plan-state pill grid (Failed, Completed, Exception, etc.). |
--device-compliance |
Opt in to device check-in compliance — adds a section listing all managed devices with their last check-in date and stale status. |
--checkin-days <n> |
Number of days without a check-in before a device is marked stale (default: 90). Only used when --device-compliance is passed. |
-h, --help |
Show help and exit |
Examples:
# Use the currently active jamf-cli profile, open on completion
./report.sh
# Target a specific profile, write to a fixed path, skip auto-open
./report.sh --profile prod --output /tmp/report.html --no-open
# Quick one-liner for download folder
./report.sh -o ~/Downloads/jamf-report.html
# Opt in to history tracking (run regularly — e.g. weekly via cron/launchd)
./report.sh --track-history
# Opt in to cleanup analysis (fetches per-object details — slower but thorough)
./report.sh --cleanup
# Combine cleanup, history tracking, and a specific profile
./report.sh --profile prod --cleanup --track-history --no-open -o ~/Downloads/report.html
# Add MDM profile failure report (last 30 days, default)
./report.sh --profile-status
# Narrow the failure look-back window to 7 days
./report.sh --profile-status --profile-days 7
# Add MDM app deployment failure report
./report.sh --app-status
# Narrow the app failure look-back window to 7 days
./report.sh --app-status --app-days 7
# Add managed software update plan state summary
./report.sh --update-status
# Add device check-in compliance (stale = not seen in 90 days, default)
./report.sh --device-compliance
# Tighten the stale threshold to 7 days
./report.sh --device-compliance --checkin-days 7
# Full report: everything enabled
./report.sh --profile prod --cleanup --track-history --patch-status --profile-status --app-status --update-status --device-compliancereport-ui.zsh is a swiftDialog
wrapper that presents a native macOS UI around report.sh. It replaces the
terminal entirely — prompting for a save location, showing a live progress bar
while the report generates, then offering to open or reveal the finished file.
Requires: swiftDialog ≥ 2.5 installed at /usr/local/bin/dialog.
./report-ui.zsh [report.sh options]All options are forwarded verbatim to report.sh. Do not pass -o /
--output — the wrapper manages the output path via a native save panel.
# Basic: save panel → progress dialog → open on completion
./report-ui.zsh
# With a specific jamf-cli profile
./report-ui.zsh --profile prod
# With history tracking and cleanup analysis
./report-ui.zsh --track-history --cleanup- Save panel — a native macOS "Save As" sheet lets the user choose the output file name and location before anything is fetched.
- Confirmation dialog — summarises what the report will include and shows the chosen save path. The user clicks Generate Report or Cancel.
- Progress dialog — a live progress bar advances through each report step, with substep text describing what is happening inside each step (e.g. "Batch 1/2 — overview & security…", "Fetching policy details…").
- Completion — the dialog re-enables its button. The user clicks Open Report to open in the default browser, or Show in Finder to reveal the file without opening it.
- Done notification — a final confirmation dialog shows the full save path.
| Step | Label | Substeps shown |
|---|---|---|
| 1 | Fetching data from Jamf Pro | Batch 1/2 (overview & security), Batch 2/2 (inventory & organisation) |
| 2 | Processing data | Patch compliance, MDM profile failure data, MDM app failure data, managed update plan data, device check-in compliance (each shown only when the matching flag is passed) |
| 3 | Building deployment hierarchy | — |
| 4 | Cleanup analysis (only when --cleanup is passed) |
Fetching policy details, fetching macOS profile details, cross-referencing packages and scripts |
| 4 or 5 | Generating HTML report | — |
| 5 or 6 | Finalising report | — |
The total step count adjusts automatically — 5 steps without --cleanup,
6 steps with it. The --profile-status, --app-status, --update-status,
--device-compliance and --patch-status flags add substep text during
step 2 but do not change the total step count.
- Parallel data collection — data is fetched from Jamf Pro in two batches:
- Batch 1:
overviewandsecurity report(run alone to avoid rate-limiting) - Batch 2: policies, profiles, scripts, packages, smart groups, categories, sites, buildings, departments, ADE instances — all in parallel
- Batch 1:
- Resilient fetching — each request is validated as JSON. Failed or invalid responses are retried once after a short delay before marking as failed. The security report has an additional two-attempt recovery loop.
- Data processing —
jqnormalises, groups and sorts all data in-shell. Version strings are normalised (trailing.0stripped) and duplicates merged. - HTML generation — a single
report.shheredoc writes the complete HTML, CSS and JavaScript inline. No templating engine or build step is needed. - Cleanup analysis (opt-in,
--cleanup) — fetches the full detail record for every policy and macOS profile in batches of 8 concurrent requests. Cross-references package and script IDs across all policies to find unused objects. Results appear in the Cleanup tab of the report. - Icon embedding — the script attempts to extract the Self Service app icon
from the local machine, convert it to PNG via
sips, and embed it as a base64data:URI. Falls back to a default icon if not found. - Cleanup — a
traponEXITremoves the temporary working directory.
A demo runner is included that generates a fully populated report from local fixture JSON files — no Jamf Pro connection or credentials required.
./demo/run-demo.sh [output.html]The output defaults to ~/Downloads/jamf-demo-report.html.
To preview the UI wrapper against demo data (requires swiftDialog):
# Puts the demo jamf-cli stub first in PATH so report-ui.zsh uses it
PATH="$(pwd)/demo:$PATH" ./report-ui.zsh --cleanupPlace a file named icon.png in the demo/ folder. The runner will
base64-encode it and pass it to the report script via the
JAMF_REPORT_ICON_B64 environment variable, bypassing Self Service icon
extraction entirely.
| File | Contents |
|---|---|
overview.json |
Server info (https://demo.jamfcloud.com), version, health, counts |
security.json |
1,247 managed devices; security summary percentages; 4 flagged devices; OS version breakdown |
policies.json |
20 sample policies across 5 categories |
macos_prof.json |
15 macOS configuration profiles |
ios_prof.json |
3 iOS configuration profiles |
scripts.json |
10 scripts with category names |
smart_groups.json |
13 generic smart groups |
categories.json |
7 categories |
packages.json |
8 packages |
jamf-cli (stub) |
Bash stub that serves all fixture data including per-ID policy and profile detail responses, used by the demo to simulate --cleanup analysis without a live Jamf Pro connection |
All fixture data is generic and contains no organisation-specific information.
| Variable | Description |
|---|---|
JAMF_REPORT_ICON_B64 |
Base64-encoded PNG to use as the report header icon. When set, the script skips Self Service app icon extraction entirely. Set automatically by demo/run-demo.sh when demo/icon.png exists. |
JAMF_REPORT_HISTORY_FILE |
Override the default history file path (~/.jamf-report.history.json). Equivalent to --history-file. |
The report can display a line chart showing how each macOS version's device count has changed over multiple report runs.
Pass --track-history each time you generate a report:
./report.sh --track-historyOn each run, the script appends a timestamped snapshot of the current macOS
version distribution to the history file (default: ~/.jamf-report.history.json).
The macOS Adoption Timeline section will appear in the report automatically as soon as 2 or more snapshots exist for the same Jamf Pro instance URL.
Run the report on a weekly or monthly schedule (e.g. via cron or a
launchd job) with --track-history. Over time the chart will show clearly
which versions are being adopted and which are stalling.
# Example: weekly cron at 08:00 on Monday
0 8 * * 1 /path/to/report.sh --track-history --no-open -o /archive/report-$(date +\%Y\%m\%d).htmlThe file is a JSON array; each entry is one snapshot:
[
{
"ts": "2026-01-10T08:00:00Z",
"instance": "https://your-instance.jamfcloud.com",
"versions": [
{ "v": "15.4", "c": 612 },
{ "v": "15.3", "c": 380 },
{ "v": "14.7", "c": 255 }
]
}
]The file keeps at most 365 entries per instance. Older entries beyond that limit are automatically removed when the file is updated.
Set JAMF_REPORT_HISTORY_FILE (or use --history-file) to a path on a shared
drive so multiple team members contribute to the same history, or commit the
file to a repository alongside the reports.
The Cleanup tab in the report surfaces objects that may be candidates for removal or remediation. It is opt-in because it requires one additional API call per policy and per macOS profile — which adds time proportional to the size of your instance.
Pass --cleanup each time you generate a report:
./report.sh --cleanup| Category | Logic |
|---|---|
| Disabled policies | Policies where general.enabled == false |
| Policies with no scope | Enabled policies not scoped to any computer, group, building, or department (and not set to "All Computers") |
| macOS profiles with no scope | Profiles not scoped to any target |
| Unused packages | Packages that do not appear in the package_configuration of any policy |
| Unused scripts | Scripts that do not appear in the scripts block of any policy |
When --cleanup is not passed the tab is still present in the report but
displays a notice explaining that cleanup analysis was skipped, along with the
flag to enable it.
Fetches are batched at 8 concurrent requests. Typical runtimes:
| Instance size | Extra time |
|---|---|
| ~50 policies + ~30 profiles | ~10–15 s |
| ~200 policies + ~100 profiles | ~45–60 s |
The MDM App Deployment Failures section surfaces managed apps that have
generated InstallApplication or InstallEnterpriseApplication MDM command
failures, grouped by app with per-app device and error counts.
./report.sh --app-status
# Narrow the look-back window to 7 days
./report.sh --app-status --app-days 7| Column | Details |
|---|---|
| App Name | Display name of the managed app (or command ID for enterprise apps) |
| Device Type | Computer or Mobile Device |
| Devices | Number of devices that received at least one failure |
| Errors | Total error count for the app in the look-back window |
| Last Error | Date of the most recent failure |
| Top Error Message | The most common error string returned by MDM |
Summary KPIs (apps with errors, devices affected, total errors, high-failure devices and look-back window) are shown above the table.
The Managed Software Update Status section shows the distribution of managed software update plan states across your fleet — useful for tracking how DDM and classic update plans are progressing.
./report.sh --update-statusA KPI row (total plans, failed, completed, exceptions) followed by a plan-state pill grid. Each pill shows:
- State label (e.g. Failed, Completed, Waiting for DDM Update)
- Count of plans in that state
- Percentage of total plans
| State | Meaning |
|---|---|
PlanFailed |
Update plan failed — device could not be updated |
PlanCompleted |
Device successfully updated |
PlanException |
Plan ended with an exception (e.g. user deferral limit reached) |
PlanCanceled |
Plan was cancelled |
WaitingToStartDDMUpdate |
DDM update scheduled but not yet started |
CollectingAvailableOSUpdates |
Device scanning for available updates |
SchedulingScanForOSUpdates |
Scan scheduled but not yet run |
UpToDate |
Device already on the target version |
The MDM Profile Failures section surfaces configuration profiles that have
generated InstallProfile command failures, grouped by profile with per-profile
device and error counts.
./report.sh --profile-status
# Narrow the look-back window to 7 days
./report.sh --profile-status --profile-days 7| Column | Details |
|---|---|
| Profile Name | Name of the configuration profile |
| Device Type | Computer or Mobile Device |
| Devices | Number of devices that received at least one failure |
| Errors | Total error count for the profile in the look-back window |
| Last Error | Date of the most recent failure |
| Top Error Message | The most common error string returned by MDM |
Summary KPIs (profiles with errors, devices affected, total errors and look-back window) are shown above the table.
The Device Check-in Compliance section lists every managed device with its last check-in date, days since contact and stale status, making it easy to identify devices that have fallen out of management.
./report.sh --device-compliance
# Mark devices stale after 7 days without a check-in
./report.sh --device-compliance --checkin-days 7| Column | Details |
|---|---|
| Device Name | Jamf-managed computer name |
| Serial | Hardware serial number |
| Days Since Check-in | Integer count; colour-coded (green / amber / red) |
| Last Contact | Formatted date of the last successful check-in |
| OS Version | macOS version string at last inventory update |
| Status | Stale or Active badge |
The table supports:
- Sort by device name or days since check-in (click column header)
- Stale only checkbox to hide active devices
- Free-text search filtering by device name or serial
A single self-contained .html file. Open it in any modern browser (Safari,
Chrome, Firefox, Edge) — no web server, internet connection, or additional
software required. The file can be emailed, shared via a file share, or
committed to a repository.
- No credentials are written to disk by this script.
- All Jamf Pro communication uses the
jamf-clitool and the credentials stored in its keychain-backed profile store. - The temporary directory (
/tmp/jamf-report-XXXX) is deleted on exit viatrap, even if the script is interrupted. - The generated HTML embeds all data inline. Treat the output file with the same sensitivity as any Jamf Pro inventory export.