Menu Close

Jamf binary self-heal via Terminal

A function for your  ~/.zshrc to self-heal the jamf 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.

Posted in API, Device Management, Jamf Pro, macOS, Scripts, Tips & Tricks

Related Posts