Skip to content

Instantly share code, notes, and snippets.

@t-lark
Created October 10, 2023 00:27
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save t-lark/0c1dc014a22035e61bd073fa48dd82f9 to your computer and use it in GitHub Desktop.
Save t-lark/0c1dc014a22035e61bd073fa48dd82f9 to your computer and use it in GitHub Desktop.
Un-manage devices in Jamf via Classic API
#!/opt/snowflake/bin/python3
"""
In Jamf Pro version 10.49 and higher, Jamf has removed the ability to mass edit management account settings
from a saved search results page via mass actions. This means that you either have to un-manage old devices by manually
un-managing them one by one in their device record, or write some sort of API script. You can also technically delete
the device record if that is an acceptable use case at your Org.
This script will take the JSS ID from a smart group, grab the ID of each device and store it in a Python list. Then it will
iterate through that list and send Classic API commands to remove the management account thus freeing up the license.
This code was used to free up licenses of unused devices recently, but
see this for more in-depth details:
https://derflounder.wordpress.com/2023/08/15/updating-management-status-in-jamf-pro-computer-inventory-records-on-jamf-pro-10-49-0-and-later/
Use this feature request to ask for this feature back:
https://ideas.jamf.com/ideas/JN-I-27551
"""
# import modules
import requests
import json
import base64
# global vars to interact with the classic API
# ensure these variables match your environment
# main API endpoint for your smart group with the criteria to remove from management
# ensure you put your smart group ID in this string below
JSS_URL = "https://your-jamf-server.com/JSSResource/computergroups/id/<id>"
# input your API creds
JSS_API_USER = "your-api-username"
JSS_API_PASSWD = "your-api-user-passwd"
# template URL for individual device records
JSS_DEVICE_URL = "https://your-jamf-server.com/JSSResource/computers/id/"
# start functions
def get_smart_group_results():
"""function to get the JSS ID from a smart group"""
# vars for headers
creds = JSS_API_USER + ":" + JSS_API_PASSWD
creds = creds.encode()
b64_creds = base64.b64encode(creds)
# define the headers for the request
headers = {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": "Basic " + b64_creds.decode("utf-8"),
}
# make the request
r = requests.get(JSS_URL, headers=headers)
# ensure results are JSON encoded data
results = json.loads(r.text)
return results
def build_device_list(data):
"""simple function to parse the JSON return of Jamf Classic API"""
# start with a blank list
device_list = []
# iterate through our JSON data, starting at the list of dicts under the "computers" key
for i in range(len(data["computer_group"]["computers"])):
# we will use jamf ID for the post that is all we need for the device record
jss_id = data["computer_group"]["computers"][i]["id"]
# append the list of jss ids
device_list.append(jss_id)
return device_list
def post_un_mange_cmd(jss_id_list):
"""function to generate the XML payload to remove management for that device"""
# get the xml payload to remove management from jamf, thus unlicensing retired systems
xml_playload = "<computer><general><remote_management><managed>false</managed></remote_management></general></computer>"
# generate basic creds for API user, encode them, add headers, the required stuff
creds = JSS_API_USER + ":" + JSS_API_PASSWD
creds = creds.encode()
b64_creds = base64.b64encode(creds)
headers = {
"Accept": "application/json",
"Content-Type": "application/xml",
"Authorization": "Basic " + b64_creds.decode("utf-8"),
}
# loop through the ID list, and send our XML payload to each device record by JSS ID
for id in jss_id_list:
# ensure the JSS ID is a string data type as you cannot concat string to integer data types
url = JSS_DEVICE_URL + str(id)
# build the request and send it with the XML payload
r = requests.put(url, data=xml_playload, headers=headers)
# print results to stdout
print(f"un-managing device ID {id} with status code {r.status_code}")
def main():
"""main function to run the jewels"""
# get the smart group data
sg_data = get_smart_group_results()
# filter out JSS IDs of devices
device_list = build_device_list(sg_data)
# send un-manage payloads to all device IDs in our list
post_un_mange_cmd(device_list)
if __name__ == "__main__":
"""call the main policy"""
main()
@t-lark
Copy link
Author

t-lark commented Oct 10, 2023

This doesn't have good error handling at all (nor is it really good code by any means) but this was a quick and dirty script written to just get this done. Therefore all output goes to stdout and you will see this

successes:

un-managing device ID 290 with status code 201
un-managing device ID 330 with status code 201
un-managing device ID 383 with status code 201
un-managing device ID 428 with status code 201
un-managing device ID 438 with status code 201
un-managing device ID 583 with status code 201

failures will return a non 20x (non 200) status

un-managing device ID 3375 with status code 409
un-managing device ID 5772 with status code 409
un-managing device ID 6594 with status code 409
un-managing device ID 7634 with status code 409
un-managing device ID 8771 with status code 409
un-managing device ID 8809 with status code 409
un-managing device ID 9222 with status code 409

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment