Supporting Java Applets in macOS Catalina

In the wake of a sudden pivot to remote instruction we’ve had to look at different ways to support applications used in classes. One specific example is an application that is provided by the US Geological Survey called TopoDrive and ParticleFlow. Available on the website is an executable for Windows machines. We have that loaded to a Windows lab on campus. Unfortunately, many students only have a macOS device to use at home and could not easily run the programs, especially as modern web browsers have deprecated java plugins making it difficult to run the applet version.

So to support these students I ran some testing. I was able to get the applet version to run if I installed a version of OpenJDK 8. I chose Amazon’s Corretto distribution. After installing I was able to run the application using the appletviewer command. Turns out that was pretty simple to get working, once the JDK is installed.

appletviewer ~/Downloads/tdpf1.0web/pflow/pflow.html

Running that line executes the html file with the java applet assuming it was in the default location of the user’s /Downloads folder. To take that one step further I can save it as a bash script to easily execute again. I could use the .command extension and it will run the script using the java JDK appletviewer command. But to make it one step simpler for students to use on personal machines, I used Platypus and created an app out of the one line script.

Platypus is a simple application that can build a few different types of apps out of basic scripts. I simply took my saved script, added a custom icon and built the .app file.

Platypus.app by Svein Bjorn

I duplicated this for both applets in the download, TopoDrive and ParticleFlow. Once you click the desktop icon you get the following window:

html page that includes the launch button

And after clicking the button the Applet launches:

the applet launches in its own window

If I was deploying this to managed machines in my own organization I would have signed the app and deployed it to /Applications, installed the correct version of Amazon Corretto and stored the files in a shared location, such as in the /Users/Shared folder. But as I am offering this to students with unknown versions of macOS, and without a management framework to use the easiest way was to email instructions and a .zip file with the .apps. The steps were:

While it is very simple I have the scripts and Platypus created applications on GitHub here.

Running Jamf policy custom triggers via script

At first glance, my script jamfEvent.sh may seem too simple. It just calls a Jamf policy by a custom trigger. But this can be very useful in a few workflows.

event=$4
if [[ -z $event ]]; then
	read -p "Jamf Custom Trigger:" event
fi

jamf policy -event $event

I use this script to create Self Service policies that are scoped specifically to the required OS or department. They refer back to a policy that actually installs the software that is only triggered by the custom event. I can also create another policy to automatically install on newly DEP enrolled machines during their provisioning workflow. I can have multiple policies that refer back to the primary one. This means I only need to edit one policy if something changes in the installer, package name, or choices XML modifications.

I also use this to kickstart our DEP Provisioning workflow in Self Service because the enrollmentComplete trigger from Jamf can be unreliable. If the network connection is interrupted during the enrollment, it may never see the trigger. So when setting up a machine, if I don’t see the notice that it’s starting the process I can click it from Self Service.

If Jamf changed the way to call a custom event it also would mean only changing the primary script which just makes things slightly easier, as opposed to using the “Execute Command” feature located under Files and Processes in a Jamf Policy.

Add and Remove from Static Groups with the Jamf API

I have 2 scripts I’ve found useful with scoping policies and profiles to a group that isn’t easily configured by other information in a device’s record. A MacBook Pro that is shared for a lab is difficult encrypt, but the same model in the same department is assigned to a specific user and that we do want to encrypt. We can scope a policy to a static group and use a Self Service policy to add a machine to that group, then trigger the policy by a custom event to kick off.

APIaddtoStaticGroup.sh grabs the serial number of the device and adds it to a static group by that groups ID number. You can easily locate the ID number of a group by checking the URL in the JSS. In the example below, the number is in the ?id= portion, represented by XX.

https://yourjssurl.com:8443/staticComputerGroups.html?id=XX&o=r

Writing the script with these variables allows for reuse, and if an update to the parameters is made it will be easy to update one script instead of every policy that may use it.

apiData="<computer_group><computer_additions><computer><serial_number>${serialNumber}</serial_number></computer></computer_additions></computer_group>"
curl \
	-s \
	-f \
	-u ${apiUser}:${apiPass} \
	-X PUT \
	-H "Content-Type: text/xml" \
	-d "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>${apiData}" ${jssHost}/JSSResource/computergroups/id/${groupID}

A few usage examples for this would be to add a machine to an exception group to temporarily remove a profile for testing at the help desk, or adding to a group that has an encryption policy scoped to it.

APIremovefromStaticGroup.sh is similarly used to remove a machine from a static group. One of the ways I’ve used this is to remove a machine from groups when repurposing a machine with an erase install policy. It could similarly be used in conjunction with the add to static group script for help desk, with a self service policy to add and remove a profile or policy for testing purposes.

apiData="<computer_group><computer_deletions><computer><serial_number>${serialNumber}</serial_number></computer></computer_deletions></computer_group>"
curl \
	-s \
	-f \
	-u ${apiUser}:${apiPass} \
	-X PUT \
	-H "Content-Type: text/xml" \
	-d "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>${apiData}" ${jssHost}/JSSResource/computergroups/id/${groupID}

Help Desk Scripts

I have added a couple items to Jamf Self Service that can be useful for Help Desk. They are simple items but can empower users and help desk staff to help when there are security obstacles, like a firmware password or out of sync user password.

NVRAMclear.sh does some minor clearing of caches and performs an NVRAM reset. This is useful on macOS systems with firmware passwords; the method to hold down CMD+Option+P+R will not traditionally work when there is a firmware password set. Clicking a Self Service policy to run this script and rebooting can potentially alleviate some issues.

update_dyld_shared_cache -root / -force -debug
/usr/libexec/xpchelper --rebuild-cache
nvram -c

resetkeychain.sh will simply remove an old keychain for a user with a keychain they can no longer access. This occasionally happens with AD mobile accounts when the password is changed on another system and then authenticated to AD updating the password. In macOS if you bypass the password change mechanism some items cannot be updated, like the Keychain password or Filevault password. This script will get rid of the issue when attempting to remember the old password is not working.

rm -rf "$home"/Library/Keychains/*

I’ve also added an installer for Recovery Selector from Two Canoes. Again firmware passwords can be a hindrance for some items, like rebooting to run diagnostics. When we have macOS devices that need diagnostics run I’ve added a Self Service policy to install this application to the /Applications/Utilities folder. The system still requires the firmware password, but you do not need to remove it just for diagnostics. In some cases you will still need to remove it to complete a repair, but this can make things quicker.

Jamf API Scripts on Github

I’ve been maintaining a Github with useful Jamf Pro scripts I’ve found helpful, and some I featured in a presentation at the 2019 Penn State MacAdmins Conference.

I specifically featured a few scripts that I use in our DEP provisioning workflow. This workflow relies heavily on using the Inventory Preload feature in Jamf Pro. It allows me to assign a machine to a user, assign it an asset tag, and put it in a department and building. The first time a machine checks in, Jamf will assign these items to the computer’s record in Jamf. These are all ways I can sort computers in Jamf Pro, but I can communicate with the Jamf API to pull some of these items and use them while preparing a new system.

For security purposes I have the username and password sent as a variable to the script. I also made the account with limited permissions to only make the call to pull computer data to mitigate concerns.

APIaddAdmin.sh is a simple script that grabs the user assigned in Jamf Pro and adds that user as an administrator. It first gets the serial number, and uses that to perform a lookup to find the assigned user via API call. It then uses a dscl command to set that user as an admin. This script would primarily be useful if you have machines prepped in advance by IT instead of initial configuration by a user.

username=$(/usr/bin/curl -H "Accept: text/xml" -sfku "${apiUser}:${apiPass}" "${jssHost}/JSSResource/computers/serialnumber/${serialNumber}/subset/location" | xmllint --format - 2>/dev/null | awk -F'>|<' '/<username>/{print $3}')
dscl . -append /Groups/admin GroupMembership $username

APIfirmware.sh could easily be customized, but this allows for a unique firmware password to be set per machine, utilizing the Asset Tag field in this case. As the firmwarepasswd command is interactive it uses the expect shell to respond. Again we get the serial number and use that to pull the asset tag from the computer’s inventory record. We use the expect shell to interact with a command line and send a unique password with a password scheme, assuming one isn’t already set. There are potential security concerns with setting a firmware password with a script. It will run in plain text and be cached to the Jamf Waiting Room, but in the case of IT preparing machines it is unlikely that any information would still be cached when the user gets the system.

doesexist=`firmwarepasswd -check`
barcode=$(/usr/bin/curl -H "Accept: text/xml" -sfku "${apiUser}:${apiPass}" "${jssHost}/JSSResource/computers/serialnumber/${serialNumber}/subset/general" | xmllint --format - 2>/dev/null | awk -F'>|<' '/<asset_tag>/{print $3}')
firmware=passwordscheme${barcode}

if [ "$doesexist" = "Password Enabled: No" ]; then
	/usr/bin/expect <<- DONE
		spawn firmwarepasswd -setpasswd
		expect "Enter new password:"
		send "$firmware\r";
		expect "Re-enter new password:"
		send "$firmware\r";
		expect EOF
	DONE
else
	echo "Firmware Password Already Exists"
fi

APIrename.sh similarly pulls the Asset Tag from the computer’s record in Jamf, again by using the serial number to perform a lookup. This sets each of the names to the same name. In this case we use the asset tag proceeded by the type of device , Mac-##### in the script. This can be coupled with a Jamf policy to keep computer name if you want to ensure you can find a machine on the network.

barcode=$(/usr/bin/curl -H "Accept: text/xml" -sfku "${apiUser}:${apiPass}" "${jssHost}/JSSResource/computers/serialnumber/${serialNumber}/subset/general" | xmllint --format - 2>/dev/null | awk -F'>|<' '/<asset_tag>/{print $3}')
hostname=Mac-$barcode
scutil --set ComputerName $hostname
scutil --set HostName $hostname
scutil --set LocalHostName $hostname