01-30-2023 08:10 AM - edited 02-03-2023 07:15 AM
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:
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:
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
Posted on 01-30-2023 08:20 AM
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?
Posted on 01-30-2023 09:13 AM
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?
Posted on 01-31-2023 02:29 AM
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>'
Posted on 02-01-2023 07:55 AM
@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!
02-01-2023 08:10 AM - edited 02-01-2023 08:19 AM
@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:
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.
Posted on 02-02-2023 08:07 AM
@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 :(
Posted on 02-03-2023 12:50 AM
@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!
Posted on 02-03-2023 03:30 AM
@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.
Posted on 02-03-2023 04:40 AM
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
Posted on 02-11-2023 11:43 AM
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.
Posted on 02-20-2023 04:34 AM
@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>
Posted on 02-20-2023 06:27 AM
I worked it out 😀
02-20-2023 06:32 AM - edited 02-20-2023 06:33 AM
@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 😀