Build the macOS MDM Package
Build a single Pult Agent .pkg with an embedded postinstall script that provisions the bootstrap token, ready to upload to any macOS MDM.
This guide describes how to build a single .pkg artifact that you upload to your MDM. The
resulting package combines the signed Pult Agent.app, its LaunchAgent plist, and a postinstall
script that writes the bootstrap token onto each enrolled device. It is the macOS analogue of the
Intune deployment package used on Windows.
The wrapper .pkg is unsigned, but the Pult Agent.app it contains is signed and notarized by
Pult. MDMs install packages as root via installer, which bypasses Gatekeeper, so an unsigned
wrapper is acceptable for MDM-only distribution. Don't redistribute the wrapper outside your MDM.
Requires Pult Agent 0.2.10 or later. Starting with 0.2.10, the canonical .pkg ships
with a system-wide LaunchAgent (/Library/LaunchAgents/com.pult.agent.plist) alongside
Pult Agent.app, installed to the filesystem root (/). Every step in this guide -- payload
extraction, postinstall logic, and pkgbuild flags -- assumes that layout and will not produce
a working wrapper from a <= 0.2.9 .pkg. Upgrade to 0.2.10+ before following this guide.
Terminology: This guide uses "bootstrap token" to refer to Pult's enrollment token. Apple uses the same term for an unrelated MDM-managed token tied to FileVault and SecureToken. The two are completely independent.
If your MDM supports running a script after a .pkg install (e.g. Kandji), you may not need a
wrapper -- see macOS Deployment via Post-install
Script instead. That path keeps the
canonical Pult-signed .pkg and provisions the bootstrap token via a separate script your MDM
runs.
Prerequisites
- Access to a Mac. This guide uses
pkgutilandpkgbuild, which are macOS-only tools. - The Pult Agent canonical
.pkg, version0.2.10or later. Earlier.pkgbuilds use a different payload layout and cannot be wrapped with this guide. The.dmgartifact also does not include the LaunchAgent plist and is not suitable as a wrapper source. - A bootstrap token generated in the Pult Dashboard.
No Mac available? Use the in-browser PKG
builder instead. It runs every step of
this guide locally in your browser and produces the same wrapper .pkg. Nothing is uploaded --
your .pkg and bootstrap token stay on your machine.
Step 1: Extract the payload
Set up a working directory:
mkdir -p ~/pult-mdm-build && cd ~/pult-mdm-build
mkdir -p payloadThen extract the canonical .pkg payload into ./payload/. The expansion produces a root-relative
file tree containing both Applications/Pult Agent.app/ and
Library/LaunchAgents/com.pult.agent.plist.
pkgutil --expand "Pult-Agent_0.2.10-beta1_universal.pkg" ./expanded
( cd ./payload && gunzip -dc ../expanded/*.pkg/Payload | cpio -i --quiet )The .dmg artifact only contains Pult Agent.app (it's intended for end-user drag-and-drop
installs) and does not include the LaunchAgent plist required for auto-start at login. Use
the .pkg as the source for MDM wrappers.
If the download is quarantined and pkgutil --expand refuses to read it, remove the quarantine flag
first:
xattr -dr com.apple.quarantine "Pult-Agent_0.2.10-beta1_universal.pkg"Verify the extraction succeeded -- this should print the agent version (e.g.
0.2.10-beta1) and the LaunchAgent label (com.pult.agent):
/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' \
"./payload/Applications/Pult Agent.app/Contents/Info.plist"
# → 0.2.10-beta1
/usr/libexec/PlistBuddy -c 'Print :Label' \
"./payload/Library/LaunchAgents/com.pult.agent.plist"
# → com.pult.agentStep 2: Create the Postinstall Script
Create the scripts directory:
mkdir -p ./scriptsSave the following as ./scripts/postinstall. Replace your-bootstrap-token-here with the token
you generated in the Pult Dashboard.
#!/bin/bash
set -euo pipefail
# Bootstrap token from the Pult Dashboard. Replace before building the package.
BOOTSTRAP_TOKEN="your-bootstrap-token-here"
# Where the postinstall drops the bootstrap token file. The Pult Agent watches
# this location and consumes the file on first launch.
# - "user": the console user's ~/Library/Application Support/com.pult.agent/.
# Compatible with all Pult Agent versions.
# - "system": /Library/Application Support/com.pult.agent/, with an ACL that
# lets the user-context agent delete the file after consumption.
# Reserved for future Pult Agent versions; keep "user" today.
# - "both": write to both locations during a transition window.
DROP_MODE="user"
SYSTEM_DIR="/Library/Application Support/com.pult.agent"
drop_user_context() {
local console_user
console_user="$(/usr/bin/stat -f%Su /dev/console)"
if [[ -z "${console_user}" || "${console_user}" == "root" ]]; then
echo "[user mode] no interactive console user; skipping user-context drop"
return 1
fi
local user_home
user_home="$(/usr/bin/dscl . -read "/Users/${console_user}" NFSHomeDirectory \
| /usr/bin/awk '{print $2}')"
local dir="${user_home}/Library/Application Support/com.pult.agent"
local file="${dir}/BOOTSTRAP_TOKEN"
/usr/bin/sudo -u "${console_user}" /bin/mkdir -p "${dir}"
/usr/bin/sudo -u "${console_user}" /usr/bin/tee "${file}" >/dev/null <<<"${BOOTSTRAP_TOKEN}"
/bin/chmod 0600 "${file}"
echo "[user mode] token written to ${file}"
}
drop_system_context() {
/bin/mkdir -p "${SYSTEM_DIR}"
/usr/sbin/chown root:wheel "${SYSTEM_DIR}"
/bin/chmod 0755 "${SYSTEM_DIR}"
/bin/chmod +a "group:everyone allow add_file,delete_child" "${SYSTEM_DIR}"
local file="${SYSTEM_DIR}/BOOTSTRAP_TOKEN"
/usr/bin/printf '%s' "${BOOTSTRAP_TOKEN}" > "${file}"
/bin/chmod 0644 "${file}"
echo "[system mode] token written to ${file}"
}
case "${DROP_MODE}" in
user) drop_user_context ;;
system) drop_system_context ;;
both)
drop_system_context
drop_user_context || true
;;
*)
echo "unknown DROP_MODE: ${DROP_MODE}"
exit 1
;;
esac
# Best-effort: (re)start the agent in the console user's context so the token
# is consumed immediately. If no user is logged in, the agent will pick up
# the token at next login via the system-wide LaunchAgent.
#
# If a previous Pult Agent version was already running, `installer` has
# already replaced the bundle at /Applications/Pult Agent.app, but the old
# process is still resident in memory. We kill it, wait briefly for the
# process to exit cleanly, and then launch the freshly installed version.
#
# Note: `sudo -H` is required, not just `-u`. On recent macOS,
# `sudo -u <user> env` reports HOME=/var/root -- sudo switches the UID to the
# target user but preserves root's HOME across the switch. The Pult Agent
# reads HOME on startup to resolve its config dir, so without -H it tries to
# create `/var/root/Library/Application Support/...` and panics with
# "Permission denied". -H forces HOME to the target user's home.
console_user="$(/usr/bin/stat -f%Su /dev/console)"
if [[ -n "${console_user}" && "${console_user}" != "root" ]]; then
console_uid="$(/usr/bin/id -u "${console_user}")"
/usr/bin/pkill -x pult-agent 2>/dev/null || true
/bin/sleep 3
/bin/launchctl asuser "${console_uid}" \
/usr/bin/sudo -Hu "${console_user}" /usr/bin/open -a "Pult Agent" || true
fi
exit 0
Make the script executable:
chmod +x ./scripts/postinstallWhat the postinstall does
The script runs as root during package installation. It:
- Detects the currently logged-in console user.
- Writes the bootstrap token to that user's
~/Library/Application Support/com.pult.agent/. - Terminates any Pult Agent process from a previous install, waits a few seconds for it to exit, and launches the freshly installed agent in the user's context. The agent reads the token, transfers it to the user's Keychain, deletes the token file, and initiates enrollment.
If no user is logged in at install time, the postinstall logs a message and exits without staging
the token. Most MDMs retry the install at the next user login or check-in. The system-wide
LaunchAgent installed at /Library/LaunchAgents/com.pult.agent.plist then auto-starts the agent
when a user signs in.
Step 3: Build the Wrapper Package
Read the agent version from the bundle so the wrapper's version matches:
VERSION=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' \
"./payload/Applications/Pult Agent.app/Contents/Info.plist")
echo "Building wrapper for Pult Agent ${VERSION}"Build the package:
pkgbuild \
--root ./payload \
--scripts ./scripts \
--identifier com.pult.agent \
--version "${VERSION}" \
--install-location / \
--ownership recommended \
./pult-agent-mdm.pkgThe output pult-agent-mdm.pkg is the file you upload to your MDM.
--install-location / lays the payload down at the filesystem root, placing Pult Agent.app under
/Applications and the LaunchAgent plist under /Library/LaunchAgents. --ownership recommended
forces root:wheel on the installed files -- launchd refuses to honor LaunchAgent plists owned by
anyone else.
Step 4: Verify Locally (Optional)
Test the package on a Mac before pushing it through your MDM. Either run the installer from the terminal:
sudo installer -pkg ./pult-agent-mdm.pkg -target /…or simply double-click pult-agent-mdm.pkg in Finder to launch the standard macOS Installer GUI.
Both paths run the postinstall script as root, identically to how an MDM would.
After install, confirm:
/Applications/Pult Agent.appexists./Library/LaunchAgents/com.pult.agent.plistexists and is owned byroot:wheel.~/Library/Application Support/com.pult.agent/BOOTSTRAP_TOKENis briefly created and then automatically removed (the agent transfers the token to the Keychain and deletes the file).- The Pult Agent tray icon appears in your menu bar within a few seconds.
- In the Pult Dashboard at Settings → Presence → Device Authentication, a new Pending Device Request appears for this Mac.
To clean up before re-running:
sudo /usr/bin/pkill -x pult-agent || true
sudo rm -rf "/Applications/Pult Agent.app"
sudo rm -f "/Library/LaunchAgents/com.pult.agent.plist"
rm -rf "${HOME}/Library/Application Support/com.pult.agent"
sudo pkgutil --forget com.pult.agentThe pkgutil --forget step removes the package receipt macOS keeps to track installed packages.
Without it, the system still considers the wrapper "installed" even after the app is gone, which can
prevent a clean re-install during testing.
To redo enrollment for an already-enrolled device, also reset the agent's stored credentials. Both flags are documented in the CLI reference -- Common compositions:
"/Applications/Pult Agent.app/Contents/MacOS/pult-agent" --reset-bootstrap-token
"/Applications/Pult Agent.app/Contents/MacOS/pult-agent" --cleanup-credentialsNext Steps
Upload the wrapper .pkg to your MDM:
- Jamf Pro
- For other macOS MDMs (Kandji, Mosyle, Workspace ONE, etc.), upload the wrapper as a standard package install. See MDM Deployment Overview.
Also configure Managed Login Items so the agent auto-starts at login and users can't disable it.
Troubleshooting
| Issue | Solution |
|---|---|
pkgutil --expand fails with "no such file" | Verify the path to the canonical .pkg and that the file isn't quarantined (xattr -dr com.apple.quarantine). |
Payload is missing Library/LaunchAgents/ | You're extracting from a Pult Agent version <= 0.2.9. The LaunchAgent was added in 0.2.10. Upgrade to a current .pkg build. |
| Token file not consumed after install | Confirm a user is logged in. The agent reads the token only when running in user context. If installed at the login window, wait for the next login. |
| Agent doesn't appear in the menu bar | Confirm Pult Agent.app is at /Applications/Pult Agent.app. Launch manually with open -a "Pult Agent" to see if it starts. |
| Agent doesn't auto-start at the next login | Confirm /Library/LaunchAgents/com.pult.agent.plist exists and is owned by root:wheel. The wrapper must be built with --install-location / and --ownership recommended (see Step 3). |
| Pending Device Request never appears in the Dashboard | Check the agent's logs in ~/Library/Logs/com.pult.agent/. Verify the bootstrap token hasn't expired in the Pult Dashboard. |
Last updated on May 13, 2026, 1:57 PM