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 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.

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.

Managing Google Software Update/Keystone

Here is an article that talks about how to manage Google Chrome updates: https://support.google.com/chrome/a/answer/7591084

By generating a configuration profile that manages the domain com.google.Keystone.plist, 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.

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: https://support.google.com/chrome/a/answer/7679871?hl=en&ref_topic=9023245

RelaunchNotification

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.

RelaunchNotificationPeriod

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/com.google.keystone.daemon.plist and 2 launch agents in /Library/LaunchAgents/com.google.keystone.agent.plist and /Library/LaunchAgents/com.google.keystone.xpcservice.plist.

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/GoogleSoftwareUpdateAgent.app/Contents/MacOS/GoogleSoftwareUpdateAgent" -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” “http://www.apple.com/DTDs/PropertyList-1.0.dtd”&gt;
<plist version=”1.0″>
<dict>
<key>Label</key>
<string>com.company.google.softwareupdatecheck</string>
<key>LimitLoadToSessionType</key>
<string>Aqua</string>
<key>ProgramArguments</key>
<array>
<string>/Library/Google/GoogleSoftwareUpdate/GoogleSoftwareUpdate.bundle/Contents/Resources/GoogleSoftwareUpdateAgent.app/Contents/MacOS/GoogleSoftwareUpdateAgent</string>
<string>-runMode</string>
<string>oneshot</string>
<string>-userInitiated</string>
<string>YES</string>
<string>”$@”</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>StartInterval</key>
<integer>21600</integer>
</dict>
</plist>
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.

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.

Revisiting: A macOS upgrade method using Jamf Pro Self Service

Back in 2017, I released my script that I use to do macOS upgrades in my organization through Self Service. Since the script I wrote has changed, I thought a new post was deserved to account for the changes I’ve made.

At my organization, we leverage the macOS installer app from Apple which has a neat little command line tool called startosinstall. There are quite a few scenarios that need to be accounted for to avoid running the upgrade in a situation where it would ultimately fail. With that in mind, here are some of the requirements I came up with that the upgrade script needed to solve:

  1. Computer has sufficient free drive space.
  2. Ensure the user is plugged into a power source.
  3. Confirm that the volume is not presently undergoing encryption/decryption.
  4. Provide a way for the end user to do FileVault authenticated restarts if possible.
  5. Provide dialogs to give the user feedback such as a time estimate and dialogs on what to expect next.
  6. Make use of Jamf Pro script parameters to allow for customization and potential re-use for future operating systems releases.
  7. Provide logging to see where failures may appear.
  8. Allow use of an install package that can be used with macOS 10.13+ installers.
  9. Make sure that the macOS installer app does not have expired certificates.
  10. Perform a jamf recon immediately after upgrade.

Continue reading Revisiting: A macOS upgrade method using Jamf Pro Self Service

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!