At the time of writing, this issue was present on the latest version of Jamf School (9.1.17) and all preceding versions. Hopefully this is something that is patched in a future release.

In our environment, we run a handful of Mac Minis as build runners for our Autopkg infrastructure. Every night, one of these runners gets picked to run AutoPkg against all of our recipe overrides, picking up any updates available for the software in our software catalog.

These runners were configured a while ago now, and some of the configuration on them is undocumented. Each node is also configured slightly differently, so we can’t make a lot of assumptions about how each one is set up. Because spring has sprung, I recently decided to do some spring cleaning, and start from scratch. This process was complicated a little by working from home, and the fact that these machines are installed in a datacenter far from my house. Fortunately, we could enroll them in Jamf School, and use the new Auto-Advance functionality in macOS Big Sur to have them automatically configure themselves after we erased them remotely using eraseinstall. After a little bit of testing on a machine I had at home, and setting up some DHCP reservations for the machines, this solution worked incredibly well. Overall I was impressed at how useful it was. Imaging is well and truly dead.

However, these machines are the 2014 model Mac Mini, and are relatively old by now. As a result, we are soon replacing them with shiny new M1 Mac Minis. I was confident that the profiles and scripts I’d created for our Intel Mac Minis would work a treat on the new M1 machines, but I decided to test that theory out using an M1 MacBook Air we already had.

The DEP profile we configured worked exactly as I had intended, but I did notice something that wasn’t quite right - none of the scripts we were pushing to the device were being executed. Immediately, I began to suspect that something was amiss regarding Rosetta 2. It turns out that Jamf School’s script-running LaunchDaemon isn’t a Universal binary. As a result, it won’t run without the presence of Rosetta 2, which isn’t included with macOS by default.

> file /Library/PrivilegedHelperTools/com.zuludesk.script
/Library/PrivilegedHelperTools/com.zuludesk.script: Mach-O 64-bit executable x86_64

This created a little bit of a chicken and egg situation for me - it’s simple enough to install Rosetta non-interactively with a script, but I didn’t yet have the capability to do so without Rosetta 2. Fortunately, I had noticed on my test machine that some assigned packages had been installed. This is because Jamf School uses Apple’s InstallApplication MDM command to install packages, instead of a custom (non-Universal) binary of their own. This provided an opportunity - if I could wrap a script in a package, I could “install” it on the target machine. Because Rosetta comes directly from Software Update, I figured it was better to have the Mac fetch the latest version than package Rosetta myself.

Unfortunately, Jamf School isn’t keen on simple component packages that you can make with pkgbuild, or munki-pkg. You need to create a real product archive instead. Luckily, you can use productbuild to basically wrap a package made with pkgbuild, provided it has a payload of some kind. So, I created a payload that was the script I wanted to run, and then included a one-liner in my postinstall script that calls my payload script.


# Installs Rosetta as needed on Apple Silicon Macs.


# Determine OS version
# Save current IFS state


IFS='.' read osvers_major osvers_minor osvers_dot_version <<< "$(/usr/bin/sw_vers -productVersion)"

# restore IFS to previous state


# Check to see if the Mac is reporting itself as running macOS 11

if [[ ${osvers_major} -ge 11 ]]; then

  # Check to see if the Mac needs Rosetta installed by testing the processor

  processor=$(/usr/sbin/sysctl -n machdep.cpu.brand_string | grep -o "Intel")
  if [[ -n "$processor" ]]; then
    echo "$processor processor installed. No need to install Rosetta."

    # Check Rosetta LaunchDaemon. If no LaunchDaemon is found,
    # perform a non-interactive install of Rosetta.
    if [[ ! -f "/Library/Apple/System/Library/LaunchDaemons/" ]]; then
        /usr/sbin/softwareupdate --install-rosetta --agree-to-license
        if [[ $? -eq 0 ]]; then
        	echo "Rosetta has been successfully installed."
        	echo "Reloading Zuludesk scripting LaunchDaemon..."
        	# unload and reload the Zuludesk scripting LaunchDaemon
        	launchctl unload /Library/LaunchDaemons/com.zuludesk.scripting.plist
			    launchctl load /Library/LaunchDaemons/com.zuludesk.scripting.plist
        	echo "Rosetta installation failed!"
    	echo "Rosetta is already installed. Nothing to do."
    echo "Mac is running macOS $osvers_major.$osvers_minor.$osvers_dot_version."
    echo "No need to install Rosetta on this version of macOS."

exit $exitcode

The bulk of the script above is Rich Trouton’s script for installing Rosetta 2. I’ve augmented it to include some instructions for reloading the Zuludesk (Jamf School) scripting agent, but you should check out the original as well.

To create the component package that Jamf School wants, you can use the following:

echo "This script uses sudo. You'll probably need to enter your password."
sudo pkgbuild --identifier com.github.jc0b.enable_arm64_scripting --root payload/ --scripts scripts/ "Install Rosetta and start LaunchDaemons.pkg"
sudo productbuild --package Install\ Rosetta\ and\ start\ LaunchDaemons.pkg Install\ Rosetta\ and\ Restart\ LDs\ product.pkg

Note that this doesn’t sign the output package, which some MDMs require you to do. You can sign this package after the fact with productsign, but Jamf School doesn’t seem to mind whether you use signed packages or not. It in fact will sign unsigned packages for you, should you upload something that isn’t signed:

> pkgutil --check-signature Rosetta\ Pre-Configuration.pkg
Package "Rosetta Pre-Configuration.pkg":
   Status: signed by a certificate trusted by macOS
   Certificate Chain:
    1. *
       Expires: 2021-03-20 23:59:59 +0000
       SHA256 Fingerprint:
           73 9C A2 99 FD D2 38 D0 5D D7 47 3C 30 9E 92 86 81 0B 34 E2 BA F3
           88 A7 89 05 1A 46 4A A1 45 32

So there we go! Once this has been installed, the LaunchDaemon will reload, and run any pending scripts on the device.

This approach is a little dirty (it leaves behind the script in /usr/local/scripts), but for my purposes it’s totally fine. You could always extend this to clean up after itself if needs be, but you might encounter some weird behaviour where Jamf School tries to push the package again after you’ve uninstalled it, depending on your setup.

All of the files needed to replicate the workaround in this post can be found on GitHub. Send me an email if you experience any issues with the provided material.