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

 

13 REPLIES 13

rcoleman
New Contributor III

Ok, think I may be looking at this wrong. I've just noticed that on my test device, all profiles have now gone, and that there is no option in the JSS Record for the device for issuing Management commands, so it looks like it may actually be "unmanaging" the device anyway. 

Does anyone know of anyway to use the API to de-select the ""Allow Jamf Pro to perform management tasks" checkbox anyway? 

rcoleman
New Contributor III

I feel like I'm narrowing it down. Looking here: https://developer.jamf.com/jamf-pro/reference/updatecomputerbyid

I've tried the following:

curl --request PUT --url "https://<our_server>.jamfcloud.com/JSSResource/computers/id/$JAMF_ID" -H "Content-Type: application/xml" -H "Accept: application/xml" -H "Authorization: Bearer $BEARER_TOKEN" '<computer><general><remote_management><managed>false</managed></remote_management></general></computer>'

But now getting an error:

curl: (6) Could not resolve host: <computer><general><remote_management><managed>false<

Any ideas what I'm missing?

rcoleman
New Contributor III

Ok figured it out. The exact command required is:

curl --request PUT --url "https://<our_server>.jamfcloud.com/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>'

 

Mikael_lofgren
New Contributor II

@rcoleman I trying to get the Unmanaged Command to work, do you know what minimum permissions that is needed? I have Jamf Pro Server Actions -> Send Computer Unmanage Command and Computer Read
Thanks!

@Mikael_lofgren I actually just ran into this issue. We use a dedicated API account with encrypted details for performing API operations and on my last test with this it complained about permissions. During my testing, I used my own account which has full permissions and it worked fine. Unfortunately I don't have time just now to investigate this further however on first look I believe for the account being used to perform API operations you'll need to make sure "Send Computer Unmanage Command" is selected. If you manage to try this let me know if it works:

Screenshot 2023-02-01 at 16.00.40.png

EDIT - Sorry, I've just realised that you've mentioned this is already selected! In that case I'm not sure what will be required. We also use the account for enabling remote management using MDM command and that works fine. When I get time to investigate further and if I come up with a solution then I'll make sure to post.

@Mikael_lofgren Just to say this worked for myself and I only have the same permissions you listed above, so apologies but I'm not sure what's going wrong at your side :(

Mikael_lofgren
New Contributor II

@rcoleman My command seems to work when using full permissions, I have opened a support case to get this sorted, can post any findings here when done. Thanks!

rcoleman
New Contributor III

@Mikael_lofgren That would be great, many thanks. It's possible I made a change to the permissions a long time ago for our API account but just can't remember, so it certainly would be useful to know exactly what permissions are required.

Mikael_lofgren
New Contributor II

Ahh think I missed, create in Computer objects, but this is the minimum to work, response code from API when working is 201.
# Jamf Pro Server Objects > Computers Create, Read and Update
# Jamf Pro Server Actions > Send Computer Unmanage Command

MLBZ521
Contributor III

Sending the MDM Command to "Unmanage" a device requires the device to be active to receive it -- in other words, the device is not marked as "unmanaged" until it receives the command and responds back to the Jamf Pro Server.

 

The other method you used, sending the remote_management > managed > false payload does not rely on the device being active, however, it does not remove anything from the device (e.g. the Jamf Management Framework, aka the jamf binary, MDM Profile, etc.); in this scenario the device does not know it has been unmanaged and will continue to attempt to check-in, as well as apply any and all configurations that it has "cached" on the device.

 

As noted, to perform this operation, the account will need Update privileges to Computers (at minimum), but it's not uncommon that additional privileges, that seem excessive given the task, are required (such as Create and Read) in Jamf's implementation of numerous API endpoints.  There's even been occurrences where permissions for completely separate objects are required to perform operations on another object.

MatG
Contributor III

@rcoleman 
Just looking at this myself but still not getting the Mac to move into the umanaged devices in Jamf, the tick box is still ticked.

Any chance you can post your full updated script as the first script posted does a full unenrol unmanage which removes all jamf framework which is not what I think we are both trying to achieve and I can't seem to get the syntax right when adding in your updated curl setting this

<remote_management><managed>false</managed></remote_management>

 

MatG
Contributor III

I worked it out 😀

rcoleman
New Contributor III

 @MatG - Sure no problem. I've removed the "Unmanage" command and you'll need to replace the server name but this works for myself for unticking:

 

#!/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://<your_server>.jamfcloud.com/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://uoe.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://<your_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"

# De-select "Allow Jamf Pro to perform management tasks" in the JSS for this device
curl --request PUT --url "https://<your_server>.jamfcloud.com/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 the token
/bin/echo "Invalidating API token..."
invalidateToken

/bin/echo "Done."

exit 0;

EDIT - Ah, I just see you got it working - good stuff 😀