Leverage swiftDialog to make user-friendly scripts
Introduction
Prior to Bart Reardon releasing swiftDialog, we used Jamf Helper for our modest needs and Carsten Blüm’s Pashua when we had more complex requirements.
(If you’re a Jamf customer and unfamiliar with Jamf Helper, launch Terminal and execute the following command on an enrolled Mac:)
/Library/Application\ Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -help
As we’ve now migrated most of our user-interactive scripts to swiftDialog, I thought it might prove helpful to others if I documented the process I follow to leverage swiftDialog to make user-friendly scripts.
Previous Projects
Here are few examples from previous projects.
- The Disk Usage with swiftDialog script end up being more than 700 lines of code for essentially the following two commands:
du -I System -axrg / 2>/dev/null | sort -nr | head -n 50 >> "$diskUsageEntireVolumeTop50" du -axrg "$loggedInUserHome" 2>/dev/null | sort -nr | head -n 50 >> "$diskUsageUsersHomeTop50"
- The User-friendly sysdiagnose script required nearly 600 lines for the following command:
echo -ne '\n' | sysdiagnose -u -A sysdiagnose_"${serialNumber}"_"${timestamp}" -f "$sysdiagnoseProgressDirectory" -V / | cat > "$sysdiagnoseExecutionLog" &
- Inventory Update Progress with swiftDialog was more than 330 lines for a simple
jamf recon
/usr/local/bin/jamf recon -endUsername "${loggedInUser}" --verbose >> "$inventoryLog" &
Obviously, writing the so-called “user-friendly” part of scripts adds a significant amount of code.
Current Project
While we’ve migrated away from Jamf Pro policies which are triggered by Enrollment Complete, we thought our new users may benefit from being greeted to their new Macs immediately after the built-in macOS Setup Assistant completes and before they start actually setting up their Mac (and if the policy executes, great; if it fails, it’s not the end-of-the-world.)
Here’s a quick-and-dirty personalized greeting using osascript
, which has been built-in since in Mac OS X 10.0. However, we’d like to display a screencast of what our new users can expected when setting up their Macs.
loggedInUser=$( echo "show State:/Users/ConsoleUser" | scutil | awk '/Name :/ { print $3 }' ) loggedInUserFullname=$( id -F "${loggedInUser}" ) loggedInUserFirstname=$( echo "$loggedInUserFullname" | cut -d " " -f 1 ) osascript -e 'display dialog "Greetings, '"${loggedInUserFirstname}"'!" with title "Setup Your Mac" buttons {"Close"} with icon note'
A. Setup
We’ve been quite happy with Visual Studio Code since making the transition from Atom as our preferred editor and James Smith has an excellent presentation on Visual Studio Code for Mac Admins.
I’m still writing in Bash — despite Armin Briegel’s repeated warnings — and I purposely end my scripts in .bash
to help me remember I need to transition them to “zsh, sh, or bash v5.”
Here are the first 15 lines of our script:
#!/bin/bash #################################################################################################### # # Introduction to Setup Your Mac via swiftDialog # https://snelson.us/2022/12/swiftdialog-izing-your-scripts/ # #################################################################################################### # # HISTORY # # Version 0.0.1, 03-Dec-2022, Dan K. Snelson (@dan-snelson) # - Greet the user immediately after Setup Assistant completes and before they start actually setting up their Mac # ####################################################################################################
B. Variables
While the finished script only needs to display the user’s first name and the day of the week, we’ll want to send a quit
command to the running dialog as the script exits (and we might want to update something), so we’ll include a dialogCommandFile
variable.
(See Armin’s Setting the PATH in Scripts to better understand the export PATH
command. )
#################################################################################################### # # Variables # #################################################################################################### scriptVersion="0.0.1" export PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin/ scriptLog="${4:-"/var/tmp/org.churchofjesuschrist.log"}" dialogBinary="/usr/local/bin/dialog" dialogCommandFile=$( mktemp /var/tmp/dialogCommandFile.XXX ) loggedInUser=$( echo "show State:/Users/ConsoleUser" | scutil | awk '/Name :/ { print $3 }' ) dayOfTheWeek=$( date +'%A' )
(I learned from Alex White’s Type in Use that for one element to contrast with another, it needs to be three times the size of the other, which is why I try to always have three blank lines between code blocks.)
C. Pre-flight Checks
Previously, this code was some of the first placed under the Program heading.
However, I think it’s more clear to have a dedicated section for Pre-flight Checks as failures here will result in the script exiting, and code in the Program section will never be executed.
#################################################################################################### # # Pre-flight Checks # #################################################################################################### # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Confirm script is running as root # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # if [[ $(id -u) -ne 0 ]]; then echo "This script must be run as root; exiting." exit 1 fi # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Validate Setup Assistant has completed # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # while pgrep -q -x "Setup Assistant" ; do echo "Setup Assistant is running; pausing for 10 seconds" sleep 10 done echo "Setup Assistant is no longer running; proceeding …" # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Confirm Dock is running / user is at Desktop # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # while ! pgrep -q -x "Dock" ; do echo "Dock is NOT running; pausing for 10 seconds" sleep 10 done echo "Dock is running; proceeding …" # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Validate logged-in user # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # if [[ -z "${loggedInUser}" || "${loggedInUser}" == "loginwindow" ]]; then echo "No user logged-in; exiting." quitScript "1" else loggedInUserFullname=$( id -F "${loggedInUser}" ) loggedInUserFirstname=$( echo "$loggedInUserFullname" | cut -d " " -f 1 ) loggedInUserID=$(id -u "${loggedInUser}") fi
D. Dialog
The following lines control both the functionality and the look-and-feel of the “Introduction” dialog and leverage swiftDialog v2.0’s ability to play videos.
#################################################################################################### # # Introduction dialog # #################################################################################################### # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # "Introduction" dialog Title, Message, Overlay Icon and Icon # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # title="Happy ${dayOfTheWeek}, ${loggedInUserFirstname}!" caption="Please review this video while we get things ready." videoID="vimeoid=774283815" # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # "Introduction" dialog Settings and Features # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # dialogIntroduction="--title \"$title\" \ --video \"$videoID\" \ --videocaption \"$caption\" \ --infotext \"$scriptVersion\" \ --button1text \"Close\" \ --autoplay \ --moveable \ --ontop \ --width '800' \ --height '600' \ --commandfile \"$dialogCommandFile\" "
E. Functions
Nearly every script which leverages swiftDialog includes the following functions, which I purposely preface with the non-required word function
to help me differentiate between where the function is defined and when its called.
#################################################################################################### # # Functions # #################################################################################################### # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Client-side Script Logging # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # function updateScriptLog() { echo -e "$( date +%Y-%m-%d\ %H:%M:%S ) - ${1}" | tee -a "${scriptLog}" } # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Client-side Logging # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # if [[ ! -f "${scriptLog}" ]]; then touch "${scriptLog}" updateScriptLog "*** Created log file via script ***" fi # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Logging preamble # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # updateScriptLog "\n\n###\n# Introduction to Setup Your Mac (${scriptVersion})\n###\n" # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Check for / install swiftDialog (Thanks big bunches, @acodega!) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # function dialogCheck() { # Get the URL of the latest PKG From the Dialog GitHub repo dialogURL=$(curl --silent --fail "https://api.github.com/repos/bartreardon/swiftDialog/releases/latest" | awk -F '"' "/browser_download_url/ && /pkg\"/ { print \$4; exit }") # Expected Team ID of the downloaded PKG expectedDialogTeamID="PWA5E9TQ59" # Check for Dialog and install if not found if [ ! -e "/Library/Application Support/Dialog/Dialog.app" ]; then updateScriptLog "Dialog not found. Installing..." # Create temporary working directory workDirectory=$( /usr/bin/basename "$0" ) tempDirectory=$( /usr/bin/mktemp -d "/private/tmp/$workDirectory.XXXXXX" ) # Download the installer package /usr/bin/curl --location --silent "$dialogURL" -o "$tempDirectory/Dialog.pkg" # Verify the download teamID=$(/usr/sbin/spctl -a -vv -t install "$tempDirectory/Dialog.pkg" 2>&1 | awk '/origin=/ {print $NF }' | tr -d '()') # Install the package if Team ID validates if [[ "$expectedDialogTeamID" == "$teamID" ]]; then /usr/sbin/installer -pkg "$tempDirectory/Dialog.pkg" -target / sleep 2 updateScriptLog "swiftDialog version $(dialog --version) installed; proceeding..." else # Display a so-called "simple" dialog if Team ID fails to validate runAsUser osascript -e 'display dialog "Please advise your Support Representative of the following error:\r\r• Dialog Team ID verification failed\r\r" with title "Setup Your Mac: Error" buttons {"Close"} with icon caution' quitScript "1" fi # Remove the temporary working directory when done /bin/rm -Rf "$tempDirectory" else updateScriptLog "swiftDialog version $(dialog --version) found; proceeding..." fi } # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Update the "Introduction" dialog # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # function dialogUpdateIntroduction(){ updateScriptLog "INTRODUCTION DIALOG: $1" echo "$1" >> "$dialogCommandFile" sleep 0.35 } # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Kill a specified process (thanks, @grahampugh!) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # function killProcess() { process="$1" if process_pid=$( pgrep -a "${process}" 2>/dev/null ) ; then updateScriptLog "Attempting to terminate the '$process' process …" updateScriptLog "(Termination message indicates success.)" kill "$process_pid" 2> /dev/null if pgrep -a "$process" >/dev/null ; then updateScriptLog "ERROR: '$process' could not be terminated." fi else updateScriptLog "The '$process' process isn't running." fi } # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Quit Script (thanks, @bartreadon!) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # function quitScript() { updateScriptLog "Quitting …" dialogUpdateIntroduction "quit:" # Stop `caffeinate` process updateScriptLog "De-caffeinate …" killProcess "caffeinate" # Remove dialogCommandFile if [[ -e ${dialogCommandFile} ]]; then updateScriptLog "Removing ${dialogCommandFile} …" rm "${dialogCommandFile}" fi exit "${1}" }
F. Program
With all of the so-called “heavy lifting” handled by the functions
, the actual Program section is fairly sparse.
#################################################################################################### # # Program # #################################################################################################### # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Ensure computer does not go to sleep while running this script (thanks, @grahampugh!) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # updateScriptLog "Caffeinating this script (PID: $$)" caffeinate -dimsu -w $$ & # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Validate swiftDialog is installed # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # dialogCheck # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Display Introduction dialog # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # eval "${dialogBinary} --args ${dialogIntroduction}" su - "${loggedInUser}" -c "open -a ${dialogBinary} --args ${dialogIntroduction} & sleep 0.35" # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Exit # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # quitScript "0"
Note: Issue No. 203 was discovered while writing this post and the normal eval
approach (shown on Line No. 301) is not currently working as expected.
G. Complete Script
Here’s the complete script, which is suitable to add to a Jamf Pro policy with an Enrollment Complete trigger, simply change the videoID
variable before deploying.
(Latest version is available on GitHub.)
#!/bin/bash #################################################################################################### # # Introduction to Setup Your Mac via swiftDialog # https://snelson.us/2022/12/swiftdialog-izing-your-scripts/ # #################################################################################################### # # HISTORY # # Version 0.0.1, 03-Dec-2022, Dan K. Snelson (@dan-snelson) # - Greet the user immediately after Setup Assistant completes and before they start actually setting up their Mac # #################################################################################################### #################################################################################################### # # Variables # #################################################################################################### scriptVersion="0.0.1" export PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin/ scriptLog="${4:-"/var/tmp/org.churchofjesuschrist.log"}" dialogBinary="/usr/local/bin/dialog" dialogCommandFile=$( mktemp /var/tmp/dialogCommandFile.XXX ) loggedInUser=$( echo "show State:/Users/ConsoleUser" | scutil | awk '/Name :/ { print $3 }' ) dayOfTheWeek=$( date +'%A' ) #################################################################################################### # # Pre-flight Checks # #################################################################################################### # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Confirm script is running as root # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # if [[ $(id -u) -ne 0 ]]; then echo "This script must be run as root; exiting." exit 1 fi # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Validate Setup Assistant has completed # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # while pgrep -q -x "Setup Assistant" ; do echo "Setup Assistant is running; pausing for 10 seconds" sleep 10 done echo "Setup Assistant is no longer running; proceeding …" # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Confirm Dock is running / user is at Desktop # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # while ! pgrep -q -x "Dock" ; do echo "Dock is NOT running; pausing for 10 seconds" sleep 10 done echo "Dock is running; proceeding …" # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Validate logged-in user # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # if [[ -z "${loggedInUser}" || "${loggedInUser}" == "loginwindow" ]]; then echo "No user logged-in; exiting." quitScript "1" else loggedInUserFullname=$( id -F "${loggedInUser}" ) loggedInUserFirstname=$( echo "$loggedInUserFullname" | cut -d " " -f 1 ) loggedInUserID=$(id -u "${loggedInUser}") fi #################################################################################################### # # Introduction dialog # #################################################################################################### # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # "Introduction" dialog Title, Message, Overlay Icon and Icon # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # title="Happy ${dayOfTheWeek}, ${loggedInUserFirstname}!" caption="Please review this video while we get things ready." videoID="vimeoid=774283815" # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # "Introduction" dialog Settings and Features # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # dialogIntroduction="--title \"$title\" \ --video \"$videoID\" \ --videocaption \"$caption\" \ --infotext \"$scriptVersion\" \ --button1text \"Close\" \ --autoplay \ --moveable \ --ontop \ --width '800' \ --height '600' \ --commandfile \"$dialogCommandFile\" " #################################################################################################### # # Functions # #################################################################################################### # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Client-side Script Logging # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # function updateScriptLog() { echo -e "$( date +%Y-%m-%d\ %H:%M:%S ) - ${1}" | tee -a "${scriptLog}" } # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Client-side Logging # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # if [[ ! -f "${scriptLog}" ]]; then touch "${scriptLog}" updateScriptLog "*** Created log file via script ***" fi # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Logging preamble # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # updateScriptLog "\n\n###\n# Introduction to Setup Your Mac (${scriptVersion})\n###\n" # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Check for / install swiftDialog (Thanks big bunches, @acodega!) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # function dialogCheck() { # Get the URL of the latest PKG From the Dialog GitHub repo dialogURL=$(curl --silent --fail "https://api.github.com/repos/bartreardon/swiftDialog/releases/latest" | awk -F '"' "/browser_download_url/ && /pkg\"/ { print \$4; exit }") # Expected Team ID of the downloaded PKG expectedDialogTeamID="PWA5E9TQ59" # Check for Dialog and install if not found if [ ! -e "/Library/Application Support/Dialog/Dialog.app" ]; then updateScriptLog "Dialog not found. Installing..." # Create temporary working directory workDirectory=$( /usr/bin/basename "$0" ) tempDirectory=$( /usr/bin/mktemp -d "/private/tmp/$workDirectory.XXXXXX" ) # Download the installer package /usr/bin/curl --location --silent "$dialogURL" -o "$tempDirectory/Dialog.pkg" # Verify the download teamID=$(/usr/sbin/spctl -a -vv -t install "$tempDirectory/Dialog.pkg" 2>&1 | awk '/origin=/ {print $NF }' | tr -d '()') # Install the package if Team ID validates if [[ "$expectedDialogTeamID" == "$teamID" ]]; then /usr/sbin/installer -pkg "$tempDirectory/Dialog.pkg" -target / sleep 2 updateScriptLog "swiftDialog version $(dialog --version) installed; proceeding..." else # Display a so-called "simple" dialog if Team ID fails to validate runAsUser osascript -e 'display dialog "Please advise your Support Representative of the following error:\r\r• Dialog Team ID verification failed\r\r" with title "Setup Your Mac: Error" buttons {"Close"} with icon caution' quitScript "1" fi # Remove the temporary working directory when done /bin/rm -Rf "$tempDirectory" else updateScriptLog "swiftDialog version $(dialog --version) found; proceeding..." fi } # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Update the "Introduction" dialog # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # function dialogUpdateIntroduction(){ updateScriptLog "INTRODUCTION DIALOG: $1" echo "$1" >> "$dialogCommandFile" sleep 0.35 } # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Kill a specified process (thanks, @grahampugh!) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # function killProcess() { process="$1" if process_pid=$( pgrep -a "${process}" 2>/dev/null ) ; then updateScriptLog "Attempting to terminate the '$process' process …" updateScriptLog "(Termination message indicates success.)" kill "$process_pid" 2> /dev/null if pgrep -a "$process" >/dev/null ; then updateScriptLog "ERROR: '$process' could not be terminated." fi else updateScriptLog "The '$process' process isn't running." fi } # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Quit Script (thanks, @bartreadon!) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # function quitScript() { updateScriptLog "Quitting …" dialogUpdateIntroduction "quit:" # Stop `caffeinate` process updateScriptLog "De-caffeinate …" killProcess "caffeinate" # Remove dialogCommandFile if [[ -e ${dialogCommandFile} ]]; then updateScriptLog "Removing ${dialogCommandFile} …" rm "${dialogCommandFile}" fi exit "${1}" } #################################################################################################### # # Program # #################################################################################################### # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Ensure computer does not go to sleep while running this script (thanks, @grahampugh!) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # updateScriptLog "Caffeinating this script (PID: $$)" caffeinate -dimsu -w $$ & # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Validate swiftDialog is installed # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # dialogCheck # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Display Introduction dialog # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # eval "${dialogBinary} --args ${dialogIntroduction}" su - "${loggedInUser}" -c "open -a ${dialogBinary} --args ${dialogIntroduction} & sleep 0.35" # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Exit # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # quitScript "0"
Bonus: Capturing User Input
While this Introduction-to-Setup-Your-Mac.bash
script didn’t require capturing user input, it’s a common feature of most swiftDialog scripts.
Simple
For a simple example, the Display Message via swiftDialog script sets a variable returncode
equal to $?
which “Expands to the status of the most recently executed foreground pipeline” to capture which button the user clicked. The various options for returncode
are then processed in a case
statement:
0
The user clickedbutton1
2
The user clickedbutton2
3
The user clickedbutton3
(i.e.,infobutton
)4
The user allowed the timer to expire20
The user had Do Not Disturb enabled*
Catch-all processing for every other condition
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Display Message: Dialog # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # updateScriptLog "Title: ${title}" updateScriptLog "Message: ${message}" ${dialogApp} \ ${titleoption} "${title}" \ ${messageoption} "${message}" \ ${iconoption} "${icon}" \ ${button1option} "${button1text}" \ ${button2option} "${button2text}" \ ${infobuttonoption} "${infobuttontext}" \ --infobuttonaction "https://servicenow.company.com/support?id=kb_article_view&sysparm_article=${infobuttontext}" \ --messagefont "size=14" \ --commandfile "$dialogMessageLog}" \ ${extraflags} returncode=$? case ${returncode} in 0) ## Process exit code 0 scenario here echo "${loggedInUser} clicked ${button1text}" updateScriptLog "${loggedInUser} clicked ${button1text};" if [[ -n "${action}" ]]; then su - "${loggedInUser}" -c "open \"${action}\"" fi quitScript "0" ;; 2) ## Process exit code 2 scenario here echo "${loggedInUser} clicked ${button2text}" updateScriptLog "${loggedInUser} clicked ${button2text};" quitScript "0" ;; 3) ## Process exit code 3 scenario here echo "${loggedInUser} clicked ${infobuttontext}" updateScriptLog "${loggedInUser} clicked ${infobuttontext};" ;; 4) ## Process exit code 4 scenario here echo "${loggedInUser} allowed timer to expire" updateScriptLog "${loggedInUser} allowed timer to expire;" ;; 20) ## Process exit code 20 scenario here echo "${loggedInUser} had Do Not Disturb enabled" updateScriptLog "${loggedInUser} had Do Not Disturb enabled" quitScript "0" ;; *) ## Catch all processing echo "Something else happened; Exit code: ${returncode}" updateScriptLog "Something else happened; Exit code: ${returncode};" quitScript "1" ;; esac
Complex
Setup Your Mac (1.5.0) includes a more complex example which leverages and parses JSON (thanks to none other than Bart Reardon):
- The JSON is defined in the
welcomeJSON
variable
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # "Welcome" JSON (thanks, @bartreardon!) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # welcomeJSON='{ "title" : "'"${welcomeTitle}"'", "message" : "'"${welcomeMessage}"'", "icon" : "'"${welcomeIcon}"'", "iconsize" : "198.0", "button1text" : "Continue", "button2text" : "Quit", "infotext" : "'"${scriptVersion}"'", "blurscreen" : "true", "ontop" : "true", "titlefont" : "size=26", "messagefont" : "size=16", "textfield" : [ { "title" : "Comment", "required" : false, "prompt" : "Enter a comment", "editor" : true }, { "title" : "Computer Name", "required" : false, "prompt" : "Computer Name" }, { "title" : "User Name", "required" : false, "prompt" : "User Name" }, { "title" : "Asset Tag", "required" : true, "prompt" : "Please enter the seven-digit Asset Tag", "regex" : "^(AP|IP)?[0-9]{7,}$", "regexerror" : "Please enter (at least) seven digits for the Asset Tag, optionally preceed by either AP or IP." } ], "selectitems" : [ { "title" : "Department", "default" : "Please select your department", "values" : [ "Please select your department", "Asset Management", "Australia Area Office", "Board of Directors", "Business Development", "Corporate Communications", "Creative Services", "Customer Service / Customer Experience", "Engineering", "Finance / Accounting", "General Management", "Human Resources", "Information Technology / Technology", "Investor Relations", "Legal", "Marketing", "Operations", "Product Management", "Production", "Project Management Office", "Purchasing / Sourcing", "Quality Assurance", "Risk Management", "Sales", "Strategic Initiatives & Programs", "Technology" ] }, { "title" : "Select B", "values" : [ "B1", "B2", "B3" ] }, { "title" : "Select C", "values" : [ "C1", "C2", "C3" ] } ], "height" : "635" }'
- The
welcomeJSON
variable is written to disk
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Write Welcome JSON to disk # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # echo "$welcomeJSON" > "$welcomeCommandFile"
- A
get_json_value_welcomeDialog
function parses the JSON
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Parse JSON via osascript and JavaScript for the Welcome dialog (thanks, @bartreardon!) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # function get_json_value_welcomeDialog () { for var in "${@:2}"; do jsonkey="${jsonkey}['${var}']"; done JSON="$1" osascript -l 'JavaScript' \ -e 'const env = $.NSProcessInfo.processInfo.environment.objectForKey("JSON").js' \ -e "JSON.parse(env)$jsonkey" }
- Using command substitution, the user’s input is assigned to the
welcomeResults
variable and additional variables are assigned for each input field via theget_json_value_welcomeDialog
function
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Display Welcome dialog and capture user's input # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # if [[ "${welcomeDialog}" == "true" ]]; then welcomeResults=$( ${dialogApp} --jsonfile "$welcomeCommandFile" --json ) if [[ -z "${welcomeResults}" ]]; then welcomeReturnCode="2" else welcomeReturnCode="0" fi case "${welcomeReturnCode}" in 0) # Process exit code 0 scenario here updateScriptLog "WELCOME DIALOG: ${loggedInUser} entered information and clicked Continue" ### # Extract the various values from the welcomeResults JSON ### comment=$(get_json_value_welcomeDialog "$welcomeResults" "Comment") computerName=$(get_json_value_welcomeDialog "$welcomeResults" "Computer Name") userName=$(get_json_value_welcomeDialog "$welcomeResults" "User Name") assetTag=$(get_json_value_welcomeDialog "$welcomeResults" "Asset Tag") department=$(get_json_value_welcomeDialog "$welcomeResults" "Department" "selectedValue") selectB=$(get_json_value_welcomeDialog "$welcomeResults" "Select B" "selectedValue") selectC=$(get_json_value_welcomeDialog "$welcomeResults" "Select C" "selectedValue") ### # Output the various values from the welcomeResults JSON to the log file ### updateScriptLog "WELCOME DIALOG: • Comment: $comment" updateScriptLog "WELCOME DIALOG: • Computer Name: $computerName" updateScriptLog "WELCOME DIALOG: • User Name: $userName" updateScriptLog "WELCOME DIALOG: • Asset Tag: $assetTag" updateScriptLog "WELCOME DIALOG: • Department: $department" updateScriptLog "WELCOME DIALOG: • Select B: $selectB" updateScriptLog "WELCOME DIALOG: • Select C: $selectC" ### # Evaluate Various User Input ### # Computer Name if [[ -n "${computerName}" ]]; then # UNTESTED, UNSUPPORTED "YOYO" EXAMPLE updateScriptLog "WELCOME DIALOG: Set Computer Name …" currentComputerName=$( scutil --get ComputerName ) currentLocalHostName=$( scutil --get LocalHostName ) # Sets LocalHostName to a maximum of 15 characters, comprised of first eight characters of the computer's # serial number and the last six characters of the client's MAC address firstEightSerialNumber=$( system_profiler SPHardwareDataType | awk '/Serial\ Number\ \(system\)/ {print $NF}' | cut -c 1-8 ) lastSixMAC=$( ifconfig en0 | awk '/ether/ {print $2}' | sed 's/://g' | cut -c 7-12 ) newLocalHostName=${firstEightSerialNumber}-${lastSixMAC} if [[ "${debugMode}" == "true" ]]; then updateScriptLog "WELCOME DIALOG: DEBUG MODE: Renamed computer from: \"${currentComputerName}\" to \"${computerName}\" " updateScriptLog "WELCOME DIALOG: DEBUG MODE: Renamed LocalHostName from: \"${currentLocalHostName}\" to \"${newLocalHostName}\" " else # Set the Computer Name to the user-entered value scutil --set ComputerName "${computerName}" # Set the LocalHostName to `newLocalHostName` scutil --set LocalHostName "${newLocalHostName}" # Delay required to reflect change … # … side-effect is a delay in the "Setup Your Mac" dialog appearing sleep 5 updateScriptLog "WELCOME DIALOG: Renamed computer from: \"${currentComputerName}\" to \"$( scutil --get ComputerName )\" " updateScriptLog "WELCOME DIALOG: Renamed LocalHostName from: \"${currentLocalHostName}\" to \"$( scutil --get LocalHostName )\" " fi else updateScriptLog "WELCOME DIALOG: ${loggedInUser} did NOT specify a new computer name" updateScriptLog "WELCOME DIALOG: • Current Computer Name: \"$( scutil --get ComputerName )\" " updateScriptLog "WELCOME DIALOG: • Current Local Host Name: \"$( scutil --get LocalHostName )\" " fi # User Name if [[ -n "${userName}" ]]; then # UNTESTED, UNSUPPORTED "YOYO" EXAMPLE reconOptions+="-endUsername \"${userName}\" " fi # Asset Tag if [[ -n "${assetTag}" ]]; then reconOptions+="-assetTag \"${assetTag}\" " fi # Department if [[ -n "${department}" ]]; then # UNTESTED, UNSUPPORTED "YOYO" EXAMPLE reconOptions+="-department \"${department}\" " fi # Output `recon` options to log updateScriptLog "WELCOME DIALOG: reconOptions: ${reconOptions}" ### # Display "Setup Your Mac" dialog (and capture Process ID) ### eval "${dialogSetupYourMacCMD[*]}" & sleep 0.3 dialogSetupYourMacProcessID=$! ;; 2) # Process exit code 2 scenario here updateScriptLog "WELCOME DIALOG: ${loggedInUser} clicked Quit at Welcome dialog" completionActionOption="Quit" quitScript "1" ;; 3) # Process exit code 3 scenario here updateScriptLog "WELCOME DIALOG: ${loggedInUser} clicked infobutton" osascript -e "set Volume 3" afplay /System/Library/Sounds/Glass.aiff ;; 4) # Process exit code 4 scenario here updateScriptLog "WELCOME DIALOG: ${loggedInUser} allowed timer to expire" quitScript "1" ;; *) # Catch all processing updateScriptLog "WELCOME DIALOG: Something else happened; Exit code: ${welcomeReturnCode}" quitScript "1" ;; esac else ### # Display "Setup Your Mac" dialog (and capture Process ID) ### eval "${dialogSetupYourMacCMD[*]}" & sleep 0.3 dialogSetupYourMacProcessID=$! fi