Menu Close

Extension Attribute Frequency

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
Posted in Extension Attributes, Jamf Pro, Scripts, Tips & Tricks

Related Posts