Posted on 08-04-2016 08:12 AM
Inspired by @elliotjordan's plea to obtain user feedback, we've been using a pop-up menu Computer Extension Attribute called "Testing Level" which has three options:
We then have Smart Computer Groups for each of the three levels and a fourth for "none" so we can more easily scope policies.
This has been working well, but has required a JSS administrator to manually edit each computer record and specify the desired Testing Level.
After being challenged by @mike.paul and @kenglish to leverage the API, a search revealed @seansb's Updating Pop-Up Extension Attribute Value via API post and @mm2270's reply about Results of single extension attribute via API we had exactly what we needed.
In my rather frustated testing, the API read / write account needs (at least) the following JSS Objects "Read" and "Update" permissions:
You'll need to update the "apiURL" in the following script which leverages parameters 4 though 7 for:
#!/bin/sh
####################################################################################################
#
# ABOUT
#
# Set a computer's Extension Attribute via the API
#
####################################################################################################
#
# HISTORY
#
# Version 1.0, 30-Jul-2016, Dan K. Snelson
#
####################################################################################################
# Import logging functions
source /path/to/logging/script/logging.sh
####################################################################################################
ScriptLog "--- Set a computer's Extension Attribute via the API ---"
### Variables
apiURL="https://jss.company.com" # JSS URL without trailing forward slash
apiUsername="${4}" # API Username
apiPassword="${5}" # API Password
eaName="${6}" # Name of Extension Attribute (i.e., "Testing Level")
eaValue="${7}" # Value for Extension Attribute (i.e., "Gamma" or "None")
computerUDID=$(/usr/sbin/system_profiler SPHardwareDataType | /usr/bin/awk '/Hardware UUID:/ { print $3 }')
# Validate a value has been specified for Parameter 4 ...
if [ ! -z "${apiUsername}" ] && [ ! -z "${apiPassword}" ] && [ ! -z "${eaName}" ] && [ ! -z "${eaValue}" ]; then
# All script parameters have been specified, proceeding ...
ScriptLog "* All script parameters have been specified, proceeding ..."
ScriptLog "* Extension Attribute Name: ${eaName}"
ScriptLog "* Extension Attribute New Value: ${eaValue}"
if [ ${eaValue} == "None" ]; then
ScriptLog "* Extension Attribute Value is 'None'; remove value ${eaName}"
eaValue=""
fi
# Read current value ...
apiRead=`curl -H "Accept: text/xml" -sfku ${apiUsername}:${apiPassword} ${apiURL}/JSSResource/computers/udid/${computerUDID}/subset/extension_attributes | xmllint --format - | grep -A3 "<name>${eaName}</name>" | awk -F'>|<' '/value/{print $3}'`
ScriptLog "* Extension Attribute ${eaName}'s Current Value: ${apiRead}"
# Construct the API data ...
apiData="<computer><extension_attributes><extension_attribute><name>${eaName}</name><value>${eaValue}</value></extension_attribute></extension_attributes></computer>"
apiPost=`curl -H "Content-Type: text/xml" -sfu ${apiUsername}:${apiPassword} ${apiURL}/JSSResource/computers/udid/${computerUDID} -d "${apiData}" -X PUT`
/bin/echo ${apiPost}
# Read the new value ...
apiRead=`curl -H "Accept: text/xml" -sfku ${apiUsername}:${apiPassword} ${apiURL}/JSSResource/computers/udid/${computerUDID}/subset/extension_attributes | xmllint --format - | grep -A3 "<name>${eaName}</name>" | awk -F'>|<' '/value/{print $3}'`
ScriptLog "* Extension Attribute ${eaName}'s New Value: ${apiRead}"
ScriptLog "--- Completed setting a computer's Extension Attribute via the API ---"
# Re-direct logging to the JSS
exec 1>&3 2>&4
/bin/echo >&1 "${eaName} changed to ${eaValue}"
else
ScriptLog "Error: Parameters 4, 5, 6 and 7 not populated; exiting."
# Re-direct logging to the JSS
exec 1>&3 2>&4
/bin/echo >&1 "Error: Parameters 4, 5, 6 and 7 not populated; exiting."
exit 3
fi
exit 0
The following script is installed client-side and is then source'd by the above script.
#!/bin/sh
####################################################################################################
#
# ABOUT
#
# Standard logging functions which are imported into other scripts
#
####################################################################################################
#
# HISTORY
#
# Version 1.0, 22-Nov-2014, Dan K. Snelson
# Version 2.0, 13-Oct-2015, Dan K. Snelson
#
####################################################################################################
# LOGGING
# Logging variables
logFile="/var/log/com.company.log"
# Check for / create logFile
if [ ! -f "${logFile}" ]; then
# logFile not found; Create logFile
/usr/bin/touch "${logFile}"
fi
# Save standard output and standard error
exec 3>&1 4>&2
# Redirect standard output to logFile
exec 1>>"${logFile}"
# Redirect standard error to logFile
exec 2>>"${logFile}"
# Enable all logging from this point forward
# set -xv; exec 1>>"${logFile}" 2>&1
# Logging Function inspired by rtrouton
ScriptLog(){
DATE=`date +%Y-%m-%d %H:%M:%S`
/bin/echo "$DATE" " $1" >> ${logFile}
}
####################################################################################################
Create an ongoing Self Sevice policy, scoped to "Testing: None" which includes a single Scripts option of "Update Extension Attribute" and specify:
Clone your Opt-in policy and change EA Value to "None" to unset a computer's Testing Level; scope to your testing groups.
Posted on 08-04-2016 09:54 AM
Very impressive! Nice work.
Have you had any issues with special characters not escaping properly in the API password? Is the password visible in any logs on the client or on the JSS?
Are those the minimum necessary API permissions? Seems like "Create" shouldn't be necessary, but it sounds like you've tested extensively.
Are there still any features you'd like help implementing? If you post it to GitHub, you might get some pull requests.
Posted on 08-04-2016 07:12 PM
@elliotjordan Thanks for the feedback.
Special Characters: No, I haven't seen any issue with special characters not escaping properly. As password such as:
9A0.ERkuAftTSei)P7k
works fine, except now I've got to change my API password ;-)
Password Visibility: The password is not visible client-side in logs or temporary JAMF directories thanks to the Script Parameters, but is visible in the JSS policy. (We use Sites extensively and few JSS admins can see Policies assigned to a Site of "None.")
API Permissions: Nice catch; fixed above.
To Dos: I believe I saw where @seansb was working on hashing the API password and I've only tested this with text-flavored EAs; it'd be nice to make that more robust.
Posted on 08-05-2016 05:40 AM
Very nice, thanks for sharing.
Posted on 10-17-2016 11:43 AM
@mike.paul Points out:
"You could leverage the project that our IT and Security team setup for this exact use:"
https://github.com/jamfit/Encrypted-Script-Parameters
Here's an updated version:
#!/bin/sh
####################################################################################################
#
# ABOUT
#
# Set a computer's Extension Attribute via the API
#
####################################################################################################
#
# HISTORY
#
# Version 1.0, 30-Jul-2016, Dan K. Snelson
# Original
# Version 1.1, 17-Oct-2016, Dan K. Snelson
# Updated to leverage an encyrpted API password
#
####################################################################################################
# Import functions
source /path/to/logging/script/functions.sh
####################################################################################################
ScriptLog "--- Set a computer's Extension Attribute via the API ---"
### Variables
apiURL="https://jss.company.com/" # JSS URL without trailing forward slash
apiUsername="${4}" # API Username
apiPasswordEncrypted="${5}" # API Encrypted Password
eaName="${6}" # Name of Extension Attribute (i.e., "Testing Level")
eaValue="${7}" # Value for Extension Attribute (i.e., "Gamma" or "None")
Salt="saltOutputGoesHere" # Salt (generated from Encrypt Password)
Passphrase="passphraseOutputGoesHere" # Passphrase (generated from Encrypt Password)
computerUDID=$(/usr/sbin/system_profiler SPHardwareDataType | /usr/bin/awk '/Hardware UUID:/ { print $3 }')
# Validate a value has been specified for Parameter 4 ...
if [ ! -z "${apiUsername}" ] && [ ! -z "${apiPasswordEncrypted}" ] && [ ! -z "${eaName}" ] && [ ! -z "${eaValue}" ]; then
# All script parameters have been specified, proceeding ...
ScriptLog "* All script parameters have been specified, proceeding ..."
apiPassword=$(decryptPassword ${apiPasswordEncrypted} ${Salt} ${Passphrase})
ScriptLog "* Extension Attribute Name: ${eaName}"
ScriptLog "* Extension Attribute New Value: ${eaValue}"
if [ ${eaValue} == "None" ]; then
ScriptLog "* Extension Attribute Value is 'None'; remove value ${eaName}"
eaValue=""
fi
# Read current value ...
apiRead=`curl -H "Accept: text/xml" -sfu ${apiUsername}:${apiPassword} ${apiURL}/JSSResource/computers/udid/${computerUDID}/subset/extension_attributes | xmllint --format - | grep -A3 "<name>${eaName}</name>" | awk -F'>|<' '/value/{print $3}'`
ScriptLog "* Extension Attribute ${eaName}'s Current Value: ${apiRead}"
# Construct the API data ...
apiData="<computer><extension_attributes><extension_attribute><name>${eaName}</name><value>${eaValue}</value></extension_attribute></extension_attributes></computer>"
apiPost=`curl -H "Content-Type: text/xml" -sfu ${apiUsername}:${apiPassword} ${apiURL}/JSSResource/computers/udid/${computerUDID} -d "${apiData}" -X PUT`
/bin/echo ${apiPost}
# Read the new value ...
apiRead=`curl -H "Accept: text/xml" -sfu ${apiUsername}:${apiPassword} ${apiURL}/JSSResource/computers/udid/${computerUDID}/subset/extension_attributes | xmllint --format - | grep -A3 "<name>${eaName}</name>" | awk -F'>|<' '/value/{print $3}'`
ScriptLog "* Extension Attribute ${eaName}'s New Value: ${apiRead}"
ScriptLog "--- Completed setting a computer's Extension Attribute via the API ---"
# Re-direct logging to the JSS
exec 1>&3 2>&4
/bin/echo >&1 "${eaName} changed to ${eaValue}"
else
ScriptLog "Error: Parameters 4, 5, 6 and 7 not populated; exiting."
# Re-direct logging to the JSS
exec 1>&3 2>&4
/bin/echo >&1 "Error: Parameters 4, 5, 6 and 7 not populated; exiting."
exit 3
fi
exit 0
Posted on 06-30-2018 05:26 PM
@elliotjordan Finally took your advice and posted this on GitHub.
Posted on 11-14-2019 02:24 PM
The GitHub page includes a link to the JNUC 2019 Keynote file and YouTube presentation.
Posted on 01-16-2021 09:25 AM
Tip: Refresh Self Service when a user opts-in / opts-out.