Apple Classroom Issues – Part 2 (Now With a LaunchDaemon!)

This is part two of my post from November with a few updates, a script that works nicely for fixing errors, and a LaunchDaemon for even more automation.

It’s February and we are still seeing problems with users that are on iPadOS 15 and getting errors when trying to load Apple Classroom. As I said in the other post, this happens when an iPad loses member: JSS Built-In Signing Certificate for unknown reasons. The fix is to pull down a new version of the EDU profile. I previously recommended assigning the iPad to a different user. That still works, but it was becoming quite a bit of work since we were getting daily reports of the issue. I decided to automate it.

This works thanks to Neil Martin’s wonderful suggestion here to assign the user to an empty class and get a new EDU profile that way.

To run this script, you will need to have an advanced computer search for computers that have the EDU profile but do not have the member: JSS Built-In Signing Certificate installed.

This image has an empty alt attribute; its file name is Screen-Shot-2021-11-16-at-2.40.04-PM-1024x212.png
Search criteria

After you create this search, you will want to make sure you save it and note its ID number. The easiest way to get this is to click on the advanced search in Jamf Pro and note the number that appears in the address bar after ?id= This is the number you will input in the script, either as the value for searchNumber or when you are prompted for it if you are running the script interactively.

This image has an empty alt attribute; its file name is Address-Bar.png
Example – this search ID number is 172.

You will also need to create an empty class in Jamf Pro and note the ID number in the same way. The script is as follows. You can run this interactively or hardcode the variables.

#!/bin/bash

#Add your credentials and Jamf Pro URL here if you don't want to be prompted for them. This is also necessary if you are running the script as root (jamf or LaunchDaemon)
jssUser=
jssPassword=
jssURL=

#You can also uncomment this line if you want the script to read which jamf server the computer it is running on connects to.
#jssURL=$(/usr/bin/defaults read ~/Library/Preferences/com.jamfsoftware.jss.plist url)


if [ -z $jssURL ]; then
	echo "Please enter the JSS URL:"
	read -r jssURL
fi 

if [ -z $jssUser ]; then
	echo "Please enter your JSS username:"
	read -r jssUser
fi 

if [ -z $jssPassword ]; then 
	echo "Please enter JSS password for account: $jssUser:"
	read -r -s jssPassword
fi


#search number - you can fill in the search number here if you don't want to be prompted
searchNumber=
if [ -z $searchNumber ]; then
	echo "Please enter the number of the advanced computer search you want to send the command to:"
	read -r searchNumber
fi 

#search number - you can fill in the search number here if you don't want to be prompted
classNumber=
if [ -z $classNumber ]; then
	echo "Please enter the number of the class you will be assigning users to:"
	read -r classNumber
fi 


xpath() {
    # the xpath tool changes in Big Sur
    if [[ $(sw_vers -buildVersion) > "20A" ]]; then
        /usr/bin/xpath -e "$@"
    else
        /usr/bin/xpath "$@"
    fi
}

echo "Logging in to $jssURL as $jssUser"

userNames=($(curl -X GET -H "Accept: application/xml" -s -u "${jssUser}":"${jssPassword}" ${jssURL}/JSSResource/advancedmobiledevicesearches/id/$searchNumber | xpath "//mobile_device//Username" 2> /dev/null | awk -F'</?Username>' '{for(i=2;i<=NF;i++) print $i}'))

fixCertificate() {

curl -X PUT -H "Accept: application/xml" -H "Content-type: application/xml" -s -u "${jssUser}":"${jssPassword}" -d "<class><teachers><teacher>$name</teacher></teachers></class>" ${jssURL}/JSSResource/classes/id/$classNumber > /dev/null
sleep 10

}

for name in "${userNames[@]}"; do
#	echo "$name"
	fixCertificate
	echo "New EDU profile will be pulled down on iPad assigned to $name"
done

#clears out teacher information
curl -X PUT -H "Accept: application/xml" -H "Content-type: application/xml" -s -u "${jssUser}":"${jssPassword}" -d "<class><teachers><teacher></teacher></teachers></class>" ${jssURL}/JSSResource/classes/id/$classNumber > /dev/null

echo "Complete. All EDU profiles have been refreshed."

This assigns the user temporarily as the teacher of the empty class, causing Jamf to send a new version of the EDU profile to the iPad. This seems to fix the issue about 99% of the time in our environment.

If you want to run this manually whenever you see an issue, that’s great! But if you want to automate it even further, you can set this up to run with a LaunchDaemon. I have this set to run every day at 8:00 am with the LaunchDaemon here:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Label</key>
	<string>com.isd701.fixcertificates</string>
	<key>ProgramArguments</key>
	<array>
		<string>sh</string>
		<string>/usr/local/fixCertificates.sh</string>
	</array>
	<key>StandardErrorPath</key>
	<string>/var/log/fixCertificatesErr.log</string>
	<key>StandardOutPath</key>
	<string>/var/log/fixCertificates.log</string>
	<key>StartCalendarInterval</key>
	<dict>
		<key>Hour</key>
		<integer>8</integer>
	</dict>
</dict>
</plist>

The script and LaunchDaemon are also on my GitHub page. If you want to run this with the LaunchDaemon, you would need to:

  1. Create a Jamf service account that has these privileges:
    • Read – Classes
    • Update – Classes
    • Read – Advanced Mobile Device Searches
    • Read – Mobile Devices
    • Update – Mobile Devices
    • Update – Users
  2. Hardcode the variables in the script with the credentials for the service account, Jamf Pro URL, and search and class ID numbers.
  3. Make sure the script is named fixCertificates.sh and move it to the /usr/local folder.
  4. Save the LaunchDaemon as com.isd701.fixcertificates.plist and move it to the /Library/LaunchDaemons folder.
  5. Run these commands (with sudo):
    • chmod +x /usr/local/fixCertificates.sh
    • chown root /Library/LaunchDaemons/com.isd701.fixcertificates.plist
    • chgrp wheel /Library/LaunchDaemons/com.isd701.fixcertificates.plist
    • launchctl load /Library/LaunchDaemons/com.isd701.fixcertificates.plist

That’s it. The LaunchDaemon will run every day at 8 am until you remove the /Library/LaunchDaemons/com.isd701.fixcertificates.plist file! All logging will go to the /var/log/fixCertificates (standard out) and /var/log/fixCertificatesErr.log (standard error).

Leave a Comment

Your email address will not be published. Required fields are marked *