Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save karthikeyan-mac/08b95e4ae6dbabaa48b3dcac9f09305e to your computer and use it in GitHub Desktop.
Save karthikeyan-mac/08b95e4ae6dbabaa48b3dcac9f09305e to your computer and use it in GitHub Desktop.
#!/bin/bash
################################################################################
# Description:
# Adds or removes a computer (by Serial Number) to/from a static group
# in Jamf Pro using the OAuth 2.0 API with client credentials.
#
# Prerequisites:
# - Jamf Pro instance must support Bearer Token API authentication.
# - client_id and client_secret must belong to an API role with proper privileges.
# - API Privileges : Update Static Computer Groups, Read Static Computer Groups
#
# Karthikeyan Marappan
# Date: 2025-04-09
# Usage:
# Configure the Jamf policy parameters.
################################################################################
###################### CONFIGURABLE VARIABLES ################################## # Ensure no trailing slash
client_id="$4" # API Client ID
client_secret="$5" # API Client Secret
staticGroupID="$6" # ID of the static computer group in Jamf Pro
actionRequired="$7" # Action to perform: ADD or REMOVE
################################################################################
# Get the SerialNumber
serialNumber=$(system_profiler SPHardwareDataType | awk '/Serial/ {print $4}') # Serial number of the Mac to manage
jamfPrefFile="/Library/Preferences/com.jamfsoftware.jamf.plist"
if [[ -f "$jamfPrefFile" ]]; then
Jamf_URL=$(defaults read "$jamfPrefFile" jss_url | sed 's:/*$::')
else
echo "Error: $jamfPrefFile not found." >&2
exit 1
fi
# Function for timestamped logging
log() {
echo "$(date +"%Y-%m-%d %H:%M:%S") - $1"
}
# Validate all required inputs before proceeding
if [[ -z "$serialNumber" || -z "$Jamf_URL" || -z "$client_id" || -z "$client_secret" || -z "$staticGroupID" || -z "$actionRequired" ]]; then
log "Error: One or more required variables are empty."
exit 1
fi
# Translate action to Jamf XML API tag
case "$actionRequired" in
ADD) action="computer_additions" ;;
REMOVE) action="computer_deletions" ;;
*)
log "Error: actionRequired must be 'ADD' or 'REMOVE'."
exit 1
;;
esac
# Function to retrieve access token from Jamf Pro
getAccessToken() {
log "Fetching Jamf API token from ${Jamf_URL}"
response=$(curl --silent --fail-with-body --location --request POST "${Jamf_URL}/api/oauth/token" \
--header "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "client_id=${client_id}" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_secret=${client_secret}")
if [[ $? -ne 0 || -z "$response" ]]; then
log "Error: Failed to obtain access token. Check Jamf URL and credentials."
exit 1
fi
access_token=$(echo "$response" | plutil -extract access_token raw -)
if [[ "$access_token" == "null" ]]; then
log "Error: Invalid API client credentials. Check API Role permissions."
exit 1
fi
log "Successfully obtained API token."
}
# Function to invalidate API token after usage
invalidateToken() {
log "Invalidating API token..."
responseCode=$(curl -w "%{http_code}" -H "Authorization: Bearer ${access_token}" \
"${Jamf_URL}/api/v1/auth/invalidate-token" -X POST -s -o /dev/null)
case "$responseCode" in
204) log "Token successfully invalidated." ;;
401) log "Token already invalid." ;;
*) log "Unexpected response code during token invalidation: $responseCode" ;;
esac
}
# Function to check if a serial number already exists in the static group
serialNumberExistsInGroup() {
log "Checking if serial number exists in static group..."
response=$(curl -s --location --request GET "$Jamf_URL/JSSResource/computergroups/id/$staticGroupID" \
--header "Accept: application/xml" \
--header "Authorization: Bearer $access_token" \
--write-out '\n%{http_code}')
http_code=$(tail -n1 <<< "$response")
xml_body=$(printf "%s\n" "$response" | sed '$d')
if [[ "$http_code" == 200 ]]; then
groupName=$(echo $xml_body | xmllint --xpath '/computer_group/name/text()' -)
log "Static Group Found. Group Name: \"$groupName\""
elif [[ "$http_code" == 401 ]]; then
log "Unauthorized to read static group. Check API role permissions."
exit 1
else
log "Failed to retrieve static group info. HTTP code: $http_code. Please check if the Static Group ID: $staticGroupID exists in JAMF"
exit 1
fi
# Check if the serial number is listed in the group
if echo "$xml_body" | grep -q "<serial_number>${serialNumber}</serial_number>"; then
return 0 # Serial number exists in group
else
return 1 # Serial number does not exist
fi
}
# Main logic for modifying static group membership
changeStaticComputerGroup() {
serialNumberExistsInGroup
exists=$?
if [[ $exists -eq 0 && "$actionRequired" == "ADD" ]]; then
log "Serial number $serialNumber already exists in group. Skipping add."
return
elif [[ $exists -eq 1 && "$actionRequired" == "REMOVE" ]]; then
log "Serial number $serialNumber not found in group. Skipping removal."
return
fi
# Construct XML payload for PUT request
local xmlData="<computer_group><${action}><computer><serial_number>${serialNumber}</serial_number></computer></${action}></computer_group>"
response=$(curl -s --location --request PUT "$Jamf_URL/JSSResource/computergroups/id/$staticGroupID" \
--header "Accept: application/xml" \
--header "Content-Type: application/xml" \
--header "Authorization: Bearer $access_token" \
--data-raw "$xmlData" \
--write-out '\n%{http_code}')
http_code=$(tail -n1 <<< "$response")
if [[ "$http_code" == 201 ]]; then
if [[ $actionRequired == "ADD" ]]; then
actionPerformed="added"
else
actionPerformed="removed"
fi
log "Successfully $actionPerformed serial: $serialNumber in Static Group: \"$groupName\""
elif [[ "$http_code" == 409 ]]; then
log "Serial number does not exist in Jamf or the device is unmanaged: $serialNumber"
elif [[ "$http_code" == 401 ]]; then
log "Unauthorized to update static group. Check API role permissions."
exit 1
else
log "API request failed with HTTP code $http_code for serial: $serialNumber"
fi
}
# Main entry point
main() {
getAccessToken
changeStaticComputerGroup
invalidateToken
}
main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment