Easily clear your users’ policy-specific deferrals when using Installomator with Jamf Pro
Background
Since fully embracing Installomator v10.0, which includes support for swiftDialog, we’ve been testing Mischa van der Bent’s approach to leveraging Jamf Patch Management Software Titles for versioning information, which, on the whole, seems to be working quite well.
“… keep in mind Jamf Patch Management and Installomator disagree at times on whether a new app is available.”
Adam Codega
However, allowing users to defer Jamf Pro policies introduces its own set of challenges.
Policy Deferral
Jamf’s official User Interaction with Policies documentation should be considered required reading before implementing this feature in production, but since none of us have time for that, here are the highlights:
Before a policy runs on a computer, the user is prompted to choose to have the policy run immediately or to defer the policy for one of the following:
- 1 hour
- 2 hours
- 4 hours
- 1 day
- The amount of time until the deferral limit is reached
User-facing Messages
When a policy has a Deferral Type of “No Deferral” and you enter a custom message, the custom message will be displayed as a macOS Notification.
For policies with a Deferral Type but no custom message, the user will be presented a generic message:
A management task is scheduled to run now.
Choose when vou want to start the task.
A word of caution
Jamf’s documentation also states:
To avoid policy deferment issues and excessive re-runs, the deferment must not exceed the execution frequency configured for the policy.
In our experience, Deferment Duration will almost always exceed the policy’s Execution Frequency (due to the limited number of options for Execution Frequency), so deferred policies which also include a Trigger of Recurring Check-in should be considered as having an Execution Frequency of Ongoing, regardless of the specified Execution Frequency.
In other words, since deferred policies don’t write policy logs to the Jamf Pro server until the deferral window has closed and the policy has actually executed, if users are allowed to defer a policy with an Execution Frequency of “Once every day” (as illustrated in the above policy screenshot) and the policy includes a Recurring Check-in Trigger, the Jamf Pro server won’t have a policy log of the client-side deferral, so the policy will actually execute — and be quickly deferred — at every check-in (i.e., Ongoing).
Recurring Check-in Trigger
sudo jamf policy -verbose # Simulate a Recurring Check-in verbose: JAMF binary already symlinked verbose: Checking for an existing instance of this application... Checking for policies triggered by "recurring check-in" for user "dan"... verbose: Checking for active ethernet connection... verbose: No active ethernet connection found... verbose: Removing any cached policies for this trigger. verbose: Parsing servers... verbose: Parsing Policy Installomator: Google Chrome Auto-Update (337)... verbose: The Management Framework Settings are up to date. verbose: Found 1 matching policies. verbose: Policy 'Installomator: Google Chrome Auto-Update' will not be executed because it was deferred by the user. Checking for patches... No patch policies were found.
Execution by Policy ID
sudo jamf policy -id 337 -verbose # Execute by Policy ID verbose: JAMF binary already symlinked verbose: Checking for an existing instance of this application... Checking for policy ID 337... verbose: Checking for active ethernet connection... verbose: No active ethernet connection found... verbose: Removing any cached policies for this trigger. verbose: Parsing servers... verbose: Parsing Policy Installomator: Google Chrome Auto-Update (337)... verbose: The Management Framework Settings are up to date. verbose: Found 1 matching policies. verbose: Policy 'Installomator: Google Chrome Auto-Update' will not be executed because it was deferred by the user.
XML?
After the user has delayed a policy’s execution, the information is stored client-side in a .PLIST
located at /Library/Application Support/JAMF/.userdelay.plist
.
In the following example, the user selected a two-day delay.
<?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"> <plist version="1.0"> <dict> <key>337</key> <dict> <key>deferStartDate</key> <date>2022-12-09T00:11:03Z</date> <key>lastChosenDeferDate</key> <date>2022-12-11T00:11:03Z</date> </dict> </dict> </plist>
While I’m no XML expert, the repeated use of key
makes parsing .userdelay.plist
for a single delayed policy using common XML tools fairly challenging:
xmllint --xpath "count(//key)" /Library/Application\ Support/JAMF/.userdelay.plist 3
If you’re allowing three browser-related policies to be deferred …
<?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"> <plist version="1.0"> <dict> <key>337</key> <dict> <key>deferStartDate</key> <date>2022-12-11T21:33:55Z</date> <key>lastChosenDeferDate</key> <date>2022-12-13T21:33:55Z</date> </dict> <key>357</key> <dict> <key>deferStartDate</key> <date>2022-12-11T21:33:58Z</date> <key>lastChosenDeferDate</key> <date>2022-12-12T21:33:58Z</date> </dict> <key>359</key> <dict> <key>deferStartDate</key> <date>2022-12-11T21:33:40Z</date> <key>lastChosenDeferDate</key> <date>2022-12-16T21:33:40Z</date> </dict> </dict> </plist>
… the results are thrice as nice:
xmllint --xpath "count(//key)" /Library/Application\ Support/JAMF/.userdelay.plist 9
macOS 12.6.1
To add to the challenge, macOS 12.6.1
includes xmllint
version 20904
which doesn’t play nice by including line breaks:
❯ sw_vers ProductName: macOS ProductVersion: 12.6.1 BuildVersion: 21G217 ❯ xmllint --version xmllint: using libxml version 20904 > xmllint --xpath "/plist/dict/key/text()" /Library/Application\ Support/JAMF/.userdelay.plist 337357359%
macOS 13.0.1
Version 20913
seems to work as one would expect:
# sw_vers ProductName: macOS ProductVersion: 13.0.1 BuildVersion: 22A400 # xmllint --version xmllint: using libxml version 20913 # xmllint --xpath "/plist/dict/key/text()" /Library/Application\ Support/JAMF/.userdelay.plist 337 357 359
plutil
Using plutil
proved to be much easier for this use-case (and Armin has a nice write-up on Editing Property Lists with plutil).
# plutil -p /Library/Application\ Support/JAMF/.userdelay.plist { "337" => { "deferStartDate" => 2022-12-11 21:33:55 +0000 "lastChosenDeferDate" => 2022-12-13 21:33:55 +0000 } "357" => { "deferStartDate" => 2022-12-11 21:33:58 +0000 "lastChosenDeferDate" => 2022-12-12 21:33:58 +0000 } "359" => { "deferStartDate" => 2022-12-11 21:33:40 +0000 "lastChosenDeferDate" => 2022-12-16 21:33:40 +0000 } }
Configuration
Complete the following steps to both report on and selectively reset user-deferred Jamf Pro polices.
A. Add the Policy Deferrals Extension Attribute to your Jamf Pro server
Manually create an Extension Attribute and use the following script:
#!/bin/bash ####################################################################### # A script to determine the user-selected delay for Jamf Pro policies # ####################################################################### plist="/Library/Application Support/JAMF/.userdelay.plist" export PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin/ if [[ -f "${plist}" ]]; then deferredPolicies=$( plutil -p /Library/Application\ Support/JAMF/.userdelay.plist | sed 's/[\"\{\}=>]//g; s/ +0000//g; s/^ *//g; s/deferStartDate /S:/g; s/lastChosenDeferDate /E:/g; 1d' ) fi if [[ -z "${deferredPolicies}" ]]; then deferredPolicies="None" fi echo "<result>${deferredPolicies}</result>"
The output will be displayed as either “None” or a listing of Policy ID(s) with the Start date(s) and End date(s):
Policy Deferrals: | 337 S: 2022-12-11 21:33:55 E: 2022-12-13 21:33:55 357 S: 2022-12-11 21:33:58 E: 2022-12-12 21:33:58 359 S: 2022-12-11 21:33:40 E: 2022-12-16 21:33:40 |
B. Add the User-deferral Removal script to your Jamf Pro server
- Add the Jamf Pro Policy User-deferral Removal script to your Jamf Pro server
- Specify the following for Options > Parameter Labels
- Parameter 4:
Script Log Location
- Parameter 5:
Policy ID (Use "0" to clear all policy deferrals)
- Parameter 4:
- Click Save
#!/bin/bash #################################################################################################### # # ABOUT # # Jamf Pro Policy User-deferral Removal # #################################################################################################### # # HISTORY # # Version 0.0.1, 06-Dec-2022, Dan K. Snelson (@dan-snelson) # Original Version # # Version 0.0.2, 10-Dec-2022, Dan K. Snelson (@dan-snelson) # Leveraged code from "Policy Delay EA" for displaying file contents # #################################################################################################### #################################################################################################### # # Variables # #################################################################################################### # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Global Variables # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # scriptVersion="0.0.2" export PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin/ testFile="/Library/Application Support/JAMF/.userdelay.plist" updateScriptLog="${4:-"/var/tmp/org.churchofjesuschrist.log"}" policyID="${5:-"359"}" # Policy ID to clear; use "0" for all #################################################################################################### # # Functions # #################################################################################################### # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Client-side Script Logging # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # function updateupdateScriptLog() { echo -e "$( date +%Y-%m-%d\ %H:%M:%S ) - ${1}" | tee -a "${updateScriptLog}" } #################################################################################################### # # Pre-flight Checks # #################################################################################################### # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Client-side Logging # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # if [[ ! -f "${updateScriptLog}" ]]; then touch "${updateScriptLog}" updateupdateScriptLog "*** Created log file via script ***" fi # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Logging preamble # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # updateupdateScriptLog "\n\n###\n# Jamf Pro Policy User-deferral Removal (${scriptVersion})\n###\n" # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Confirm script is running as root # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # if [[ $(id -u) -ne 0 ]]; then updateupdateScriptLog "This script must be run as root; exiting." exit 1 else updateupdateScriptLog "Script running as \"root\"; proceeding …" fi #################################################################################################### # # Program # #################################################################################################### # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Remove User Deferrals # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # if [[ -f "${testFile}" ]]; then updateupdateScriptLog "\"${testFile}\" does exists; proceeding …" updateupdateScriptLog "Reading contents of \"${testFile}\" … " # echo "$(<"$testFile")" | tee -a "${updateScriptLog}" plutil -p "${testFile}" | sed 's/[\"\{\}=>]//g; s/ +0000//g; s/^ *//g; s/deferStartDate /S:/g; s/lastChosenDeferDate /E:/g; 1d' | tee -a "${updateScriptLog}" if [[ "${policyID}" == "0" ]]; then updateupdateScriptLog "Removing entire \"${testFile}\" … " rm -v "${testFile}" | tee -a "${updateScriptLog}" else updateupdateScriptLog "Removing user deferral for Policy ID \"${policyID}\" … " plutil -remove "${policyID}" "${testFile}" | tee -a "${updateScriptLog}" updateupdateScriptLog "Reading contents of \"${testFile}\" … " # echo "$(<"$testFile")" | tee -a "${updateScriptLog}" plutil -p "${testFile}" | sed 's/[\"\{\}=>]//g; s/ +0000//g; s/^ *//g; s/deferStartDate /S:/g; s/lastChosenDeferDate /E:/g; 1d' | tee -a "${updateScriptLog}" fi else updateupdateScriptLog "\"${testFile}\" does NOT exist" fi # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Exit # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # updateupdateScriptLog "Goodbye!" exit 0
C. Configure User Interaction for auto-update Installomator policies
Configure user interaction for your auto-update Installomator policies as desired.
D. Configure User-deferral Removal for manual Installomator policies
- Clone your auto-update Installomator policy
- Select the Scripts payload and add the
Jamf Pro Policy User-deferral Removal
script, specifying the following Parameter Values- Script Log Location:
/var/log/com.company.log
- Policy ID (Use “0” to clear all policy deferrals):
Policy ID of auto-update policy
- Script Log Location:
- Use the following for Self Service
- Remove all User Interaction settings
- Click Save
Conclusion
The payoff for your end-users is that if they have previously delayed your auto-update policies, they can simply login to Self Service and update at-will.
Product Issue
If you find you’re frequently using policy deferrals, you may wish to open a case with Jamf Support to inform them you’re impacted by the following (currently unlisted) Product Issue:
Management Action (User Interaction) notification does not contain the icon uploaded to the Policy.
PI103485