Menu Close

Breadcrumbs

Good, better and best methods to leave and retrieve client-side breadcrumbs with Jamf Pro

Introduction

As a Jamf Pro administrator managing Apple computers running macOS, you frequently need to leave a client-side record when a policy was executed, which is sometimes referred to as a “breadcrumb”; you also need an easy way to retrieve this data.

Terminal Example

The following example Terminal command will:

  1. Create a text file on your Desktop: ~/Desktop
  2. Containing your user name: id -n -u
  3. With the date when the command was executed: date
echo `date` >> ~/Desktop/`id -n -u`_was_here.txt

Copy-pasta the above command into a Terminal window and press Return.

You can press the Up Arrow key once to display the last command again, then press Return.

Repeat this a few more times, then run the following command to read the evidence that You. Were. Here.

ls ~/Desktop/`id -n -u`_was_here.txt && cat ~/Desktop/`id -n -u`_was_here.txt

Options

In the author’s highly biased opinion, here are the good, better and best options for writing and reading client-side breadcrumbs.

Leave
Good

Execute Command

If you have modest needs, Jamf Pro’s built-in Files and Processes > Execute Command feature is a great option.

Policy

In either a new or existing policy, add the Files and Processes payload and specify the following in the Execute Command field, taking note of the included description:

Command to execute on computers. This command is executed as the root user

/bin/mkdir -pv /Library/Management/org.churchofjesuschrist && /usr/bin/touch /Library/Management/org.churchofjesuschrist/.breadcrumb

The above example will:

  1. Create an organizational-specific directory, nested inside the “Management” directory, which is nested inside the /Library directory:
    /bin/mkdir -pv /Library/Management/org.churchofjesuschrist
  2. If — and only if — the first command succeeds:
    &&
  3. Create the .breadcrumb file inside the organizational-specific directory:
    /usr/bin/touch /Library/Management/org.churchofjesuschrist/.breadcrumb

While you can’t create files beginning with a period (.) in the Finder …

Files beginning with periods (.) are hidden by default in the Finder

… doing so via the above command hides the file from most users, as shown in the output of:
ls -lah /Library/Management/org.churchofjesuschrist

# ls -lah /Library/Management/org.churchofjesuschrist
total 0
drwxr-xr-x  3 root  wheel    96B Dec  2 05:02 .
drwxr-xr-x  3 root  wheel    96B Dec  2 05:02 ..
-rw-r--r--  1 root  wheel     0B Dec  2 05:02 .breadcrumb
Better

Temporary File Create (0.0.1)

Leveraging a dedicated script to create temporary files provides both predictability and speed.

Script

After adding the following Temporary File Create script to your Jamf Pro server, adjust the path variable for your environment …

#!/bin/bash
################################################################################
#
# ABOUT
#
# Create a static file client-side which a JSS Extension Attribute will use
# to determine Smart Group membership
#
################################################################################
#
# HISTORY
#
# Version 0.0.1, 24-Aug-2015, Dan K. Snelson
#   Original version
#
################################################################################

# Variables
path="/Library/Management/org.churchofjesuschrist/" # Hard-coded path
file="$4" # Unique filename (i.e., ".issueNewRecoveryKey")

# Validate a value has been specified for Parameter 4
if [ -n "${file}" ]; then

    # Create the directory
    /bin/mkdir -pv "${path}"

	# Create the file at the path specified
	/usr/bin/touch "${path}${file}"

	# Set the permission on the file
	/usr/sbin/chown root:wheel "${path}${file}"
	/usr/sbin/chown 644 "${path}${file}"

	echo "Created ${file}"

else
	
	echo "Error: Parameter 4 not populated; exiting."
	exit 1
	
fi



exit 0

… and specify Unique filename as the value for Parameter 4.

Specify Unique filename as the description for Parameter 4
Specify Unique filename as the description for Parameter 4

Policy

In either a new or existing policy, add the Scripts payload, select the Temporary File Create script and specify the desired filename.

  • Unique filename: .issueNewRecoveryKey

Pro Tip: Include a period (.) as the first character to the filename to hide it from most users in the Finder

Include a period (.) as the first character to the filename to hide it from most users in the Finder
Files beginning with periods (.) are hidden by default in the Finder
# ls -lah /Library/Management/org.churchofjesuschrist/
total 0
drwxr-xr-x  3 root  wheel    96B Dec  2 08:20 .
drwxr-xr-x  3 root  wheel    96B Dec  2 08:20 ..
-rw-r--r--  1 644   wheel     0B Dec  2 08:20 .issueNewRecoveryKey
Bonus

Temporary File Delete (0.0.1)

The following, undocumented script will remove the file created by its companion script (referenced above).

#!/bin/bash
################################################################################
#
# ABOUT
#
# Remove a static file client-side which a JSS Extension Attribute will use
# to determine Smart Group membership
#
################################################################################
#
# HISTORY
#
# Version 0.0.1, 24-Aug-2015, Dan K. Snelson
#   Original version
#
################################################################################

# Variables
path="/Library/Management/org.churchofjesuschrist/" # Hard-coded path
file="$4" # Unique filename (i.e., ".issueNewRecoveryKey")

# Validate a value has been specified for Parameter 4
if [ -n "${file}" ]; then

	# Remove the file at the path specified
	/bin/rm -fv "${path}${file}"

else
	
	echo "Error: Parameter 4 not populated; exiting."
	exit 1
	
fi



exit 0
Best

Property List Writer (0.0.3)

Script

Leverages Jamf Pro Script Parameters to write a given string to the specified key in a hard-coded filepath.

  1. Add the Property List Writer script to your Jamf Pro server
  2. Adjust the value of reverseDomainNameNotation for your environment
  3. Specify the following for Options > Parameter Labels
    • Parameter 4: Key (i.e., Name of the "key" for which the value will be set)
    • Parameter 5: Value (i.e., The value to which "key" will be set)
  4. Click Save
#!/bin/bash
####################################################################################################
#
# Property List Writer
#
# Leverages Jamf Pro Script Parameters to write a given string to the specified key
# in a hard-coded filepath.
#
# Reference: https://support.apple.com/guide/terminal/edit-property-lists-apda49a1bb2-577e-4721-8f25-ffc0836f6997/mac
#
####################################################################################################
#
# HISTORY
#
# Version 0.0.1, 19-Mar-2021, Dan K. Snelson (@dan-snelson)
#   Original version
#
# Version 0.0.2, 25-Mar-2022, Dan K. Snelson (@dan-snelson)
#   Remove dependency on client-side functions
#
# Version 0.0.3, 06-May-2022, Dan K. Snelson (@dan-snelson)
#   Added timestamp
#
####################################################################################################



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

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Script Version and Jamf Pro Script Parameters
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

scriptVersion="0.0.3"
export PATH=/usr/bin:/bin:/usr/sbin:/sbin
reverseDomainNameNotation="org.churchofjesuschrist"
filepath="/Library/Preferences/${reverseDomainNameNotation}.plist"
scriptLog="/var/log/${reverseDomainNameNotation}.log"
timestamp=$( date '+%Y-%m-%d-%H%M%S' )
key="${4}"      # Name of the "key" for which the value will be set
value="${5}"    # The value to which "key" will be set



####################################################################################################
#
# 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# Property List Writer (${scriptVersion})\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
else
    updateScriptLog "PRE-FLIGHT CHECK: Running as 'root'; proceeding …"
fi



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Exit if either "key" or "value" are blank
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

if [[ -z "${key}" ]] || [[ -z "${value}" ]]; then

    updateScriptLog "PRE-FLIGHT CHECK: Error: Please provide data for both the 'key' and 'value'."
    exit 1

else

    updateScriptLog "PRE-FLIGHT CHECK: Both the 'key' and 'value' populated; proceeding …"
    updateScriptLog "PRE-FLIGHT CHECK: Complete"

fi



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

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Write Plist Value
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function writePlistValue() {

    # Variables
    key="${1}"        # Name of the "key" for which the value will be set
    value="${2}"    # The value to which "key" will be set

    updateScriptLog "Write Plist Value: \"${key}\" \"${value}\" "
    /usr/bin/defaults write "${filepath}" "${key}" -string "${value}"

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Read Plist Value
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function readPlistValue() {

    # Variables
    key="${1}"        # Name of the "key" for which the value will be set

    updateScriptLog "Read Plist Value:\"${key}\""
    writtenValue=$( /usr/bin/defaults read "${filepath}" "${key}" 2>&1 )
    updateScriptLog "${writtenValue}"

}



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

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Backup Plist File
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

# updateScriptLog "Backup Plist File"
# /bin/cp -v ${filepath}{,-backup-$(date '+%Y-%m-%d-%H%M%S')}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Write Plist Value
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

writePlistValue "${key}" "${value}"
writePlistValue "${key} timestamp" "${timestamp}"



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Read Plist Value
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

readPlistValue "${key}"



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

updateScriptLog "So Long, Farewell!"

exit 0

Policy

In either a new or existing policy, add the Scripts payload, select the Property List Writer script and specify the desired key and value.

For example, if you were leveraging Graham Pugh’s erase-install, specify the following to know to which version — and when — users upgraded to macOS Sonoma:

  • Key: macOS Sonoma Upgrade
  • Value: 14.1.2 (23B92)
# defaults read /Library/Preferences/org.churchofjesuschrist.plist 
{
    "macOS Sonoma Upgrade" = "14.1.2 (23B92)";
    "macOS Sonoma Upgrade timestamp" = "2023-12-02-063100";
}
Retrieve
Good

Extension Attribute

Again, if you have modest needs — while more error-prone — a quick-and-dirty Extension Attribute will get you on your way.

Note: Ensure to accurately specify the filepath of the desired client-side breadcrumb.

#!/bin/bash
# Extension Attribute to read the status of FileVault New Recovery Key

if [ -f "/Library/Management/org.churchofjesuschrist/.issueNewRecoveryKey" ] ; then
	result="True"
else
	result="False"
fi


echo "<result>$result</result>"
Note: Ensure to accurately specify the filepath of the desired client-side breadcrumb.
Better

In this case, there is no better option than good; proceed to the best option.

Best

Extension Attribute

Property List Reader (0.0.1)

A script to determine the value of Property List key. If the key is not found, “N/A” will be returned.

The first time you create a so-called “Property List Reader” Extension Attribute, pay close attention to the value of filepath, ensuring it exactly matches the value of filepath specified in the  Property List Writer script.

Once you have your first “Property List Reader” Extension Attribute working as expected, you’ll need only change the key variable in each additional EA to match what you specified in the corresponding policy.

Hand? Meet glove.

#!/bin/bash
#########################################################
# A script to determine the value of Property List key. #
# If the key is not found, "N/A" will be returned.      #
#########################################################

filepath="/Library/Preferences/org.churchofjesuschrist.plist"
key="macOS Sonoma Upgrade" # Name of the "key" for which the value will be read
value=$( /usr/bin/defaults read "${filepath}" "${key}" 2>&1 )

case "${value}" in

    *"does not exist"   )
        RESULT="N/A"
        ;;

    *                   )
        timestamp=$( /usr/bin/defaults read "${filepath}" "${key} timestamp" 2>&1 )
        RESULT="${value}: ${timestamp}"
        ;;

esac

/bin/echo "<result>${RESULT}</result>"

exit 0
Property List Reader: key="macOS Sonoma Upgrade"
Jamf Pro Computer Record: macOS Sonoma Installation

Use-cases

Posted in Jamf Pro, macOS, Scripts, Setup Your Mac, Tips & Tricks

Related Posts