Background
While we’re waiting for @NightFlight‘s Extention Attribute Execution Frequency feature request to be implimented, here’s my two cents, which was inspired by @brad‘s approach for only occasionallycapturing the status of a computer’s Recovery HD.
Approach
As one of the first steps of an Extension Attribute script, you pass the name of the Extension Attribute and the desired execution frequency (in days) to a client-side function. A client-side plist stores the epoch and the result.
During subsequent inventory updates, if the current epoch is less than the given frequency, it just reads the previous result from the plist instead of executing the entire Extension Attribute script.
For example, I have an EA for “Model Name”; how many times do you need to run that Extension Attribute? (Once per quarter? Once per year? Certainly not every time.)
Results
Early tests show an overall inventory collection that is 1.6x faster, using the following as a gauge before and after:
time -p sudo jamf recon -verbose
(Some individual EA scripts which curl external Web sites or query the Sophos AV binary have realized a 113x increase!)
Scripts
Client-side Functions
You’ll need to install the following functions client-side (i.e., when enrollment in complete) and update the path for “organizationalPlist”.
#!/bin/sh #################################################################################################### # # ABOUT # # Standard functions which are imported into other scripts # #################################################################################################### # # HISTORY # # Version 1.2, 26-Apr-2017, Dan K. Snelson # Added Extension Attribute Execution Frequency & Results # #################################################################################################### # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # LOGGING # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ## Variables logFile="/var/log/com.company.log" alias now="/bin/date '+%Y-%m-%d %H:%M:%S'" ## Check for / create logFile if [ ! -f "${logFile}" ]; then # logFile not found; Create logFile ... /usr/bin/touch "${logFile}" /bin/echo "`/bin/date +%Y-%m-%d %H:%M:%S` *** Created log file via function ***" >>"${logFile}" fi ## I/O Redirection to client-side log file exec 3>&1 4>&2 # Save standard output (stdout) and standard error (stderr) to new file descriptors exec 1>>"${logFile}" # Redirect standard output, stdout, to logFile exec 2>>"${logFile}" # Redirect standard error, stderr, to logFile function ScriptLog() { # Write to client-side log file ... /bin/echo "`/bin/date +%Y-%m-%d %H:%M:%S` ${1}" } function jssLog() { # Write to JSS ... ScriptLog "${1}" # Write to the client-side log ... ## I/O Redirection to JSS exec 1>&3 3>&- 2>&4 4>&- # Restore standard output (stdout) and standard error (stderr) /bin/echo "${1}" # Record output in the JSS } # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # JAMF Display Message # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # function jamfDisplayMessage() { ScriptLog "${1}" /usr/local/jamf/bin/jamf displayMessage -message "${1}" & } # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Extension Attribute Execution Frequency # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # function eaFrequency() { # Validate parameters if [ -z "$1" ] || [ -z "$2" ] ; then ScriptLog "Error calling "eaFrequency" function: One or more parameters are blank; exiting." exit 1 fi # Variables organizationalPlist="/client-side/path/to/com.company.plist" plistKey="$1" # Supplied plistKey frequency="$2" # Supplied frequency in days frequencyInSeconds=$((frequency * 86400)) # There are 86,400 seconds in 1 day # Check for / create plist ... if [ ! -f "${organizationalPlist}" ]; then ScriptLog "The plist, "${organizationalPlist}", does NOT exist; create it ..." /usr/bin/touch "${organizationalPlist}" /usr/sbin/chown root:wheel "${organizationalPlist}" /bin/chmod 0600 "${organizationalPlist}" fi # Query for the given plistKey; suppress any error message, if key not found. plistKeyTest=$( /usr/libexec/PlistBuddy -c 'print "'"${plistKey} Epoch"'"' ${organizationalPlist} 2>/dev/null ) # Capture the exit code, which indicates success v. failure exitCode=$? if [ "${exitCode}" != 0 ]; then ScriptLog "The key, "${plistKey} Epoch", does NOT exist; create it with a value of zero ..." /usr/bin/defaults write "${organizationalPlist}" "${plistKey} Epoch" "0" fi # Read the last execution time ... lastExecutionTime=$( /usr/bin/defaults read "${organizationalPlist}" "${plistKey} Epoch" ) # Calculate the elapsed time since last execution ... elapsedTimeSinceLastExecution=$(( $(date +%s) - ${lastExecutionTime} )) # If the elapsed time is less than the frequency, read the previous result ... if [ "${elapsedTimeSinceLastExecution}" -lt "${frequencyInSeconds}" ]; then ScriptLog "Elapsed time since last execution for "$plistKey", $elapsedTimeSinceLastExecution, is less than $frequencyInSeconds; read previous result." eaExecution="No" eaResult "${plistKey}" # Obtain the current result else # If the elapsed time is less than the frequency, read the previous result ... ScriptLog "Elapsed time since last execution for "$plistKey", $elapsedTimeSinceLastExecution, is greater than $frequencyInSeconds; execute the Extension Attribute." /usr/bin/defaults write "${organizationalPlist}" "${plistKey} Epoch" "`/bin/date +%s`" eaExecution="Yes" fi } # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Extension Attribute Result # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # function eaResult() { # Validate parameters if [ -z "$1" ] ; then ScriptLog "Error calling "eaResult" function: Parameter 1 is blank; exiting." exit 1 fi # Variables organizationalPlist="/client-side/path/to/com.company.plist" plistKey="$1" result="$2" if [ -z "$2" ] ; then # If the function is called with a single parameter, then just read the previously recorded result returnedResult=$( /usr/bin/defaults read "${organizationalPlist}" "${plistKey} Result" ) else # If the function is called with two parameters, then write / read the new result /usr/bin/defaults write "${organizationalPlist}" "${plistKey} Result" ""${result}"" returnedResult=$( /usr/bin/defaults read "${organizationalPlist}" "${plistKey} Result" ) fi }
Extension Attribute Modifications
Make the following modifications to your most time-consuming EA scripts, which output their results to a variable called results
. (Also, update the source
path for your environment.)
We just looked for time-consuming EAs while running the following:
time -p sudo jamf recon -verbose
#!/bin/sh #################################################################################################### # Import client-side functions source /client-side/path/to/functions.sh #################################################################################################### # Variables eaName="Friendly name for Extension Attribute" # Name of Extension Attribute eaDays="30" # Number of days between executions # Check for Extension Attribute execution eaFrequency "${eaName}" "${eaDays}" if [ ${eaExecution} == "Yes" ]; then # # # # Insert your Current Extension Attribute script here. # # # eaResult "${eaName}" "${result}" else eaResult "${eaName}" fi jssLog "<result>${returnedResult}</result>" exit 0
Real-world Example
Here’s a real-world example, using @jake‘s VMware – Virtual Machine List.
If my math is right, it’s a speed improvement of 181.6x.
Traditional Extension Attribute
Execution Time:
- real 18.36
- user 2.29
- sys 6.59
#!/bin/sh OS=`/usr/bin/sw_vers -productVersion | /usr/bin/colrm 5` if [[ "$OS" < "10.6" ]]; then myVMList=`find /Users -name "*.vmx"` else myVMList=`mdfind -name ".vmx" | grep -Ev "vmx.lck" | grep -Ev "vmxf"` fi IFS=$' ' myCount=1 echo "<result>" for myFile in $myVMList do myNetwork=`cat "$myFile"| grep "ethernet.*.connectionType"| awk '{print $3}'| sed 's/"//g'` myDisplayName=`cat "$myFile"| grep "displayName"| sed 's/displayName = //g'| sed 's/"//g'` myMemSize=`cat "$myFile"| grep "memsize"| awk '{print $3}'| sed 's/"//g'` myUUID=`cat "$myFile"| grep "uuid.bios"| sed 's/uuid.bios = //g'| sed 's/"//g'` myMAC=`cat "$myFile"| grep "ethernet.*.generatedAddress"| grep -v "Offset"| awk '{print $3}'| sed 's/"//g'` echo "=-=-=-=-=-=-=-=-=-=-=-=-=-" echo "VMWare VM #$myCount" echo "File Name: $myFile" echo "Display Name: $myDisplayName" echo "Network Type: $myNetwork" echo "MAC Address: $myMAC" echo "Memory: $myMemSize MB" echo "UUID: $myUUID" let myCount=myCount+1 done echo "</result>" unset IFS
Updated Extension Attribute
Execution Time (after initial run):
- real 0.10
- user 0.03
- sys 0.03
#!/bin/sh #################################################################################################### # # Extension Attribute to determine VMWare - Virtual Machine List # #################################################################################################### # Import client-side functions source /path/to/client-side/functions.sh #################################################################################################### # Variables eaName="VMWare - Virtual Machine List" # Name of Extension Attribute eaDays="90" # Number of days between executions # Check for Extension Attribute execution eaFrequency "${eaName}" "${eaDays}" if [ ${eaExecution} == "Yes" ]; then OS=`/usr/bin/sw_vers -productVersion | /usr/bin/colrm 5` if [[ "$OS" < "10.6" ]]; then myVMList=`find /Users -name "*.vmx"` else myVMList=`mdfind -name ".vmx" | grep -Ev "vmx.lck" | grep -Ev "vmxf"` fi IFS=$' ' myCount=1 for myFile in $myVMList; do myNetwork=`cat "$myFile"| grep "ethernet.*.connectionType"| awk '{print $3}'| sed 's/"//g'` myDisplayName=`cat "$myFile"| grep "displayName"| sed 's/displayName = //g'| sed 's/"//g'` myMemSize=`cat "$myFile"| grep "memsize"| awk '{print $3}'| sed 's/"//g'` myUUID=`cat "$myFile"| grep "uuid.bios"| sed 's/uuid.bios = //g'| sed 's/"//g'` myMAC=`cat "$myFile"| grep "ethernet.*.generatedAddress"| grep -v "Offset"| awk '{print $3}'| sed 's/"//g'` result="=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-" result="${result}"$' '"VMWare VM #${myCount}" result="${result}"$' '"File Name: ${myFile}" result="${result}"$' '"Display Name: ${myDisplayName}" result="${result}"$' '"Network Type: ${myNetwork}" result="${result}"$' '"MAC Address: ${myMAC}" result="${result}"$' '"Memory: ${myMemSize} MB" result="${result}"$' '"UUID: ${myUUID}" let myCount=myCount+1 done unset IFS eaResult "${eaName}" "${result}" else eaResult "${eaName}" fi jssLog "<result>${returnedResult}</result>" exit 0