A function for your
~/.zshrc
to self-heal thejamf
binary via the Jamf Pro API
Background
We’ve had one too many Macs with sideways configurations lately, which we’ve attempted to resolve by various re-enrollment strategies.
After reviewing Emily’s Jamf binary self-heal with the Jamf API post, we wanted to easily issue the redeploy
command via macOS Terminal.
Assumptions
This method assumes the problematic computer is still receiving and executing MDM commands successfully; please first confirm this by reviewing the computer’s Management History in Jamf Pro.
Caution
To help protect your Jamf Pro server credentials, only execute this script from your computer (i.e., do not configure this script on the affected Mac).
Jamf Pro Configuration
You’ll need a Jamf Pro User Account with (at least) the following privileges:
- Jamf Pro Server Objects
- Computers > Read
- Jamf Pro Server Settings
- Check-in > Read
- Jamf Pro Server Actions
- Flush MDM Commands
- Flush Policy Logs
- Send Computer Remote Command to Install Package
~/.zshrc
Configuration
Backup your current ~/.zshrc
and add the following jbsh
function:
jbsh () { # [j]amf [b]inary [s]elf-[h]eal if [ -z ${1} ]; then printf "\n###\n# [j]amf [b]inary [s]elf-[h]eal\n###\n\n" printf "Usage:\n1. Type \"jbsh\", followed by a [Space]\n2. Paste the computer's Serial Number or Jamf Pro Computer ID\n3. Press [Return]\n\n" printf "(Enter \"jbsh help\" for initial setup.)\n\n" return fi if [ ${1} = "help" ]; then apiURL=$( /usr/bin/defaults read "/Library/Preferences/com.jamfsoftware.jamf.plist" jss_url ) /usr/bin/clear printf "\n###\n# [j]amf [b]inary [s]elf-[h]eal Help\n###\n\n" printf "Use the following commands to create entries in Keychain Access:\n\n" printf " security add-generic-password -s \"jbshApiUrl\" -a ${USER} -w \"${apiURL}\"\n" printf " security add-generic-password -s \"jbshApiUser\" -a ${USER} -w \"API Username Goes Here\"\n" printf " security add-generic-password -s \"jbshApiPassword\" -a ${USER} -w \"API Password Goes Here\"\n\n" printf "Once the Keychain entries have been created …\n\n" printf " 1. Enter \"jbsh\", followed by a [Space]\n 2. Paste the computer's Serial Number or Jamf Pro Computer ID\n 3. Press [Return]\n\n" else apiURL=$( security find-generic-password -s "jbshApiUrl" -a "${USER}" -w 2>/dev/null ) apiUser=$( security find-generic-password -s "jbshApiUser" -a "${USER}" -w 2>/dev/null ) apiPassword=$( security find-generic-password -s "jbshApiPassword" -a "${USER}" -w 2>/dev/null ) if [ -z ${apiURL} ] || [ -z ${apiUser} ] || [ -z ${apiUser} ]; then printf "\n###\n# ERROR: Unable to read API credentials\n###\n\nPlease run \"jbsh help\" to configure API credentials.\n\n" return fi # Obtain Jamf Pro Bearer Token via Basic Authentication apiBearerToken=$( /usr/bin/curl -X POST --silent -u "${apiUser}:${apiPassword}" "${apiURL}/api/v1/auth/token" | /usr/bin/plutil -extract token raw - ) # Determine if user provided a Jamf Pro Computer ID (i.e., a number) or a Serial Number (i.e., NOT a number) numberValidation='^[0-9]+$' if ! [[ "${1}" =~ "${numberValidation}" ]] ; then # Determine the computer's Jamf Pro Computer ID via the computer's Serial Number, "${1}" jssID=$( /usr/bin/curl -H "Authorization: Bearer ${apiBearerToken}" -s "${apiURL}"/JSSResource/computers/serialnumber/"${1}"/subset/general | xpath -e "/computer/general/id/text()" ) else jssID="${1}" fi generalComputerInfo=$( /usr/bin/curl -H "Authorization: Bearer ${apiBearerToken}" -H "Accept: text/xml" -sfk "${apiURL}"/JSSResource/computers/id/"${jssID}/subset/General" -X GET ) # echo ${generalComputerInfo} computerName=$( echo ${generalComputerInfo} | xpath -q -e "/computer/general/name/text()" ) computerSerialNumber=$( echo ${generalComputerInfo} | xpath -q -e "/computer/general/serial_number/text()" ) computerIpAddress=$( echo ${generalComputerInfo} | xpath -q -e "/computer/general/ip_address/text()" ) computerIpAddressLastReported=$( echo ${generalComputerInfo} | xpath -q -e "/computer/general/last_reported_ip/text()" ) printf "\nSelf-healing the Jamf binary for:\n" printf "• Name: $computerName\n" printf "• Serial Number: $computerSerialNumber\n" printf "• IP Address: $computerIpAddress\n" printf "• IP Address (LR): $computerIpAddressLastReported\n\n" printf "Via MDM:\n" printf "• Server: ${apiURL}\n" # printf "• Username: ${apiUser}\n" printf "• Computer ID: ${jssID}\n\n" # Brute-force clear all failed MDM Commands printf "Brute-force clear all failed MDM Commands …\n" /usr/bin/curl -H "Authorization: Bearer ${apiBearerToken}" --progress-bar --fail-with-body "${apiURL}/JSSResource/commandflush/computers/id/${jssID}/status/Failed" -X DELETE # Redeploy Jamf binary printf "\n\nRedeploy Jamf binary …\n" /usr/bin/curl -H "Authorization: Bearer ${apiBearerToken}" -H "accept: application/json" --progress-bar --fail-with-body "${apiURL}"/api/v1/jamf-management-framework/redeploy/"${jssID}" -X POST # Invalidate the Bearer Token apiBearerToken=$( /usr/bin/curl "${apiURL}/api/v1/auth/invalidate-token" --silent --header "Authorization: Bearer ${apiBearerToken}" -X POST ) apiBearerToken="" # Set the default browser browser="" if [ "${browser}" == "" ]; then printf "\n\n(Preferred browser not specified; using Safari.)\n\n" browser="Safari" fi case ${browser} in Chrome ) browserPath="/Applications/Google Chrome.app/" ;; Firefox ) browserPath="/Applications/Firefox.app/" ;; Safari ) browserPath="/Applications/Safari.app/" ;; * ) browserPath="/Applications/Safari.app/" ;; esac # Open the "Management" tab for the Jamf Pro Computer Record printf "Viewing Computer Record in ${browser} …\n\n" url="${apiURL}/computers.html?id=${jssID}&o=r&v=management" /usr/bin/open -a "${browserPath}" "${url}" printf "\n\n" fi }
Function Configuration
After you’ve added the above jbsh
function and saved your ~/.zshrc
, quit and re-launch Terminal.
In Terminal, enter jbsh
and press Return; you should observe the following:
❯ jbsh ### # [j]amf [b]inary [s]elf-[h]eal ### Usage: 1. Type "jbsh", followed by a [Space] 2. Paste the computer's Serial Number or Jamf Pro Computer ID 3. Press [Return] (Enter "jbsh help" for initial setup.)
Again in Terminal, enter jbsh help
, press Return and you should observe a personalized version of the following:
❯ jbsh help ### # [j]amf [b]inary [s]elf-[h]eal Help ### Use the following commands to create entries in Keychain Access: security add-generic-password -s "jbshApiUrl" -a myShortName -w "https://companyname.jamfcloud.com/" security add-generic-password -s "jbshApiUser" -a myShortName -w "API Username Goes Here" security add-generic-password -s "jbshApiPassword" -a myShortName -w "API Password Goes Here" Once the Keychain entries have been created … 1. Enter "jbsh", followed by a [Space] 2. Paste the computer's Serial Number or Jamf Pro Computer ID 3. Press [Return]
Follow the on-screen instructions to use the security
command to add the three entries to your login Keychain, using your Jamf Pro server address and the Jamf Pro User Account credentials you created earlier:
- Jamf Pro URL
- API Username
- API Password
Function Usage
Enter jbsh
followed by a Space, paste either the computer’s Serial Number
or its Jamf Pro Computer ID
and press Return.
For at least your first run, you’ll most likely see a dialog box(es) similar to the following; enter your Mac’s login
keychain credentials to proceed.
Presuming I didn’t make any coding errors, you should observe something similar to the following:
❯ jbsh D13YC7QRTE8Y Self-healing the Jamf binary for: • Name: Test MacBook Pro • Serial Number: D13YC7QRTE8Y • IP Address: 17.32.61.186 • IP Address (LR): 172.17.0.29 Via MDM: • Server: https://companyname.jamfcloud.com • Computer ID: 7279 Brute-force clear all failed MDM Commands … <?xml version="1.0" encoding="UTF-8"?><commandflush><status>+failed</status><computers>[7279]</computers></commandflush> Redeploy Jamf binary … { "deviceId" : "7279", "commandUuid" : "d11deddf-9ad6-41e2-8e2d-3141bfb1db61" } (Preferred browser not specified; using Safari.) Viewing Computer Record in Safari …
Safari should open the Computer Record to the Management tab, where you can view any pending MDM commands:
In my testing, occasionally the command would fail, so the function also leverages a brute-force commandflush
for all failed commands.
Also, on occasion, the command was successfully issued faster than I could observe its progress. If this happens, switch over to the History tab and view Management History:
Monitor Progress
As Emily points out, you can use the following command — client-side — to check the ”healing” progress:
log show --info --debug --predicate 'subsystem == "com.apple.ManagedClient" AND category == "ManagedApps"' --last 1h
Troubleshooting
Jamf Pro User Account Privileges
You may wish to manually validate the Jamf Pro User Account’s privileges by using the Swagger UI to confirm the Redeploy Jamf Management Framework command works as expected:
# Authorize Jamf Pro User Account # The Jamf Pro API uses a token-based authentication. Enter username and password to generate a token to be used with the 'Try it out!' feature. apiURL=$( /usr/bin/defaults read "/Library/Preferences/com.jamfsoftware.jamf.plist" jss_url ) ; open ${apiURL}/api/doc/ # Redeploy Jamf Management Framework open ${apiURL}/api/doc/#/jamf-management-framework
Updates
January 2023
Be sure to checkout Richard Mallion’s Jamf Framework Redeploy.