Configuring Printers Programmatically for AirPrint Part 2: Now with Icons!

Several years ago, I wrote a post about the challenges around using lpd:// for printing on Macs running 10.14 Mojave and later. While I came up with a workaround involving programmatically configuring printers to use AirPrint, I found a minor (but annoying) issue where configuring a printer in this way when run as root (which any management tool would do) would produce a PPD file without a printer icon. Running the same ipp2ppd binary as the logged in user did correctly include the printer icon.

A fellow MacAdmin came up with a clever workaround using outset for running the necessary script as the logged-in user, but I had hoped that my post would nudge others in the community to come up with a workaround to allow the inclusion of the printer icon rather than force others to implement another (albeit useful & powerful) tool. Unfortunately, this did not come to pass. The topic of the #printers-n-cups MacAdmins Slack channel is apt: Abandon all hope, ye who enter here

Part of the ipp2ppd binary processing involves downloading all of the available printer icons (.png) from the printer and converting these into a single .icns file. As I learned years ago, these icons are discoverable using the ipptool and searching for printer-icons from the output.

Since the only difference between a root triggered call of ipp2ppd and a regular user call is this icon file reference in the PPD, it occurred to me that so long as it was possible to query and download just one of these PNG icons that this could be subsequently appended to the PPD file in order to install a printer in this programmatic way with an icon.

Sure enough, some tweaking later and I can confirmed the below script would successfully add several different AirPrint printers with their appropriate icons!

#!/bin/bash
# Use the built-in ipp2ppd tool to create a PPD file for AirPrint
# Add icon to PPD (if we can get one) and install the printer
# Required printer info
readonly PRINTER_IP='XXX.XXX.XXX.XXX'
readonly PRINTER_NAME='PRINTER_NAME'
readonly PRINTER_DISPLAY_NAME='PRINTER NAME'
readonly PRINTER_LOCATION='PRINTER LOCATION'
# Requiring icon will prevent install if we can't get it
readonly REQUIRE_ICON=true
#readonly REQUIRE_ICON=false
# Number of seconds to wait for TCP verification before exiting
readonly CHECK_TIMEOUT=2
# Custom PPD info
readonly PPD_PATH='/tmp'
readonly PPD="${PPD_PATH}/${PRINTER_NAME}.ppd"
# Base info
readonly AIR_PPD='/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/PrintCore.framework/Versions/A/Resources/AirPrint.ppd'
readonly EXE='/System/Library/Printers/Libraries/ipp2ppd'
readonly ICON_PATH='/Library/Printers/Icons'
readonly ICON="${ICON_PATH}/${PRINTER_NAME}.icns"
AppendPPDIcon() {
# Verify we have a file
if [ ! -f "$ICON" ] && [ "$ICON_AVAILABLE" != "false" ]; then
/bin/echo "Don't have an icon. Exiting…"
exit 1
fi
/bin/echo "Appending ${ICON} to ${PPD}"
# Append the icon to the PPD
/bin/echo "*APPrinterIconPath: \"${ICON}\"" >> "${PPD}"
}
CheckPrinter() {
# Verify we can communicat with the printer via the AirPrint port via TCP
local CHECK=$(/usr/bin/nc -G ${CHECK_TIMEOUT} -z ${PRINTER_IP} 631 2&> /dev/null; /bin/echo $?)
if [ "$CHECK" != 0 ]; then
/bin/echo "Cannot communicate with ${PRINTER_IP} on port 631/tcp. Exiting…"
exit 1
fi
}
CheckIcon() {
# Query & parse printer icon, largest usually last?
readonly PRINTER_ICON=$(/usr/bin/ipptool -tv ipp://${PRINTER_IP}/ipp/print get-printer-attributes.test \
| /usr/bin/awk -F, '/printer-icons/ {print $NF}')
# Verify we have an icon to download
if [ -z "$PRINTER_ICON" ] && [ "$REQUIRE_ICON" = "true" ]; then
/bin/echo "Did not successfully query a printer icon. Will not install printer. Exiting…"
exit 1
elif [ -z "$PRINTER_ICON" ]; then
/bin/echo "Did not successfully query printer icon. Will continue with printer install…"
readonly ICON_AVAILABLE=false
fi
/bin/echo "Downloading printer icon from ${PRINTER_ICON} to ${ICON}"
# Download the PNG icon and make it an .icns file
/usr/bin/curl -skL "$PRINTER_ICON" -o "$ICON"
}
CreatePPD() {
# Create the PPD file
/bin/echo "Creating the .ppd file at ${PPD}"
$EXE ipp://${PRINTER_IP} "$AIR_PPD" > "$PPD"
}
InstallPrinter() {
/bin/echo "Installing printer…"
/usr/sbin/lpadmin -p ${PRINTER_NAME} -D "${PRINTER_DISPLAY_NAME}" -L "${PRINTER_LOCATION}" -E -v ipp://${PRINTER_IP} -P ${PPD} -o printer-is-shared=false
}
main() {
CheckPrinter
CreatePPD
CheckIcon
AppendPPDIcon
InstallPrinter
}
main "@"

To use the above script effectively, the following is required:

  1. Your printer information:
    1. Name
    2. Display Name
    3. IP Address
    4. Location
  2. Specifying whether to forcibly fail the printer install if no icon is downloaded
  3. Specifying a timeout period where failure to communicate with the printer via TCP port 631 (used for ipp) will automatically cancel the install (default is 2 seconds)

Hopefully this discovery and script helps others to scratch that itch of wanting to both programmatically install their AirPrint printers and to include their icons without third-party tools!

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s