Pult Presence Docs
Pult AgentMDM Deployment

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 pkgutil and pkgbuild, which are macOS-only tools.
  • The Pult Agent canonical .pkg, version 0.2.10 or later. Earlier .pkg builds use a different payload layout and cannot be wrapped with this guide. The .dmg artifact 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 payload

Then 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.agent

Step 2: Create the Postinstall Script

Create the scripts directory:

mkdir -p ./scripts

Save 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/postinstall

What the postinstall does

The script runs as root during package installation. It:

  1. Detects the currently logged-in console user.
  2. Writes the bootstrap token to that user's ~/Library/Application Support/com.pult.agent/.
  3. 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.pkg

The 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:

  1. /Applications/Pult Agent.app exists.
  2. /Library/LaunchAgents/com.pult.agent.plist exists and is owned by root:wheel.
  3. ~/Library/Application Support/com.pult.agent/BOOTSTRAP_TOKEN is briefly created and then automatically removed (the agent transfers the token to the Keychain and deletes the file).
  4. The Pult Agent tray icon appears in your menu bar within a few seconds.
  5. 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.agent

The 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-credentials

Next Steps

Upload the wrapper .pkg to your MDM:

Also configure Managed Login Items so the agent auto-starts at login and users can't disable it.

Troubleshooting

IssueSolution
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 installConfirm 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 barConfirm 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 loginConfirm /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 DashboardCheck 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

On this page