AutoPkg recipe writing: things to look out for

AutoPkg is a cool project for Mac admins (in theory, Windows admins could use it, too, and there are even a few Windows recipes). Although it's a flexible framework that can be applied in many different ways, what it's most useful for is automating the tedious process of going to a website, downloading a new version of the software, and then importing that download into whatever you're using to push updates out to your Mac clients.

For a while, I was using existing recipes (there are many, so this is a totally valid approach), but eventually there was software I didn't see recipes for, so I started writing my own recipes. At first, I just started by copying existing templates and just modifying certain parts (the download URL, or the regular expressions to search for within the search URL).

Here are some things I noticed, in case you ever want to write your own recipes and run into these issues.

Arguments need to be separate

I ran into this issue where I was trying to purge the destination before unarchiving a .zip file, but it didn't seem to be working. Even though the archive_path and destination_path seemed to work fine without being in the Arguments dictionary, the purge_destination key wasn't registering until I put them all into the Arguments dictionary, as I should have from the start... so, remember to always put all arguments in an actual Arguments dictionary. Example:


Code signature verification within disk images

When you're doing code signature verification on a disk image, you don't have to explicitly use the DmgMounter processor to mount the disk image. Instead, you can just treat the .dmg as a folder that includes the bundle to be verified. Here's an example (where %pathname% refers to the downloaded .dmg):

<string>identifier "net.gete.diskmakerx" and anchor apple generic and certificate 1[field.1.2.840.113635.] /* exists */ and certificate leaf[field.1.2.840.113635.] /* exists */ and certificate leaf[subject.OU] = "2U4ZFMT67D"</string>

Dealing with regular expressions

If you're not a regex expert, some of the regular expression searches for the URLTextSearcher processor may look like gibberish to you.

A few tips to help with that, apart from (or maybe in addition to?) reading up on all the details of the Python regex documentation:

  • Before you put the regex into your recipe, you can test out your regex using Regex101 (select the Python one).
  • Generally speaking, the most useful thing I've found is creating a capture group with
  • Just as you're about to put the regex into your recipe, make sure to substitute &lt; for < and &gt; for >

Code: Debugging the Gender Gap screening at SI

On Wednesday, May 17, we will be screening Code: Debugging the Gender Gap here at SI. Students, faculty, and staff are all welcome to come, and the event counts as a GOYB!


After the screening, we will have a discussion with guest panelists from the industry:

Lin Ling, Director of Growth at SourceClear

Lin loves building scalable growth stories at early stage B2B start ups. Today, she works with engineers and data scientists to grow paying customers at SourceClear - a software security firm. Originally a STEM nerd from NYC, she never thought she would be leading growth strategies in Silicon Valley. Other: ran growth at Spigit - innovation software firm, over caffeinated as a Deloitte Consultant doing tech systems for Google, etc. Loves yoga, D3.js, and converting websites.

Monica Garde, Software Engineer at Google Inc.

Monica has been at Google for the past 4 years as a Software Engineer on a number efforts, most recently on the Search team bringing features to emerging markets. Outside of the office, Monica runs a Girls Who Code club in Mountain View and works with to help local nonprofits improve their technical infrastructure. Prior to Google, Monica was a student at Cornell University where she studied Computer Science.

Katie Lane, Product Marketing at Bugsnag

Katie is a Texas native, and graduated from the University of Texas where she studied Electrical Engineering. Katie has spent time in various tech roles such as designing high availability data-centers, to working on the wifi chips in your cell phones, and now in product marketing, helping tech companies better communicate their products to the world. She loves teaching Girls Who Code and hopes to inspire lifelong learning for girls in tech.

Movie Trailer

Updating MS Office dock icons from 2011 to 2016 using dockutil

Managing client machines while also giving your users freedom to customize their machines as they want can be a bit tricky. On the one hand, you want to automate things as much as possible so users don't have to be bothered with too many update prompts and other maintenance nuisances. On the other hand, you don't want to automate things in a way that will confuse your users.

Jamie (our Dir. of IT) and I had a discussion about moving people from Microsoft Office 2011 to Microsoft Office 2016 and what that would look like. We didn't want to just uninstall Office 2011 right away, especially since it's the only thing MathType will reliably work with (in February, 2016, Design Science announced compatibility with Office 2016 for Windows, with a note that compatibility with Office 2016 for Mac would be coming "soon"—still hasn't come over a year later, as of this writing). And, even though installing Office 2016 side by side with Office 2011 makes 2016 the default for Office files, we wanted to update the Dock icons, so people would launch Office 2016 applications instead of Office 2011 ones.

These were the situations we thought we'd encounter:

  1. User has MathType installed. If that's the case, we don't want to touch the Dock icons. We have only a handful of MathType users, and most of them have already installed Office 2016 (previously an optional install through Munki's Managed Software Center). Only one user asked about how to change the default application to be Office 2011's instead of Office 2016's.
  2. User has only Office 2016 icons in the Dock. Nothing to do in this scenario, because everything's cool already.
  3. User has a mix of Office 2016 and Office 2011 icons in the Dock. If both Word 2011 and Word 2016 are in the Dock, we're going to assume the user wants it that way, and we aren't going to mess with it. But if Excel 2011 and Excel 2016 are in the Dock but only Word 2011 is in the Dock, we want to switch that up to be Word 2016.
  4. User has only Office 2011 icons in the Dock. If this isn't a MathType user, let's switch these all up for Office 2016.
  5. User has no Office icons in the Dock. Leave it alone. If the user doesn't want shortcuts to Office, don't put any in there.

The tricky thing about changing up Office icons in the Dock is that dockutil goes by name or bundleid to add, and both the name and the bundleid is the same for Office 2011 and Office 2016 applications.

So I wrote up a script that checks based on the dockutil --list output to see if the Dock icon is for 2011 or not. It may not work exactly for your organization, but you can see the logic in there, and it's easily tweakable.

Can’t change Safari homepage in Sierra, even with no profiles managing homepage

So I came across something weird that's affected only my 10.12.4 clients (none of my 10.11.6 clients seem to be affected by this). Even though I have only one Safari profile enabled, which is set-once and doesn't manage the homepage, my 10.12.4 clients are unable to change the homepage in Safari manually. Whatever the homepage was is stuck like that. If you enter a new homepage in the Safari preferences, it will just not take and revert back to the old homepage once you hit Enter or click out of the address entry field.

The only workaround I've found for this is to delete all profiles (again, even though I don't have any profiles managing the Safari homepage):

sudo profiles -D
Are you sure you want to delete all configuration profiles? [y/n]:y
reboot the computer, and then reinstall (via Munki) all the previously installed profiles (yes, including the set-once profile for Safari that was installed before)... and then I'm able to change the homepage on the client manually. Very bizarre.

Also, after testing on a couple of other clients, there do seem to be situations in which the Safari profile was never set at all, and you still can't modify the homepage, even after deleting any other profiles and rebooting, and it's not account-specific either (freshly created account experiences it, too). It's a real head-scratcher.

Script AutoPkg trust verification and trust update process

Starting with version 1, AutoPkg began evaluating trust info for recipes, so you could see what changes were made to a recipe (if changes were made) and then accept the changes if you wanted to. Here is what the typical trust verification workflow looks like.

Whether running a list of recipes via script or via AutoPkgr schedule, I'd occasionally get error'ed recipes when trust was broken, have to manually run

autopkg verify-trust-info -vv NAMEOFRECIPE
and then, after review, run
autopkg update-trust-info NAMEOFRECIPE
and then run the recipe after updating the trust info:
autopkg run -v NAMEOFRECIPE
So I thought I'd take a stab at scripting the whole process. Basically my script updates all the repos (to see if there are changes), verifies trust info on each of the recipes in the recipe list, and then prompts the user to review changes and approve them or not, before running all the approved or unchanged recipes.

It's still in the early testing phase, but it seems to work so far....

Guided Access mode after a reboot on iOS 9 vs. iOS 10 vs. iOS 11 vs. iOS 12

iOS 12 Update: Just tested on iOS 12, and it gets out of guided access mode after a reboot.

iOS 11 Update: Just tested on iOS 11, and it stays in guided access mode after a reboot. Same with iOS 11.1.

Update: Just saw an iPad with 10.3.3 not get out of guided access mode after a reboot.

Just a quick observation based on testing:

If you're in Guided Access mode on an iPad running iOS 9.3.5 (say, an older model that can't install iOS 10 and above), and you do a forced reboot (hold home and power buttons until the Apple symbol appears), the device stays in Guided Access mode.

If, however, you're in Guided Access mode on an iPad running iOS 10 (and perhaps in future versions?) and do a forced reboot, the device gets out of Guided Access mode.

P.S. I was able to use this to help a student out who was stuck in Guided Access mode on an older iOS version—updated it to iOS 10, rebooted, and then the iPad was out of Guided Access mode, and a new Guided Access mode passcode could be set.

Basics of Crypt 2 and Crypt Server

Graham Gilbert created a pretty cool project called Crypt 2, which forces client machines to enable FileVault2 encryption, and then sends the recovery key to a Crypt Server.

So far the documentation on Crypt 2 is rather sparse, so this is what I was able to piece together based on the README, some asking around, and a lot of trial and error.

The server

The server bit was tricky for me to figure out. I happened to have a Ubuntu 16.04 LTS server I wanted to try it out on, but the Ubuntu 14.04 and Ubuntu 12.04 instructions did not work for 16.04. I got this error when trying to run the command to get the requirements:

Collecting django-extensions==1.6.8 (from -r
crypt/setup/requirements.txt (line 4))
Could not find a version that satisfies
the requirement django-extensions==1.6.8 (from -r crypt/setup/requirements.txt (line 4)) (from versions: 0.4, 0.4.1, 0.5, 0.6, 0.7, 0.7.1, 0.8, 0.9, 1.0.0, 1.0.1, 1.0.2, 1.0.3, 1.1.0, 1.1.1, 1.2.0, 1.2.1, 1.2.2, 1.2.3, 1.2.4, 1.2.5, 1.3.0, 1.3.1, 1.3.2, 1.3.3, 1.3.4, 1.3.5, 1.3.6, 1.3.7, 1.3.8, 1.3.9, 1.3.10, 1.3.11, 1.4.0, 1.4.1, 1.4.2, 1.4.3, 1.4.4, 1.4.5, 1.4.6, 1.4.7, 1.4.8, 1.4.9, 1.5.0, 1.5.1, 1.5.2, 1.5.3, 1.5.4, 1.5.5, 1.5.6, 1.5.7, 1.5.8, 1.5.9, 1.6.1, 1.6.2, 1.6.3, 1.6.5, 1.6.6, 1.6.7, 1.7.0, 1.7.1, 1.7.2, 1.7.3, 1.7.4, 1.7.5, 1.7.6, 1.7.7)
No matching distribution found for django-extensions==1.6.8 (from -r crypt/setup/requirements.txt (line 4))

It seems a lot of people go the Docker route. I'm not really an experienced Docker user, but most of the instructions are fairly straightforward.

After you install Docker, you just pull Crypt Server:

docker pull macadmins/crypt-server

Then, to test Crypt out, try the sqlite instructions from the docs.

And then, when you're ready to go full production, scrap that container, get a proper postgresql database and then follow the Using Postgres as an external database instructions.

Special notes based on things Graham Gilbert (primary developer on Crypt) told me:

  • You should not use the sqlite3 database for production. Nor should you use grahamgilbert/postgres as a docker container for a postgres database to link up. For a production Crypt server, you should link up to an external postgres database, preferably Amazon RDS or Google Cloud SQL.
  • For an external postgres database (whether self-hosted or hosted in the cloud), all you need is a database name, a username, and a password. Crypt will do the rest (creating tables and populating them).
  • If you decide to host the postgres database on the server that's running the docker container, localhost (or is the actual Crypt docker container—it is not the host running docker. So you should specify the host as the actual fqdn or IP address.

Whether you ran the test Crypt with sqlite3 or a proper production Crypt with postgres, you should be able to see your site at

There are ton of ways, apparently, to get the site more secure. Since I'm most familiar with using Apache, I just put in a :443 VirtualHost entry with

ProxyPass /
ProxyPassReverse /
and then that worked out for getting SSL going (as long as you've got SSL going for SUBDOMAIN.MAINDOMAIN.COM... that's too much to go into for one blog entry).

Once you have that set up, go to https://SUBDOMAIN.MAINDOMAIN.COM and log in with admin and password, and then immediately change the password to a new one.

You'll then have the option to add other users with various types of permissions.

The client

The Crypt 2 client is a standard .pkg you can deploy with whatever you're using to manage your client machines. You can configure your clients' Crypt 2 preferences before deploying the .pkg.

When the user who's okay to enable encryption (one not in the SkipUsers array) logs in, a window will pop up that says This machine must be encrypted. It will reboot shortly after clicking continue.

Crypt will write a file to /var/root/crypt_output.plist with values for EnabledDate, EnabledUser, HardwareUUID, LVGUUID, LVUUID, PVUUID, RecoveryKey, and SerialNumber.

After that, probably nothing will happen for some time. Crypt 2 runs a Launch Daemon that invokes /Library/Crypt/checkin every 900 seconds (15 minutes) 120 seconds (2 minutes). After around that time, you should see the client machine show up in the web interface the admin sees.

If you're using a self-signed certificate on your Crypt server, the curl command in the checkin script will fail when escrowing the recovery key. To avoid that (thanks to Graham Gilbert for the tip), you can add the .pem somewhere on the client machine and modify the .curlrc file for root to point to it.

If you click Info... ...and then Info / Request... ...and then Retrieve Key... ... you should be able to see the recovery key.

You may have to ask permission of yourself to view the recovery key (presumably if you have users with different permission levels, a user with lesser permission would ask an admin user for temporary permissions to view the recovery key).

Other options

This isn't a comprehensive list:

Using startosinstall to install a macOS upgrade with Munki

Update: The instructions below will be obsolete once Munki 3 is released. More details on the Munki 3 implementation can be found on the Munki wiki.

createOSXinstallPkg is a great project for making an Apple macOS installer into a .pkg you can deploy with Munki.

Apple did some things to break that process for 10.12.4. People are in the process of finding workarounds for it.

One option is to use the built-in startosinstall tool that comes with the installer bundle.

If you import the bundle into Munki, you'll want to have both a preinstall_script and a postinstall_script.

The preinstall_script checks to make sure there aren't other updates pending, since startosinstall will run its own reboot independent of Munki. The pending updates should be 1 (it's the only 1) or 0 (it was part of a set of updates that did complete and then the pending updates cleared, and you're trying again):


# Make sure there is only one pending update (this one)
pending_count=$(defaults read /Library/Preferences/ManagedInstalls PendingUpdateCount)

# If it's 1 or 0, we're good to go
if [ "$pending_count" == 1 ] || [ "$pending_count" == 0 ]; then

exit 0


# Otherwise, abort the installation
exit 1

The postinstall_script does the actual install:

sudo "/Applications/Install macOS" --applicationpath "/Applications/Install macOS" --agreetolicense --nointeraction
Just as you would with a normal OS upgrade item, you want the installs array to reflect the OS version (not the presence of the installer bundle in the /Applications folder), and you want to mark this as an Apple item. (Check the Munki wiki for more details about those two things.)

P.S. There is now a recommendation on the createOSXinstallPkg README to upgrade using 10.12.3 or investigate using startosinstall.

P.P.S It's possible, instead of my funky workaround with the preinstall_script, that you could use the --pidtosignal option instead with Munki. Here's an example using JAMF.

P.P.P.S. Looks as if Greg Neagle has started working on integrating startosinstall into Munki "natively"—yes!

Students unsubscribing from mailing lists within Google Apps for Education

GAM solution

Apparently, there is a GAM solution to this, which is

gam update group nameofgroup who_can_leave_group ALL_MANAGERS_CAN_LEAVE

I did a quick test, and it appears to work. Users will still see the option to "unsubscribe," but after they do, they'll still be subscribed anyway.

Thanks to Zack McCauley for the tip.

What's the problem?

We used to have students come in and say they'd accidentally unsubscribed from an important school mailing list and needed to be resubscribed. This confused us, because we didn't think students could unsubscribe themselves. Turns out not only can they (click on the arrow at the top of the email and then select Unsubscribe from this mailing-list), but we contacted Google directly, and they confirmed there isn't a way for Google Apps admins to prevent users from unsubscribing (and apparently this issue goes back at least as far as 2012, if not longer).

I get that people in general should be able to unsubscribe from mailing lists, but this is a controlled environment. These are email addresses provided by the institution (school, organization, company) and so the institution should be able to decide what mailing lists its own employees and students are on, right? Well, apparently not.

Fortunately, we don't have a ton of people who make it a habit of unsubscribing themselves from mailing lists. Most of the time when students do unsubscribe, they soon realize they're missing important messages, and then they ask to resubscribe.

Nevertheless, it can be handy to find which students are unsubscribed from the lists they should be subscribed to.


This is something I thought GAM should be able to handle, and I even got some suggested commands from another Mac admin. Unfortunately, I couldn't get gam to work. I followed all the instructions and still ended up with this error, no matter how much "coffee" I got and despite enabling access to the suggested scopes:

Are you ready to authorize GAM to manage G Suite user data and settings? (yes or no) Y
Great! Checking service account scopes.This will fail the first time. Follow the steps to authorize and retry. It can take a few minutes for scopes to PASS after they've been authorized in the admin console.
Scope: FAIL
Scope: FAIL
Scope: FAIL
Scope: FAIL
Scope: FAIL
Scope: FAIL
Scope: FAIL
ERROR: Some scopes failed! Please go to:
and grant Client name:
Access to scopes:,,,,,,
Service account authorization failed. Confirm you entered the scopes correctly in the admin console. It can take a few minutes for scopes to PASS after they are entered in the admin console so if you're sure you entered them correctly, go grab a coffee and then hit Y to try again. Say N to skip admin authorization.

Google Apps Script

I started looking into Google Apps Script, which is pretty cool, except the documentation isn't comprehensive enough to suggest all the steps involved to find unsubscribed users.. For example, there isn't any documentation on how to construct a query that has two parts to it. Frankly, I couldn't even find official Google documentation on how to include a query—I had to find that on Stack Overflow.

In addition to missing pieces in the documentation, Google Apps Script also suffers from arbitrarily imposed limits to what you can do. For example, you can't fetch more than 500 user records at once. Also, even though there's a function to check if a user is a member of a group, if you run that in a loop over several hundred users at once, you'll get this error:

Service invoked too many times in a short time: groups read. Try Utilities.sleep(1000) between calls.

I played around with scripting putting users back into groups, but it gave uneven results (some of that was our own fault—we had a couple of users in the incorrect organizational units, but it also seemed to sometimes not actually put users in groups successfully).

The working script

So ultimately, I didn't script this in the "ideal" way (due to limitations put in place by Google), but the script basically works. For each grade level, it will find all the students who aren't suspended users, put them into an array, find all the members of the appropriate mailing list, and then take each of those members out of the original array. Anyone still left in the array is potentially unsubscribed to a list she should be subscribed to.

Finally, an email is sent to specified recipients to let them know either everything's okay or something has to be investigated. You can script this to run every day or every week and then make fixes yourself afterwards.

Google Groups / mailing list quirk

I also found one quirk to Google Groups. There was one student I had trouble adding back to a group. It would seem I could add the student in the admin console, but then the student still wouldn't be added (no error message). It was only when I went to Google Groups itself (not the admin console) and tried to add the student directly that I saw a message saying I couldn't add the student because the student already had an pending request that needed approval (the student had asked to re-join the mailing list). Once I approved that request, the student was back in.

Why you should use FileVault personal recovery keys instead of institutional recovery keys

In my previous blog posts on FileVault, I talked about or showed how to use an institutional recovery key for FileVault encryption:
Enabling FileVault Encryption for Client Macs
Setting up deferred FileVault encryption
Using a FileVault institutional recovery key to unlock an encrypted disk

But in exploring FileVault further, I've found it's much better to use personal recovery keys instead of a single institutional recovery key, and it's not for the reason you might think.

IRK not necessarily less secure than PRK

Yes, from a security standpoint, you could make the case that an institutional recovery key creates a single breach point (someone obtains that one recovery key and thus can decrypt all your institution's machines), but I don't think this makes personal recovery keys more secure necessarily. First of all, the personal recovery key itself can unlock a machine, but the institutional recovery key is used in combination with a password to unlock the keychain. Secondly, most likely you're storing your personal recovery keys all in one place—it may be a secure place, but it's also a single breach point. If you somehow access that one storage location (database, spreadsheet, whatever you're using to store the personal recovery keys), you have access to all the recovery keys for all the machines.

I suppose you could scatter the personal recovery keys in multiple storage locations. There is always an artistic (not scientific) balance between security and convenience, so that's up to you how you decide to store things. The point, though, is that an IRK is not necessarily less secure than a PRK.

You could make the case that with something like Crypt2 you at least have an audit trail of when recovery keys were accessed, and you can generate new ones, so personal recovery keys do have one small distinct security advantage.

IRK is less useful than a PRK, though

As I was rolling out encryption to our fleet using an institutional recovery key, I started to realize through testing (fortunately not through an actual emergency) how limited in functionality the institutional recovery key is compared to the personal recovery key.

First of all, unless you are physically in front of the machine or using ARD to remote into a virtual session, you cannot enable another FileVault user without storing the password for it in plain text. If you try to do so via SSH and the command-line, you'll be prompted for the password of an FV-enabled user or for the personal recovery key, so having the IRK doesn't help there.

That's not really the worst part. The worst part is that, as far as I can tell (based on Google searches, asking other Mac admins, and just trial and error), there is no way to reset a forgotten user password with just the institutional recovery key. You can unlock the encrypted volume and save the data, but you can't just say "Reset this user's password." You can, as a horribly long workaround, decrypt the drive, log in as another admin user, reset the other user's forgotten password, wait for the decryption to finish completely, and then re-encrypt. That can take a really long time.

But if you just use personal recovery keys, you can have the user try to log in three times, and she'll be prompted to enter the recovery key to reset the forgotten password, and then be prompted to enter a new password. (You can also just hit the question mark next to the password field, which will take you there directly instead of having to fail a few times.)

Wesley Whetstone has created a neat little pkg that can generate/regenerate personal recovery keys: fde-rekey. Wesley Whetstone has since abandoned fde-rekey in favor of Crypt.

Once you've switched that over, you can also remove the institutional recovery key (yes, it's possible to have both an IRK and a PRK). If you're using Munki, I wrote a nopkg that will remove the IRK after fde-rekey is installed.

P.S. Another good reason not to use institutional recovery keys: Unlocking or decrypting using an institutional recovery key does not work with encrypted APFS boot drives on macOS High Sierra 10.13.0. It may work in 10.13.1, but that hasn't been verified yet (as of this writing).