Unmanaging macOS devices through API

rcoleman
New Contributor III

Hi folks, struggling to find an answer for this.

We have a number of machines unable to run Big Sur, so I wish to deploy a script on all of these devices that remove these devices from support. I've achieved similar in the past where I can actually remove all the local jamf components, licensed apps etc.. and also delete the JSS record for the specific device using an API call. However, looking forward, I'd like to keep the JSS record available as they may contain encryption keys that we want to keep a hold of.

Basically, I'm looking for a way to "Unmanage" these devices by unticking the "Allow Jamf Pro to perform management tasks" checkbox for each machine through the API, as I believe this will free up licenses:

Screenshot 2023-01-30 at 15.39.47.png

I've had a look online, and found some instances that I believe provide a resolution but I can't get anything to de-select this checkbox. For example :

https://community.jamf.com/t5/jamf-pro/script-to-remove-management-through-api/m-p/233611

https://community.jamf.com/t5/jamf-pro/help-sending-post-command-to-jss-api/td-p/192968

But the above are older posts and don't seem to be using tokens for authentication, and I just don't seem to be able to get the proper syntax when attempting a POST or PUT.

I can obtain the bearer tokens fine (using encrypted JSS variables with a dedicated API account) and send other GET API calls with no problems, so don't believe this to be an authorisation issue. The account I'm using for testing purposes has full access to perform all API calls.

The script I'm attempting to create, grabs the serial from the local device, uses the serial to grab the specific computer ID in the JSS using an API call (this also works fine), and then uses the ID to attempt to unmanage the device (this below is just a stand alone script for testing purposes):

 

#!/bin/bash

echo "Enter JSS username:"
read USERNAME
echo "Enter JSS password:"
read -s PASSWORD

TOKEN_EXPIRATION_EPOCH="0"

function getBearerToken() {
    RESPONSE=$(curl -s -u "$USERNAME":"$PASSWORD" "https://<our_server>.jamfcloud.com/api/v1/auth/token" -X POST)
    BEARER_TOKEN=$(echo "$RESPONSE" | plutil -extract token raw -)
	TOKEN_EXPIRATION=$(echo "$RESPONSE" | plutil -extract expires raw - | awk -F . '{print $1}')
	TOKEN_EXPIRATION_EPOCH=$(date -j -f "%Y-%m-%dT%T" "$TOKEN_EXPIRATION" +"%s")
}

function checkTokenExpiration() {
    NOW_EPOCH_UTC=$(date -j -f "%Y-%m-%dT%T" "$(date -u +"%Y-%m-%dT%T")" +"%s")
    if [[ TOKEN_EXPIRATION_EPOCH -gt NOW_EPOCH_UTC ]]
    then
        echo "Token valid until the following epoch time: " "$TOKEN_EXPIRATION_EPOCH"
    else
        echo "No valid token available, getting new token"
        getBearerToken
    fi
}

function invalidateToken() {
	RESPONSE_CODE=$(curl -w "%{http_code}" -H "Authorization: Bearer ${BEARER_TOKEN}" "https://<our_server>.jamfcloud.com/api/v1/auth/invalidate-token" -X POST -s -o /dev/null)
	if [[ ${RESPONSE_CODE} == 204 ]]
	then
		echo "Token successfully invalidated"
		BEARER_TOKEN=""
		TOKEN_EXPIRATION_EPOCH="0"
	elif [[ ${RESPONSE_CODE} == 401 ]]
	then
		echo "Token already invalid"
	else
		echo "An unknown error occurred invalidating the token"
	fi
}

echo "Getting API token..."
checkTokenExpiration

# Get serial number
SERIAL=$(system_profiler SPHardwareDataType | awk '/Serial/ {print $4}')
/bin/echo "Serial number is $SERIAL"

# Get JAMF ID of device
JAMF_ID=$(curl -X GET "https://<our_server>.jamfcloud.com/JSSResource/computers/serialnumber/$SERIAL" -H "accept: application/xml" -H "Authorization: Bearer $BEARER_TOKEN" | xmllint --xpath '/computer/general/id/text()' -)
/bin/echo "JAMF ID for $SERIAL is $JAMF_ID"

# Unmanage the device
/bin/echo "Attempting to remove device from Jamf management..."
curl -X POST "https://<our_server>.jamfcloud.com/JSSResource/computercommands/command/UnmanageDevice/id/$JAMF_ID" -H "accept: application/xml" -H "Authorization: Bearer $BEARER_TOKEN"

# Bin the token
/bin/echo "Invalidating API token..."
invalidateToken

/bin/echo "Done."
exit 0;

 

Strangely, when I run the above, there does appear to be an "Unenroll" command sent to the JSS:

Screenshot 2023-01-30 at 16.05.54.png

However, nothing seems to be changing.

Apologies if I'm missing something which is fairly straight forward, but I'm just going round in circles.

Anyone any ideas?

Many thanks

 

47 REPLIES 47

Guys, Please find the below screenshot and let me know how to remove this profile.. It's not getting removed by running remove jamf framework command. 

sk25_2-1687172144575.png

Because of which unable to enroll it in another MDM solution. Kindly help. Thanks.

 

jamf-42
Valued Contributor II

that CA is installed at user level..  so you can remove that by clicking on the minus button, or via terminal / script

MLBZ521
Contributor III

It looks like this device was enrolled via UIE and not ADE.  If that the case, you don't even need the API script to unmanage the device.  Simply running `jamf removeFramework` would remove the MDM Profile and the Jamf Pro management Framework.

If that Profile isn't being removed with the MDM Profile, then, as jamf-42 eluded to, you can remove it via the `profiles` command.  You'll just need to identify which Profile it is to remove it.

The reason why I've API is to unmanage machine from Jamf side thatsy.. 

MLBZ521
Contributor III

Are you wanting to unmanage the device in Jamf Pro when it is being "unmanaged" (Framework and such) on the device, in other words, at the same time?

If so, I have a little project I wrote years ago to migrate devices from one Jamf Pro instance to another.  It could adapt the code from it for your needs:  https://github.com/MLBZ521/jamfMigrator 

ScottyBeach
Contributor

@rcoleman , very nice script! Worked for me. We've got a couple hundred Macs on stock shelves so it's not practical to fire each one up to run the script locally. Neither is it all that practical to deselect "Allow Jamf Pro to perform management tasks" for each record individually. I'd like to make a smart group of Macs not checked in in, say, 60 days and listed as unassigned and have those each pulled out of management by having this script run on their JSS records.

I can't figure out how to trigger that. Without those shelved Macs checking in, how would I prompt this policy to run? As they hit the 60 day mark or if someone marks their inventory record as "unassigned" I'd like the script to run. 

Any ideas to make this happen?

Thanks,

- Scott

rcoleman
New Contributor III

Glad it helps! Sorry, I'm not exactly sure what you mean when you say 'unassigned'? If I'm understanding correctly, you want to create a smart group for machines that have not checked in within 60 days, and then have this script execute and disable the 'Allow Jamf Pro to perform management tasks' checkbox for all devices in the smart group?

I'm certain there would be a better way to go about this but all I can think of at the moment is to create the smart group, then use an API call to obtain a list of all the devices in the smart group and then iterate through them and disable the option. You would still need to manually run the script once every day though, unless you create a LaunchDaemon to run the script every day using the 'StartCalendarInterval' option. Performing it this way though would require passwords to be entered into the script (needed for the API call), either in plain text or obfuscated, which of course is not ideal.

ScottyBeach
Contributor

Yes, that was it. "Unassigned" means not currently attached to a user. On a stock room shelf.

I opted to just export the contents of a smarlist periodically (two or three times a year maybe) and copy paste those SNs into your script as an array and run that locally from my Mac. Works very nicely. Thanks again for it!

- Scott

Looks like this:

 

#!/bin/bash
 
# This can't be run from Jamf. We're just storing it here.
# Download it to your Mac and run it in Terminal.
 
echo "Enter JSS username:"
read USERNAME
echo "Enter JSS password:"
read -s PASSWORD
 
TOKEN_EXPIRATION_EPOCH="0"
 
function getBearerToken() {
    RESPONSE=$(curl -s -u "$USERNAME":"$PASSWORD" "https://jssurl:8443/api/v1/auth/token" -X POST)
    OS_MAJOR_VERSION=$(sw_vers -buildVersion | cut -c 1-2)
    echo "OS Major Version: $OS_MAJOR_VERSION"
    if [ "$OS_MAJOR_VERSION" -lt 21 ]; then
        # Get the token info
        BEARER_TOKEN=$(echo $RESPONSE | python -c 'import json,sys;obj=json.load(sys.stdin);print obj["token"]')
        # Get the expiration date
        TOKEN_EXPIRATION=$(echo $RESPONSE | python -c 'import json,sys;obj=json.load(sys.stdin);print obj["expires"]')
    # If we are running Monterey or later then we can use plutil to parse json
    else    
        # Get the token info
        BEARER_TOKEN=$(echo "$RESPONSE" | plutil -extract token raw -)    
        # Get the token expiration date
        TOKEN_EXPIRATION=$(echo "$RESPONSE" | plutil -extract expires raw - | awk -F . '{print $1}')
    fi
TOKEN_EXPIRATION_EPOCH=$(date -j -f "%Y-%m-%dT%T" "$TOKEN_EXPIRATION" +"%s")
}
 
function checkTokenExpiration() {
    NOW_EPOCH_UTC=$(date -j -f "%Y-%m-%dT%T" "$(date -u +"%Y-%m-%dT%T")" +"%s")
    if [[ TOKEN_EXPIRATION_EPOCH -gt NOW_EPOCH_UTC ]]
    then
        echo "Token valid until the following epoch time: " "$TOKEN_EXPIRATION_EPOCH"
    else
        echo "No valid token available, getting new token"
        getBearerToken
    fi
}
 
function invalidateToken() {
RESPONSE_CODE=$(curl -w "%{http_code}" -H "Authorization: Bearer ${BEARER_TOKEN}" "https://rcimac.rci.rogers.com:8443/api/v1/auth/invalidate-token" -X POST -s -o /dev/null)
if [[ ${RESPONSE_CODE} == 204 ]]
then
echo "Token successfully invalidated"
BEARER_TOKEN=""
TOKEN_EXPIRATION_EPOCH="0"
elif [[ ${RESPONSE_CODE} == 401 ]]
then
echo "Token already invalid"
else
echo "An unknown error occurred invalidating the token"
fi
}
 
echo "Getting API token..."
checkTokenExpiration
 
# Paste in a list of Mac SNs to be removed from management:
 
unmanage=(
SN#####
SN#####
SN#####
)
for SERIAL in ${unmanage[@]}
do
 
# This next commented code is to get the serial number of the Mac from which the script 
# is running in the case of performing the script on this local Mac to remove it from management.
# I've turned it off in favour of using an array of provided SNs of other Macs. See above.
# to remove from management.
 
# Get local serial number:
 
# SERIAL=$(system_profiler SPHardwareDataType | awk '/Serial/ {print $4}')
# /bin/echo "Serial number is $SERIAL"
 
# Get JAMF ID of device from API looked by SN found locally or provided in
# $unmanage array:
JAMF_ID=$(curl -X GET "https://rcimac.rci.rogers.com:8443/JSSResource/computers/serialnumber/$SERIAL" -H "accept: application/xml" -H "Authorization: Bearer $BEARER_TOKEN" | xmllint --xpath '/computer/general/id/text()' -)
 
# API call to de-select "Allow Jamf Pro to perform management tasks" in the JSS for this device:
curl --request PUT --url "https://rcimac.rci.rogers.com:8443/JSSResource/computers/id/$JAMF_ID" -H "Content-Type: application/xml" -H "Accept: application/xml" -H "Authorization: Bearer $BEARER_TOKEN" -d '<computer><general><remote_management><managed>false</managed></remote_management></general></computer>'
 
/bin/echo "JAMF ID for $SERIAL is $JAMF_ID and it is now unmanaged in the JSS"
done
 
# Bin the token
/bin/echo "Invalidating API token..."
invalidateToken
 
/bin/echo "Done."
 
exit 0;