Home > iOS, Jamf Pro, Jamf Pro API, Mac administration, macOS, Scripting > Backing up extension attributes from Jamf Pro

Backing up extension attributes from Jamf Pro

While working with extension attributes on Jamf Pro, I prefer to download then and back them up to GitHub or a similar internal source control tool. The reasons I do this are the following:

  1. I have an off-server backup for the extension attributes
  2. I can track changes to the extension attributes

To help me manage this, I have two scripts which do the following:

  1. Use the Jamf Pro API to identify the Jamf Pro ID numbers of the extension attributes.
  2. Download each extension attribute as an XML file using its Jamf Pro ID number.
  3. Format the downloaded XML.
  4. Identify the display name of the extension attribute.
  5. Identify if it was a String, Integer or Date extension attribute.
  6. If it’s a macOS or Windows extension attribute and it has a script, extract the script.
  7. Save the downloaded XML or script as Extension Attribute Name Here to a specified download directory, based on whether it was a String, Integer or Date extension attribute.

For more details, please see below the jump.

I’ve written two scripts for this purpose:

  • Jamf_Pro_Computer_Extension_Attribute_Download.sh – This script is designed to download and handle macOS extension attributes.
  • Jamf_Pro_Mobile_Device_Extension_Attribute_Download.sh – This script is designed to download and handle iOS and tvOS extension attributes.

For authentication, the scripts can accept hard-coded values in the script, manual input or values stored in a ~/Library/Preferences/com.github.jamfpro-info.plist file. The plist file can be created by running the following commands and substituting your own values where appropriate:

To store the Jamf Pro URL in the plist file:

defaults write com.github.jamfpro-info jamfpro_url https://jamf.pro.server.goes.here:port_number_goes_here

To store the account username in the plist file:

defaults write com.github.jamfpro-info jamfpro_user account_username_goes_here

To store the account password in the plist file:

defaults write com.github.jamfpro-info jamfpro_password account_password_goes_here

Both scripts run in similar ways, with the main difference being which kind of extension attributes are being downloaded. One notable difference is that the macOS extension attributes may download scripts, while the iOS and tvOS extension attributes will only ever be XML files.

To download macOS extension attributes:

/path/to/Jamf_Pro_Computer_Extension_Attribute_Download.sh

To download iOS and tvOS extension attributes:

/path/to/Jamf_Pro_Mobile_Device_Extension_Attribute_Download.sh

When run, you should see output similar to that shown below (in this case, Jamf_Pro_Computer_Extension_Attribute_Download.sh is being run.)

computername:~ username$ /path/to/Jamf_Pro_Computer_Extension_Attribute_Download.sh
A location to store downloaded groups has not been specified.
Downloaded groups will be stored in /var/folders/q6/z5m752w547sbrcjqwd0783340000kt/T/tmp.jY7wPPo1.
Please enter your Jamf Pro server URL : https://jamf.pro.server.here:8443
Please enter your Jamf Pro user account : jamfproadmin
Please enter the password for the jamfproadmin account:
Downloading extension attributes from https://jamf.pro.server.here:8443…
Downloaded extension attribute is named: "APFS Encryption Check"
"APFS Encryption Check" is a "String" extension attribute.
"APFS Encryption Check" is an extension attribute of type "script".
"APFS Encryption Check" runs on Mac.
Saving "APFS Encryption Check.sh" file to /var/folders/q6/z5m752w547sbrcjqwd0783340000kt/T/tmp.jY7wPPo1/String/script/Mac.
Downloaded extension attribute is named: "Asset Number"
"Asset Number" is a "Integer" extension attribute.
"Asset Number" is an extension attribute of type "Text Field".
Saving "Asset Number.xml" file to /var/folders/q6/z5m752w547sbrcjqwd0783340000kt/T/tmp.jY7wPPo1/Integer/Text Field.
Downloaded extension attribute is named: "Firewall"
"Firewall" is a "String" extension attribute.
"Firewall" is an extension attribute of type "script".
"Firewall" runs on Windows.
Saving "Firewall.wsf" file to /var/folders/q6/z5m752w547sbrcjqwd0783340000kt/T/tmp.jY7wPPo1/String/script/Windows.
"Firewall" is an extension attribute of type "script".
"Firewall" runs on Mac.
Saving "Firewall.sh" file to /var/folders/q6/z5m752w547sbrcjqwd0783340000kt/T/tmp.jY7wPPo1/String/script/Mac.
Downloaded extension attribute is named: "Logged-in user has Secure Token"
"Logged-in user has Secure Token" is a "String" extension attribute.
"Logged-in user has Secure Token" is an extension attribute of type "script".
"Logged-in user has Secure Token" runs on Mac.
Saving "Logged-in user has Secure Token.sh" file to /var/folders/q6/z5m752w547sbrcjqwd0783340000kt/T/tmp.jY7wPPo1/String/script/Mac.
Downloaded extension attribute is named: "System Integrity Protection status"
"System Integrity Protection status" is a "String" extension attribute.
"System Integrity Protection status" is an extension attribute of type "script".
"System Integrity Protection status" runs on Mac.
Saving "System Integrity Protection status.sh" file to /var/folders/q6/z5m752w547sbrcjqwd0783340000kt/T/tmp.jY7wPPo1/String/script/Mac.
Downloaded extension attribute is named: "Test Date"
"Test Date" is a "Date" extension attribute.
"Test Date" is an extension attribute of type "Text Field".
Saving "Test Date.xml" file to /var/folders/q6/z5m752w547sbrcjqwd0783340000kt/T/tmp.jY7wPPo1/Date/Text Field.
Downloaded extension attribute is named: "User Approved MDM Enabled"
"User Approved MDM Enabled" is a "String" extension attribute.
"User Approved MDM Enabled" is an extension attribute of type "script".
"User Approved MDM Enabled" runs on Mac.
Saving "User Approved MDM Enabled.sh" file to /var/folders/q6/z5m752w547sbrcjqwd0783340000kt/T/tmp.jY7wPPo1/String/script/Mac.
computername:~ username$

view raw
gistfile1.txt
hosted with ❤ by GitHub

The extension attributes themselves will be stored in either a user-specified directory or, if no directory is specified, a directory created by the script.

Screen Shot 2018 12 19 at 9 52 02 PM

Screen Shot 2018 12 19 at 10 04 33 PM

The scripts are available below, and at the following addresses on GitHub:

https://github.com/rtrouton/rtrouton_scripts/tree/master/rtrouton_scripts/Casper_Scripts/Jamf_Pro_Computer_Extension_Attribute_Download

https://github.com/rtrouton/rtrouton_scripts/tree/master/rtrouton_scripts/Casper_Scripts/Jamf_Pro_Mobile_Device_Extension_Attribute_Download

Jamf_Pro_Computer_Extension_Attribute_Download.sh:

#!/bin/bash
# This script is designed to use the Jamf Pro API to identify the individual IDs of
# the computer extension attributes stored on a Jamf Pro server then do the following:
#
# 1. Download the extension attribute as XML
# 2. Identify the extension attribute name
# 3. Categorize the downloaded extension attribute
# 4. If it's a macOS or Windows extension attribute and it has a script, extract the script.
# 5. Save the extension attribute to a specified directory
# If setting up a specific user account with limited rights, here are the required API privileges
# for the account on the Jamf Pro server:
#
# Jamf Pro Server Objects:
#
# Computer Extension Attributes: Read
# If you choose to specify a directory to save the downloaded extension attributes into,
# please enter the complete directory path into the ExtensionAttributeDownloadDirectory
# variable below.
ExtensionAttributeDownloadDirectory=""
# If the ExtensionAttributeDownloadDirectory isn't specified above, a directory will be
# created and the complete directory path displayed by the script.
if [[ -z "$ExtensionAttributeDownloadDirectory" ]]; then
ExtensionAttributeDownloadDirectory=$(mktemp -d)
echo "A location to store downloaded groups has not been specified."
echo "Downloaded groups will be stored in $ExtensionAttributeDownloadDirectory."
fi
# If you choose to hardcode API information into the script, set one or more of the following values:
#
# The username for an account on the Jamf Pro server with sufficient API privileges
# The password for the account
# The Jamf Pro URL
# Set the Jamf Pro URL here if you want it hardcoded.
jamfpro_url=""
# Set the username here if you want it hardcoded.
jamfpro_user=""
# Set the password here if you want it hardcoded.
jamfpro_password=""
# If you do not want to hardcode API information into the script, you can also store
# these values in a ~/Library/Preferences/com.github.jamfpro-info.plist file.
#
# To create the file and set the values, run the following commands and substitute
# your own values where appropriate:
#
# To store the Jamf Pro URL in the plist file:
# defaults write com.github.jamfpro-info jamfpro_url https://jamf.pro.server.goes.here:port_number_goes_here
#
# To store the account username in the plist file:
# defaults write com.github.jamfpro-info jamfpro_user account_username_goes_here
#
# To store the account password in the plist file:
# defaults write com.github.jamfpro-info jamfpro_password account_password_goes_here
#
# If the com.github.jamfpro-info.plist file is available, the script will read in the
# relevant information from the plist file.
PLIST="$HOME/Library/Preferences/com.github.jamfpro-info.plist"
if [[ -r "$PLIST" ]]; then
if [[ -z "$jamfpro_url" ]]; then
jamfpro_url=$(defaults read "${PLIST%.*}" jamfpro_url)
fi
if [[ -z "$jamfpro_user" ]]; then
jamfpro_user=$(defaults read "${PLIST%.*}" jamfpro_user)
fi
if [[ -z "$jamfpro_password" ]]; then
jamfpro_password=$(defaults read "${PLIST%.*}" jamfpro_password)
fi
fi
# If the Jamf Pro URL, the account username or the account password aren't available
# otherwise, you will be prompted to enter the requested URL or account credentials.
if [[ -z "$jamfpro_url" ]]; then
read -p "Please enter your Jamf Pro server URL : " jamfpro_url
fi
if [[ -z "$jamfpro_user" ]]; then
read -p "Please enter your Jamf Pro user account : " jamfpro_user
fi
if [[ -z "$jamfpro_password" ]]; then
read -p "Please enter the password for the $jamfpro_user account: " -s jamfpro_password
fi
echo
# Remove the trailing slash from the Jamf Pro URL if needed.
jamfpro_url=${jamfpro_url%%/}
# Remove the trailing slash from the ExtensionAttributeDownloadDirectory variable if needed.
ExtensionAttributeDownloadDirectory=${ExtensionAttributeDownloadDirectory%%/}
DownloadComputerExtensionAttribute(){
# Download the extension attribute information as raw XML,
# then format it to be readable.
ComputerExtensionAttribute=$(curl -su "${jamfpro_user}:${jamfpro_password}" -H "Accept: application/xml" "${jamfpro_url}/JSSResource/computerextensionattributes/id/${ID}")
FormattedComputerExtensionAttribute=$(echo "$ComputerExtensionAttribute" | xmllint –format –)
# Identify and display the extension attribute's name.
DisplayName=$(echo "$FormattedComputerExtensionAttribute" | xpath "/computer_extension_attribute/name/text()" 2>/dev/null | sed -e 's|:|(colon)|g' -e 's/\//\\/g')
echo "Downloaded extension attribute is named: \"$DisplayName\""
# Identify the EA type.
EAType=$(echo "$FormattedComputerExtensionAttribute" | xpath "/computer_extension_attribute/data_type/text()" 2>/dev/null)
echo "\"$DisplayName\" is a \"$EAType\" extension attribute."
# get the number of input_type nodes. In some cases an EA may contain scripts for macOS and Windows. So if we find
# more than one input_tpye node, we just use the one for macOS
EAInputTypeNodeCount=$(echo "$FormattedComputerExtensionAttribute" | xpath "count(/computer_extension_attribute/input_type/type)" 2>/dev/null)
while [[ $EAInputTypeNodeCount -gt 0 ]]; do
EAInputType=$(echo "$FormattedComputerExtensionAttribute" | xpath "/computer_extension_attribute/input_type[$EAInputTypeNodeCount]/type/text()" 2>/dev/null)
FinalAttribute=""
FileName=""
if [[ -n "$EAInputType" ]]; then
echo "\"$DisplayName\" is an extension attribute of type \"$EAInputType\"."
TargetDirectory="$ExtensionAttributeDownloadDirectory/$EAType/$EAInputType"
if [[ "$EAInputType" = "script" ]]; then
# get the platform
ScriptPlatform=$(echo "$FormattedComputerExtensionAttribute" | xpath "/computer_extension_attribute/input_type[$EAInputTypeNodeCount]/platform/text()" 2>/dev/null)
if [[ -n "$ScriptPlatform" ]]; then
echo "\"$DisplayName\" runs on $ScriptPlatform."
TargetDirectory="$TargetDirectory/$ScriptPlatform"
if [[ "$ScriptPlatform" = "Mac" ]]; then
FileName=$(echo "$DisplayName" | sed 's/[:/[:cntrl:]]/_/g')
FileName="${FileName}.sh"
elif [[ "$ScriptPlatform" = "Windows" ]]; then
FileName=$(echo "$DisplayName" | sed 's/[.<>:"/\|?*[:cntrl:]]/_/g')
FileName="${FileName}.wsf"
fi
fi
FinalAttribute=$(echo "$FormattedComputerExtensionAttribute" | xpath "/computer_extension_attribute/input_type[$EAInputTypeNodeCount]/script/text()" 2>/dev/null | perl -MHTML::Entities -pe 'decode_entities($_);')
else
FileName="${DisplayName}.xml"
FinalAttribute="$FormattedComputerExtensionAttribute"
fi
# create the directory if needed
if [[ ! -d "$TargetDirectory" ]]; then
mkdir -p "$TargetDirectory"
fi
echo "Saving \"$FileName\" file to $TargetDirectory."
echo "$FinalAttribute" | perl -MHTML::Entities -pe 'decode_entities($_);' > "$TargetDirectory/$FileName"
else
echo "Error! Unable to determine the attribute's input type"
fi
((EAInputTypeNodeCount))
done
echo
}
ComputerExtensionAttribute_id_list=$(curl -su "${jamfpro_user}:${jamfpro_password}" -H "Accept: application/xml" "${jamfpro_url}/JSSResource/computerextensionattributes" | xpath "//id" 2>/dev/null)
if [[ -n "$ComputerExtensionAttribute_id_list" ]]; then
echo "Downloading extension attributes from $jamfpro_url"
ComputerExtensionAttribute_id=$(echo "$ComputerExtensionAttribute_id_list" | grep -Eo "[0-9]+")
for ID in ${ComputerExtensionAttribute_id}; do
DownloadComputerExtensionAttribute
done
else
echo "ERROR! Unable to get extension attribute list"
fi
exit 0

Jamf_Pro_Mobile_Device_Extension_Attribute_Download.sh:

#!/bin/bash
# This script is designed to use the Jamf Pro API to identify the individual IDs of
# the mobile device extension attributes stored on a Jamf Pro server then do the following:
#
# 1. Download the extension attribute as XML
# 2. Identify the extension attribute name
# 4. Categorize the downloaded extension attribute
# 4. Save the extension attribute to a specified directory
# If setting up a specific user account with limited rights, here are the required API privileges
# for the account on the Jamf Pro server:
#
# Jamf Pro Server Objects:
#
# Mobile Device Extension Attributes: Read
# If you choose to specify a directory to save the downloaded extension attributes into,
# please enter the complete directory path into the ExtensionAttributeDownloadDirectory
# variable below.
ExtensionAttributeDownloadDirectory=""
# If the ExtensionAttributeDownloadDirectory isn't specified above, a directory will be
# created and the complete directory path displayed by the script.
if [[ -z "$ExtensionAttributeDownloadDirectory" ]]; then
ExtensionAttributeDownloadDirectory=$(mktemp -d)
echo "A location to store downloaded groups has not been specified."
echo "Downloaded groups will be stored in $ExtensionAttributeDownloadDirectory."
fi
# If you choose to hardcode API information into the script, set one or more of the following values:
#
# The username for an account on the Jamf Pro server with sufficient API privileges
# The password for the account
# The Jamf Pro URL
# Set the Jamf Pro URL here if you want it hardcoded.
jamfpro_url=""
# Set the username here if you want it hardcoded.
jamfpro_user=""
# Set the password here if you want it hardcoded.
jamfpro_password=""
# If you do not want to hardcode API information into the script, you can also store
# these values in a ~/Library/Preferences/com.github.jamfpro-info.plist file.
#
# To create the file and set the values, run the following commands and substitute
# your own values where appropriate:
#
# To store the Jamf Pro URL in the plist file:
# defaults write com.github.jamfpro-info jamfpro_url https://jamf.pro.server.goes.here:port_number_goes_here
#
# To store the account username in the plist file:
# defaults write com.github.jamfpro-info jamfpro_user account_username_goes_here
#
# To store the account password in the plist file:
# defaults write com.github.jamfpro-info jamfpro_password account_password_goes_here
#
# If the com.github.jamfpro-info.plist file is available, the script will read in the
# relevant information from the plist file.
if [[ -f "$HOME/Library/Preferences/com.github.jamfpro-info.plist" ]]; then
if [[ -z "$jamfpro_url" ]]; then
jamfpro_url=$(defaults read $HOME/Library/Preferences/com.github.jamfpro-info jamfpro_url)
fi
if [[ -z "$jamfpro_user" ]]; then
jamfpro_user=$(defaults read $HOME/Library/Preferences/com.github.jamfpro-info jamfpro_user)
fi
if [[ -z "$jamfpro_password" ]]; then
jamfpro_password=$(defaults read $HOME/Library/Preferences/com.github.jamfpro-info jamfpro_password)
fi
fi
# If the Jamf Pro URL, the account username or the account password aren't available
# otherwise, you will be prompted to enter the requested URL or account credentials.
if [[ -z "$jamfpro_url" ]]; then
read -p "Please enter your Jamf Pro server URL : " jamfpro_url
fi
if [[ -z "$jamfpro_user" ]]; then
read -p "Please enter your Jamf Pro user account : " jamfpro_user
fi
if [[ -z "$jamfpro_password" ]]; then
read -p "Please enter the password for the $jamfpro_user account: " -s jamfpro_password
fi
echo ""
# Remove the trailing slash from the Jamf Pro URL if needed.
jamfpro_url=${jamfpro_url%%/}
# Remove the trailing slash from the ExtensionAttributeDownloadDirectory variable if needed.
ExtensionAttributeDownloadDirectory=${ExtensionAttributeDownloadDirectory%%/}
DownloadMobileDeviceExtensionAttribute(){
# Download the extension attribute information as raw XML,
# then format it to be readable.
echo "Downloading extension attributes from $jamfpro_url"
FormattedMobileDeviceExtensionAttribute=$(curl -su "${jamfpro_user}:${jamfpro_password}" -H "Accept: application/xml" "${jamfpro_url}/JSSResource/mobiledeviceextensionattributes/id/${ID}" -X GET | tr $'\n' $'\t' | sed -E 's|<mobile_device_extension_attributes>.*</mobile_device_extension_attributes>||' | tr $'\t' $'\n' | xmllint –format – )
# Identify and display the extension attribute's name.
DisplayName=$(echo "$FormattedMobileDeviceExtensionAttribute" | xpath "/mobile_device_extension_attribute/name/text()" 2>/dev/null | sed -e 's|:|(colon)|g' -e 's/\//\\/g')
echo "Downloaded extension attribute is named: $DisplayName"
# Identify the EA type.
if [[ $(echo "$FormattedMobileDeviceExtensionAttribute" | xpath "/mobile_device_extension_attribute/data_type/text()" 2>/dev/null) == "Date" ]]; then
EAType="Date"
elif [[ $(echo "$FormattedMobileDeviceExtensionAttribute" | xpath "/mobile_device_extension_attribute/data_type/text()" 2>/dev/null) == "Integer" ]]; then
EAType="Integer"
elif [[ $(echo "$FormattedMobileDeviceExtensionAttribute" | xpath "/mobile_device_extension_attribute/data_type/text()" 2>/dev/null) == "String" ]]; then
EAType="String"
fi
# Save the downloaded extension attribute.
echo "$DisplayName is a $EAType extension attribute."
echo "Saving ${DisplayName}.xml file to $ExtensionAttributeDownloadDirectory/$EAType."
if [[ "$EAType" = "Date" ]]; then
EAInputType=$(echo "$FormattedMobileDeviceExtensionAttribute" | xpath "/mobile_device_extension_attribute/input_type/type/text()" 2>/dev/null)
if [[ -d "$ExtensionAttributeDownloadDirectory/$EAType/$EAInputType" ]]; then
echo "$FormattedMobileDeviceExtensionAttribute" > "$ExtensionAttributeDownloadDirectory/$EAType/$EAInputType/${DisplayName}.xml"
else
mkdir -p "$ExtensionAttributeDownloadDirectory/$EAType/$EAInputType"
echo "$FormattedMobileDeviceExtensionAttribute" > "$ExtensionAttributeDownloadDirectory/$EAType/$EAInputType/${DisplayName}.xml"
fi
fi
if [[ "$EAType" = "Integer" ]]; then
EAInputType=$(echo "$FormattedMobileDeviceExtensionAttribute" | xpath "/mobile_device_extension_attribute/input_type/type/text()" 2>/dev/null)
if [[ -d "$ExtensionAttributeDownloadDirectory/$EAType/$EAInputType" ]]; then
echo "$FormattedMobileDeviceExtensionAttribute" > "$ExtensionAttributeDownloadDirectory/$EAType/$EAInputType/${DisplayName}.xml"
else
mkdir -p "$ExtensionAttributeDownloadDirectory/$EAType/$EAInputType"
echo "$FormattedMobileDeviceExtensionAttribute" > "$ExtensionAttributeDownloadDirectory/$EAType/$EAInputType/${DisplayName}.xml"
fi
fi
if [[ "$EAType" = "String" ]]; then
EAInputType=$(echo "$FormattedMobileDeviceExtensionAttribute" | xpath "/mobile_device_extension_attribute/input_type/type/text()" 2>/dev/null)
if [[ -d "$ExtensionAttributeDownloadDirectory/$EAType/$EAInputType" ]]; then
echo "$FormattedMobileDeviceExtensionAttribute" > "$ExtensionAttributeDownloadDirectory/$EAType/$EAInputType/${DisplayName}.xml"
else
mkdir -p "$ExtensionAttributeDownloadDirectory/$EAType/$EAInputType"
echo "$FormattedMobileDeviceExtensionAttribute" > "$ExtensionAttributeDownloadDirectory/$EAType/$EAInputType/${DisplayName}.xml"
fi
fi
}
MobileDeviceExtensionAttribute_id_list=$(curl -su "${jamfpro_user}:${jamfpro_password}" -H "Accept: application/xml" "${jamfpro_url}/JSSResource/mobiledeviceextensionattributes" | xpath "//id" 2>/dev/null)
MobileDeviceExtensionAttribute_id=$(echo "$MobileDeviceExtensionAttribute_id_list" | grep -Eo "[0-9]+")
for ID in ${MobileDeviceExtensionAttribute_id}; do
DownloadMobileDeviceExtensionAttribute
done

  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: