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 Sur.app” 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 Sur.app” silently in the background.

I’ve written a simple script that is designed to run immediately after “Install macOS Big Sur.app” 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: https://knight.sc/reverse%20engineering/2019/02/20/syspolicyd-internals.html

There is a Launch Daemon at /System/Library/LaunchDaemons/com.apple.security.syspolicy.plist which seems to have the frequency at which some syspolicyd actions might be taking place depending on the LaunchEvent. The com.apple.security.syspolicy.find.bundles 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 com.apple.quarantine recursively from app bundle
xattr -dr com.apple.quarantine "/Applications/Install macOS Big Sur Beta.app"

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

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

# 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 Beta.app"

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

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

# Put label on app bundle and then enable label so that Gatekeepr will bypass it
spctl --add --label "Approved" "/Applications/Install macOS Big Sur Beta.app"
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','com.apple.InstallAssistant.Seed.macOS1016Seed1','45bc1b465728e65fb091f81bcc0ea147370f455f','','com.apple.InstallAssistant.Seed.macOS1016Seed1',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 Beta.app/Contents/Resources/startosinstall --usage would normally take 120 secs
But with Terminal enabled in Developer Tools, that runs immediately.

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 https://semver.org/:

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: https://scriptingosx.com/2017/11/on-the-macos-version/ 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
OR
macOS major version equals 10 AND macOS minor version is greater than 12
OR
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.

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.

Preemptively granting permission to Camera and Microphone in macOS

Starting with macOS 10.14, Apple introduced the requirement that applications requesting access to certain APIs would require permissions from the end-user. This is commonly referred to by other IT administrators are Privacy Preference Policy Control (PPPC) or Transparency Consent Control (TCC). The intent is to make the end user aware of what the application they are using requires access to. In 10.14, the list of services that could be whitelisted included 25 TCC services and it has grown to 39 services. From a consumer perspective, the can be a good thing, albeit exhausting, as you need to essentially provide access to multiple services for individual apps.

Apple to their credit has allowed IT administrators to manage most of these services that applications require in order to function by use of configuration profiles when your device is enrolled into an MDM server (whether user approved or via DEP). However for 4 services (Microphone, Camera, ListenEvent, and ScreenCapture), Apple decided that even IT administrators on enterprise-owned devices cannot preemptively provide allow access to these services (one can only deny access). This complicates things because end-users often need to join video conferences on the fly and those applications typically require access to the microphone and camera. If the user gets bombarded with different alerts to provide access, they may accidentally deny it and not be aware how to resolve the issue. Every application/developer is on their own in terms of how to best communicate how to resolve accidental denials of apps to functional services. And for some services, it is implied that you need to quit the app and restart it. Yes, you need to get out of the meeting and re-join it. This becomes an IT support issue as you can imagine.

It’d be great if Apple would provide proper options to allow these services on enterprise-owned devices that are supervised by an MDM server. You can submit feedback to Apple at http://feedbackassistant.apple.com.

To cut to the chase, I was alerted to this article from Zoom last week where it seems that Zoom is recommending that you provide full disk access to Zoom Rooms so that it can then provide itself Camera and Microphone access. Shame on Zoom for doing this since this is after all how we got here in the first place (Dropbox previously got caught doing similar stuff in 2016, but quickly rectified it). But I do get it to some extent. From their perspective, they are concerned about providing as seamless of an experience as possible. And the same is true of IT administrators to some extent. Computers are tools for people to get work done. It shouldn’t be super complicated to join video conference meetings on your computer in 2019. But Apple has made it so.

Since the cat’s out the bag, I made a script that provides camera and microphone access. It was designed to work with Jamf Pro which automatically has granted its binaries full disk access when its MDM profile has been user approved (or installed via DEP). This allows the script to make the modifications to the user’s TCC.db. I tested this against Microphone, Camera, ListenEvent, and ScreenCapture services (kTCCServiceMicrophone, kTCCServiceListenEventkTCCServiceCamera, and kTCCServiceScreenCapture). The last two don’t work just so you’re aware.

To use this script, add it to your Jamf Pro server and make use of Jamf Pro Script Parameters:

Parameter 4: is used to provide the full path to the application (e.g. /Application/Firefox.app)
Parameter 5: is used to provide the TCC service name. See $tcc_service_list array for valid entries. Of importance to you will be: “kTCCServiceMicrophone” and “kTCCServiceCamera

The script can be found on my GitHub repo. Feedback/improvements always welcomed.

Jamf Pro OS Deprecator

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. 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.
The idea behind this script is that it alerts the user that the OS they are running is
no longer supported. However it’s important to try and provide a little bit of flexibility in how often the user gets notifications. Rather than forcing updates through, the script allows you to control the pace of notifications the user receives to perform the OS upgrade by letting you set different dates of importance.

If the user opts to proceed with the upgrade, they will get taken to a second policy that you’ve configured to do the OS upgrade. Or if you don’t configure a second policy, it will simply point them to the Mac App Store.

There are three optional dates that you can supply to the script that will dictate when the user receives the notifications:
Start Date: This provides a notification to the user, but also allows them to delay when
they will receive the next reminder.
Nag Date: This provides a notification to the user. The user cannot select when to be
reminded. This will be set 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: Similar to the Nag Date, this provides a notification to the user where they cannot select when they will be reminded. However it nows forces the computer to go into the final countdown. Once this date has been reached, you can set it so that the user has X amount of attempts left before they are forced to upgrade.

Dates can be supplied in either epoch seconds or string format (e.g. Sep 03 12:34:56 -0400 2019). 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.

With each notification, there is a “More Info” button in the Jamf Helper which will always open up a URL. This URL should provide the user will instructions on how to proceed to update. Because it’s a URL, you could also provide a Self Service URL as well if you want with the assumption you have a detailed description.

This script does try to account for the fact that it’s Self Service-centric. That means
there are a few checks in place to try and give the user the best chance to perform the
upgrade:

  • Ensure a user is logged in.
  • Power source is connected.
  • No app is opened that has a display sleep assertion active.
  • Idle time of the computer.

Given the above overview, here are the Jamf script parameters:
Parameter 4: Optional. A minimum OS version to compare against what’s running on the client. If the client is running an OS that’s the same version or greater then the
script will exit. Provide OS version in form of 10.14.6.
Parameter 5: Optional. The custom trigger name for a Jamf policy that will initiate the OS upgrade.
Parameter 6: Optional. 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 are done.
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.
MoreInfoURL: Optional. A URL that points the user to a page on the macOS upgrade process. If left blank, this will default to Apple’s macOS upgrade page which has been active since September 2016. You can optionally use a Self Service URL as well.
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.
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.

Verbiage:
I’ve written the verbiage with the idea that the end user would open up Self Service
to perform upgrade macOS. Maybe this doesn’t work in your environment because you don’t have a Self Service workflow. Or maybe there’s just something else you want to change in the verbiage. 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
FinalCall: The text used when End date has been reached with no more deferrals left.

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: Minimum Supported OS Major Version is not an integer.
13: Minimum Supported OS Minor Version is not an integer.
15: Incorrect property list type used. Valid types include: “array” “dict” “string” “data” “date” “integer” “real” “bool”
16: End Date has been provided without custom trigger.

If you’ve followed along up to this point, you’ll notice that there are a lot of optional variables. That was intentional. There are some organizations where each user has full admin access and the end user is expected to upgrade to macOS using the App Store as a regular consumer would at home. If you leave all parameters empty, the end user is pointed to the Mac App Store to upgrade or to check out the Apple macOS upgrade page if they want to learn more. This is without a doubt the most commonly tested scenario that Apple tries to ensure works without issue. Command line based upgrades are simply not always 100% reliable depending on which version of the macOS installer app you are deploying.

However, not all environments are like that. In some environments, the end user needs a bit more hand holding because there may be a few pre-requisites to upgrade and maintain compliance. Perhaps some software needs to be uninstalled or updated beforehand to avoid problems post-upgrade. With that in mind, you can point to a second policy that should kick off the OS upgrade as expected in your environment.

Below are a few screenshots so you can get an idea of what the notifications look like in a Self Service based workflow where a second policy is linked.

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

1.png

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.

2a.png

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

2b.png

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.

3.png

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.

4.png

How to use this script in Jamf Pro?

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 > JamfDreprecationNotifier.sh and click on the Options tab.

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

Parameter 4: Min Supported OS Version (eg 10.14.6)
Parameter 5: Custom Trigger for OS Upgrade Policy
Parameter 6: Custom Trigger for Deprecation Policy
Parameter 7: Start Date (Sep 03 12:34:56 -0400 2019)
Parameter 8: Nag Date (Sep 03 12:34:56 -0400 2019)
Parameter 9: End Date (Sep 03 12:34:56 -0400 2019)
Parameter 10: Renotification Period (in seconds)
Parameter 11: Time Out (in seconds)

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:

1: Trigger: Recurring Check-in and Custom (this is a required parameter)
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 and that would meet the qualifications for the operating system you want users to upgrade to.

Other things to note:

If you’re familiar with shell scripting you should be able to see where some of the information from this script is stored. That would also allow you to potentially get certain data in the form of extension attributes. However that’s an exercise I will leave up to you if you decide you want to track that information.

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.

Handling macOS Software Updates with Jamf Pro

Jamf Pro has not handled software updates successfully on all Mac hardware since Apple introduced the T2 processor with the iMac Pro back in December 2017. It’s been requested that they address this issue in a feature request, but it’s gone completely unacknowledged (Edit: As of November 11, 2019, the feature request is now marked as Under Review).

The problem with the software update process on Macs with T2 processors is that sometimes there is a bridgeOS update (the OS on the T2 processor) which requires a shutdown instead of a restart. The Mac will read the shutdown and automatically power back on to apply the bridgeOS update. However, not all software updates have a bridegeOS update which would mean a shutdown in those situations would actually leave the computer powered down. Unfortunately, Jamf Pro does not know how to handle this situation. Apple did introduce the --restart option for softwareupdate but that also comes with its own problems in that it hasn’t worked reliably in all scenarios. Since the solution to this isn’t particularly difficult to work around, I created a script to address this workflow in our environment.

Before continuing, I’d like to mention that we do leverage macOS’s ability to do automatic updates. This has one benefit of doing automated authenticated restarts which is important on Macs with FileVault enabled. However, we’ve found in our environment that after a month only 60% of computers running macOS 10.14 are up to date on the latest version. It’s a bit of a black box as to how macOS determines when to do automatic updates. Needless to say, the rate of updates is unacceptable.

This script is meant to be used with Jamf Pro and makes use of Jamf Helper. The idea behind this script is that it alerts the user that there are required OS updates that need to be installed. Rather than forcing updates to take place through the command line using “softwareupdate”, the user is encouraged to use the macOS GUI to update. When I say macOS GUI, I’m referring to the Software Update mechanism that Apple refers consumers to: https://support.apple.com/en-us/HT201541

In recent OS versions, Apple has done a poor job of testing command line-based workflows of updates and failed to account for scenarios where an end-user may or may not be logged in. The update process through the GUI has not suffered from these kind of issues. The script will allow end users to postpone/defer updates X amount of times and then will give them one last chance to postpone. We run this script using the Once A Day policy frequency which means the user will get this once a day so long as it checks in.

This script should work rather reliably going back to 10.12 and maybe further, but at
this point the real testing has only been done on 10.14. Please note, that this script does NOT cache updates in advance. Sometimes Apple releases updates that get superseded in a short time frame. This can result in downloaded updates that are in the /Library/Updates path that cannot be removed in 10.14+ due to System Integrity Protection.

This script does make use of Jamf Pro Script Parameters:
Parameter 4: Optional. Number of postponements allowed. Default: 3
Parameter 5: Optional. Number of seconds dialog should remain up. Default: 900 seconds
Parameter 6: Optional. Contact email, number, or department name used in messaging. Default: IT

Here is the expected workflow with this script:

  1. If no user is logged in, the script will install updates through the command line and
    shutdown/restart as required.
  2. If a user is logged in and there are updates that require a restart, the user will get
    prompted to update or to postpone.
  3. If a user is logged in and there are no updates that require a restart, the updates will get installed in the background (unless either Safari or iTunes are running.)

There are a few exit codes in this script that may indicate points of failure:
11: No power source detected while doing CLI update.
12: Software Update failed.
13: FV encryption is still in progress.
14: Incorrect deferral type used.

Below are some screenshots for what you will see on macOS Mojave. However the text is aware of at least 10.8 and higher where the instructions to get to Software Update might differ.

This is the initial message you will see when prompted to update:

Update1.png

When you click Continue, you will be taken to Apple’s Software Update:

Update3.pngUpdate4.png

This is the final message you will get when you’ve postponed the maximum number times:

Update2.png

Note: “Please make selection in HH:MM:SS” is not text I can modify. It serves as a countdown for the end user to know how much time they have before they are forced to update.

And lastly when the forced update is taking place, a headsup display window pops up:

Update5.png

The script is easy to modify if you don’t like the verbiage or if you want to use it for inspiration on other workflows. The script can be found here on my Github page.

How to set the icon for a folder or file with a little bit of PyObjC

There may be situations in which you need to set an icon for a folder or file. If you start searching online, you might run into a bunch of recommendations that rely on Xcode command line tools or methods that simply no longer work.

The following method is quite simple because it only relies on the Python Objective-C bridge which has been bundled with macOS for many years now. The relevant API is in the NSWorkspace.

For just the PyObjC code, here it is taken from a StackOverFlow answer:

#!/usr/bin/python
# https://developer.apple.com/documentation/appkit/nsworkspace/1529882-seticon?language=objc
# Argument 1: Path to icon
# Argument 2: Path to folder/file to set icon for

import Cocoa
import sys

Cocoa.NSWorkspace.sharedWorkspace().setIcon_forFile_options_(Cocoa.NSImage.alloc().initWithContentsOfFile_(sys.argv[1].decode('utf-8')), sys.argv[2].decode('utf-8'), 0) or sys.exit("Unable to set file icon")

 
And if you want to make use of that code in a Bash script, it’s not too hard:

#!/bin/bash
# This is PyObjC code that can be used in a bash script
# https://developer.apple.com/documentation/appkit/nsworkspace/1529882-seticon?language=objc

FolderIcon="/path/to/icon.icns"
PathToSetIconFor="/path/to/folder"

setFolderIcon (){
/usr/bin/python - "$1" "$2" << EOF
import Cocoa
import sys
Cocoa.NSWorkspace.sharedWorkspace().setIcon_forFile_options_(Cocoa.NSImage.alloc().initWithContentsOfFile_(sys.argv[1].decode('utf-8')), sys.argv[2].decode('utf-8'), 0)
EOF
}
setFolderIcon "$FolderIcon" "$PathToSetIconFor"

Enjoy!

Ignore a specific macOS update using softwareupdate

There are a few different ways you can go about managing updates for macOS. They’ll all have their pros and cons. In this post, I’m just going to focus on one method that may come in handy for you.

The command softwareupdate has an ignore flag which lets you specify an update you want to ignore when the OS tries to check for software updates. The man page for softwareupdate tries to explain it’s usage:

NAME
softwareupdate -- system software update tool

SYNOPSIS
softwareupdate command [args ...]

DESCRIPTION
......

--ignore identifier ...
Manages the per-machine list of ignored updates. The identifier is the first part of the item name (before the dash and version num-
ber) that is shown by --list. See EXAMPLES.

--reset-ignored
Clears the per-machine list of ignored updates.
......

EXAMPLES
The following examples are shown as given to the shell:

softwareupdate --list

Software Update Tool

Finding available software
Software Update found the following new or updated software:
* MacBookAirEFIUpdate2.4-2.4
MacBook Air EFI Firmware Update (2.4), 3817K [recommended] [restart]
* ProAppsQTCodecs-1.0
ProApps QuickTime codecs (1.0), 968K [recommended]
* JavaForOSX-1.0
Java for OS X 2012-005 (1.0), 65288K [recommended]
......

sudo softwareupdate --ignore JavaForOSX

Ignored updates:
(JavaForOSX)

The problem I’ve run into is that the “identifier” is not always very clear. For example, if you look at the example they provide, the identifier to ignore is “JavaForOSX” but the update is listed as “JavaForOSX-1.0” or “Java for OS X 2012-005 (1.0)” when you pull a list of all available software updates. Which one are you supposed to use? And why are they all different?

Someone on the MacAdmins Slack was nice enough to share a little bit of knowledge which I wanted to share forward.

  1. Find a Mac with updates you want to block. You can either open up Software Update or the App Store > Update tab or alternatively using sudo softwareupdate -l.
  2. Once you’ve got a list of updates, run the command: defaults read /Library/Preferences/com.apple.SoftwareUpdate.plist which should list some of the updates available in a dictionary key:
    Below is an example. Pay attention to the Product Key value:

    {
    "Display Name" = Safari;
    "Display Version" = "12.0.2";
    Identifier = "Safari12.0.2HighSierraAuto";
    "Product Key" = "041-08765";
    }
  3. Technically, the Identifier is all you need there. The rest of the steps are not necessary. However for the sake of documentation, you can gain the same information elsewhere.
  4. Open up Finder and go to: /Library/Updates/
  5. Assuming there are available updates, you will find a folder containing a Product Key number (e.g. 041-20511). Each of those folders represents an available update. You can reconcile the Product Key IDs with what you found earlier.
  6. Open that directory and you should see a .dist file (e.g. 041-20511.English.dist). Open that .dist in a text editor. You will see that this file is simply XML.
  7. Do a find on the .dist file for the property tag suDisabledGroupID. This key is what holds the value you want. To continue with the example, suDisabledGroupID="Security Update 2018-003"

You can now use softwareupdate to ignore this specific update: sudo softwareupdate --ignore "Security Update 2018-003"

You can also update multiple updates at once:

sudo softwareupdate --ignore "Security Update 2018-002" "Security Update 2018-003"

Note: This does NOT prevent someone from manually downloading the update and installing it. It only prevents macOS from listing the specific ignored update as an available update via the command line and user interface (App Store, System Preferences).

If you want to reset the updates you’ve ignored, run the command sudo softwareupdate --reset-ignored

Or you can alternatively make use of a tool like SUS Inspector to give you this and more information.