Menu Close

“Setup Your Mac, please”

When auto-launching Self Service post-enrollment isn’t enough, continually prompt your users to actually setup their new Macs

Background

While we thought we’d done everything to help ensure our users had a seamless experience in setting up their new Macs, we recently realized we should prompt those users with computers which have successfully enrolled, but still have yet to run our Setup Your Mac post-enrollment policies.

Overview

As a Mac Admin, you’ve worked hard to make it easy for your users to enroll their new company-issued Mac computers and execute critical post-enrollment policies to help ensure device compliance.

However, for those users who didn’t login to Self Service post-enrollment — even though you auto-launched it for them — the Prompt-to-Setup-Your-Mac script will:

  1. Validate the Mac’s current state
  2. Auto-launch Self Service
  3. Prompt users to run the Setup Your Mac policy
  4. Document user behavior in the Jamf Pro Policy log

Walkthrough*

Checks

The Prompt-to-Setup-Your-Mac script performs the following checks:

  1. Device enrollment date and time
  2. Self Service installation
  3. User’s Self Service log
  4. Number of Self Service launches
  5. Number of user’s Self Service logins
  6. Computer’s Jamf log
  7. Number of times the user has started the Setup Your Mac policy
  8. Competition of the Setup Your Mac policy

Configuration

Presuming the script’s core functionality meets your needs, writing code should not be required.

However, you should be comfortable modifying the following before using this approach (i.e., customizing existing settings to meet your needs).

Variables

  • jamfProPolicyName The name of your Setup Your Mac Jamf Pro policy
  • plistPath The full path to your client-side plist (which tracks completion of your Setup Your Mac Jamf Pro policy)
  • plistKey The key used in the above plist

Note: See Property List Writer for information on creating the client-side plist.

promptUser Function

Modify the promptUser function to your liking; a sample is shown at the beginning of this post.

Script

Latest version on GitHub.

#!/bin/bash

#################################################################################
#
# Prompt to Setup Your Mac
#
# Purpose: Prompts users to login to Self Service / run the Setup Your Mac policy
#
# Reference: https://snelson.us/2022/07/setup-your-mac-please/
#
#################################################################################
#
# HISTORY
#
# Version 0.0.1, 22-Jul-2022, Dan K. Snelson (@dan-snelson)
#   Original version
#
# Version 0.0.2, 22-Jul-2022, Dan K. Snelson (@dan-snelson)
#   Added script execution delay (Parameter 4)
#
# Version 0.0.3, 28-Jul-2022, Dan K. Snelson (@dan-snelson)
#   Exit if odd-ball user is logged-in
#   Added "--ignorednd" and "--blurscreen" to Dialog command
#
#################################################################################



#################################################################################
#
# Environmental Checks
#
#################################################################################

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Exit gracefully if odd-ball users are logged in
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

loggedInUser=$( /bin/echo "show State:/Users/ConsoleUser" | /usr/sbin/scutil | /usr/bin/awk '/Name :/ { print $3 }' )

if [[ -z "${loggedInUser}" ]] \
|| [[ ${loggedInUser} == "loginwindow" ]] \
|| [[ ${loggedInUser} == "_mbsetupuser" ]] ; then
  echo "Odd-ball user \"${loggedInUser}\" logged in; exiting."
  exit 0
fi



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

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Global variables
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

scriptVersion="0.0.3"
scriptResult="Prompt to Setup Your Mac (${scriptVersion}); "
jamfProPolicyName="@Setup Your Mac"
plistPath="/Library/Preferences/com.company.plist"
plistKey="Setup Your Mac"
selfServiceAppPath=$( /usr/bin/defaults read /Library/Preferences/com.jamfsoftware.jamf.plist self_service_app_path )
dialogApp="/usr/local/bin/dialog"
dialogCommandFile=$( /usr/bin/mktemp "/var/tmp/Prompt-to-Setup-Your-Mac.XXXXXXX" )



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Check for a specified "secondsToWait" setting (Parameter 4); defaults to "2700"
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

if [[ "${4}" != "" ]] && [[ "${secondsToWait}" == "" ]]; then
  secondsToWait="${4}"
  scriptResult+="Using ${secondsToWait} as the number of seconds before script execution; "
else
  secondsToWait="2700"
  scriptResult+="Parameter 4 is blank; using ${secondsToWait} as the number of seconds before script execution; "
fi



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

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Prompt user to execute the Self Service policy via swiftDialog
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function promptUser() {

    scriptResult+="Prompting user to execute the \"${jamfProPolicyName}\" policy; "

    dialogCheck

    title="Welcome to your new Mac!"
    message="Please complete the following steps to apply Church settings to your new Mac:  \n1. Login to the **Workforce App Store**  \n2. Locate the **Setup Your Mac** policy  \n3. Click **Setup**  \n\nIf you need assistance, please contact the GSD:  \n+1 (801) 555-1212 and mention **KB12345678**."

    appleInterfaceStyle=$( /usr/bin/defaults read /Users/"${loggedInUser}"/Library/Preferences/.GlobalPreferences.plist AppleInterfaceStyle 2>&1 )

    if [[ "${appleInterfaceStyle}" == "Dark" ]]; then
        icon="/System/Library/CoreServices/Finder.app"
    else
        icon="/System/Library/CoreServices/Finder.app"
    fi

    overlayicon=$( defaults read /Library/Preferences/com.jamfsoftware.jamf.plist self_service_app_path )

    dialogCMD="$dialogApp --ontop --title \"$title\" \
    --message \"$message\" \
    --icon \"$icon\" \
    --button1text \"OK\" \
    --overlayicon \"$overlayicon\" \
    --titlefont 'size=28' \
    --messagefont 'size=14' \
    --infobuttontext 'KB12345678' \
    --infobuttonaction 'https://servicenow.company.com/support?id=kb_article_view&sysparm_article=KB12345678' \
    --small \
    --moveable \
    --timer 120 \
    --position 'centre' \
    --ignorednd \
    --blurscreen \
    --commandfile \"$dialogCommandFile\" "

    eval "$dialogCMD"

    returnCode=$?

    case ${returnCode} in

        0)  scriptResult+="${loggedInUser} clicked OK; "
            ;;
        2)  scriptResult+="${loggedInUser} clicked Button2; "
            ;;
        3)  scriptResult+="${loggedInUser} clicked KB12345678; "
            ;;
        4)  scriptResult+="${loggedInUser} allowed timer to expire; "
            ;;
        *)  scriptResult+="Something else happened; swiftDialog Return Code: ${returnCode}; "
            ;;

    esac

    /bin/rm -f "$dialogCommandFile"

}



#--------------------- Edits below this line are optional ---------------------#



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Check for / install swiftDialog (thanks, Adam!)
# https://github.com/acodega/dialog-scripts/blob/main/dialogCheckFunction.sh
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

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
    echo "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" ] || [ "$expectedDialogTeamID" = "" ]; then
      /usr/sbin/installer -pkg "$tempDirectory/Dialog.pkg" -target /
    else
      jamfDisplayMessage "Dialog Team ID verification failed."
      exit 1
    fi
    # Remove the temporary working directory when done
    /bin/rm -Rf "$tempDirectory"  
  else
    scriptResult+="Dialog v$(dialog --version) installed, proceeding; "
  fi
}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Display Message via the jamf binary
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function jamfDisplayMessage() {
    scriptResult+="${1}; "
	/usr/local/jamf/bin/jamf displayMessage -message "${1}" &
}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Gracefully exit for new enrollments
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function gracefullyExitForNewEnrollments() {

    testFile="/var/db/.AppleSetupDone"
    testFileSeconds=$( /bin/date -j -f "%s" "$(/usr/bin/stat -f "%m" $testFile)" +"%s" )
    nowSeconds=$( /bin/date +"%s" )
    ageInSeconds=$((nowSeconds-testFileSeconds))
    secondsToWaitHumanReadable=$( printf '"%dd, %dh, %dm, %ds"\n' $((secondsToWait/86400)) $((secondsToWait%86400/3600)) $((secondsToWait%3600/60)) $((secondsToWait%60)) )
    ageInSecondsHumanReadable=$( printf '"%dd, %dh, %dm, %ds"\n' $((ageInSeconds/86400)) $((ageInSeconds%86400/3600)) $((ageInSeconds%3600/60)) $((ageInSeconds%60)) )

    if [[ ${ageInSeconds} -le ${secondsToWait} ]]; then
        scriptResult+="Set to wait ${secondsToWaitHumanReadable} and enrollment was ${ageInSecondsHumanReadable} ago; exiting."
        echo "${scriptResult}"
        exit 0
    else
        scriptResult+="Set to wait ${secondsToWaitHumanReadable} and enrollment was ${ageInSecondsHumanReadable} ago, proceeding; "
    fi

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Validate Self Service installation
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function selfServiceValidation() {

    if [[ -d "${selfServiceAppPath}" ]]; then
        selfServiceInstalled="Yes"
    else
        selfServiceInstalled="No"
    fi

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Validate Logged-in User's Self Service Log
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function selfServiceLogValidation() {

    if [[ -f "/Users/${loggedInUser}/Library/Logs/JAMF/selfservice.log" ]]; then
        selfServiceLogInstalled="Yes"
    else
        selfServiceLogInstalled="No"
    fi

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Validate the number of times Self Service has been launched
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function selfServiceLaunchValidation() {

    if [[ ${selfServiceLogInstalled} == "Yes" ]]; then
        selfServiceLaunches=$( /usr/bin/grep "Application successfully launched" /Users/"${loggedInUser}"/Library/Logs/JAMF/selfservice.log | /usr/bin/wc -l | /usr/bin/tr -d ' ' )
    else
        scriptResult+="Something went sideways when checking the number of times \"${selfServiceAppPath}\" has been launched; exiting."
        echo "${scriptResult}"
        exit 1
    fi

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Validate the number of times the user has logged into Self Service
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function selfServiceLoginValidation() {

    if [[ ${selfServiceLogInstalled} == "Yes" ]]; then
        selfServiceLogins=$( /usr/bin/grep "user logged in" /Users/"${loggedInUser}"/Library/Logs/JAMF/selfservice.log | /usr/bin/wc -l | /usr/bin/tr -d ' ' )
    else
        scriptResult+="Something went sideways when checking the number of times ${loggedInUser} has logged in to \"${selfServiceAppPath}\"; exiting."
        echo "${scriptResult}"
        exit 1
    fi

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Validate Jamf's log
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function jamfLogValidation() {

    if [[ -f "/private/var/log/jamf.log" ]]; then
        jamfLogInstalled="Yes"
    else
        jamfLogInstalled="No"
    fi

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Validate Self Service Policy execution
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function selfServicePolicyValidation() {

    if [[ ${jamfLogInstalled} == "Yes" ]]; then
        selfServicePolicyExecutions=$( /usr/bin/grep "${jamfProPolicyName}" /private/var/log/jamf.log | /usr/bin/wc -l | /usr/bin/tr -d ' ' )
    else
        scriptResult+="Something went sideways when checking the number of times ${loggedInUser} has executed the \"${jamfProPolicyName}\" policy; exiting."
        echo "${scriptResult}"
        exit 1
    fi

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Validate Self Service Policy completion
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function selfServicePolicyCompletionValidation() {

    plistValue=$( /usr/bin/defaults read "${plistPath}" "${plistKey}" 2>&1 )

    case "${plistValue}" in

        *"does not exist"   )   selfServicePolicyCompletion="No"    ;;
        *                   )   selfServicePolicyCompletion="Yes"   ;;

    esac

}



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

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Check 1. Gracefully exit for new enrollments
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

gracefullyExitForNewEnrollments



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Check 2. Validate Self Service installation
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

selfServiceValidation

if [[ ${selfServiceInstalled} == "No" ]]; then

    scriptResult+="\"${selfServiceAppPath}\" was NOT installed; attempting re-installation; "
    /usr/local/bin/jamf update -verbose

    scriptResult+="Updating inventory; "
    /usr/local/bin/jamf recon

    scriptResult+="Exiting with error."
    echo "${scriptResult}"
    exit 1

else

    scriptResult+="\"${selfServiceAppPath}\" is installed, proceeding; "

fi



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Check 3. Validate Logged-in User's Self Service Log
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

selfServiceLogValidation

if [[ ${selfServiceLogInstalled} == "No" ]]; then

    scriptResult+="${loggedInUser}'s selfservice.log NOT found, launching \"${selfServiceAppPath}\"; "
    /usr/bin/su "${loggedInUser}" -c "/usr/bin/open \"${selfServiceAppPath}\""

    promptUser

    echo "${scriptResult}"
    exit 1

else

    scriptResult+="${loggedInUser}'s selfservice.log found, proceeding; "

fi



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Check 4. Validate the number of times Self Service has been launched
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

selfServiceLaunchValidation

scriptResult+="\"${selfServiceAppPath}\" has been launched for ${loggedInUser} ${selfServiceLaunches} times; "



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Check 5. Validate the number of times the user has logged into Self Service
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

selfServiceLoginValidation

if [[ ${selfServiceLogins} -le "0" ]]; then

    scriptResult+="${loggedInUser} has logged in to \"${selfServiceAppPath}\" ${selfServiceLogins} times; "
    /usr/bin/su "${loggedInUser}" -c "/usr/bin/open \"${selfServiceAppPath}\""

    promptUser

    echo "${scriptResult}"
    exit 0

else

    scriptResult+="${loggedInUser} has logged in to \"${selfServiceAppPath}\" ${selfServiceLogins} times; "

fi



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Check 6. Validate Jamf's log
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

jamfLogValidation

if [[ ${jamfLogInstalled} == "No" ]]; then

    scriptResult+="jamf.log NOT found, attempting to re-manage; "
    /usr/local/bin/jamf manage -verbose

    scriptResult+="Updating inventory; "
    /usr/local/bin/jamf recon

    scriptResult+="Exiting with error."
    echo "${scriptResult}"
    exit 1

else

    scriptResult+="jamf.log found, proceeding; "

fi



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Check 7. Validate Self Service Policy execution
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

selfServicePolicyValidation

if [[ ${selfServicePolicyExecutions} -le "0" ]]; then

    scriptResult+="${loggedInUser} has started the \"${jamfProPolicyName}\" policy ${selfServicePolicyExecutions} times; "
    /usr/bin/su "${loggedInUser}" -c "/usr/bin/open \"${selfServiceAppPath}\""

    promptUser

    echo "${scriptResult}"
    exit 0

else

     scriptResult+="${loggedInUser} has started the \"${jamfProPolicyName}\" policy ${selfServicePolicyExecutions} times; "

fi



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Check 8. Validate Self Service Policy completion
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

selfServicePolicyCompletionValidation

if [[ ${selfServicePolicyCompletion} == "No" ]]; then

    scriptResult+="The \"${jamfProPolicyName}\" policy has NOT completed; launching \"${selfServiceAppPath}\"; "
    /usr/bin/su "${loggedInUser}" -c "/usr/bin/open \"${selfServiceAppPath}\""

    promptUser

    echo "${scriptResult}"
    exit 0

else

    scriptResult+="Success! The \"${jamfProPolicyName}\" policy has completed; "

    scriptResult+="Updating inventory; "
    /usr/local/bin/jamf recon
    
    scriptResult+="Goodbye!"

    echo "${scriptResult}"
    exit 0

fi



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

scriptResult+="Catch-all exit; thank you!"

echo "${scriptResult}"

exit 0

Options

  • Parameter 4: Seconds to Wait Post-enrollment (blank defaults to 45 minutes)

Output

“Failure”

As a means of alerting a Jamf Pro Administrator, the script is designed to error-out — exit 1 — under the following conditions:

  • Self Service is not installed
  • Self Service is not auto-launching post-enrollment (i.e., the user’s selfservice.log is not found)
  • The computer’s jamf.log is not found (which means you’ve got bigger issues)
Script result: Prompt to Setup Your Mac (0.0.2); Using 2099 as the number of seconds before script execution; Set to wait "0d, 0h, 34m, 59s" and enrollment was "1d, 21h, 28m, 34s" ago, proceeding; "/Applications/Workforce App Store.app" is installed, proceeding; dan's selfservice.log NOT found, launching "/Applications/Workforce App Store.app"; Prompting user to execute the "@Setup Your Mac" policy; Dialog v1.11.1.2839 installed, proceeding; dan allowed timer to expire;

“Success”

While the true measure of success is for the user to actually complete the Setup Your Mac policy, the script will exit 0 if the tests listed in the “Failure” section above pass as expected (even if users have yet to actually setup their Macs).

Script result: Prompt to Setup Your Mac (0.0.2); Using 2099 as the number of seconds before script execution; Set to wait "0d, 0h, 34m, 59s" and enrollment was "1d, 9h, 11m, 56s" ago, proceeding; "/Applications/Workforce App Store.app" is installed, proceeding; dan's selfservice.log found, proceeding; "/Applications/Workforce App Store.app" has been launched for dan 34 times; dan has logged in to "/Applications/Workforce App Store.app" 6 times; jamf.log found, proceeeding; dan has started the "@Setup Your Mac" policy 3 times; The "@Setup Your Mac" policy has NOT completed; launching "/Applications/Workforce App Store.app"; Prompting user to execute the "@Setup Your Mac" policy; Dialog v1.11.1.2839 installed, proceeding; dan allowed timer to expire;

*Reverse navigation bug reported to Stonly technical support

Posted in Jamf Pro, Scripts, Tips & Tricks

Related Posts