Using the Jamf Pro API to mass-delete computers and mobile devices
Periodically, it may be necessary to delete a large number of computers or mobile devices from a Jamf Pro server. However, there is currently a problem in Jamf Pro 10 where trying to delete multiple devices can fail. Jamf is aware of the issue and has assigned it a product issue code (PI-004957), but it has not yet been resolved and remains a known issue as of Jamf Pro 10.4.1.
To work around this issue, you can delete computers and mobile devices one at a time. This does not trigger the performance issues seen with PI-004957, but this can get tedious if you have multiple devices to delete. To help with this, I’ve adapted an earlier script written by Randy Saeks to help automate the deletion process by using a list of Jamf IDs and the API to delete the relevant computers or mobile devices one by one. For more details, please see below the jump.
I’ve adapted Randy’s original script into two scripts, one for deleting computers and the other for deleting mobile devices. Both scripts work with a text file of Jamf Pro IDs, and also include error checking to make sure that the text file’s entries contained only positive numbers.
To use these scripts, you will need four things:
- A text file containing the Jamf Pro computer or mobile device IDs you wish to delete.
- The address of the appropriate Jamf Pro server
- The username of an account on the Jamf Pro server which has the necessary privileges to delete computers and/or mobile devices.
- The password to that account.
The test file should contain only the relevant Jamf Pro IDs and its contents should appear similar to this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12020 | |
5389 | |
5485 | |
13201 | |
7500 | |
5820 | |
3816 | |
6383 | |
9589 | |
530 | |
5528 | |
7870 | |
3473 | |
14331 | |
14419 | |
3479 | |
2487 | |
9034 | |
13356 | |
9053 | |
3326 | |
11328 | |
12180 | |
4327 | |
10863 | |
8084 | |
4699 |
Once you have the text file and the other prerequisites, the scripts can be run using the following commands:
To delete computers:
/path/to/delete_Jamf_Pro_Computers.sh /path/to/text_filename_here.txt
To delete mobile devices:
/path/to/delete_Jamf_Pro_Mobile_Devices.sh /path/to/text_filename_here.txt
For authentication, the scripts can accept manual input or values stored in a ~/Library/Preferences/com.github.jamfpro-info.plist file.
The plist file can be created by running the following commands and substituting your own values where appropriate:
To store the Jamf Pro URL in the plist file:
defaults write com.github.jamfpro-info jamfpro_url https://jamf.pro.server.goes.here:port_number_goes_here
To store the account username in the plist file:
defaults write com.github.jamfpro-info jamfpro_user account_username_goes_here
To store the account password in the plist file:
defaults write com.github.jamfpro-info jamfpro_password account_password_goes_here
It is also possible to simulate a run of the script, to make sure everything is working before running the actual deletion. To put the script into simulation mode, comment out the following line of the script.
To take it out of simulation mode, uncomment the line.
In simulation mode, you can test out if the script is reading the text file properly and the authentication method. For example, the following output should be seen in simulation mode if the text file is being read properly and manual input is being used.
The following output should be seen in simulation mode if the text file is being read properly and the needed values are being read from a ~/Library/Preferences/com.github.jamfpro-info.plist file.
The scripts are available below, and at the following addresses on GitHub:
delete_Jamf_Pro_Computers.sh:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
########################################################################################## | |
# Computer Delete Script for Jamf Pro | |
# | |
# Original script by Randy Saeks: | |
# https://github.com/rsaeks/scripts/blob/master/delMobileDevice.sh | |
# | |
# Please create a backup of the Jamf Pro database prior to deleting computers | |
# | |
# Usage: Call script with the following four parameters | |
# – a text file containing the Jamf Pro IDs of the computer(s) you wish to delete. | |
# – The URL of the appropriate Jamf Pro server | |
# – username for an account on the Jamf Pro server with sufficient privileges | |
# to delete computers from the Jamf Pro server. | |
# – password for the account on the Jamf Pro server | |
# | |
# Example: ./delete_Jamf_Pro_Computers.sh jamf_pro_id_numbers.txt | |
# | |
########################################################################################## | |
filename="$1" | |
ERROR=0 | |
if [[ -n $filename && -r $filename ]]; then | |
# If you choose to hardcode API information into the script, uncomment the lines below | |
# and set one or more of the following values: | |
# | |
# The username for an account on the Jamf Pro server with sufficient API privileges | |
# The password for the account | |
# The Jamf Pro URL | |
#jamfproURL="" ## Set the Jamf Pro URL here if you want it hardcoded. | |
#apiUser="" ## Set the username here if you want it hardcoded. | |
#apiPass="" ## Set the password here if you want it hardcoded. | |
# If you do not want to hardcode API information into the script, you can also store | |
# these values in a ~/Library/Preferences/com.github.jamfpro-info.plist file. | |
# | |
# To create the file and set the values, run the following commands and substitute | |
# your own values where appropriate: | |
# | |
# To store the Jamf Pro URL in the plist file: | |
# defaults write com.github.jamfpro-info jamfpro_url https://jamf.pro.server.goes.here:port_number_goes_here | |
# | |
# To store the account username in the plist file: | |
# defaults write com.github.jamfpro-info jamfpro_user account_username_goes_here | |
# | |
# To store the account password in the plist file: | |
# defaults write com.github.jamfpro-info jamfpro_password account_password_goes_here | |
# | |
# If the com.github.jamfpro-info.plist file is available, the script will read in the | |
# relevant information from the plist file. | |
if [[ -f "$HOME/Library/Preferences/com.github.jamfpro-info.plist" ]]; then | |
if [[ -z "$jamfproURL" ]]; then | |
jamfproURL=$(defaults read $HOME/Library/Preferences/com.github.jamfpro-info jamfpro_url) | |
fi | |
if [[ -z "$apiUser" ]]; then | |
apiUser=$(defaults read $HOME/Library/Preferences/com.github.jamfpro-info jamfpro_user) | |
fi | |
if [[ -z "$apiPass" ]]; then | |
apiPass=$(defaults read $HOME/Library/Preferences/com.github.jamfpro-info jamfpro_password) | |
fi | |
fi | |
# If the Jamf Pro URL, the account username or the account password aren't available | |
# otherwise, you will be prompted to enter the requested URL or account credentials. | |
if [[ -z "$jamfproURL" ]]; then | |
read -p "Please enter your Jamf Pro server URL : " jamfproURL | |
fi | |
if [[ -z "$apiUser" ]]; then | |
read -p "Please enter your Jamf Pro user account : " apiUser | |
fi | |
if [[ -z "$apiPass" ]]; then | |
read -p "Please enter the password for the $apiUser account: " -s apiPass | |
fi | |
echo "" | |
# Remove the trailing slash from the Jamf Pro URL if needed. | |
jamfproURL=${jamfproURL%%/} | |
# Set up the Jamf Pro Computer ID URL | |
jamfproIDURL="${jamfproURL}/JSSResource/computers/id" | |
while read -r ID | |
do | |
# Verify that the input is a number. All Jamf Pro | |
# IDs are positive numbers, so any other input will | |
# not be a valid Jamf Pro ID. | |
if [[ "$ID" =~ ^[0-9]+$ ]]; then | |
# The line below previews the results of the | |
# deletion command. Comment out the line below | |
# if this preview is not desired. | |
echo "curl -X DELETE ${jamfproIDURL}/$ID" | |
# The line below runs the deletion command. | |
# Comment out the line below if you want to | |
# only simulate running the deletion command. | |
curl -X DELETE "${jamfproIDURL}/$ID" -u $apiUser:${apiPass} | |
else | |
echo "All Jamf Pro IDs are expressed as numbers. The following input is not a number: $ID" | |
fi | |
done < "$filename" | |
else | |
echo "Input file does not exist or is not readable" | |
ERROR=1 | |
fi | |
exit "$ERROR" |
delete_Jamf_Pro_Mobile_Devices.sh:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
########################################################################################## | |
# Mobile Device Delete Script for Jamf Pro | |
# | |
# Original script by Randy Saeks: | |
# https://github.com/rsaeks/scripts/blob/master/delMobileDevice.sh | |
# | |
# Please create a backup of the Jamf Pro database prior to deleting mobile devices | |
# | |
# Usage: Call script with the following four parameters | |
# – a text file containing the Jamf Pro IDs of the mobile device(s) you wish to delete. | |
# – The URL of the appropriate Jamf Pro server | |
# – username for an account on the Jamf Pro server with sufficient privileges | |
# to delete mobile devices from the Jamf Pro server. | |
# – password for the account on the Jamf Pro server | |
# | |
# Example: ./delete_Jamf_Pro_Mobile_Devices.sh jamf_pro_id_numbers.txt | |
# | |
########################################################################################## | |
filename="$1" | |
ERROR=0 | |
if [[ -n $filename && -r $filename ]]; then | |
# If you choose to hardcode API information into the script, uncomment the lines below | |
# and set one or more of the following values: | |
# | |
# The username for an account on the Jamf Pro server with sufficient API privileges | |
# The password for the account | |
# The Jamf Pro URL | |
#jamfproURL="" ## Set the Jamf Pro URL here if you want it hardcoded. | |
#apiUser="" ## Set the username here if you want it hardcoded. | |
#apiPass="" ## Set the password here if you want it hardcoded. | |
# If you do not want to hardcode API information into the script, you can also store | |
# these values in a ~/Library/Preferences/com.github.jamfpro-info.plist file. | |
# | |
# To create the file and set the values, run the following commands and substitute | |
# your own values where appropriate: | |
# | |
# To store the Jamf Pro URL in the plist file: | |
# defaults write com.github.jamfpro-info jamfpro_url https://jamf.pro.server.goes.here:port_number_goes_here | |
# | |
# To store the account username in the plist file: | |
# defaults write com.github.jamfpro-info jamfpro_user account_username_goes_here | |
# | |
# To store the account password in the plist file: | |
# defaults write com.github.jamfpro-info jamfpro_password account_password_goes_here | |
# | |
# If the com.github.jamfpro-info.plist file is available, the script will read in the | |
# relevant information from the plist file. | |
if [[ -f "$HOME/Library/Preferences/com.github.jamfpro-info.plist" ]]; then | |
if [[ -z "$jamfproURL" ]]; then | |
jamfproURL=$(defaults read $HOME/Library/Preferences/com.github.jamfpro-info jamfpro_url) | |
fi | |
if [[ -z "$apiUser" ]]; then | |
apiUser=$(defaults read $HOME/Library/Preferences/com.github.jamfpro-info jamfpro_user) | |
fi | |
if [[ -z "$apiPass" ]]; then | |
apiPass=$(defaults read $HOME/Library/Preferences/com.github.jamfpro-info jamfpro_password) | |
fi | |
fi | |
# If the Jamf Pro URL, the account username or the account password aren't available | |
# otherwise, you will be prompted to enter the requested URL or account credentials. | |
if [[ -z "$jamfproURL" ]]; then | |
read -p "Please enter your Jamf Pro server URL : " jamfproURL | |
fi | |
if [[ -z "$apiUser" ]]; then | |
read -p "Please enter your Jamf Pro user account : " apiUser | |
fi | |
if [[ -z "$apiPass" ]]; then | |
read -p "Please enter the password for the $apiUser account: " -s apiPass | |
fi | |
echo "" | |
# Remove the trailing slash from the Jamf Pro URL if needed. | |
jamfproURL=${jamfproURL%%/} | |
# Set up the Jamf Pro Mobile Devices ID URL | |
jamfproIDURL="${jamfproURL}/JSSResource/mobiledevices/id" | |
while read -r ID | |
do | |
# Verify that the input is a number. All Jamf Pro | |
# IDs are positive numbers, so any other input will | |
# not be a valid Jamf Pro ID. | |
if [[ "$ID" =~ ^[0-9]+$ ]]; then | |
# The line below previews the results of the | |
# deletion command. Comment out the line below | |
# if this preview is not desired. | |
echo "curl -X DELETE ${jamfproIDURL}/$ID" | |
# The line below runs the deletion command. | |
# Comment out the line below if you want to | |
# only simulate running the deletion command. | |
curl -X DELETE "${jamfproIDURL}/$ID" -u $username:${password} | |
else | |
echo "All Jamf Pro IDs are expressed as numbers. The following input is not a number: $ID" | |
fi | |
done < "$filename" | |
else | |
echo "Input file does not exist or is not readable" | |
ERROR=1 | |
fi | |
exit "$ERROR" |
Hi Rich!
First day back and my Jamf Pro renewal reminders are out! I’m currently doing a clean up in our JSS and following this tutorial. I ran an advanced search for older lab devices that hadn’t checked in for a while and exported them to a .csv. I then took the Jamd ID’s from the column and put them in a text file.
I then ran the script in simulation mode. All of the results from the Id’s say: The following input is not a number. I’m just wondering how to resolve that response. I’m currently running Jamf pro version 10.8.0-t1539715549. Thanks in advance!
I had that issue and was able to fix it by using a command-line text editor (in my case, pico) to create the text file in question. My process is to create a new file using pico, then paste the list of Jamf ID numbers into the Terminal window.
Hi Rich, Does this work for JAMF Cloud? I tried it and got the following.
curl -X DELETE https://not.mydomain.com:443/JSSResource/computers/id/469
Status page
Unauthorized
The request requires user authentication
You can get technical details here.
Please continue your visit at our home page.
I see you are getting the API token here:
if [[ -z “$NoBearerToken” ]]; then
GetJamfProAPIToken
fi
But why do you need to renew it here?
if [[ -z “$NoBearerToken” ]]; then
CheckAndRenewAPIToken
/usr/bin/curl –header “Authorization: Bearer ${api_token}” -X DELETE “${jamfproIDURL}/$ID”
Also if the token is Valid why are you invalidating and getting a new token?
if [[ ${api_authentication_check} == 200 ]]; then
# If the current bearer token is valid, it is used to connect to the keep-alive endpoint. This will
# trigger the issuing of a new bearer token and the invalidation of the previous one.
if [[ $(/usr/bin/sw_vers -productVersion | awk -F . ‘{print $1}’) -lt 12 ]]; then
api_token=$(/usr/bin/curl “${jamfpro_url}/api/v1/auth/keep-alive” –silent –request POST –header “Authorization: Bearer ${api_token}” | python -c ‘import sys, json; print json.load(sys.stdin)[“token”]’)
Four reasons I chose this route:
1. Bearer tokens are good for 30 minutes and then they expire.
2. As long as you have a valid bearer token, you can exchange for a new valid bearer token.
Note: As part of the process of getting a new bearer token, the previous bearer token is automatically invalidated by the API.
3. The number of bearer tokens which can be generated is unlimited. Zero need to worry about running out of bearer tokens.
4. I didn’t want to figure out the math involved with figuring out if the current token had expired. I didn’t need to because there was an option for figuring out if I had a valid token and if I did, using it to get a new valid bearer token. So I chose to skip figuring out if the current bearer token had expired in favor of checking if it was valid and if it was, using it to get a new valid bearer token.
Hopefully this addresses your concerns.