This framework will prompt users of Jamf Pro-managed Macs to install Apple software updates when specific updates that the IT department has deemed "critical" are available. Users will have the option to Run Updates or Defer. After a specified amount of time, the Mac will be forced to install the updates, then restart automatically if any updates require it.
This workflow is most useful for updates that require a restart and include important security-related patches (e.g. Security Update 2018-003 High Sierra, macOS Mojave 10.14.2), but also applies to critical security updates that don't require a restart (e.g. Safari 12.0.2). Basically, anything with the [recommended]
and/or [restart]
label in the softwareupdate
catalog is in scope.
This framework is distributed in the form of a munkipkg project, which allows easy creation of a new installer package when changes are made to the script or to the LaunchDaemon that runs it (despite the name, packages generated with munkipkg don't require Munki; they work great with Jamf Pro). See the Installer Creation section below for specific steps on creating the installer for this framework.
Here's what needs to be in place in order to use this framework:
- The current version of this framework has been tested only on macOS 10.12 through 10.14, but will most likely work on 10.8+ (note that any changes to install-or-defer will likely not be tested thoroughly in versions of macOS which no longer receive security updates from Apple, but older versions should continue to function normally in those environments).
- Target Macs must be enrolled in Jamf Pro and have the
jamfHelper
binary installed. - We're assuming that an automatic restart is desired when updates require it.
- Optional: A company logo graphic file in a "stash" on each Mac (if no logo is provided, the Software Update icon will be used).
- Optional but recommended: A Mac with Content Caching service active at all major office locations. This will conserve network bandwidth and improve the download speed of updates.
Here's how everything works, once it's configured:
- When a new critical Apple security update is released, the Jamf Pro administrator creates a smart group for this update and adds it to the existing policy.
- People who fall into the smart group start running the policy at next check-in.
- The policy installs a package that places a LaunchDaemon and a script.
- The LaunchDaemon executes the script, which performs the following actions:
-
The script runs
softwareupdate --list
to determine if any updates are required (determined by whether a[restart]
or[recommended]
label is found in the update check). If no such updates are found, the script and LaunchDaemon self-destruct. -
If a required update is found, the script runs
softwareupdate --download --all
orsoftwareupdate --download --recommended
to cache all available recommended Apple updates in the background (--all
if a restart is required for any updates,--recommended
if not). -
An onscreen message appears, indicating the new updates are required to be installed. Two options are given: Run Updates or Defer.
(Note: Your company logo will appear in place of the Software Update icon, if you specify the
LOGO
path.) -
If the user clicks Defer, the prompt will be dismissed. The next prompt will reappear after 4 hours (customizable). Users can defer for up to 72 hours (also customizable). After the deferral period has ended, the Mac automatically runs the cached updates.
-
When the user clicks Run Updates, the script runs the cached software updates.
-
- If the deferral deadline passes, the script behaves differently:
- After the updates are done installing, if a restart is required:
- A "soft" restart is attempted.
- 5 minutes after the "soft" restart attempt, if the user still has not restarted (or if unsaved work prevents the "soft" restart from occurring), the script forces a restart to occur.
- When finished, the script and LaunchDaemon self-destruct in order to prevent the prompt from incorrectly appearing again after the updates have been installed.
The framework has two major limitations:
- Sequential updates cannot be installed as a group. If multiple sequential critical updates are available, they are treated as two separate rounds of prompting/deferring. Macs requiring sequential updates may take up to 6 days (2x defer deadline) to be fully patched.
- Possible solution: Install a LaunchDaemon that installs any remaining updates upon the next restart, enabling all updates to be installed in a single session. We did not take this approach due to the risk of false-positives causing update loops to occur.
- Reasonable attempts have been made to make this workflow enforceable, but there's nothing stopping an administrator of a Mac from unloading the LaunchDaemon or resetting the preference file.
Open the script file with a text editor (e.g. TextWrangler or Atom): payload/Library/Scripts/install_or_defer.sh
There are several variables in the script that should be customized to your organization's needs:
-
PLIST
Path to a plist file that is used to store settings locally. Omit ".plist" extension. -
LOGO
(Optional) Path to a logo that will be used in messaging. Recommend 512px, PNG format. If no logo is provided, the Software Update icon will be used (as shown in the screenshots above). -
BUNDLE_ID
The identifier of the LaunchDaemon that is used to call this script, which should match the file in the payload/Library/LaunchDaemons folder. Omit ".plist" extension.
-
MSG_ACT_OR_DEFER_HEADING
The heading/title of the message users will receive when updates are available. -
MSG_ACT_OR_DEFER
The body of the message users will receive when updates are available. -
MSG_ACT_HEADING
The heading/title of the message users will receive when they must run updates immediately. -
MSG_ACT
The body of the message users will receive when they must run updates immediately. -
MSG_UPDATING_HEADING
The heading/title of the message users will receive when updates are running in the background. -
MSG_UPDATING
The body of the message users will receive when updates are running in the background.
The above messages use the following dynamic substitutions:
%DEFER_HOURS%
will be automatically replaced by the number of hours remaining in the deferral period.%UPDATE_MECHANISM%
will be automatically replaced by either "App Store > Updates" or "System Preferences > Software Update" depending on the version of macOS.- The section in the
<<double comparison operators>>
will be removed if a restart is not required. - The section in the
{{double curly brackets}}
will be removed when this message is displayed for the final time before the deferral deadline.
-
MAX_DEFERRAL_TIME
Number of seconds between the first script run and the updates being forced. -
EACH_DEFER
When the user clicks "Defer" the next prompt is delayed by this much time. -
UPDATE_DELAY
The number of seconds to wait between displaying the "run updates" message and applying updates, then attempting a soft restart. -
HARD_RESTART_DELAY
The number of seconds to wait between attempting a soft restart and forcing a restart.
Download and install munkipkg, if you haven't already. Add the munkipkg
binary location to your PATH
or create an alias in your bash profile so that you can reference the command directly.
Each time you make changes to the script, we recommend changing the following three things:
- The Last Modified metadata in the script.
- The Version metadata in the script.
- The
version
key in the build-info.plist file (recommend matching the script version).
With munkipkg installed, his command will generate a new installer package in the build folder:
munkipkg /path/to/install_or_defer
The subsequent installer package can be uploaded to Jamf Pro and scoped as specified below in the JSS setup section.
The following objects should be created on the JSS in order to implement this framework:
Upload this package (created with munkipkg above) to the JSS via Jamf Admin or via the JSS web app:
- install_or_defer-x.x.x.pkg
Create a smart group for each software update or operating system patch you wish to enforce. Here are some examples to serve as guides.
-
Critical Update Needed: macOS Mojave 10.14.2
Last Check-In
less than x days ago
7
and
Operating System Build
matches regex
^18[A-B]
-
Critical Update Needed: Security Update 2018-003 High Sierra
Last Check-In
less than x days ago
7
and
(
Operating System Build
matches regex
^17G6[5-6]
or
Operating System Build
matches regex
^17G[1-3]
)
The "Last Check-In" criteria has been added in the examples above in order to make the smart group membership count more accurately reflect the number of active computers that need patching, rather than including computers that have been lost, decommissioned, or shelved. The presence or absence of the "Last Check-In" criteria does not have a significant effect on behavior or scope of this framework.
Searching with regular expressions (regex) was added to Jamf Pro as of version 10.7.0. If you're running an older version, you can use is
to approximate the above behavior and call out each specific version you're targeting for update, as per this example:
- Critical Update Needed: macOS High Sierra 10.13.6
Last Check-In
less than x days ago
7
and
(
Operating System
is
10.13
or
Operating System
is
10.13.1
or
Operating System
is
10.13.2
or
Operating System
is
10.13.3
or
Operating System
is
10.13.4
or
Operating System
is
10.13.5
)
For completion's sake, here's an update that won't require a restart but is still tagged as [recommended]
in the softwareupdate
catalog:
- Critical Update Needed: Safari 12.0.2
Application Title
is
Safari.app
and
(
Application Version
matches regex
^11
or
Application Version
matches regex
^12.0.[0-1]
)
Create the following two policies:
-
Update inventory at startup
- Triggers:
- Startup
- Execution Frequency: Ongoing
- Maintenance:
- Update inventory
- Scope: All computers
- Triggers:
-
Prompt to install or defer critical Apple updates
- Triggers:
- Recurring check-in
- Custom: critical-updates
- Execution Frequency: Once every day
- Packages:
- install_or_defer-x.x.x.pkg
- Scope:
- For now, just a handful of Macs you can test on.
- Triggers:
-
Add your test Mac to the scope of the Prompt to install or defer critical Apple updates policy.
-
On the test Mac, open Console.app. To display activity in OS X 10.11 and lower, filter for
install_or_defer
.Or run this Terminal command:
tail -f /var/log/system.log | grep "install_or_defer"
To display activity in macOS 10.12 and higher, filter for the Process
logger
.Or run this Terminal command:
log stream --style syslog --predicate 'senderImagePath ENDSWITH "logger"'
-
Open Terminal and trigger the "stash" policy that deploys the logo graphics, if not already installed:
sudo jamf policy -event stash
-
Then trigger the Prompt to install or defer critical Apple updates policy:
sudo jamf policy -event critical-updates
-
Enter your administrative password when prompted.
-
The policy should run and install the script/LaunchDaemon. Switch back to Console to view the output. You should see something like the following:
Starting install_or_defer.sh script. Performing validation and error checking... Validation and error checking passed. Starting main process... Deferral deadline: 2019-02-12 14:54:45 Time remaining: 72h:00m:00s Checking for pending system updates... Caching all system updates... Software Update Tool Copyright 2002-2015 Apple Inc. Finding available software Downloaded Security Update 2019-001 Done. Prompting to install updates now or defer...
-
After the updates are downloaded, you should see the following prompt appear onscreen:
-
Click Defer. You should see the following output appear in Console:
User clicked Defer after 00h:00m:20s. Next prompt will appear after 2016-09-08 20:31:45.
-
Run the following command in Terminal:
sudo defaults read /Library/Preferences/com.elliotjordan.install_or_defer | grep AppleSoftwareUpdates
-
You should see something similar to the following output (the numbers, which represent dates, will vary):
AppleSoftwareUpdatesDeferredUntil = 1473391905; AppleSoftwareUpdatesForcedAfter = 1473636653;
-
Enter the following commands to "skip ahead" to the next deferral and re-trigger the prompt:
sudo defaults write /Library/Preferences/com.elliotjordan.install_or_defer AppleSoftwareUpdatesDeferredUntil -int $(date +%s) sudo launchctl unload /Library/LaunchDaemons/com.elliotjordan.install_or_defer.plist sudo launchctl load /Library/LaunchDaemons/com.elliotjordan.install_or_defer.plist
-
You should see the install/defer prompt appear again.
-
Click Run Updates. As long as there are no apps with unsaved changes, the Mac will run updates in the background. You should see the following prompt appear onscreen:
- If you want to test the "hard restart" feature of this framework, open Terminal and type
top
before clicking the Run Updates button. Then wait 5 minutes and confirm that the Mac restarts successfully.
- If you want to test the "hard restart" feature of this framework, open Terminal and type
-
After updates are installed and (optionally) the Mac is successfully restarted, you should not see any more onscreen messages.
-
(OPTIONAL) If an additional round of updates is needed (e.g. Security Update 2016-001), run
sudo jamf policy -event critical-updates
again to start the process over. Sequential updates cannot be installed as a group (see Limitations section above).
Once the Testing steps above have been followed, there are only a few steps remaining to deploy the framework:
- On the JSS web app, edit the Prompt to install or defer critical Apple updates policy and click on the Scope tab.
- Remove the test Macs from the scope.
- Add all the Critical updates available smart groups into the scope.
- Click Save.
- Monitor the policy logs to ensure the script is working as expected.
If major problems are detected with the critical update prompt or installation workflow, disable the Prompt to install or defer critical Apple updates policy. This will prevent computers from being newly prompted for installation of updates.
Once the script is debugged and updated, you can generate a new installer, upload the installer to the JSS and link it to the policy, and re-enable the policy.
- Feel free to change the
com.elliotjordan
style identifier to match your company instead. If you do this, make sure to update the filenames of the LaunchDaemons, and their corresponding file paths in the preinstall and postinstall scripts. - You can also specify a different default logo, if you'd rather not use the Software Update icon.
jamfHelper
supports .icns and .png files. - If you encounter any issues or have questions, please open an issue on this GitHub repo.
Enjoy!