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)
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.
Configuration
Complete the following steps to create custom branded macOS Notifications with swiftDialog 2.4.0
.
A. Deploy Notifications Configuration Profile for swiftDialog
- Review Bart’s Notifications documentation
- Update your Notifications Configuration Profile to include the following
- App Name:
swiftDialog 2.4.0
- Bundle ID:
au.csiro.dialog
- App Name:
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).
- Add the
swiftDialog Pre-install
script to your Jamf Pro server - Specify the following for Options > Parameter Labels
- Parameter 4:
Script Log Location
- Parameter 5:
Installation Action [ none (default) | remove ]
- Parameter 4:
- 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
- Modify your
swiftDialog
installation policy, by selecting the Scripts payload and adding the
script, specifying the following Parameter ValueswiftDialog Pre-install
- Script Log Location:
/var/log/com.company.log
- Installation Action:
none
- Script Log Location:
- 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/"
- 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 forscriptLog
— 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.)
- Add the
swiftDialog Notifications
script to your Jamf Pro server - 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
- Parameter 4:
- Click Save
Latest version available on GitHub.
#!/bin/zsh --no-rcs # shellcheck shell=bash # shellcheck disable=SC2317 #################################################################################################### # # ABOUT # # swiftDialog Notifications # # See: https://snelson.us/2024/02/actionable-custom-branded-macos-notifications-with-swiftdialog-2-4-0/ # #################################################################################################### # # 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. # # Version `0.0.5`, 21-Mar-2024, Andrew Spokes (@techtrekkie) # - Added `--no-rcs` to shebang of script. This addresses CVE-2024-27301. https://nvd.nist.gov/vuln/detail/CVE-2024-27301/change-record?changeRecordedOn=03/14/2024T15:15:50.680-0400 # #################################################################################################### #################################################################################################### # # Variables # #################################################################################################### # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Global Variables # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # export PATH=/usr/bin:/bin:/usr/sbin:/sbin # Script Version & Client-side Log scriptVersion="0.0.5" 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.
- In Jamf Pro, navigate to the Update Computer Inventory policy which you’ve previously made available in Self Service and note its
ID
number - 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
- Set Display Name to
- Select the Scripts payload and add the
script, specifying the following Parameter Values (and replacingswiftDialog Notifications
NNN
with theID
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
- Title:
- Adjust Scope to your liking; we limited testing policies to our opt-in Beta Testers
- 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
- Self Service Display Name:
- Use the following for User Interaction
- Start Message:
Jamf Pro built-in Notification
- Start Message:
- Click Save
- 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
withexecute
.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:
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"
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"