Strategies Recovery Lock Removal and License Reclamation When Benching Devices in Jamf

New Contributor III

Hey Guys!

I'm looking to gather insights on the best practices for using a script to bench and store devices while also ensuring the removal of all essential settings, including management licenses, to free up licenses until the devices are reactivated.




log_message() {
    echo "$1" >> "$LOGFILE"
    echo "$1"

log_message "Script started on $(date)"
log_message ""

invalidateToken() {
   /usr/bin/curl --header "Authorization: Bearer $token" --request POST --silent --url "$jamfProURL/api/v1/auth/invalidate-token"
   log_message "Token invalidated."
   log_message ""

trap 'invalidateToken' EXIT

function sendToGoogleSheet {
    # Gather system details
    timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    loggedinuser=$(stat -f%Su /dev/console)
    serialnumber=$(system_profiler SPHardwareDataType | awk '/Serial/ {print $4}')
    macosversion=$(sw_vers -productVersion)

    # Check for the presence of the jamf binary
    if which jamf > /dev/null 2>&1; then
        log_message "Jamf Status Active"
        log_message ""
        log_message "Jamf Removed"
        log_message ""

    # This will base64-encode your data
    ENCODED_DATA=$(echo -n "$DATA_TO_SEND" | base64)

    # Ensure DATA_TO_SEND is not empty
    if [[ -z "$DATA_TO_SEND" ]]; then
        log_message "Error: DATA_TO_SEND is empty."
        return 1

    # Ensure ENCODED_DATA is not empty
    if [[ -z "$ENCODED_DATA" ]]; then
        log_message "Error: ENCODED_DATA is empty."
        return 1

    # URL of the deployed Google Apps Script web app

    # Use curl with the -L flag to follow redirects and send a GET request to the Google Apps Script web app
    curl -L "$WEB_APP_URL"

    log_message "Data sent to Google Sheet."

# Get the logged in user
LOGGED_IN_USER=$(stat -f%Su /dev/console)
log_message "Logged in user: $LOGGED_IN_USER"

# Function to prompt for Jamf admin credentials
getJamfCredentials() {
    local uname=$(sudo -u $LOGGED_IN_USER osascript -e 'tell app "System Events" to text returned of (display dialog "Enter Jamf admin username:" default answer "" buttons {"OK"} default button "OK")')
    local pwd=$(sudo -u $LOGGED_IN_USER osascript -e 'tell app "System Events" to text returned of (display dialog "Enter Jamf admin password:" default answer "" with hidden answer buttons {"OK"} default button "OK")')
    echo "$uname:$pwd"

# Prompt technician for Jamf admin credentials


# Request auth token
#authToken=$( /usr/bin/curl --request POST --silent --url "$jamfProURL/api/v1/auth/token" --user "$username:$password" )
#token=$(echo "$authToken" | /usr/bin/plutil -p - | awk -F\" '/"token"/ {print $4}')

# Request auth token
authToken=$( /usr/bin/curl --request POST --silent --url "$jamfProURL/api/v1/auth/token" --user "$username:$password" )
token=$( /usr/bin/plutil -extract token raw - <<< "$authToken" )

# Error handling for missing token
if [[ -z "$token" ]]; then
    log_message "Failed to retrieve authentication token."
    sudo -u $LOGGED_IN_USER osascript -e 'display dialog "Failed to retrieve authentication token from Jamf server. Check your credentials and ensure Jamf Pro is reachable." buttons {"OK"} default button "OK"'
    exit 1

log_message "Token acquired: $token"

udid=$(system_profiler SPHardwareDataType | awk '/UUID/ { print $3; }')
log_message "Machine UDID: $udid"

compIdResponse=$(curl -H "Authorization: Bearer $token" -H "Accept: application/xml" "${jamfProURL}/JSSResource/computers/udid/${udid}/subset/general")

# Check for curl errors
if [ $? -ne 0 ]; then
    log_message "Error encountered while fetching Comp ID:"
    log_message "$compIdResponse"
    exit 1

compId=$(echo "$compIdResponse" | xpath -e "//computer/general/id/text()" 2>/dev/null)

# Error handling for missing Computer ID
if [[ -z "$compId" ]]; then
    log_message "Error: Computer ID is missing."
    exit 1

echo "Computer ID is: $compId"

architecture=$(uname -m)
log_message "Machine architecture: $architecture"

case "$architecture" in
    # ... [No changes to the "x86_64" section]

        recoveryPassword=""  # Note: This should be securely populated
        response=$(curl --request POST \
            --url "$jamfProURL/api/preview/mdm/commands" \
            --header "Authorization: Bearer $token" \
            --header 'accept: application/json' \
            --header 'content-type: application/json' \
            --data '
                "clientData": [
                        "managementId": "'$udid'",
                        "clientType": "COMPUTER"
                "commandData": {
                    "commandType": "SetRecoveryLockCommand",
                    "newPassword": ""
        ') 2>&1

        # Check for curl errors
        if [ $? -ne 0 ]; then
            log_message "Error encountered while setting recovery lock:"
            log_message "$response"
            exit 1

        log_message "Recovery lock set successfully"
        log_message "Unknown architecture. Exiting script."
        exit 1

# Updating Computer Name and Reporting Back to Jamf Pro

log_message "Renaming computer from '${currentName}' to '${newName}'"
log_message ""

/usr/local/jamf/bin/jamf setComputerName -name "$newName"
/usr/local/jamf/bin/jamf recon

    log_message "Sending info to Google..."

sudo -u $LOGGED_IN_USER osascript -e "display dialog \"The device has been renamed to $newName and will be wiped shortly. Please ensure you've backed up any important data.\" buttons {\"OK\"} default button \"OK\" giving up after 5" &

sleep 5

# Send the EraseDevice command
response=$( /usr/bin/curl \
    --header "Authorization: Bearer $token" \
    --header "Content-Type: text/xml" \
    --request POST \
    --silent \
    --url "$jamfProURL/JSSResource/computercommands/command/EraseDevice/passcode/164700/id/$compId" )

# Check for curl errors
if [ $? -ne 0 ]; then
    log_message "Error encountered while attempting to wipe the device:"
    log_message "$response"
    exit 1
log_message ""
log_message "Device wipe command sent successfully."
log_message "Script completed on $(date)"

exit 0


The script I'm currently using does the following:

  • Invalidates the authentication token at the end of the session for security.
  • Sends updated device details to a Google Sheet via encoded data.
  • Requests Jamf Pro credentials from the technician for operations that require admin rights.
  • Retrieves the machine UDID and computer ID from Jamf Pro for accurate targeting.
  • Renames the device to a benching-specific identifier and reports back to Jamf Pro.
  • Sends an EraseDevice command to the Mac, ensuring it is ready for benching and storage.
  • Logs all actions to a file for a clear audit trail.

The script's primary goals are:

  1. To ensure devices are prepared for storage by removing critical settings.
  2. To free up management licenses when a device is benched, making them available for future use.

My principal concern pertains to the persistence of the 'Recovery Lock' after initiating the 'Erase Device' command. Curiously, no errors are flagged during the process, yet the 'Recovery Lock' remains intact. If anyone has devised a reliable method to effectively clear the 'Recovery Lock', ensuring that it's fully removed following an erase, I'd be eager to review your approach.

Additionally, I would be very happy to look at alternative processes if someone has found a different effective pathway.

I'm grateful for any assistance and the opportunity to learn from the community's collective wisdom.