Skip to content

Instantly share code, notes, and snippets.

@talkingmoose
Last active August 26, 2024 03:53
Show Gist options
  • Save talkingmoose/4a950a715ad07ae3baecce2f46b0a7e5 to your computer and use it in GitHub Desktop.
Save talkingmoose/4a950a715ad07ae3baecce2f46b0a7e5 to your computer and use it in GitHub Desktop.
Download JSON or XML files for multiple Jamf Pro object types.
#!/bin/zsh
# set -x # show command
:<<ABOUT_THIS_SCRIPT
Written by:William Smith
Technical Enablement Manager
Jamf
bill@talkingmoose.net
https://gist.github.com/talkingmoose/4a950a715ad07ae3baecce2f46b0a7e5
Originally posted: August 19, 2024
Purpose: Download JSON or XML files for multiple Jamf Pro object types.
Instructions:
1. In Jamf Pro click Settings > System > API roles and clients.
2. Under API Roles create a new API role such as "Read Jamf Pro objects".
Set Privileges to include:
Read Accounts Read macOS Configuration Profiles
Read Advanced Computer Searches Read Mobile Device Applications
Read Advanced Mobile Device Searches Read Mobile Device Extension Attributes
Read Advanced User Content Searches Read Mobile Device PreStage Enrollments
Read Advanced User Searches Read Network Segments
Read Buildings Read Packages
Read Categories Read Patch Management Software Titles
Read Cloud Distribution Point Read Policies
Read Computer Extension Attributes Read Printers
Read Computer PreStage Enrollments Read Restricted Software
Read Departments Read Scripts
Read Directory Bindings Read Sites
Read Distribution Points Read Smart Computer Groups
Read Dock Items Read Smart Mobile Device Groups
Read eBooks Read Smart User Groups
Read Enrollment Customizations Read Static Computer Groups
Read Infrastructure Managers Read Static Mobile Device Groups
Read iOS Configuration Profiles Read Static User Groups
Read JSON Web Token Configuration Read User Extension Attributes
Read LDAP Servers Read Volume Purchasing Locations
Read Mac Applications Read Webhooks
Under API Clients create a new API client such as "Back up Jamf Pro objects".
Set API roles to:
Read Jamf Pro objects
Set access token lifetime to a higher value such as 300 seconds (5 minutes).
The script will renew the token as needed.
Enable the API client and copy the client ID and client secret.
3. Update the jamfProURL, clientID, and clientSecret variables below.
4. Specify a saveLocation such as "/Users/Shared" or a folder managed
by file sync service that offers version control (e.g. Dropbox or GitHub).
5. By default, this script will download and create object JSON or XML
files for all the OBJECT TYPES listed below. Add a "#" to the beginning
of those object types to comment them and prevent download.
NOTE: You may need to add additional object types for download and
add them to the API role.
Except where otherwise noted, this work is licensed under
http://creativecommons.org/licenses/by/4.0/
"If you fail to plan, you are planning to fail."
— Benjamin Franklin
ABOUT_THIS_SCRIPT
jamfProURL="https://talkingmoose.jamfcloud.com"
clientID="3c21600a-9740-4fb0-906b-3e6d9986f540"
clientSecret="MEE2yrbMcvH_KQPnOjf4HrXFQoK8tONTKlXdIiKrkxoIEJK2YcQlr_2Ek73eRGVi"
saveLocation="/Users/Shared/"
# create a log file with the same name as the script
currentScript=$( /usr/bin/basename -s .zsh "$0" )
log="${saveLocation}Jamf Pro Objects/$currentScript.log"
# when checkResponseCode is called in the script, return the HTTP status code
# with its human-readable meaning
function checkResponseCode() {
httpErrorCodes="000 No HTTP code received
200 Request successful
201 Request to create or update object successful
204 No content (successful)
400 Bad request
401 Authentication failed
403 Invalid permissions
404 Object/resource not found
409 Conflict
422 Unprocessable Content
429 Too many requests
500 Internal server error
503 Service unavailable"
responseCode=${1: -3}
code=$( /usr/bin/grep "$responseCode" <<< "$httpErrorCodes" )
echo "$code"
}
# when logcomment is called in the script, write a time-stamped comment
# to the log
function logcomment() {
/bin/date "+%Y-%m-%d %H:%M:%S $1" >> "$log"
echo "$1"
}
# when logresult is called in the script, evaluate whether the prior command
# succeeded or failed and write a time-stammped result to the log
function logresult() {
if [ $? = 0 ] ; then
/bin/date "+%Y-%m-%d %H:%M:%S $1" >> "$log"
else
/bin/date "+%Y-%m-%d %H:%M:%S $2" >> "$log"
fi
}
# when requestAccessToken is called in the script, expire the current
# access token, then request a new access token that will live for 10 minutes (as specified in the API client settings
# in Jamf Pro
function requestAccessToken {
# destroy current access token
destroyAccessToken
# request access token
logcomment "Requesting new access token."
accessTokenResponse=$( /usr/bin/curl \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_id=$clientID" \
--data-urlencode "client_secret=$clientSecret" \
--header "Content-Type: application/x-www-form-urlencoded" \
--request POST \
--silent \
--url "$jamfProURL/api/oauth/token" \
--write-out "%{http_code}" )
# report the status of the access token
checkResponseCode "$accessTokenResponse"
# extract token data from response
accessToken=${accessTokenResponse%???}
# parse token from response
token=$( /usr/bin/plutil -extract access_token raw - <<< "$accessToken" )
# parse token life from response
tokenLife=$( /usr/bin/plutil -extract expires_in raw - <<< "$accessToken" )
logcomment "Access token expires in $tokenLife seconds"
# Get the access token lifetime set in API roles and clients
# and renew 15 seconds before it expires
renewalTime=$(( $( date '+%s' ) + $tokenLife - 15 ))
}
function destroyAccessToken {
# expire the access token
logcomment "Destroying current access token."
expireToken=$( /usr/bin/curl \
--header "Authorization: Bearer $token" \
--request POST \
--silent \
--url "$jamfProURL/api/v1/auth/invalidate-token" \
--write-out "%{http_code}" )
# report the status of the access token
checkResponseCode "$expireToken"
}
# when downloadClassicObjectsXML is called in the script,
# 1. get a list of IDs for the given Jamf Pro object type
# 2. download the object XML for each ID found
function downloadClassicObjectsXML {
logcomment "Downloading list of $1 IDs."
# create list of object IDs
ids=$( /usr/bin/curl \
--header "accept: text/xml" \
--header "Authorization: Bearer $token" \
--request GET \
--silent \
--url "$jamfProURL/JSSResource${2}" \
--write-out "%{http_code}" )
# print the HTTP response code and its meaning
checkResponseCode "$ids"
# convert XML into human-readable XML
formattedXML=$( /usr/bin/xmllint --format - <<< "${ids%???}" )
# extract a list of all the object ID numbers and sort them
idsList=$( /usr/bin/sed -n 's/.*<id>\(.*\)<\/id>.*/\1/p' <<< "$formattedXML" | /usr/bin/sort --human-numeric-sort --unique )
if [[ "$idsList" = "" ]]; then
logcomment "Found no $1 objects"
return
fi
# create directories for saving files as needed
/bin/mkdir -p "$saveLocation/Jamf Pro Objects/$1"
# download object XML for each ID
while IFS= read anID
do
now=$( /bin/date +"%s" )
if [[ "$renewalTime" -lt "$now" ]]; then
requestAccessToken
fi
logcomment "Downloading \"$1 ID $anID\" XML"
# download object XML
objectXML=$( /usr/bin/curl \
--header "accept: text/xml" \
--header "Authorization: Bearer $token" \
--request GET \
--silent \
--url "$jamfProURL/JSSResource${2}/id/$anID" \
--write-out "%{http_code}" )
formattedObjectXML=$( /usr/bin/xmllint --format - <<< "${objectXML%???}" )
checkResponseCode "$objectXML"
objectName=$( /usr/bin/awk -F '[><]' '/displayName|name|packageName/ { print $3; exit }' <<< "$formattedObjectXML" )
# extract data from request
echo "$( /usr/bin/xmllint --format - <<< ${objectXML%???} )" > "${saveLocation}Jamf Pro Objects/$1/$objectName (ID $anID).xml"
logresult "Downloaded \"$objectName (ID $anID).xml\"" "FAILED downloading \"$objectName (ID $anID).xml\""
done <<< "$idsList"
}
# when downloadObjectsJSON is called in the script,
# 1. get a list of IDs for the given Jamf Pro object type
# 2. download the object JSON for each ID found
function downloadObjectsJSON {
logcomment "Downloading list of $1 IDs."
# create list of object IDs
ids=$( /usr/bin/curl \
--header "accept: application/json" \
--header "Authorization: Bearer $token" \
--request GET \
--silent \
--url "$jamfProURL/api${2}" \
--write-out "%{http_code}" )
# print the HTTP response code and its meaning
checkResponseCode "$ids"
# extract a list of all the object ID numbers and sort them
idsList=$( /usr/bin/awk -F '"' '/"id"/ { print $4 }' <<< "${ids%???}" | /usr/bin/sort --human-numeric-sort --unique )
if [[ "$idsList" = "" ]]; then
logcomment "Found no $1 objects"
return
fi
# create directories for saving files as needed
/bin/mkdir -p "$saveLocation/Jamf Pro Objects/$1"
# download object JSON for each ID
while IFS= read anID
do
now=$( /bin/date +"%s" )
if [[ "$renewalTime" -lt "$now" ]]; then
requestAccessToken
fi
logcomment "Downloading \"$1 ID $anID\" JSON"
# download object JSON
objectJSON=$( /usr/bin/curl \
--header "accept: application/json" \
--header "Authorization: Bearer $token" \
--request GET \
--silent \
--url "$jamfProURL/api${2}/$anID" \
--write-out "%{http_code}" )
checkResponseCode "$objectJSON"
objectName=$( /usr/bin/awk -F '"' '/displayName|name|packageName/ { print $4; exit }' <<< "$objectJSON" )
# extract data from request
echo "${objectJSON%???}" > "$saveLocation/Jamf Pro Objects/$1/$objectName (ID $anID).json"
logresult "Downloaded \"$objectName (ID $anID).json\"" "FAILED downloading \"$objectName (ID $anID).json\""
done <<< "$idsList"
}
# timestamp when the script begins
startTime=$( /bin/date '+%s' )
requestAccessToken
# OBJECT TYPES
# Each line below downloads every object's data for each object type specified.
# First determine whether to use the Classic or Jamf Pro API.
# Provide a human-readable name for the object type in the first parameter.
# Provide the name of the endpoint in the second parameter.
# Use a "#" at the beginning of each line you want to prevent from downloading.
downloadClassicObjectsXML "Advanced Computer Searches" "/advancedcomputersearches"
downloadClassicObjectsXML "Computer Groups" "/computergroups"
downloadClassicObjectsXML "Directory Bindings" "/directorybindings"
downloadClassicObjectsXML "Distribution Points" "/distributionpoints"
downloadClassicObjectsXML "Dock Items" "/dockitems"
# downloadClassicObjectsXML "Healthcare Listener" "/healthcarelistener"
# downloadClassicObjectsXML "Healthcare Listener Rule" "/healthcarelistenerrule"
downloadClassicObjectsXML "Infrastructure Manager" "/infrastructuremanager"
downloadClassicObjectsXML "JSON Web Token Configurations" "/jsonwebtokenconfigurations"
downloadClassicObjectsXML "LDAP Servers" "/ldapservers"
downloadClassicObjectsXML "Mac Applications" "/macapplications"
downloadClassicObjectsXML "Mobile Device Applications" "/mobiledeviceapplications"
downloadClassicObjectsXML "Mobile Device Configuration Profiles" "/mobiledeviceconfigurationprofiles"
downloadClassicObjectsXML "Mobile Device Extension Attributes" "/mobiledeviceextensionattributes"
downloadClassicObjectsXML "Mobile Device Groups" "/mobiledevicegroups"
downloadClassicObjectsXML "Network Segments" "/networksegments"
downloadClassicObjectsXML "Computer Configuration Profiles" "/osxconfigurationprofiles"
downloadClassicObjectsXML "Policies" "/policies"
downloadClassicObjectsXML "Printers" "/printers"
downloadClassicObjectsXML "Restricted Software" "/restrictedsoftware"
downloadClassicObjectsXML "Sites" "/sites"
downloadClassicObjectsXML "User Extension Attributes" "/userextensionattributes"
downloadClassicObjectsXML "User Groups" "/usergroups"
downloadClassicObjectsXML "Webhooks" "/webhooks"
downloadObjectsJSON "Advanced Mobile Device Searches" "/v1/advanced-mobile-device-searches"
downloadObjectsJSON "Buildings" "/v1/buildings"
downloadObjectsJSON "Computer Extension Attributes" "/v1/computer-extension-attributes"
downloadObjectsJSON "Computer PreStages" "/v3/computer-prestages"
downloadObjectsJSON "Departments" "/v1/departments"
downloadObjectsJSON "eBooks" "/v1/ebooks"
downloadObjectsJSON "Enrollment Customizations" "/v2/enrollment-customizations"
downloadObjectsJSON "Mobile Device PreStages" "/v2/mobile-device-prestages"
downloadObjectsJSON "Packages" "/v1/packages"
downloadObjectsJSON "Patch Software Title Configurations" "/v2/patch-software-title-configurations"
downloadObjectsJSON "Scripts" "/v1/scripts"
downloadObjectsJSON "Volume Purchasing Locations" "/v1/volume-purchasing-locations"
destroyAccessToken
# timestamp when the script ends
stopTime=$( /bin/date '+%s' )
# calculate script run time
logcomment "Script run time: $(($stopTime-$startTime)) seconds
"
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment