Skip to main content

Deploying BusyContacts via MDM or Scripted Installation

BusyContacts does not currently support direct MDM-based licensing for App Store subscriptions, due to limitations imposed by Apple. The Mac App Store is designed around individual iCloud-based subscriptions, which unfortunately do not support business-wide deployments under a single MDM license.

For organizations and IT admins who wish to manage multiple installations of BusyContacts across devices, we recommend using our direct license version—available from our website—which allows volume purchases (e.g., a 5-seat license) and supports custom deployment workflows.

Benefits of our Direct Version vs. App Store

For more details, please visit the licensing page as we have given a detailed comparison there.

Essentially, our direct version allows you to:

  • Purchase multi-seat licenses with 18 months of updates included.
  • Deploy BusyContacts manually or via scripts.
  • Register and activate using a license key
  • Optional Upgrades

Managed Installation on a Single Machine

You can manually install and activate BusyContacts on any Mac using the following steps:

1. Download the latest DMG

Download BusyContacts

2. Mount the DMG

Open Terminal and run:

hdiutil attach /path/to/BusyContacts.dmg

This command will mount the DMG file as a volume on your system. Take note of the mount point displayed in the command output (e.g., /Volumes/BusyContacts).

3. Copy BusyContacts to Applications

cp -R /Volumes/BusyContacts/BusyContacts.app /Applications/

4. Register BusyContacts with a License Key

You can register BusyContacts by opening a special URL with your license key:

open "busycontacts://register/YOUR-LICENSE-KEY-HERE"

Replace YOUR-LICENSE-KEY-HERE with your actual license key. This will silently register the installed copy in the background.

Sample Deployment Script for Automation

For automating this across multiple machines, here’s a sample Bash script:

note

This script can be adapted for deployment tools like Jamf, Munki, or custom shell workflows across managed Macs.

#!/bin/bash

# --- Configuration ---
BASE_URL="https://www.busymac.com/download" # Base URL for downloads
DMG_NAME="BusyContacts.dmg" # Base name for the downloaded file and in URL
DMG_URL="$BASE_URL/$DMG_NAME" # Construct the full download URL
DMG_PATH="/tmp/$DMG_NAME" # Full path for the downloaded file
APP_NAME="BusyContacts.app" # Name of the application bundle inside the DMG
LICENSE_KEY="YOUR-LICENSE-KEY-HERE" # Replace with actual license key
# --- End Configuration ---

# Variable to store the determined mount point
MOUNT_POINT=""

# Function for cleanup actions
cleanup() {
echo "--- Cleaning up ---"
# Attempt to detach using the determined mount point, if available and mounted
if [ -n "$MOUNT_POINT" ] && hdiutil info | grep -q -F "$MOUNT_POINT"; then
echo "Unmounting volume at $MOUNT_POINT..."
hdiutil detach "$MOUNT_POINT" -force > /dev/null 2>&1
fi
# Remove the downloaded DMG file
if [ -f "$DMG_PATH" ]; then
echo "Removing downloaded DMG at $DMG_PATH..."
rm "$DMG_PATH"
fi
echo "Cleanup finished."
}

trap cleanup EXIT

# --- Main Script ---
echo "Starting BusyContacts installation script..."

# Check for xmllint availability
if ! command -v xmllint &> /dev/null; then
echo "Error: xmllint command not found. Install via: brew install libxml2" >&2
exit 1
fi

# Download BusyContacts DMG
echo "Downloading $DMG_URL to $DMG_PATH..."
if ! curl --fail -L -o "$DMG_PATH" "$DMG_URL"; then
echo "Error: Failed to download DMG from $DMG_URL." >&2
exit 1
fi
echo "Download complete."

# Mount the DMG and capture the output plist
echo "Mounting DMG: $DMG_PATH..."

MOUNT_INFO_PLIST=$(hdiutil attach "$DMG_PATH" -nobrowse -noverify -plist)
ATTACH_STATUS=$? # Capture exit status of hdiutil attach

if [ $ATTACH_STATUS -ne 0 ]; then
echo "Error: Failed to mount DMG. hdiutil exited with status $ATTACH_STATUS." >&2
exit 1
fi

# Parse the plist output using plutil and xmllint to find the mount point
echo "Parsing mount point information..."

# Convert plist to XML, pipe to xmllint, use XPath to extract the string value
# following the 'mount-point' key. Suppress stderr from plutil.
MOUNT_POINT=$(echo "$MOUNT_INFO_PLIST" | plutil -convert xml1 -o - - 2>/dev/null | \
xmllint --xpath 'string(//key[.="mount-point"]/following-sibling::string[1])' - 2>/dev/null)
PARSE_STATUS=$? # Capture exit status of the command pipeline

if [ $PARSE_STATUS -ne 0 ]; then
echo "Error: Failed to parse mount point using xmllint (exit status $PARSE_STATUS)." >&2
# Attempt to detach using the DMG path as a fallback
hdiutil detach "$DMG_PATH" -force > /dev/null 2>&1
exit 1
fi

# Validate that we found a mount point
if [ -z "$MOUNT_POINT" ]; then
echo "Error: Could not determine mount point from hdiutil output (xmllint parsing failed to find it)." >&2
# Attempt to detach using the DMG path as a fallback
hdiutil detach "$DMG_PATH" -force > /dev/null 2>&1
exit 1
fi
echo "DMG mounted successfully at: $MOUNT_POINT"

# Define the source path for the application
SOURCE_APP_PATH="$MOUNT_POINT/$APP_NAME"

# Check if the application exists at the expected path within the mounted volume
if [ ! -d "$SOURCE_APP_PATH" ]; then
echo "Error: Application '$APP_NAME' not found at '$SOURCE_APP_PATH'." >&2
exit 1
fi
echo "Located application at: $SOURCE_APP_PATH"

# Copy the app to /Applications
echo "Copying $APP_NAME to /Applications/ ..."
if ! cp -R "$SOURCE_APP_PATH" /Applications/; then
echo "Error: Failed to copy '$APP_NAME' to /Applications/." >&2
exit 1
fi
echo "Application copied successfully."

# Register the app using the URL scheme
echo "Attempting to register the application..."

# Add a small delay before trying to register, sometimes needed for the system
# to recognize the newly copied app and its URL scheme handler.
sleep 2

if ! open -g "busycontacts://register/$LICENSE_KEY"; then
echo "Registration failed."
exit 1
fi
echo "Registration command sent."

# Unmounting and cleanup will be handled by the EXIT trap

echo "BusyContacts installation script completed successfully."

# Explicitly exit successfully, triggering the cleanup trap
exit 0