Tracking Secure Token and Volume Owner users in Jamf Pro

With the introduction of APFS, Apple introduced secure tokens which allow users to unlock FileVault. If there are no users who have a secure token, you may find yourself in a position where you cannot enable FileVault or unlock FileVault.

Most recently, with Macs running on Apple silicon, Apple introduced the concept of volume ownership which determines which users are authorized to make changes to the volume. This can happen in at least two important scenarios that come to mind: OS upgrades and OS updates. If there are no users who have volume ownership, you may find yourself unable to perform OS updates.

Whether a user is an administrator or not, can also determine whether there are certain tasks that can be performed.

Apple goes into better detail in this enterprise focused deployment guide over the concepts of Secure Tokens, Volume Ownership and Bootstrap Token. If you’re running newer versions of macOS, Apple has tried to close all gaps where a managed device might be configured without a secure token or volume owner user. Ideally, you don’t have any Macs like this in your environment.

Needless to say, it’s useful to track this information in Jamf Pro. I created two extension attributes to track Secure Token users and Volume Owner users a while ago but never wrote a blog post to cover them.

Secure Token Users

The Secure Token Users extension attribute will report all user accounts who have a secure token. If a user is found to have a secure token, the results will be displayed as:

  • Admins: user1, user2 (or “None” if none found)
  • Non-Admins: user1, user2 (or “None” if none found)

If no user is found to have a secure token, the result will be:

  • “No Secure Token Users”

If an unsupported file system is found, the result will be:

  • Unsupported File System: (File System Type)

Volume Owner Users

The Volume Owner Users extension attribute will report all Volume Owners on Apple Silicon Macs. If a user is found to be a volume owner, the results will be displayed as:

  • Admins: user1, user2 (or “None” if none found)
  • Non-Admins: user1, user2 (or “None” if none found)

If no user is found to have be a volume owner, the result will be:

  • “No Volume Owners”

If an unsupported file system is found, the result will be:

  • Unsupported File System: (File System Type)

If an unsupported architecture, the result will be:

  • Unsupported Platform: (architecture)

Once you’ve got these extension attributes scripts running in your Jamf environment, you can then start to run reports against computers that do not have secure token users and/or volume owner users and take steps to remediate. Or if you want to look at devices in a one-off situation, you can tell which secure token & volume owner users and their admin status. Worst case scenario is the device will need to be backed up, wiped and OS re-installed to go through provisioning again.

Looking at logs in macOS and troubleshooting DNS/VPN issues

Over the past few months, I’ve found myself having to troubleshoot a variety of DNS and VPN-related issues that a small subset of our users have encountered. These kind of issues can be caused by all sorts of reasons: network configuration, DNS resolvers, the VPN connection and its configuration, third party clients, updates to macOS, etc. Working from home makes it even more difficult to determine what the cause might be.

The goal of this post is in a way to help future me but also to share what I’ve learned in the process in terms of troubleshooting these kinds of networking issues.

The Questions

There should be a series of questions that you ask the end user having the problem which are not limited but should include:

  • What version of macOS are you running?
  • Have you recently installed any software?
  • Are you running any security software?
  • If you go to the Apple menu > System Preferences > Network, can you confirm the active connections (with a green dot) and the order in which they appear?
  • Are you experiencing the problem on a wired connection?
  • Are you experiencing the problem on a wireless connection?
  • Are you experiencing the problem on a personal hotspot connection?
  • Are you experiencing the problem on a completely different network (e.g. a friend’s house, at home, in the office, etc.)?
  • Does the problem occur after you’ve left the computer idle for some time?
  • Do you have dates/times for when the problem occurred?
  • Are you using any VPN connection when this problem occurs?
  • Have you noticed a pattern as to when the problem occurs?
  • Does the problem occur if X software is disabled/uninstalled?

Some of those questions you may easily be able to answer yourself through your own management tools. Additionally, some of these questions the user may have already answered when they reached out for assistance.

The questions above give you a bit of context as to how the problem manifests. However, it may not be sufficient to reach a resolution. You need to look at logs.

The Logs

It’s important to gather logs to at least start looking what could be causing the problem. Ideally, the user provides some a timestamps to make looking at the logs a little easier. However, what are these “logs” that we’re supposed to be looking at?

You will first want to look at logs generated by the OS itself. macOS has a unified log system. I won’t go into the history, but will instead reference a few good resources that have helped me understand things a bit better as it pertains to the unified log system:

If you look at the man page for the log command, it can be quite overwhelming at first glance. They do provide a few examples which I do think are helpful for how you can combine different expressions under the --predicate filter option. But the page alone isn’t sufficient which is why the above blog posts are really handy.

Once you have an idea of how to use the log command, you need to then know what to look for. For this part of the process, I had some assistance from the third party Cisco Umbrella Diagnostic Tool. It captures quite a decent amount of information. One of the commands that it runs:

/bin/sh -c /usr/bin/tail -n 15000 /var/log/system.log | /usr/bin/egrep -i "kernel|launchd|vpn|dns|configd|racoon|umbrella"

This command will go through the last 1500 lines in the system.log and only show lines which match any of the phrases separated by “|”.

One of the first things I noticed, is that not as much is written as one would expect in /var/log/system.log. Obviously, this is problematic if we we’re looking for information. However, the commands do provide a little bit of clarity as to what processes or terms we want to look for. Combined with the knowledge that you need to really look at the log stream, I went to work on writing a lengthy one line command that I could then run to capture relevant information that the system has logged.

The Logs Commands

Before I get to the one line of code, I’d like to go through walk you through the process on how I got there.

First off, it’s important to understand that I’m asking users to run this and it’s much easier to write the log to a common location which I’ve simply chosen: /Users/Shared/system-dns-logstream.log. You can obviously write out the log to any other location.

Secondly, the log command can be very verbose and depending on the predicate filter, the resulting log file might be too large. For that reason, you’ll notice I use --last 8h which references the fact that I want only the last 8 hours of logged information to be shown. You can modify it accordingly if you want to 48h (for 2 days) or 10m (for minutes) depending on how far back you need to look.

Network Extension framework subsystem

To look at anything logged by the Network Extension framework subsystem run this command. “raccoon” is traditionally the vpn process in macOS that you’d want to look for in logs. However everything it logs will be written to the Network Extension subsystem.

sudo log show --last 8h --predicate 'subsystem == ""' > /Users/Shared/system-dns-logstream.log

System Configuration framework subsystem

To look at anything logged by the System Configuration framework subsystem, run this command:

sudo log show --last 8h --predicate 'subsystem == ""' > /Users/Shared/system-dns-logstream.log

symptomsd framework subsystem

symptomsd is a Symptom framework service daemon. I don’t have much official documentation I can link to here except to note that it was mentioned in one of the useful links I provided earlier. What I noticed is that everything networking related fell under the category “netepoch” which provides more targeted information.

sudo log show --last 8h --predicate 'subsystem == "" AND category == "netepochs"' > /Users/Shared/system-dns-logstream.log


To look at any event messages that contains the phrase “DNS”, run the command:

sudo log show --last 8h --predicate 'eventMessage CONTAINS[cd] "dns"' > /Users/Shared/system-dns-logstream.log


To look at any event messages that contain the phrase “VPN”, run the command:

sudo log show --last 8h --predicate 'eventMessage CONTAINS[cd] "vpn"' > /Users/Shared/system-dns-logstream.log

Network framework

To look at anything logged by the Network framework under the “connection” category, run the command:

sudo log show --last 8h --predicate 'subsystem == "" AND category == "connection"' > /Users/Shared/system-dns-logstream.log


configd is the System Configuration daemon that, according to the man page: “is responsible for many of the configuration aspects of the local system”. Although, not everything is networking related, a good chunk is. You may also note from one of the earlier links that one of the commands specifically looks at the subsystem “”. This command will cover events logged under that subsystem. To look at any event messages that contain the phrase “configd”, run the command:

sudo log show --last 8h --predicate 'eventMessage CONTAINS[cd] "configd" OR process == "configd"' > /Users/Shared/system-dns-logstream.log


I do not have anything to reference here other than seeing it referenced in one of the links I mentioned above. Rather than filter for specific categories, I’m looking at the entire CoreUtils subsystem. Run the command:

sudo log show --last 8h --predicate 'subsystem == ""' > /Users/Shared/system-dns-logstream.log

Umbrella (or your own third party tool)

Cisco Umbrella technically logs under a process called “dns-updater”, but there may be references where Umbrella is referenced in. To look at any event messages that contain the phrase “Umbrella”, run the following command:

sudo log show --last 8h --predicate 'eventMessage CONTAINS[cd] "umbrella"' > /Users/Shared/system-dns-logstream.log

Note: if you’re using another networking tool or service, you can alternatively include the name of that tool instead of “Umbrella”.

dns-updater (or your own third party tool)

dns-updater is the process under which Cisco Umbrella runs and will do its logging. To look at any event messages generated by the process dns-updater used by Cisco Umbrella, run the following command:

sudo log show --last 8h --predicate 'process == "dns-updater"' > /Users/Shared/system-dns-logstream.log

Note: if you’re using another networking tool or service, you can replace “dns-updater” with whatever the process name is for that tool.


It may be helpful to look for other potential events related to launchd which is why this command is included. However, this can be quite noisy which is why there are additional expressions in the predicate filter to exclude certain event messages. To look at any event messages that contain the phrase “launchd”, run the command:

sudo log show --last 8h --predicate 'eventMessage CONTAINS[cd] "launchd" AND NOT eventMessage CONTAINS[cd] "invoked (by pid 1/launchd)" AND NOT eventMessage CONTAINS[cd] "OSLaunchdJob"' > /Users/Shared/system-dns-logstream.log

Log: Putting It All Together

Obviously, we’re not going to ask someone to run each of those commands individually. Plus it would be really difficult to lineup the times from each command. The log command makes it easy to combine all these expressions even if it doesn’t necessarily look pretty.

When putting it all together, I did add an additional filter to ignore any references to the log command using: AND NOT eventMessage MATCHES ".(/usr/bin/log)."

In the end, the command looks like:

sudo log show --last 8h --predicate '((subsystem == "") || (subsystem == "") || (subsystem == "" AND category == "netepochs") || (eventMessage CONTAINS[cd] "dns") || (eventMessage CONTAINS[cd] "vpn") || (subsystem == "" AND category == "connection") || (eventMessage CONTAINS[cd] "configd" OR process == "configd") || (subsystem == "") || (eventMessage CONTAINS[cd] "umbrella") || (process == "dns-updater") || (eventMessage CONTAINS[cd] "launchd" AND NOT eventMessage CONTAINS[cd] "invoked (by pid 1/launchd)" AND NOT eventMessage CONTAINS[cd] "OSLaunchdJob")) && (NOT eventMessage MATCHES ".(/usr/bin/log).")' > /Users/Shared/system-dns-logstream.log

It’s not pretty, but it is readable. An alternative was to make more use of the MATCHES operator and make use of regular expressions. Be mindful of the fact that the resulting file may easily be bigger than most users might be able to send over email.

Once you get the log file, make use of either the Console app or your log reader/text editor of choice to navigate its content. It will look overwhelming initially, but you’re essentially looking for references to where network changes may have occurred.

Because the log system can go back so far, you can ask the user to run this command at the very end of any troubleshooting you might be asking them to do.

Other Logs

I’d be remiss if I didn’t mention that if you’re dealing with third party tools, there may be specific logs they write to. Places to look may include:

  • /var/log/
  • /var/logs/
  • /Library/Logs/
  • ~/Library/Logs

I would also consult the vendor and/or their documentation for where their application(s) may keep logs on the system.

Network Troubleshooting

In addition to looking at logs, there are some basic networking troubleshooting steps you can run through that may be helpful as well. Replace with the fully qualified domain name of the host you’re trying to reach.

  • ping -t 5 fdqn.server.domain

Use ping to send ICMP ECHO_REQUEST packets to the server that cannot be reached. The server may not respond to these kind of packets. This will send 5 packets. Assuming you get a response, pay attention to the time it takes to get a response. High values may indicate high latency issues. Anything over 100ms may indicate high latency which can impact communication between the system and host/service you’re trying to reaching.

  • ping -t 5 <IP Address>

Same as before, except we’re using the IP address to send the request which can start to fill in the picture as to whether there may be a DNS issue at play.

  • dig fdqn.server.domain

Use the DNS lookup utility to do a lookup using the default DNS resolver

  • dig fdqn.server.domain @

Use the DNS lookup utility to do a lookup using CloudFlare’s DNS resolver. Other alternatives include: (Google), (Quad9), (OpenDNS)

  • /usr/sbin/traceroute -I -w 2 fdqn.server.domain

Use the traceroute tool to determine the route packets are taking to reach a particular host based on the host name.

  • /usr/sbin/traceroute -I -w 2 <IP Address>

Use the traceroute tool to determine the route packets are taking to reach a particular host based on the IP address.

  • ifconfig

The network interface configuration command will show you all network interfaces that are currently configured on the system.

  • scutil --dns

This reports the DNS configuration on the system. This is useful for seeing what domains may be configured to use specific name servers.

  • netstat -rn

Show the network routing table configured on the system using network addresses in numeric format. With a VPN connection, you will see additional routes. On macOS Big Sur, you’ll note that the first column representing destination network ranges are in CIDR format. The second column will point to the IP address of the server that does the resolving. Depending on your networking experience, this may or may not make too much sense. But it’s important to understand nonetheless. If you’re trying to reach a network address and it falls under one of the listed entries that is not default, then it means the traffic will be route to a different server than default.

  • route -n get fdqn.server.domain

This will display the route taken to reach a specific host by and attempt to show the network addresses in numeric format.

  • Other commands that assist with doing name lookups include nslookup and host. Apple includes a particularly useful and important note at the bottom of the nslookup man page that is applicable to the 3 tools:

The nslookup command does not use the host name and address resolution or the DNS query routing mechanisms used by other processes running on macOS.  The results of name or address queries printed by nslookup may differ from those found by other processes that use the macOS native name and address resolution mechanisms. The results of DNS queries may also differ from queries that use the macOS DNS routing library.

man nslookup (macOS Big Sur)

That does not invalidate the usefulness of the commands. But it is important to keep in mind that some applications/processes using the built-in OS routing mechanisms may differ from what you see in some of these command line tools.

Also, keep in mind you may want to run through all these commands under different scenarios. For example, you may want to run these commands with a security tool turned off and turned on. Or maybe when on a specific network vs another network. Or perhaps when connected to VPN vs disconnected from VPN.

Packet Capture

Packet captures can be really useful for determining what might be going on. I won’t go into how to analyze the packet captures. I found this particular Youtube video useful as a starter. Your network engineer may also be a good resource. And of course you can just search the internet on how to use tcpdump.

To create a capture of all network interfaces on the system:

  • Run the command in Terminal: sudo tcpdump -i any -w /Users/Shared/packet.pcap 
  • Keep the Terminal window open
  • Reproduce the issue as soon as possible.
  • Go back to the Terminal window and stop the packet capture by press CTRL + C

It’s important to note that packet capture files can result in huge files if left running for more than 1 minute. In fact, 1 minute may be too long. Once you do have the packet capture, you will want to use an application like Wireshark to visually analyze the packet capture. To avoid large packet captures, you will most likely need to be able to reproduce the problem very quickly.

It may be useful to have a packet capture from a device (either the same device or another) where the problem cannot be reproduce (things work as expected) to compare to the packet capture from the device where the problem can be reproduced (things do not work as expected).

Other considerations

Wireless connections can tend to be less reliable than wired connections. A particularly useful hidden function in macOS is the ability to get wireless statistics by simply OPTION + Clicking the Wi-Fi menu. You can then ask a user to take a screenshot CMD + SHIFT + 4 + Spacebar:

The further away from 0 a signal gets, the more noise on the connection. This can impact the reliability on the connection and could potentially result in sporadic networking issues that are hard to reproduce.

You’ll also note that you can run some diagnostics as well which may also be useful to go through as well.

That’s all I have for the time being. If there are any particular network troubleshooting tips you find useful in macOS, feel free to share in the comments.

Handling major upgrades and minor updates for macOS with Jamf

Getting end users to upgrade to the latest supported version of macOS in an enterprise environment can be a little tricky. Some see it as a time consuming and tedious task that can get in the way of actual work. It doesn’t help you need to do this once every year for major OS releases and every 2 months for regular minor updates (or more frequently in some cases). However, there are not only security benefits to upgrading, but usually newer features that end-users can take advantage of that may increase their productivity. But more importantly for the IT admin, there’s less time and resources spent supporting multiple operating systems when you simply have only one version to support.

Depending on the environment, there are different approaches an organization can take in tackling the issue of getting company devices upgraded:

  • Compliance: The idea behind this approach is simple. Only provide access to the work resources that an end-user needs so long as the device they are on is considered “compliant” (or in this case, up-to-date).
  • Reminders: Your organization communicates and strongly encourages getting folks to upgrade through notifications, emails, and maybe even messages coming from your business communication tool of choice.
  • Force:  It doesn’t matter what the circumstances, an OS upgrade is pushed out to all devices and each of them get it at some specified time.
  • Anarchy: End users dictate the OS they run and you will support it.

There are probably other approaches that fall somewhere in between. The focus of this blog post is a script I wrote that takes a combination of the 2nd and 3rd approach. This may or may not work for your environment.


To start off, this script is meant to be used with Jamf Pro and makes use of Jamf Helper. I’ve previously written two other tools that tackle the issue in two separate ways:

Because the use cases are similar enough I combined the two scripts into one. The other scripts mentioned in those blog posts will no longer be maintained.

The idea behind this new script is that it alerts the user that the OS they are running is no longer supported through Jamf Helper dialogs. It’s important to try and provide a little bit of flexibility in how often the user gets notifications. Rather than forcing updates after, the script allows you to control the pace of notifications the user receives to perform the OS upgrade/update by letting you set different dates of importance. 

This script can be used to handle either major OS upgrades or minor OS updates. By default, if you do not set any parameters or variables, the script will check for minor OS updates. One thing to note is that the script will not do BOTH at the same time. In other words, you will need one policy in Jamf Pro when this script is used to handle minor OS updates and a second policy to handle major OS upgrades.

This script does try to account for the fact that it requires user interaction. That means there are a few checks in place to try and give the user the best chance to perform the upgrade/update before they get a notification:

  • Ensure a user is logged in.
  • Power source is connected when an update is being forced.
  • No app is opened that has a display sleep assertion active (e.g. web conferencing apps, presentation tools, screen sharing tools, etc. tend to make this assertion to prevent the computer from going to sleep)
  • Idle time of the computer.

If the user opts to proceed with the upgrade/update, the default experience will take the user to the Software Update preference pane to perform the minor update or the Mac App Store to perform the major upgrade or a custom policy that you’ve configured to do the OS upgrade. The one exception to this is if you do supply custom policy name for major OS upgrades (discussed a little later), the user will be directed to Self Service as an alternative to perform the major OS upgrade instead of going through the App Store.

With each notification, there is a “More Info” button in the Jamf Helper dialog which will always open up a URL. This URL should provide the user with instructions on how to proceed to update. Because it’s a URL, you could also provide a Self Service URL as well. If you do not provide a URL, it will simply default to Apple pages:

As you can tell, this script mostly attempts to get the user to take action for most steps. However if there are minor OS updates available that do not require a restart, an attempt will be made to install them through the command line so long as there are not other updates that do require a restart.

There are three optional dates that you can supply to the script that will dictate when the user receives the notifications:

  • Start Date: This is the date when the user will start receiving notification with the option to delay when
    they will receive the next reminder.
  • Nag Date: This is the date when the user will start receiving notifications without an option on when to receive the next reminder. This allows the user to essentially turn things up and nag the user to update a bit more aggressively. The frequency between notifications will be based on the re-notification period set by the admin. This re-notification period essentially lets you determine how often you want to remind the user to update.
  • End Date: Once this date has been reached, the user has X amount of attempts (3 by default) left before they are forced to upgrade. The user will be reminded every 24 hours until they’ve exhausted their X postponements. Similar to the Nag Date, this provides a notification to the user where they cannot select when they will be reminded. If you were previously handling minor OS updates using the script in this blog post then you can emulate the same behavior by simply setting an End Date.

Dates can be supplied in either epoch seconds or string format (e.g. Sep 03 12:34:56 -0400 2019). Note that the date is evaluated against the computer’s local time. The notifications are meant to get increasingly more forceful.

Because the dates are optional, you can either supply no dates, some of the dates (e.g. Start And End Date only), or all of the dates at your discretion. What this means is that the user will only see the notification after that date has been reached. If no dates are supplied, the user will simply get the same notification as if a start date had been set with an option to be reminded later. Each date needs to be greater than the previous; your Start date can’t be set to a date after the End date.

If you’ve configured an End Date and the user has no more deferrals/postponements left, the script will take a few actions:

  • if we’re dealing with major OS upgrades and minor OS updates on Apple Silicon Macs, the user will simply be given a final chance to perform the upgrade/update before the computer is shut down. The user will get at least an hour (or longer if you’ve set the Time Out period to be longer) to perform the OS upgrade/update once they’ve reached this state. On Apple Silicon Macs, you cannot perform a command line install without user interaction which is why a command line install is not attempted.
  • if we’re dealing with minor OS updates on Intel Macs, a command line install will take place.


Given the above overview, here are the Jamf script parameters:

  • Parameter 4: Optional. Enter “major” or “minor” which will dictate if the script alerts against a necessary major OS upgrade or minor OS update. If not set, the default will be “minor.”
  • Parameter 5: Optional. The custom trigger name for a Jamf policy that will initiate the major OS upgrade.
  • Parameter 6: Required. Provide the policy kicking off this script a custom trigger name and supply it here. This is used for situations when the user tries to quit Jamf Helper notifications.
  • Parameter 7: Optional. The Start Date at which point the logic in this script will provide the end-user a notification with the option to set when they will receive the next reminder. If not set, the user will start to receive notifications the second time the script is run based on the re-notification period. Date can be supplied in epoch seconds or string format (e.g. Sep 03 12:34:56 -0400 2019).
  • Parameter 8: Optional. The Nag Date at which point the logic in this script will provide the end-user a notification which they can dismiss. User cannot select when to be reminded as this is determined by the renotification period.
  • Parameter 9: Optional. The End Date at which point the logic in this script will provide the end-user a notification which they can defer only X amount of times (defaults to 3 times). The user will get reminded every 24 hours until those deferrals have been exhausted.
  • Parameter 10: Optional. The re-notification period before the user will get the notification again. This becomes applicable when you’re passed the Nag date. Default to 60 minutes.
  • Parameter 11: Optional. The time out period in seconds which determines how long the Jamf Helper notification will stay up. Defaults to 90 minutes.

Unfortunately, there are not enough Jamf parameters available to use for all the customizations allowed in this script. Due to this limitation, there are other variables in this script you can change under the section titled “Variables You Can Modify”:

  • MaxDeferralAttempts: Optional. Determines the number of deferral attempts a user has after the end date has been reached. Defaults to “3” if left blank.
  • MaxIdleTime: Optional. Number of seconds in which a computer has been idle for too long to expect someone to be sitting in front of it. Script will exit if computer has been
    idle longer than this time. Defaults to 10 minutes if left blank.
  • DelayOptions: Optional. A list of comma separated seconds to provide delay options to
  • the user. The seconds will show up in Jamf Helper as time values. Defaults to “0, 3600, 14400, 86400” which represent “Now, 1 hour, 4 hours, 1 day” in seconds.
  • AssertionsToIgnore: Comma separated string of process names to ignore when evaluating display sleep assertions (e.g. “firefox,Google Chrome,Safari,Microsoft Edge,Opera,Amphetamine,caffeinate”). If a process you’ve listed has a display sleep assertion, the script will resume as normal rather than exit. This may result in valid display sleep assertions being ignored. If left blank, any Display Sleep assertion detected during execution of the script will be honored.
  • MoreInfoURL: Optional. A URL that points the user to a page on the macOS upgrade/update process. If left blank, this will default to either (for major OS updates) or (for minor OS updates). You can optionally use a Self Service URL as well.
  • TimeOutinSecForForcedCLI: Optional. Time out period for CLI installs. This is useful for situations where you want there to be a shorter time out period than normal. If left blank, defaults to the regular default time out period.
  • ITContact: Optional. Enter either an email address, phone number, or IT department name for the end user to contact your team for assistance. Defaults to “IT” if left blank.


I’ve written the verbiage just generically enough that that only a few words are swapped out based on whether the user is performing a major OS upgrade or minor OS update. The user is always given instructions on how to perform the upgrade/update on their own since we want the user to take action where possible. The one exception to this is If you provide a custom policy name for doing major OS upgrades, the user will be pointed to Self Service.

However, I understand my verbiage may not work for all organizations. Below are the variable names so that you can alter the verbiage to your liking should you want to. There is a bit of variable logic inside the verbiage so modify at your own risk.

Variable Names for the notifications:

  • ReminderNotification: The text used when Start date has been reached or if no Start date has been supplied.
  • NaggingNotification: The text used when Nag date has been reached.
  • FinalNotification: The text used when End date has been reached but there are still deferrals left.
  • FinalCallNotificationForCLIUpdate: The text used during a CLI update when End date has
    been reached with no more deferrals left.
  • FinalCallNotificationForGUIUpdate: The text used during a forced GUI update when End
    date has been reached with no more deferrals left.
  • ShutdownWarningMessage: The text used just before shutting down.
  • BackgroundInstallMessage: The text used when CLI updates are being actively performed.

Exit Codes

There are a few exit codes in this script that may indicate points of failure:

  • 10: Required parameter has been left empty.
  • 11: Make sure Start date < Nag date < End date.
  • 12: Insufficient space to perform update.
  • 13: /usr/bin/softwareupdate failed.
  • 15: Incorrect property list type used. Valid types include: “array” “dict” “string”
  • “data” “date” “integer” “real” “bool”
  • 16: Invalid date entered.


Below are a few screenshots so you can get an idea of what the notifications look like.

Start Date/Default Reminder notification:
You get this if the start date has been reached or no dates have been provided.

minor OS update
major OS upgrade (if you point to a custom Jamf policy the instructions will point to Finder > Self Service)

Nag Date notification:
You will get this reminder once the Nag date has been reached. It will re-appear after the re-notification period has been reached.

minor OS update
major OS upgrade

Nag Date notification (with an End Date that has not been reached):

minor OS update
major OS upgrade

End Date notification (with deferrals left):
You will get this reminder once the end date has been reached and there are still deferrals available. Note: The Postpone button will still open up the MoreInfoURL link.

minor OS updates
major OS upgrade

End Date notification (with no deferrals left):
You will get this reminder once the end date has been reached and there are no deferrals available.

minor OS update (on Intel)
minor OS update (on Apple Silicon)
major OS upgrade

Shut Down notification which comes up if the user fails to proceed with the OS upgrade/update:

Configuring script in Jamf Pro

Upload script and configure parameters

Once you’ve uploaded the script to Jamf Pro, I would recommend setting up Parameter Labels. To do this, go into Jamf Pro > Settings > Scripts > and click on the Options tab.

Here are the labels I’m using but you can feel free to adjust your labels:

Parameter 4: Mode (“major” or “minor”)
Parameter 5: Custom Trigger For OS Upgrade Policy (optional)
Parameter 6: Custom Trigger For This Policy (required)
Parameter 7: Start Date (eg Sep 03 12:34:56 -0400 2019)
Parameter 8: Nag Date (eg Sep 03 12:34:56 -0400 2019)
Parameter 9: End Date (eg Sep 03 12:34:56 -0400 2019)
Parameter 10: Renotification Period (in seconds)
Parameter 11: Time Out (in seconds)

Here is a screenshot of what the Parameter labels look like when I’ve uploaded the script to Jamf Pro:

Create Jamf policy

Create a new policy in Jamf Pro and use the Script payload to add this script. When you add this script to a policy, you will need to setup the following in the policy:

1: Trigger: Recurring Check-in and Custom (this is a required in parameter 6)
2. Execution Frequency: Ongoing
3. You can optionally set client-side limitations for the policy so that it only runs between certain days and hours.
4. Scope: I recommend scoping against smart groups based on the operating systems you want upgraded/updated. For example, if you want to use this script for doing major OS upgrades, you could create a smart group with criteria that uses “Operating System Versions that are less than 11.0” since macOS 11 is the latest at the time of this post. Alternatively, for minor OS updates, you can create smart groups for the latest OS (e.g. “OS Version is greater than or equal to 11.0″ AND “OS Version is less than 11.2.3″; 11.2.3 is the latest version as of this post). Or maybe you can scope it against “OS Version is greater than or equal to 11.0″ AND “Number of Available Updates more than 0″. These are just basic ideas. As a Jamf admin in your org, you’ll have a better idea of how to better scope against your devices you want updated.

Because the script will run at every check-in, one thing to keep in mind is that the script does rely on checking for updates through command line: softwareupdate -l . To avoid having the system repeatedly check for updates at every check-in, the script will only check for updates after 4 hours since the last time it checked.

That’s it for now. I hope you can find some use of this script in your environment. You can find the script here. Feedback always welcomed.

Other Mentions

Here are other tools that may be of interest to you which should also work in achieving a similar purpose. These should work with any management tool including Jamf Pro:

No more sudo with softwareupdate or unattended updates on macOS running on Apple Silicon

Today, Apple announced new Macs running on Apple Silicon running on macOS Big Sur. Let’s talk about a change that should concern IT admins. On Apple Silicon Macs, you get the following message when you try to run:

sudo softwareupdate -i

Using softwareupdate to install updates on Apple Silicon should not be run with sudo

And immediately after, you get prompted to authenticate with a GUI prompt.

Two things to note here:

  1. Almost all software management tools for macOS are built to run as root and run as sudo.
  2. Never before has an update required a user to confirm an update by authenticating their credentials. This makes unattended updates impossible.

What are the methods to get users to update their operating system?

There are a few different methods that have been used by admins to get users to update their Macs.

  • Schedule an OS update using the MDM command. Apple approved.
    • This is unreliable. It does not work consistently on either iOS or tvOS, let alone with macOS. There is also not a very good user experience attached. If the command goes through successfully, the user simply sees their computer restart immediately.
  • Manage automatic update settings though a configuration profile. Apple approved.
    • This is not reliable either. There are specific requirements that need to be met in order for updates to automatically install that Apple has not documented. Running applications can also easily prevent an OS update from taking place. This also seems to take place in the middle of the night when laptops may not be powered on.
  • Force updates using the command line tool: softwareupdate.
    • Up until Macs with T2 chips were introduced, this was the most reliable method. With T2 Macs, this has been a less reliable particularly when bridgeOS updates are part of the equation.
  • Constantly notify the user that they need to update their OS using custom tooling.
    • Perhaps the most reliable method today since this is the method that Apple has designed their software update process around for consumers. This has the drawback of annoying your end users. This also partially relies on being able to read what updates may be available through softwareupdate.
  • Using Zero Trust security policies, block end users from accessing company resources until they’ve updated their OS.
    • This works great if your environment has been setup with all resources being gated behind networked resources and your company has been able to implement zero trust policies. However if there are resources that are not gated behind zero trust policy, you may end up with end users that never update.
  • Deploy the latest full OS installer every time a new update is released.
    • To be fair, I don’t know of anyone using this method. But it is an option nonetheless. Updates will likely take 30-35 minutes if you go this route. Big Sur is supposed to improve and speed up the update process. Deploying the full OS installer would likely undo any of these update improvements. This also would mean deploying ~12GB installers every time there is an update as opposed to the ~5GB downloads you’d be dealing with (combo updates have largely been hovering around this size). Another thing to consider is that it may be possible that a full installer is skipped on certain updates. And there are other side effects of performing a full install vs an update.

Each method has their pros and cons. They all work great in environments where a user is assigned to them. However, they don’t really address the methods in which 1) there is no human available to install an update or 2) where the end user simply is not complying with the notifications or requirements.

Apple has not provided better management options for macOS updates. In fact, with each major OS release they’ve gotten rid of the options that have been available to us. You can no longer run your own Apple Software Update Server. You can no longer ignore specific updates. The command line tool softwareupdate has become less reliable depending on the conditions in which you tried to run it on Macs with T2 chips. Now it seems

I (and I’m hoping other IT admins, too) have requested better options and improvements to the current management tooling, but Apple has yet to implement any of them. As of today, you have the two options listed above which involve sending MDM commands or a configuration profile. Additionally, you have the ability to delay updates up to 90 days where major and minor OS updates are treated the same. But that doesn’t achieve the purpose of getting devices on to the latest OS version.

In our environment, this would be quite disastrous as we have computers that are online, but unattended. There’s no reliable automated method to update Macs to a specific OS version with a good user experience.

The request to Apple: better update enforcement tooling

From an IT admin perspective, I do not believe what is being asked would conflict with Apple’s goal of having users running a secure operating system.

I am (and hopefully other admins are too) just asking for a method that allows us to force a device to be updated by a set date using a configuration profile. We cannot rely on MDM commands that simply may or may not reach a device. This workflow should also account for situations where a user may be turning on a computer for the first time after the deadline has passed, they should have X amount of reminders before the update is forced at this point.

The UX around this request should be handled by Apple (a company that prides itself on its UX) so that IT admins don’t have to deal with custom tooling. This would result in the user getting reminded regularly and then forced to upgrade if the deadline has passed. I believe the above request would work well for other Apple platforms like iOS and tvOS as well.

Final Thoughts

This is not the first time I’ve written about managing software updates on macOS. I cannot help but wonder how a company like Apple can promote and put so much focus on the security of their products and yet not make proper management tooling to keep those same products up-to-date when used in a business environment? Microsoft has figured out how to manage OS updates on Windows and Google has figured it out on ChromeOS so why can’t Apple do the same for its various platforms? Why does Apple make it more difficult for companies to keep devices they have purchased/owned up-to-date?

As of right now, I’m not entirely sure how we’ll be able to manage macOS updates going forward on these machines that are unattended. In the grand scheme of things, our organization is probably insignificant to Apple in terms of the amount of money we spend. I’m writing this in hopes that other admins provide feedback to Apple because this makes the macOS platform considerably harder to keep secure (on the latest version) in business environments. Jamf has a good article that discusses the various options for providing Apple with feedback. Please let your fellow admins know and start filing that feedback with Apple.

Waiting for the macOS Big Sur installer to launch

As part of Gatekeeper, macOS runs a code signature validation scan on all apps when they are run the first time. This results in some really large apps taking 2-3 minutes before they can run. Some apps include: Xcode, Matlab, Mathematica, etc.

The macOS installer app for Big Sur suffers from this too and as a result if you try to run “Install macOS Big” it will take at least 2 minutes while macOS scans it. If you launch the app installer through the GUI, the app will simply bounce in the dock until the scan completes. If you run the app through the CLI using startosinstall, it will show no activity until the scan completes.

There’s no way to force macOS to scan a particular app for its code signature validation other than actually trying to run it. This would obviously lead to a bad user experience for the end user.

As a workaround, you can trigger the code signing validation by using startosinstall --usage. This will not actually run an OS upgrade and macOS will start to scan “Install macOS Big” silently in the background.

I’ve written a simple script that is designed to run immediately after “Install macOS Big” has been installed. I’ve designed it for use in Jamf Pro, but you could easily modify it for other uses if you wanted. The idea is to make the user experience better so that when the user launches an OS upgrade whether through a GUI install (as Apple intends) or a CLI install relying on “startosinstall” there’s no a 2-3 minute period of silence. You can run the script immediately after deploying the installer app to the computer.

File feedback with Apple so that they can improve this user experience and macOS installer apps can be scanned immediately after a download has taken place. This would also have benefits to other large apps as well. Jamf has a good article that discusses the various options for providing Apple with feedback.

Additional research

XProtectService and syspolicyd

If you try to see what’s going on in the when you launch the installer macOS app, you will see XProtectService mentioned in the Console logging when dealing with one of these apps and syspolicyd. Some research on syspolicyd that helped a bit in my testing and research:

There is a Launch Daemon at /System/Library/LaunchDaemons/ which seems to have the frequency at which some syspolicyd actions might be taking place depending on the LaunchEvent. The key seems to be relevant based on the name. That runs weekly (604800 secs). There is a CLI to this: /usr/libexec/syspolicyd but it’s not very clear to me how to interface with this or whether I’m supposed to. I assume that spctl is the tool that’s supposed to interact with syspolicyd.

Investigating options to workaround code signing validation

For the curious, I did try a bunch of things to kick off the code signing validation before I tried to launch the app or to avoid code signing validation altogether.

The following does not work:

# Remove recursively from app bundle
xattr -dr "/Applications/Install macOS Big Sur"

# Clear all extended attributes recursively from app bundle
xattr -cr "/Applications/Install macOS Big Sur"

# Asses app
spctl -a "/Applications/Install macOS Big Sur"

# Stop spotlight from trying to index app
mdutil -i off -a

# Register app with launch services
/Volumes/Macintosh\ HD/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -R -f "/Applications/Install macOS Big Sur"

# Add path to app to Gatekeeper
spctl --add "/Applications/Install macOS Big Sur"

# Not sure if using the –path option makes a difference
spctl --add --path "/Applications/Install macOS Big Sur"

# Put label on app bundle and then enable label so that Gatekeepr will bypass it
spctl --add --label "Approved" "/Applications/Install macOS Big Sur"
spctl --enable --label "Approved"

# Disable Gatekeeper
spctl --master-disable

# Tried to modify the syspolicy database sqlite3 /var/db/SystemPolicyConfiguration/ExecPolicy using:

INSERT INTO policy_scan_cache (volume_uuid,object_id,fs_type_name,bundle_id,cdhash,team_identifier,signing_identifier,policy_match,malware_result,flags,mod_time,timestamp,revocation_check_time)
VALUES ('C202AB2C-4F93-44BE-9F4F-CB5DA760F07E','189698','apfs','','45bc1b465728e65fb091f81bcc0ea147370f455f','','',11,0,512,1604217029,1604217029,1604217029);Error: attempt to write a readonly database

This failed because its a read-only database naturally.

# Tried to change the date more than 7 days into the future hoping that might trigger the OS to do its scanning:
date -u 1111000020

The following did work:

I did find one way to avoid the code signing validation but it cannot be automated with either CLI tools or through MDM. Go to Sys Pref > Security & Privacy > Privacy tab > Developer Tools > Enable Terminal. Going forward, no security check takes place when I run (from Terminal) the exact binary that’s supposed to get loaded.

With Terminal not enabled in Developer Tools:
/Applications/Install macOS Big Sur --usage would normally take 120 secs
But with Terminal enabled in Developer Tools, that runs immediately.

This iPhone is supervised and managed by … Learn more about device supervision…on a personal iPhone?

In some organizations, some employees are provided a company purchased iPhone and then allowed to keep it after a certain time has passed. The first thing that you as an IT administrator want to do is most likely ensure the device is no longer managed and contains no company data. Apple walks you through the steps of releasing a iOS device from Apple Business Manager with a final note stating: “After a device is released, it must be erased and restored.

You would think this would be very simple, but unfortunately it’s not. The process of personalizing an iPhone can be quite convoluted in some scenarios because there are some remnants that get left behind if you try to simply restore the data on the iPhone after an erase and restore.

You might argue, the user should not keep any personal data on a company phone, but the lines get blurred especially as most people prefer to carry one device. It’s not unreasonable for someone who is getting gifted a company phone to perhaps want to retain the settings, apps and data on the phone that are personal without having any of the company data on it restored. Sure, you can just tell the user to erase and setup the phone as new, but reconfiguring an iPhone can be quite a lot to ask of someone! In any case, this blog post is not about the merits of mixing personal and corporate data on one device so that’s all I’ll say on the matter.

The problem: This iPhone is supervised…even after a restore!

Before I continue, note that the following scenario was tested in iOS 12 & iOS 13 and with a MDM profile that was set to be unremovable.

There are going to be a lot of steps here for you to reproduce the issue and a subsequent number of steps if you want to work around the issue.

Continue reading This iPhone is supervised and managed by … Learn more about device supervision…on a personal iPhone?

macOS 11 and Semantic Versioning

Last week at WWDC 2020, Apple announced macOS 11 (Big Sur). One of the questions that has come up in the MacAdmins Slack come up is whether macOS is really macOS 11 or macOS 10.16. Right now, we have nothing more to go by then what is in the betas which I won’t discuss, but it’s my understanding that Apple does indeed intend to go by macOS 11 in the end.

Many admins who write scripts that check for the OS version tend to rely on sw_vers -productVersion which would normally spit out a value such as 10.15.5. The first of the 3 digit groups has not changed in 20 years which means many scripts simply don’t check for that change. That leaves the last 2 digit groups which often get referred to as major and minor versions.

With macOS 11, it’s going to be important to check all 3 digit groups for backwards and forwards compatibility. We do have a point of reference on how these changes should go if we look at iOS, iPadOS, tvOS, and watchOS which have 3 digit versions.

This changes things a bit and I’m proposing that it’d be great if other IT admins used similar naming convention when referring to the the variable names representing each digit group should you need to split each value up.

I propose using the following semantic version guidance on how to refer to 3 digit group separated versions from

1. MAJOR version when you make incompatible API changes,
2. MINOR version when you add functionality in a backwards compatible manner, and
3. PATCH version when you make backwards compatible bug fixes.

Obviously, we cannot know for sure what next year may bring, but I’m hoping that Apple sticks to these changes and continues to increment the macOS major version as it has on its other platforms. This would mean that macOS 11 would then move on to macOS 12 by the end of 2021.

Presumably if the same behavior from iOS comes to macOS, the days of “supplemental updates” should be no more. If macOS 11.0.0 releases today and a “supplemental update” needs to come out a few days later then that’s really just a patch and the macOS version would increment from 11.0.0 to 11.0.1. However when macOS gets its minor OS releases that tend to come out every 6-8 weeks then we might go from say 11.1.0 to 11.2.0. That’s a simple change to track in most version comparison logic found in programs or scripts.

To highlight the issue a bit more clearly, a “supplemental update” would in the past would usually mean we’d go from macOS 10.15.5 to macOS 10.15.5 with the only way to know the difference being the build number which has always been incremented. Although not impossible, it’s a little less straight forward to compare a mix of alphanumeric characters vs integers. Here’s a really good blog post by Armin Briegel: that covers OS versioning and build numbers in more detail.

These are going to be very welcomed changes going forward because right now once macOS has hit the last minor update the only way to determine if it was on the latest version was to look at the build number of the OS.

However in shell scripting languages, it is much easier to just focus on splitting the version number into different groups since most admins will be familiar with the marketing version compared to dissecting the build numbers. Here is one way to do this going forward in a shell language:

Major Version: /usr/bin/sw_vers -productVersion | /usr/bin/cut -d . -f 1

Minor Version: /usr/bin/sw_vers -productVersion | /usr/bin/cut -d . -f 2

Patch Version: /usr/bin/sw_vers -productVersion | /usr/bin/cut -d . -f 3

Here’s a shell example on how this might be used:

[[ "$os_major_ver" -ge 11 || "$os_major_ver" -eq 10 && "$os_minor_ver" -gt 12 || "$os_major_ver" -eq 10 && "$os_minor_ver" -eq 12 && "$os_patch_ver" -ge 3 ]]

In this example, I’m testing for a condition that means either:

macOS major version is greater than or equal to 11
macOS major version equals 10 AND macOS minor version is greater than 12
macOS major version equals 10 AND macOS minor version equals 12 AND macOS patch version is greater than 3

In any case, this blog post is mostly going out as a reminder to admins that if you haven’t already heard of this coming change, I would suggest that you review any scripts that rely on checking the OS version as they may need some updating.

Managing Google Chrome Auto Updates

Google Chrome has a few ways in which you can manage its update mechanism which I wanted to post about.

Google Chrome Enterprise Installer

If you’re not already doing so, I recommend you start using the enterprise Google Chrome installer package. This package installs Google Chrome but also configures the Google Software Update Agent which many admins were doing with a separate script. Once Chrome is registered with the Google Software Update agent, there’s a few options for managing it to ensure that Google Chrome stays up to date.

Options for Managing Google Software Update/Keystone

Configuration Profiles

Here is an article that talks about how to manage Google Chrome updates:

By generating a configuration profile that manages the domain, you can ensure that Google’s auto update mechanism is configured according to your needs. Keep in mind that the Google Software Update Agent can manage the updates for various Google apps on macOS. Unfortunately, the Google Software Update Agent won’t necessarily force users to do anything as it’s pretty transparent in the background and its job when unmanaged is just to check for updates and stage them for installation on a relaunch of a Google application. Once a registered Google application is relaunched, it will ensure the app is running the latest version.

Chrome Browser Cloud Management

Here is an article that talks about how to manage Chrome using the Chrome Browser Cloud Management console:

This would allow you to manage the Chrome web browser from a single console. To do so, you would generate a token that would get pushed out through a configuration profile to your Macs. Once enrolled, you can manage the same settings through the Google Admin console for each Chrome web browser. Please note that the user does not need to be signed in with their Google account for this option to work.

You are able to combine both options if you wish: 1) push configuration profiles from your MDM server to manage settings and enroll the device into Chrome Browser Cloud Management and 2) use Chrome Browser Cloud Management for reporting only.

Managing Update Notifications

Fortunately, Google has provided a way for admins to notify users that a Chrome update is pending and force them to relaunch. This article talks about how to manage relaunch notifications:


Tells users to relaunch Chrome Browser or restart their device running Chrome OS to get the latest update. Choose one of the options:

Relaunch recommended—Users can close the notification and keep using the old version of Chrome Browser or Chrome OS until they choose to relaunch Chrome Browser or restart their Chrome device.
Relaunch required—Users can close the notification but will see a recurring message that Chrome Browser will automatically relaunch or their Chrome device will restart after a certain time. Use the RelaunchNotificationPeriod policy to set the relaunch time (details below).

Unset: On the toolbar, the More icon More changes to indicate that an update is available but users aren’t forced to relaunch.


Sets the time period, in milliseconds (ms), that a user is repeatedly notified to relaunch Chrome Browser or restart their Chrome device to apply an update.

Unset: The default time period is 604,800,000 ms (7 days).

In our environment, we’ve got it set to  86400000 ms (or 24 hours). It has worked out pretty well for us. As an aside, I have no idea why Google chose to work with milliseconds.

Here is an example screenshot of what the notification looks like in Google Chrome if you set it to require a relaunch in 4 days:

Screen Shot 2019-06-21 at 9.39.38 AM

When does Google Software Update run?

The last part of the equation to account for is controlling when Google Software Update Agent runs. At the moment, there is no way to do so. Google Software Update Agents runs based on a launch daemon in /Library/LaunchDaemons/ and 2 launch agents in /Library/LaunchAgents/ and /Library/LaunchAgents/

Here are my observations:

  • The agent will automatically check for updates when the user logs into macOS.
  • The agent will automatically check for updates when Google Chrome is launched. However, it will only install any updates after a relaunch of Chrome.
  • The agent is supposed to automatically check for updates every 3623 seconds (this may be randomized on each device). As someone who does not use Chrome, I found that the app remained unpatched for days even when updates were definitely available which leads me to believe that this is not checking aggressively enough or there are some strings attached. I purposely did not launch Chrome to see how long it would take for it to auto update.

This becomes a bit of a problem because most people rarely restart their web browser, let alone log out and log back into their computer. I wanted a more guaranteed way that would ensure that Chrome was at least regularly checking for updates in the background. The idea here is if someone is not using Chrome then I want it to be updated as soon as they do decide to launch it the next time. I don’t want to try and check for updates in the background only when the browser is launched.

I did a bit of searching and found the following discussion on Jamf Nation which shows you can run the following command as the user (it will not work if you run this as root or a user other than the one in the current session):

"/Library/Google/GoogleSoftwareUpdate/GoogleSoftwareUpdate.bundle/Contents/Resources/" -runMode oneshot -userInitiated YES "$@"

That eventually led me to create a launch agent that I could then deploy myself to devices:

<?xml version=”1.0″ encoding=”UTF-8″?>
<!DOCTYPE plist PUBLIC “-//Apple//DTD PLIST 1.0//EN” “”&gt;
<plist version=”1.0″>
I was able to push this out through a simple package that installed this at /Library/LaunchAgents/ with permissions set to 644 and owner/group set to root:wheel. The idea here is that the launch agent will run every 21600 seconds (or 6 hours). I chose 6 hours because it would at least ensure that the Google Software Update agent checks for an update at least once a day during the work day. Feel free to change those values for yourself.
If you think I missed anything or have something useful to add, feel free to write in the comments. I hope you’ve found this at least somewhat informative.

Apple plans on removing enterprise options for macOS software update

For sometime now, Apple has allowed IT administrators to manage updates for macOS. However in a very near future that may change unless other IT administrators start to provide feedback to Apple. This will be long but please read as now is a critical time to provide Apple feedback before WWDC (whenever that takes place) and the next major OS is released.

Continue reading Apple plans on removing enterprise options for macOS software update

Reset the macOS printing system through the command line

Sometimes you need to reset your printing system to resolve some weird issues with one or more of your print queues. Prior to macOS Catalina, one way to reset the printing system was through the GUI. For example in macOS Mojave, you could go through System Preferences > Printers & Scanners and right-click (cmd + click) the list of printers and select “Reset printing system…” from the contextual menu.

If you’re reading this blog post you’re probably interested in automating that task. Back in 2015, I set out to find a way to do this programmatically so that it could be scripted. I shared the script on JamfNation at the time. Today, the logic and script still works, but it sure would be nice if it wasn’t needed in the first place.

With macOS Catalina, Apple silently introduced a new option in their printtool command line tool to reset the printing system. Type the following in Terminal:

/System/Library/Frameworks/ApplicationServices.framework/Frameworks/PrintCore.framework/Versions/A/printtool --reset -f

And just like that, all your print queues should disappear! There’s not much more to this blog post than that. I just wanted to document this new option because it hasn’t really been documented anywhere that I can tell. And it’s a handy one liner that may come in handy when troubleshooting. Hope this is helpful to you in some way. I’ve also gone ahead and uploaded my original script to Github as well.