Back

Configure printers for macOS

Learn how to easily set up and manage printers on your macOS devices for efficient printing capabilities.

This article explains how to configure IPP printers on macOS devices using Factorial IT. The process involves discovering the printers available on the network, identifying their IP addresses, and importing the configuration into Factorial IT via a custom MDM setting.


 

Scan IPP printers on the network

If you already know your printer config, you can skip this part.

  1. Open Terminal on a macOS device.
  2. Run the following command to discover all printers available through the Internet Printing Protocol (IPP):
    ippfind
  3. Note the list of discovered printers and their IPP URIs.

 

Identify the printer IP addresses

If you already know your printer config, you can skip this part.

  1. For each discovered printer, run a ping command to verify its availability and obtain its IP address:
    ping [printer-hostname]
    Replace [printer-hostname] with the actual hostname displayed by ippfind
  2. Record the IP addresses and corresponding IPP URIs.

 

Generate the mobileconfig file

If you need help generating the mobileconfig file with ippfind command and ping, you can run this script on your mac while you’re connected to your office network. It will display the mobileconfig file in your terminal.

#!/bin/bash# macOS + bash 3.2 compatible# Discovers IPP printers and generates a .mobileconfig# Dependencies: ippfind, ipptool, uuidgen, ping, nc, plutil, arp# Usage:#   ./gen_airprint_profile.sh [-o file.mobileconfig] [-i com.profile.id] [-s CIDR]# Example:#   ./gen_airprint_profile.sh -o Factorial IT-AirPrint.mobileconfig -i com.getprimo.printer.airprint -s 192.168.1.0/24set -euo pipefailOUTPUT="AirPrint.mobileconfig"ROOT_ID="com.getprimo.printer.airprint"CIDR=""TIMEOUT_DISC=5PING_WAIT_MS=1000while getopts ":o:i:s:" opt; do  case "$opt" in    o) OUTPUT="$OPTARG" ;;    i) ROOT_ID="$OPTARG" ;;    s) CIDR="$OPTARG" ;;    \?) echo "Invalid option: -$OPTARG" >&2; exit 2 ;;  esacdoneneed() { command -v "$1" >/dev/null 2>&1 || { echo "Missing required tool: $1" >&2; exit 1; }; }need uuidgen; need ping; need plutil; need ipptool# ippfind and nc are recommended; without ippfind the script will fallback to CIDR/ARPcommand -v ippfind >/dev/null 2>&1 || echo "⚠️  ippfind not found: mDNS discovery will be skipped."command -v nc >/dev/null 2>&1 || { echo "nc (netcat) is required for the network fallback."; exit 1; }xml_escape() {  sed -e 's/&/\&amp;/g' -e 's/</\&lt;/g' -e 's/>/\&gt;/g' \      -e 's/\"/\&quot;/g' -e "s/'/\&apos;/g"}# -------- mDNS discovery via ippfind ----------URIS=()discover_mdns() {  [ -x "$(command -v ippfind)" ] || return 0  # --timeout varies across versions; try short IPv4 timeout when available  local out  if ippfind --help 2>/dev/null | grep -q -- '--timeout'; then    out=$(ippfind --timeout ${TIMEOUT_DISC} _ipp._tcp,_print _ipps._tcp,_print -q 2>/dev/null || true)  else    out=$(ippfind _ipp._tcp _ipps._tcp -q 2>/dev/null || true)  fi  if [ -n "$out" ]; then    while IFS= read -r line; do      [ -n "$line" ] && URIS+=("$line")    done <<< "$out"  fi}# -------- Fallback without mDNS ----------# 1) Build candidate IPs: explicit CIDR (/24 only) or ARP tablecandidates_from_arp() {  arp -an 2>/dev/null | awk '{print $2}' | tr -d '()' | grep -E '^[0-9.]+$' | sort -u}candidates_from_cidr() {  # Minimal /24 generator (e.g., 192.168.1.0/24); other masks are not supported here  local cidr="$1"  if ! echo "$cidr" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/24$'; then    echo "⚠️  Unsupported CIDR for this mini-scanner (only /24): $cidr" >&2    return 1  fi  local base="${cidr%/24}"  local net=$(echo "$base" | awk -F. '{print $1"."$2"."$3"."}')  local i  for i in $(seq 1 254); do echo "${net}${i}"; done}# 2) Probe port 631 and try common IPP pathstry_make_uri() {  local ip="$1"  local path  for path in "/ipp/print" "/.well-known/ipp" "/printers/print" "/ipp" "/printers/ipp"; do    if ipptool -T 3 -q "ipp://$ip:631$path" get-printer-attributes.test >/dev/null 2>&1; then      echo "ipp://$ip:631$path"      return 0    fi  done  for path in "/ipp/print" "/.well-known/ipp" "/ipp"; do    if ipptool -T 5 -q "ipps://$ip:631$path" get-printer-attributes.test >/dev/null 2>&1; then      echo "ipps://$ip:631$path"      return 0    fi  done  echo ""  return 1}discover_fallback() {  local candidates  if [ -n "$CIDR" ]; then    candidates=$(candidates_from_cidr "$CIDR" || true)  else    candidates=$(candidates_from_arp || true)  fi  [ -z "$candidates" ] && return 0  local ip uri  while IFS= read -r ip; do    [ -z "$ip" ] && continue    if nc -G 1 -z "$ip" 631 2>/dev/null; then      uri=$(try_make_uri "$ip")      [ -n "$uri" ] && URIS+=("$uri")    fi  done <<< "$candidates"}# -------- Collect attributes for each URI ----------IPs=(); Ports=(); Paths=(); Names=(); Models=(); Locs=(); ForceTLS=()extract_components() {  local uri="$1" scheme host port path ip name model loc  scheme=$(printf '%s' "$uri" | sed -E 's#^([a-zA-Z0-9+.-]+)://.*#\1#')  host=$(printf '%s' "$uri" | sed -E 's#^[a-zA-Z0-9+.-]+://([^/:]+).*#\1#')  port=$(printf '%s' "$uri" | sed -E 's#^[a-zA-Z0-9+.-]+://[^/:]+:([0-9]+).*#\1#')  path=$(printf '%s' "$uri" | sed -E 's#^[a-zA-Z0-9+.-]+://[^/]+(/.*)$#\1#')  [ -z "${port:-}" ] && port=631  [ -z "${path:-}" ] && path="/ipp/print"  ip=$(ping -c 1 -W ${PING_WAIT_MS} "$host" 2>/dev/null | sed -n '1s/.*(\([0-9.]*\)).*/\1/p')  [ -z "${ip:-}" ] && ip="$host"  name=""; model=""; loc=""  if ipptool -T 5 -q "$uri" get-printer-attributes.test >/tmp/ipp.$$ 2>/dev/null; then    name=$(sed -n 's/^printer-info[^=]*= //p' /tmp/ipp.$$ | head -n1)    model=$(sed -n 's/^printer-make-and-model[^=]*= //p' /tmp/ipp.$$ | head -n1)    loc=$(sed -n 's/^printer-location[^=]*= //p' /tmp/ipp.$$ | head -n1)    rm -f /tmp/ipp.$$  fi  [ -z "$name" ] && name="$host"  [ -z "$model" ] && model="$name"  IPs+=("$ip"); Ports+=("$port"); Paths+=("$path");  Names+=("$name"); Models+=("$model"); Locs+=("$loc");  if [ "$scheme" = "ipps" ]; then ForceTLS+=("true"); else ForceTLS+=("false"); fi}# --------- RUN ----------discover_mdnsif [ ${#URIS[@]} -eq 0 ]; then  echo "ℹ️  No printer visible via mDNS (ippfind). Attempting network discovery…"  discover_fallbackfiif [ ${#URIS[@]} -eq 0 ]; then  echo "No IPP printer found" >&2  exit 1fi# Remove duplicates / keep stable ordertmp_uniq="$(mktemp)"printf "%s\n" "${URIS[@]}" | awk '!seen[$0]++' > "$tmp_uniq"URIS=()while IFS= read -r line; do [ -n "$line" ] && URIS+=("$line"); done < "$tmp_uniq"rm -f "$tmp_uniq"# Gather attributesi=0while [ $i -lt ${#URIS[@]} ]; do  extract_components "${URIS[$i]}"  i=$((i+1))done# --------- Generate .mobileconfig ----------PROFILE_UUID=$(uuidgen)AIRPRINT_UUID=$(uuidgen)MCX_UUID=$(uuidgen)TMP="$(mktemp /tmp/airprint.XXXXXX.mobileconfig)"{cat <<'XML'<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict>  <key>PayloadContent</key>  <array>    <dict>      <key>AirPrint</key>      <array>XMLidx=0for uri in "${URIS[@]}"; do  ft="${ForceTLS[$idx]}"; ip="${IPs[$idx]}"; port="${Ports[$idx]}"; path="${Paths[$idx]}"  printf '        <dict>\n'  printf '          <key>ForceTLS</key>\n'  [ "$ft" = "true" ] && printf '          <true/>\n' || printf '          <false/>\n'  printf '          <key>IPAddress</key>\n'  printf '          <string>%s</string>\n' "$(printf '%s' "$ip" | xml_escape)"  printf '          <key>Port</key>\n'  printf '          <integer>%s</integer>\n' "$port"  printf '          <key>ResourcePath</key>\n'  printf '          <string>%s</string>\n' "$(printf '%s' "$path" | xml_escape)"  printf '        </dict>\n'  idx=$((idx+1))donecat <<XML      </array>      <key>PayloadDisplayName</key>      <string>AirPrint</string>      <key>PayloadIdentifier</key>      <string>com.apple.airprint.${AIRPRINT_UUID}</string>      <key>PayloadType</key>      <string>com.apple.airprint</string>      <key>PayloadUUID</key>      <string>${AIRPRINT_UUID}</string>      <key>PayloadVersion</key>      <integer>1</integer>    </dict>    <dict>      <key>DefaultPrinter</key>      <dict>        <key>DeviceURI</key>      <string>$(printf '%s' "${URIS[0]}" | xml_escape)</string>        <key>DisplayName</key>      <string>$(printf '%s' "${Names[0]}" | xml_escape)</string>      </dict>      <key>PayloadDisplayName</key>      <string>Printing</string>      <key>PayloadIdentifier</key>      <string>com.apple.mcxprinting.${MCX_UUID}</string>      <key>PayloadType</key>      <string>com.apple.mcxprinting</string>      <key>PayloadUUID</key>      <string>${MCX_UUID}</string>      <key>PayloadVersion</key>      <integer>1</integer>      <key>UserPrinterList</key>      <dict>        <key>Printer</key>        <array>XMLidx=0for uri in "${URIS[@]}"; do  name="${Names[$idx]}"; model="${Models[$idx]}"; loc="${Locs[$idx]}"  cat <<XML          <dict>            <key>DeviceURI</key>            <string>$(printf '%s' "$uri" | xml_escape)</string>            <key>DisplayName</key>            <string>$(printf '%s' "$name" | xml_escape)</string>            <key>Location</key>            <string>$(printf '%s' "$loc" | xml_escape)</string>            <key>Model</key>            <string>$(printf '%s' "$model" | xml_escape)</string>            <key>PPDURL</key>            <string>file://localhost/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/PrintCore.framework/Resources/Generic.ppd</string>            <key>PrinterLocked</key>            <false/>          </dict>XML  idx=$((idx+1))donecat <<XML        </array>      </dict>    </dict>  </array>  <key>PayloadDisplayName</key>  <string>AirPrint</string>  <key>PayloadIdentifier</key>  <string>${ROOT_ID}</string>  <key>PayloadType</key>  <string>Configuration</string>  <key>PayloadUUID</key>  <string>${PROFILE_UUID}</string>  <key>PayloadVersion</key>  <integer>1</integer></dict></plist>XML} > "$TMP"plutil -lint "$TMP" >/dev/nullplutil -convert xml1 -o "$OUTPUT" "$TMP"rm -f "$TMP"echo "✅ Profile generated: $OUTPUT"echo "🖨️  Default Printer: ${Names[0]} (${URIS[0]})"cat "$OUTPUT"

 

Import the configuration into Factorial IT

  1. Access MDM > Profiles. Select the profile you want to target.
  2. Select Add a custom MDM setting.
  3. Upload the following .mobileconfig file, replacing the placeholders (IP, UUID, etc) with your printer’s configurations details: 
    <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict>	<key>PayloadContent</key>	<array>		<dict>			<key>AirPrint</key>			<array>				<dict>					<key>ForceTLS</key>					<false/>					<key>IPAddress</key>					<string>[IP_ADDRESS]</string>					<key>Port</key>					<integer>631</integer>					<key>ResourcePath</key>					<string>[RESOURCE_PATH]</string>				</dict>			</array>			<key>PayloadDisplayName</key>			<string>AirPrint</string>			<key>PayloadIdentifier</key>			<string>com.apple.airprint.[UUID]</string>			<key>PayloadType</key>			<string>com.apple.airprint</string>			<key>PayloadUUID</key>			<string>[UUID]</string>			<key>PayloadVersion</key>			<integer>1</integer>		</dict>		<dict>			<key>DefaultPrinter</key>			<dict>				<key>DeviceURI</key>				<string>ipp://[IP_ADDRESS]/[RESOURCE_PATH]</string>				<key>DisplayName</key>				<string>[DISPLAY_NAME]</string>			</dict>			<key>PayloadDisplayName</key>			<string>Printing</string>			<key>PayloadIdentifier</key>			<string>com.apple.mcxprinting.[UUID]</string>			<key>PayloadType</key>			<string>com.apple.mcxprinting</string>			<key>PayloadUUID</key>			<string>[UUID]</string>			<key>PayloadVersion</key>			<integer>1</integer>			<key>UserPrinterList</key>			<dict>				<key>Printer</key>				<array>					<dict>						<key>DeviceURI</key>						<string>ipp://[IP_ADDRESS]/[RESOURCE_PATH]</string>						<key>DisplayName</key>						<string>[DISPLAY_NAME]</string>						<key>Location</key>						<string>[LOCATION]</string>						<key>Model</key>						<string>[MODEL]</string>						<key>PPDURL</key>						<string>file://localhost/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/PrintCore.framework/Resources/Generic.ppd</string>						<key>PrinterLocked</key>						<false/>					</dict>				</array>			</dict>		</dict>	</array>	<key>PayloadDisplayName</key>	<string>AirPrint</string>	<key>PayloadIdentifier</key>	<string>com.getprimo.printer.airprint</string>	<key>PayloadType</key>	<string>Configuration</string>	<key>PayloadUUID</key>	<string>[UUID]</string>	<key>PayloadVersion</key>	<integer>1</integer></dict></plist>
  4. Save and deploy the profile.

 

Was this article helpful?

Give feedback about this article

Can’t find what you’re looking for?

Our customer care team is here for you.

Contact us

Knowledge Base Software powered by Helpjuice