Menu Close

Actionable, Custom Branded macOS Notifications with swiftDialog 2.4.0

A pair of scripts to help Jamf Pro admins easily display actionable, custom branded macOS Notifications

swiftDialog Pre-install (0.0.4) & swiftDialog Notifications (0.0.4)

Jamf Pro built-in Notifications compared to actionable, custom branded swiftDialog Notifications

Introduction

One of the more welcome features of swiftDialog 2.4.0 is actionable, custom branded macOS Notifications. When leveraged with Script Parameters, Jamf Pro administrators can easily display custom branded macOS Notifications to their users.

swiftDialog Notifications (0.0.4)
swiftDialog Notifications (0.0.4)

Configuration

Complete the following steps to create custom branded macOS Notifications with swiftDialog 2.4.0.

A. Deploy Notifications Configuration Profile for swiftDialog
  1. Review Bart’s Notifications documentation
  2. Update your Notifications Configuration Profile to include the following
    • App Name: swiftDialog 2.4.0
    • Bundle ID: au.csiro.dialog
B. Add the swiftDialog Pre-install script to your Jamf Pro server

The swiftDialog Pre-install script automates the creation of Dialog.png, based on your customized Self Service icon (thanks, @meschwartz).

/Library/Application Support/Dialog/Dialog.png
  1. Add the swiftDialog Pre-install script to your Jamf Pro server
  2. Specify the following for Options > Parameter Labels
    • Parameter 4: Script Log Location
    • Parameter 5: Installation Action [ none (default) | remove ]
  3. Click Save

Latest version available on GitHub.

#!/bin/bash
####################################################################################################
#
# ABOUT
#
#   swiftDialog Pre-install
#   Pre-install Company Logo for swiftDialog v2 Notifications
#
#   See: https://snelson.us/2023/03/swiftdialog-notifications/
#
####################################################################################################
#
# HISTORY
#
#   Version 0.0.1, 14-Nov-2022, Dan K. Snelson (@dan-snelson)
#       - Original proof-of-concept version
#
#   Version 0.0.2, 16-Nov-2022, Dan K. Snelson (@dan-snelson)
#       - Added "last logged-in user" logic
#       - Added check for Dialog.png (with graceful exit)
#
#   Version 0.0.3, 16-Mar-2023, Dan K. Snelson (@dan-snelson)
#       - Create 'Dialog.png' from Self Service's custom icon (thanks, @meschwartz!)
#       - Remove no longer required 'loggedInUser'-related code
#
#   Version 0.0.4, 10-Jul-2023, Dan K. Snelson (@dan-snelson)
#       - Installation Action (Parameter 5) [ none (default) | remove ]
#
####################################################################################################



####################################################################################################
#
# Variables
#
####################################################################################################

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Global Variables
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

scriptVersion="0.0.4"
export PATH=/usr/bin:/bin:/usr/sbin:/sbin
scriptLog="${4:-"/var/tmp/org.churchofjesuschrist.log"}"
installationAction="${5:-"none"}"   # [ none (default) | remove ]



####################################################################################################
#
# Pre-flight Checks
#
####################################################################################################

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Client-side Logging
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

if [[ ! -f "${scriptLog}" ]]; then
    touch "${scriptLog}"
fi



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Client-side Script Logging Function
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function updateScriptLog() {
    echo -e "$( date +%Y-%m-%d\ %H:%M:%S ) - ${1}" | tee -a "${scriptLog}"
}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Logging Preamble
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

updateScriptLog "\n\n###\n# swiftDialog Pre-install (${scriptVersion})\n# https://snelson.us\n###\n"
updateScriptLog "PRE-FLIGHT CHECK: Initiating …"



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Confirm script is running as root
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

if [[ $(id -u) -ne 0 ]]; then
    updateScriptLog "PRE-FLIGHT CHECK: This script must be run as root; exiting."
    exit 1
fi



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Complete
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

updateScriptLog "PRE-FLIGHT CHECK: Complete"



####################################################################################################
#
# Program
#
####################################################################################################

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Installation Action
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

updateScriptLog "Installation Action: ${installationAction} …"

case ${installationAction} in

    "remove" )
        updateScriptLog "Removing swiftDialog …"
        rm -fv /usr/local/bin/dialog
        rm -Rfv /Library/Application\ Support/Dialog/
        updateScriptLog "swiftDialog has been removed"
        ;;

    "none" | * )
        updateScriptLog "Skipping Installation Action"
        ;;

esac



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Validate Dialog Branding Image
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

updateScriptLog "Validate 'Dialog.png' …"
if [[ -f "/Library/Application Support/Dialog/Dialog.png" ]]; then
    updateScriptLog "The file '/Library/Application Support/Dialog/Dialog.png' already exists; exiting."
    exit 0
else
    updateScriptLog "The file '/Library/Application Support/Dialog/Dialog.png' does NOT exist; proceeding …"
fi



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Create Dialog directory
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

if [[ ! -d "/Library/Application Support/Dialog/" ]]; then
    updateScriptLog "Creating '/Library/Application Support/Dialog/' …"
    mkdir -p "/Library/Application Support/Dialog/"
else
    updateScriptLog "The directory '/Library/Application Support/Dialog/' exists …"
fi



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Create Dialog.png from Self Service's custom icon (thanks, @meschwartz!)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

updateScriptLog "Create 'Dialog.png' …"
xxd -p -s 260 "$(defaults read /Library/Preferences/com.jamfsoftware.jamf self_service_app_path)"/Icon$'\r'/..namedfork/rsrc | xxd -r -p > "/Library/Application Support/Dialog/Dialog.png"



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Validate Dialog Branding Image
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

updateScriptLog "Validate 'Dialog.png' …"
if [[ ! -f "/Library/Application Support/Dialog/Dialog.png" ]]; then
    updateScriptLog "Error: The file '/Library/Application Support/Dialog/Dialog.png' was NOT found."
    exit 1
else
    updateScriptLog "The file '/Library/Application Support/Dialog/Dialog.png' was created sucessfully."
    find "/Library/Application Support/Dialog/Dialog.png" | tee -a "${scriptLog}"
fi



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Exit
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

updateScriptLog "End-of-line."

exit 0
C. Modify your swiftDialog installation policy
  1. Modify your swiftDialog installation policy, by selecting the Scripts payload and adding the swiftDialog Pre-install script, specifying the following Parameter Value
    • Script Log Location: /var/log/com.company.log
    • Installation Action: none
  2. Optionally add the Files and Processes payload and specify the following one-liner for the Execute Command
/usr/bin/chflags hidden "/Library/Application Support/Dialog/"
  1. Click Save
D. Add the swiftDialog Notifications script to your Jamf Pro server

Breaking change for Jamf Pro Admins prior to version 0.0.4

Version 0.0.4 modifies the Script Parameter Label for scriptLog — changing it to a hard-coded variable in the script (as it should have been all along) — Sorry for any Dan-induced headaches.

Additionally, the Script Parameter for subtitle has been removed.

(We used Object Info to determine which policies needed to be updated.)

  1. Add the swiftDialog Notifications script to your Jamf Pro server
  2. Specify the following for Options > Parameter Labels
    • Parameter 4: Title
    • Parameter 5: Message
    • Parameter 6: Button 1 Text
    • Parameter 7: Button 1 Action
    • Parameter 8: Button 2 Text
    • Parameter 9: Button 2 Action
  3. Click Save

Latest version available on GitHub.

#!/bin/zsh
# shellcheck shell=bash
# shellcheck disable=SC2317

####################################################################################################
#
# ABOUT
#
#   swiftDialog Notifications
#
#   See: https://snelson.us/?s=swiftdialog
#
####################################################################################################
#
# HISTORY
#
#   Version 0.0.4, 28-Jan-2024, Dan K. Snelson (@dan-snelson)
#       - Updated for swiftDialog v2.4.0
#
#       :fire: **Breaking Change** for users prior to `0.0.4` :fire:
#       
#       Version `0.0.4` modifies the Script Parameter Label for `scriptLog` — changing it to a
#       hard-coded variable in the script (as it should have been all along) — Sorry for any
#       Dan-induced headaches.
#       
#       Additionally, the Script Parameter for `subtitle` has been removed.
#
####################################################################################################



####################################################################################################
#
# Variables
#
####################################################################################################

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Global Variables
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

export PATH=/usr/bin:/bin:/usr/sbin:/sbin

# Script Version & Client-side Log
scriptVersion="0.0.4"
scriptLog="/var/tmp/org.churchofjesuschrist.log"

# swiftDialog Binary & Log 
dialogBinary="/usr/local/bin/dialog"
dialogNotificationLog=$( mktemp -u /var/tmp/dialogNotificationLog.XXXX )

# Current logged-in user
loggedInUser=$( echo "show State:/Users/ConsoleUser" | scutil | awk '/Name :/ { print $3 }' )



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Jamf Pro Script Parameters
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

# Parameter 4: Title
title="${4:-"Title [Parameter 4]"}"

# Parameter 5: Message
message="${5:-"Message [Parameter 5]"}"

# Parameter 6: Button 1 Text
if [[ -n ${6} ]]; then button1TextOption="--button1text"; button1text="${6}"; fi

# Parameter 7: Button 1 Action
if [[ -n ${7} ]]; then button1ActionOption="--button1action"; button1action="${7}"; fi

# Parameter 8: Button 2 Text
if [[ -n ${8} ]]; then button2TextOption="--button2text"; button2text="${8}"; fi

# Parameter 9: Button 2 Action
if [[ -n ${9} ]]; then button2ActionOption="--button2action"; button2action="${9}"; fi



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Organization Variables
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

# Script Human-readable Name
humanReadableScriptName="swiftDialog Notifications"

# Organization's Script Name
organizationScriptName="sdNotify"



####################################################################################################
#
# Functions
#
####################################################################################################

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Client-side Logging
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function updateScriptLog() {
    echo "${organizationScriptName} ($scriptVersion): $( date +%Y-%m-%d\ %H:%M:%S ) - ${1}" | tee -a "${scriptLog}"
}

function preFlight() {
    updateScriptLog "[PRE-FLIGHT]      ${1}"
}

function logComment() {
    updateScriptLog "                  ${1}"
}

function notice() {
    updateScriptLog "[NOTICE]          ${1}"
}

function info() {
    updateScriptLog "[INFO]            ${1}"
}

function errorOut(){
    updateScriptLog "[ERROR]           ${1}"
}

function error() {
    updateScriptLog "[ERROR]           ${1}"
    let errorCount++
}

function warning() {
    updateScriptLog "[WARNING]         ${1}"
    let errorCount++
}

function fatal() {
    updateScriptLog "[FATAL ERROR]     ${1}"
    exit #1
}

function quitOut(){
    updateScriptLog "[QUIT]            ${1}"
}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Quit Script (thanks, @bartreadon!)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function quitScript() {

    notice "*** QUITTING ***"

    # Remove dialogNotificationLog
    if [[ -f "${dialogNotificationLog}" ]]; then
        logComment "Removing ${dialogNotificationLog} …"
        rm "${dialogNotificationLog}"

    fi

    logComment "Goodbye!"
    exit "${1}"

}



####################################################################################################
#
# Pre-flight Checks
#
####################################################################################################

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Client-side Logging
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

if [[ ! -f "${scriptLog}" ]]; then
    touch "${scriptLog}"
    if [[ -f "${scriptLog}" ]]; then
        preFlight "Created specified scriptLog: ${scriptLog}"
    else
        fatal "Unable to create specified scriptLog '${scriptLog}'; exiting.\n\n(Is this script running as 'root' ?)"
    fi
else
    preFlight "Specified scriptLog '${scriptLog}' exists; writing log entries to it"
fi



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Logging Preamble
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

preFlight "\n\n###\n# $humanReadableScriptName (${scriptVersion})\n# https://snelson.us/\n###\n"
preFlight "Initiating …"



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Confirm script is running as root
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

if [[ $(id -u) -ne 0 ]]; then
    fatal "This script must be run as root; exiting."
fi



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Validate logged-in user
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

if [[ -z "${loggedInUser}" || "${loggedInUser}" == "loginwindow" ]]; then
    fatal "No user logged-in; exiting"
fi


# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Validate Script Parameters
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

if [[ -z "${title}" || "${title}" == "Title [Parameter 4]" ]] ; then

    warning "Title [Parameter 4] is either empty or NOT set; displaying instructions …"

    title="Title [Parameter 4]: swiftDialog Wiki"

    message="Message [Parameter 5]: Have you checked Bart's Wiki?"

    button1TextOption="--button1text"
    button1text="Button 1 Text [Parameter 6]: No"

    button1ActionOption="--button1action"
    button1action="https://github.com/swiftDialog/swiftDialog/wiki"

    button2TextOption="--button2text"
    button2text="Button 2 Text [Parameter 8]: Yes"

    button2ActionOption="--button2action"
    button2action="https://snelson.us/?s=swiftdialog"

else

    updateScriptLog "Parameter 4, \"title,\" is populated; proceeding ..."

fi

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Complete
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

preFlight "Complete!"



####################################################################################################
#
# Program
#
####################################################################################################

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Display Notification
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

notice "*** DISPLAY NOTIFICATION ***"

logComment "Title (Parameter 4):           ${title}"
logComment "Message (Parameter 5):         ${message}"

if [[ -n "${button1text}" ]]; then logComment "Button 1 Text (Parameter 6):   ${button1text}" ; fi
if [[ -n "${button1action}" ]]; then logComment "Button 1 Action (Parameter 7): ${button1action}" ; fi
if [[ -n "${button2text}" ]]; then logComment "Button 2 Text (Parameter 8):   ${button2text}" ; fi
if [[ -n "${button2action}" ]]; then logComment "Button 2 Action (Parameter 9): ${button2action}" ; fi

${dialogBinary} \
    --notification \
    --title "${title}" \
    --message "${message}" \
    "${button1TextOption}" "${button1text}" \
    "${button1ActionOption}" "${button1action}" \
    "${button2TextOption}" "${button2text}" \
    "${button2ActionOption}" "${button2action}" \
    --commandfile "${dialogNotificationLog}"



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Exit
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

quitScript "0"
E. Create a test Jamf Pro policy to display the notification

For this test, we prompted the user to login to the Workforce App Store (a.k.a. Self Service) and manually run the update computer inventory policy.

  1. In Jamf Pro, navigate to the Update Computer Inventory policy which you’ve previously made available in Self Service and note its ID number
  2. Create a new Jamf Pro policy, using the following as a guide for Options > General:
    • Set Display Name to swfitDialog Notification Test (0.0.4)
    • Set Execution Frequency to Ongoing
  3. Select the Scripts payload and add the swiftDialog Notifications script, specifying the following Parameter Values (and replacing NNN with the ID from Step No. 1.
    • Title: Update Inventory
    • Message: Please login to Self Service and update your computer inventory
    • Button 1 Text: Update
    • Button 1 Action: jamfselfservice://content?entity=policy&id=NNN&action=view
    • Button 2 Text: Close
  1. Adjust Scope to your liking; we limited testing policies to our opt-in Beta Testers
  2. Use the following for Self Service
    • Self Service Display Name: swfitDialog Notification Test (0.0.4)
    • Button Name Before Initiation: Test
    • Button Name After Initiation: Test
    • Description: Tests macOS Notifications with swiftDialog
  3. Use the following for User Interaction
    • Start Message: Jamf Pro built-in Notification
  4. Click Save
  5. Launch Self Service and test the swfitDialog Notification Test (0.0.4) policy

Test modifying Button 1 Action to execute the Self Service policy by replacing view with execute.

Button 1 Action: jamfselfservice://content?entity=policy&id=NNN&action=execute

Rule of Two

To display any options in a Notification, you must follow the Rule of Two and ensure you populate both Button 1 Text and (at least) Button 2 Text as illustrated above and below:

The Notification “Options” menu will NOT display when only a single Button is specified
dialog \
    --notification \
    --title "Single Option?" \
    --subtitle "Single choice does NOT display" \
    --message "(The entire Notification is a button.)" \
    --button1text "This will NOT display in macOS" \
    --button1action "https://developer.apple.com"
Yoda says: “Obey the ‘Rule of Two,’ you must … hmmm?”
dialog \
    --notification \
    --title "swiftDialog Wiki" \
    --message "Have you checked Bart's Wiki?" \
    --button1text "No" \
    --button1action "https://github.com/swiftDialog/swiftDialog/wiki" \
    --button2text "Yes" \
    --button2action "https://snelson.us/?s=swiftdialog"
Posted in Jamf Pro, macOS, Scripts, swiftDialog, Tips & Tricks

Related Posts