Home > Jamf Pro, Jamf Pro API, Mac administration, Scripting > Identifying and deleting Jamf Pro inventory records with duplicate serial numbers

Identifying and deleting Jamf Pro inventory records with duplicate serial numbers

I recently saw an issue where several computers in Jamf Pro were showing up with the same serial number listed in their inventory records. This made it difficult to work with this serial number using the API because Jamf Pro Classic API calls may fail if we’re referencing the serial number in the API call and more than one inventory record exists with that serial number.

First off, how can this happen? Aren’t serial numbers supposed to be unique? They are, but there’s two instances where serial numbers may unfortunately be associated with more than one Mac.

Hardware repair:

When you send a Mac out for repair and the logic board is replaced as part of the repair, the Mac’s existing serial number is flashed onto the replacement logic board.

However, both the old and new logic boards have separate Unique Device Identifiers (UDID) associated with them. When enrolling a device into Jamf Pro, it is possible for a new inventory record to be set up if a device has:

  • The same serial number listed in as an existing inventory record
  • A UDID not found in other inventory records

Parallels macOS virtual machine:

macOS virtual machines set up by Parallels Desktop and other Parallels hypervisor products use the same serial number as the Mac which is running the Parallels hypervisor software. These VMs will likewise have separate Hardware UDIDs associated with them.

So what to do with these duplicate records? My recommendation is to delete them from your Jamf Pro server when you find them, especially if you do a lot of work using the API. To help with this task, a script has been developed to identify and delete unwanted duplicates. For more details, please see below the jump.

This script downloads all computer inventory records from a Jamf Pro server. The list of records is then parsed for inventory records with the same Apple serial number as at least one other record.

Once the duplicate serial numbers are identified, the script takes the following actions:

  1. Loop through the duplicate serial number list and get all of the associated Jamf Pro computer IDs
  2. Loop through the Jamf Pro IDs and identify the IDs with the most recent enrollment dates.
  3. Verify that the individual Jamf Pro IDs are associated with Macs, as opposed to virtual machines running macOS.
  4. Loop through the list of identified Macs with Jamf Pro IDs and delete all Macs except for the one with the most recent enrollment date.
  5. Create a report in tab-separated value (.tsv) format which contains the following information about the deleted Macs.
  • Jamf Pro ID
  • Manufacturer
  • Model
  • Serial Number
  • Hardware UDID

For authentication, the script can accept hard-coded values in the script, 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

When the script is run, you should see output which looks similar to this.

Screen Shot 2020 05 26 at 4 14 31 PM

The report generated in tab-separated value (.tsv) format should be openable natively by both Microsoft Excel and Apple Numbers.

Screen Shot 2020 05 26 at 4 33 53 PM

The script is available below and at the following address on GitHub:

https://github.com/rtrouton/rtrouton_scripts/tree/master/rtrouton_scripts/Casper_Scripts/Identify_and_Delete_Jamf_Pro_Inventory_Records_With_Duplicate_Serial_Numbers


#!/bin/bash
# This script identifies all Mac Jamf Pro inventory records which have the same Apple serial number
# as at least one more Mac's inventory record.
#
# This duplication is usually caused by a Mac having a logic board repair, as the Mac's existing serial number
# will be flashed onto the replacement logic board but the board itself will have a new and unique hardware UUID.
# If the Mac is subsequently un-enrolled and re-enrolled into Jamf Pro, the new hardware UUID will prompt Jamf Pro
# to set up a new inventory record for the Mac.
#
# Once the duplicate serial numbers are identified, the script takes the following actions:
#
# 1. Loop through the duplicate serial number list and get all of the associated Jamf Pro computer IDs
# 2. Loop through the Jamf Pro IDs and identify the IDs with the most recent enrollment dates.
# 3. Verify that the individual Jamf Pro IDs are associated with Macs, as opposed to virtual machines running macOS.
# 4. Loop through the list of identified Macs with Jamf Pro IDs and delete all Macs except for the one with
# the most recent enrollment date.
# 5. Create a report in tab-separated value (.tsv) format which contains the following information
# about the deleted Macs
#
# Jamf Pro ID
# Manufacturer
# Model
# Serial Number
# Hardware UDID
report_file="$(mktemp).tsv"
# If you choose to hardcode API information into the script, 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
# Set the Jamf Pro URL here if you want it hardcoded.
jamfpro_url=""
# Set the username here if you want it hardcoded.
jamfpro_user=""
# Set the password here if you want it hardcoded.
jamfpro_password=""
# 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.
jamf_plist="$HOME/Library/Preferences/com.github.jamfpro-info.plist"
if [[ -r "$jamf_plist" ]]; then
if [[ -z "$jamfpro_url" ]]; then
jamfpro_url=$(defaults read "${jamf_plist%.*}" jamfpro_url)
fi
if [[ -z "$jamfpro_user" ]]; then
jamfpro_user=$(defaults read "${jamf_plist%.*}" jamfpro_user)
fi
if [[ -z "$jamfpro_password" ]]; then
jamfpro_password=$(defaults read "${jamf_plist%.*}" 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 "$jamfpro_url" ]]; then
read -p "Please enter your Jamf Pro server URL : " jamfpro_url
fi
if [[ -z "$jamfpro_user" ]]; then
read -p "Please enter your Jamf Pro user account : " jamfpro_user
fi
if [[ -z "$jamfpro_password" ]]; then
read -p "Please enter the password for the $jamfpro_user account: " -s jamfpro_password
fi
echo
# Remove the trailing slash from the Jamf Pro URL if needed.
jamfpro_url=${jamfpro_url%%/}
IFS=$'\n'
echo "Downloading list of computer information…"
ComputerXML=$(curl -sfu "$jamfpro_user:$jamfpro_password" "${jamfpro_url}/JSSResource/computers/subset/basic" -H "Accept: application/xml" 2>/dev/null)
if [[ -n "$ComputerXML" ]]; then
echo "Checking for duplicates …"
# get a list of serial number tags
SerialList=$(echo "$ComputerXML" | xmllint –xpath "//computers/computer/serial_number" – 2>/dev/null)
# get a list of sorted serial numbers
SortedSerialList=$(echo "$SerialList" | grep -Eo "<serial_number[^<]*" | sed -n 's/<serial_number\/*>\([^<]*\).*/\1/p' | sort)
# get a list of duplicate serials
Duplicate_Computer_Serials_List=$(echo "$SortedSerialList" | uniq -d)
printf "Found %d serial number(s) with duplicates\n\n" $(echo "$Duplicate_Computer_Serials_List" | grep -Ec "^")
# loop through all duplicates and get the respective computer ids
for aDuplicateSerial in ${Duplicate_Computer_Serials_List}; do
# check the variable to skip blank serial numbers
if [[ -n "$aDuplicateSerial" ]]; then
echo
echo "Processing serial number $aDuplicateSerial"
# get all ids matching the serial
matchingIDs=$(echo "$ComputerXML" | xmllint –xpath "//computers/computer[serial_number='$aDuplicateSerial']/id" – 2>/dev/null | grep -Eo "<id[^<]*" | grep -Eo "[0-9]+")
IDtoKeep=
IgnoredIDs=()
NewestEnrollmentDate=0
# loop through all ids and get the one with the newest enrollment date
for anID in ${matchingIDs}; do
ComputerRecord=$(curl -sfu "$jamfpro_user:$jamfpro_password" "${jamfpro_url}/JSSResource/computers/id/$anID" -H "Accept: application/xml" 2>/dev/null)
MachineModel=$(echo "$ComputerRecord" | xmllint –xpath "//computer/hardware/model_identifier/text()" – 2>/dev/null)
if [[ ! "$MachineModel" =~ ^i?Mac.*$ ]]; then
echo "Computer with id $anID seems not to be a Mac ($MachineModel). Will be ignored."
IgnoredIDs+=($anID)
else
echo "Getting enrollment date of computer with id $anID"
EnrollmentDate=$(echo "$ComputerRecord" | xmllint –xpath "//computer/general/last_enrolled_date_epoch/text()" – 2>/dev/null)
if [[ "$EnrollmentDate" =~ ^[0-9]+$ && $EnrollmentDate -gt $NewestEnrollmentDate ]]; then
NewestEnrollmentDate=$EnrollmentDate
IDtoKeep=$anID
fi
fi
done
echo "Keeping computer record with id $IDtoKeep"
# loop through the ids again and delete all computers
# except the one with the newest enrollment date
for anID in ${matchingIDs}; do
if [[ ! "$anID" = "$IDtoKeep" && ! " ${IgnoredIDs[@]} " =~ "$anID " ]]; then
if [[ ! -f "$report_file" ]]; then
touch "$report_file"
printf "Jamf Pro ID Number\tMake\tModel\tSerial Number\tUDID\n" > "$report_file"
fi
Make=$(echo "$ComputerRecord" | xmllint –xpath '//computer/hardware/make/text()' – 2>/dev/null)
SerialNumber=$(echo "$ComputerRecord" | xmllint –xpath '//computer/general/serial_number/text()' – 2>/dev/null)
UDIDIdentifier=$(echo "$ComputerRecord" | xmllint –xpath '//computer/general/udid/text()' – 2>/dev/null)
curl -sfu "$jamfpro_user:$jamfpro_password" "${jamfpro_url}/JSSResource/computers/id/$anID" -X DELETE
if [[ $? -eq 0 ]]; then
echo "Deleted computer record with id $anID"
printf "$anID\t$Make\t$MachineModel\t$SerialNumber\t$UDIDIdentifier\n" >> "$report_file"
else
echo "ERROR! Failed to delete computer record with id $anID"
fi
fi
done
fi
done
if [[ -f "$report_file" ]]; then
echo "Report on deleted Macs available here: $report_file"
fi
fi
exit 0

  1. R.Manikandan
    May 29, 2020 at 2:18 pm

    Jamf_Pro_Inventory_Records to live google sheet is that possible too. if you have can you pls help me to get the script.

    this will help to improve more for me.

  2. Jeff
    June 16, 2020 at 2:44 pm

    Is there a way to ignore the duplicate serial number issue? I have a VM that I am testing with and need to be able to test the API commands on this VM before pushing to production. Would change the SN on the VM but DEP was part of the test as well.

  1. No trackbacks yet.

Leave a comment