Monitoring disk space across a Mac fleet with Intune

Photo: Jakub Zerdzicki on Pexels

Intune shows a Mac's storage in its hardware details, but you cannot easily sort or report on it. Custom attributes for macOS fill that gap with a small shell script. Here is how to set one up, a ready-made script, and how dux adds what is filling the disk.

Updated October 2026 7 min readmacOS 15 to 27

What custom attributes for macOS do

A custom attribute profile in Intune runs a shell script on each Mac it is assigned to and stores whatever the script prints. It is the Intune counterpart of a Jamf extension attribute.

The points that matter for disk reporting:

  • The Microsoft Intune management agent runs the script. Intune installs the agent automatically on Macs that have scripts or custom attributes assigned.
  • The script runs about every 8 hours, and the result shows up in the admin center.
  • The output is the value, as echoed, up to 20 KB. There is no tag to wrap it in.
  • You choose a data type: String, Integer or Date. The output has to match it. Dates must be ISO 8601, such as 2026-10-29T09:14:00Z.
  • A script that exits with anything other than 0 is reported as failed, with its error in the result.
  • The script runs with root privileges, so it can read the whole disk but has no home folder of the person using the Mac.

A free space script with built-in tools

This script needs nothing but macOS. It prints free space on the startup disk in whole gigabytes (1 GB is 1,000,000,000 bytes, the way Finder counts):

#!/bin/sh
# Free space on the startup disk in GB, for an Intune
# custom attribute with data type Integer.
kb=$(df -k / | awk 'NR == 2 { print $4 }')
if [ -z "$kb" ]; then
  echo "could not read free space" >&2
  exit 1
fi
echo $(( kb * 1024 / 1000000000 ))

On APFS, every volume in a container shares the same free space, so / gives the right number even though files live on the Data volume.

Users may see a higher “available” number in Storage settings, because macOS adds purgeable space that it can free on demand. For monitoring, the lower number is the honest one: purgeable space is not always freed when an app needs it.

Add the custom attribute in Intune

  1. Save the script as a file, for example free-space-gb.sh. It has to start with a line such as #!/bin/sh.
  2. Sign in to the Microsoft Intune admin center.
  3. Go to Devices > By platform > macOS > Organize devices > Custom attributes for macOS, and select Add.
  4. Under Basics, enter a name such as “Free space (GB)” and a description.
  5. Under Attribute settings, set Data type of attribute to Integer and upload the script.
  6. Under Assignments, select the device groups to include.
  7. Review and select Add.

Results appear as the agent checks in. Open the custom attribute and choose Device status to see one row per Mac, with the value in the Result column. You can export that list, or read the same data through Microsoft Graph for your own reports.

Make the numbers useful

A list of numbers is a start. To act on it:

  • Export the Device status list and sort it by result to find the Macs with the least free space.
  • Compare over time. Save an export every week, or feed the results into Power BI or another reporting tool through Microsoft Graph.
  • Plan updates around it. Major macOS updates need a lot more free space than minor ones. Checking the list before a major update rollout tells you which Macs will need help first.

Keep a separate profile per value. A custom attribute stores one result, so free space, purgeable space and the date of the last check are three profiles with three small scripts.

Go further with dux

Free space tells you which Macs are running low. It does not tell you why. dux for Business, which uses the direct download, includes a command line helper that reports disk usage for management tools, and the same values work as Intune custom attributes.

The helper lives inside the app bundle at /Applications/dux.app/Contents/Helpers/dux, so scripts work even when people never install the command line link. Its report --extension-attribute option prints one value in the <result>value</result> format Jamf expects. Intune stores output exactly as printed, so strip the tags:

#!/bin/sh
# Purgeable space in GB (data type Integer).
DUX="/Applications/dux.app/Contents/Helpers/dux"
[ -x "$DUX" ] || { echo "dux is not installed" >&2; exit 1; }
out=$("$DUX" report --extension-attribute purgeable-bytes) || exit $?
bytes=$(printf '%s' "$out" | sed -e 's/^<result>//' -e 's/<\/result>$//')
[ -n "$bytes" ] || { echo "no value" >&2; exit 1; }
echo $(( bytes / 1000000000 ))

The fields that answer right away, without a scan:

  • capacity-bytes, used-bytes, free-bytes and purgeable-bytes for the startup disk
  • cloud-locations, the number of cloud storage folders on the Mac
  • last-scan, the start of the latest scan saved by the account that runs the command. Custom attributes run as root, which has no saved scans, so read lastScanAt from the saved report instead (see below)

Two more, reclaimable-bytes and largest-folder, run a scan first.

Reclaimable space

reclaimable-bytes is the most useful value for a fleet: an estimate of what cleanups could free, from caches, developer files, old downloads and more. Because it needs a scan of the person’s home folder, run it from a daily platform script and let the custom attribute read the saved result.

Add a shell script under Devices > By platform > macOS > Manage devices > Scripts, with Run script as signed-in user set to No and a Script frequency of once a day:

#!/bin/sh
# Daily: save a dux report for the logged-in user.
DUX="/Applications/dux.app/Contents/Helpers/dux"
OUT="/Library/Application Support/dux-report"
user=$(stat -f%Su /dev/console)
[ -n "$user" ] && [ "$user" != "root" ] || exit 0
mkdir -p "$OUT"
launchctl asuser "$(id -u "$user")" sudo -u "$user" \
  "$DUX" report --json > "$OUT/report.json.tmp" &&
  mv "$OUT/report.json.tmp" "$OUT/report.json"

The scan runs as the person who is logged in, so it covers their home folder with their settings. The custom attribute then only reads the file, with plutil, which ships with macOS:

#!/bin/sh
# Reclaimable space in GB from the last dux report
# (data type Integer).
f="/Library/Application Support/dux-report/report.json"
v=$(plutil -extract scan.reclaimableBytes raw -o - "$f" 2>/dev/null)
[ -n "$v" ] || { echo "no report yet" >&2; exit 1; }
echo $(( v / 1000000000 ))

The same file holds volume.freeBytes, lastScanAt, the reclaimable bytes per category in scan.reclaimableByCategory and the largest folders, with a schemaVersion your scripts can check.

lastScanAt is the start of the scan in ISO 8601 UTC, such as 2026-10-29T09:14:00Z, which fits a Date attribute as is:

#!/bin/sh
# Date of the last dux scan (data type Date).
f="/Library/Application Support/dux-report/report.json"
v=$(plutil -extract lastScanAt raw -o - "$f" 2>/dev/null)
[ -n "$v" ] || { echo "no report yet" >&2; exit 1; }
echo "$v"

Licensing and exit codes

Fleet reporting is part of the Business plan. The license key reaches each Mac through the LicenseKey managed setting in a configuration profile, and it is validated offline. The helper uses stable exit codes, which show up as failed results in Intune when something is off: 1 error, 2 usage error, 3 blocked by an administrator policy, and 4 when the Mac has no license that includes fleet reporting.

Reporting changes nothing on the Mac. To make sure people cannot clean up through dux either, add ReportOnly to the same profile.

Was this guide helpful?