Run any Jamf Pro policy at the next user login or computer reboot
Trigger Policy at Login or Reboot screencast (06:55) [Music: bensound.com]
Background
Recently, we had a need to run a particular Jamf Pro policy only the next time the computer rebooted.
Having previously created Recon at Reboot, I started on a modification for this one-off need. About a third of the way into the modifications, a Heaven-inspired question came to mind:
Why don’t you write a script to execute any Jamf Pro policy at the next reboot?
Built-in Options
For policies which should always execute at login or reboot, you can leverage Jamf Pro’s built-in options:
When you’d prefer to not duplicate polices just so they’ll execute at the next login or reboot, the following “trigger” scripts may prove helpful.
Overview
Workflow
For a detailed workflow, see the Trigger Policy at Login or Reboot screencast.
- Add both “trigger” scripts to your Jamf Pro server
- Create a new (or edit an existing) policy
- Add the “create” script to the policy’s Script payload
- Specify the preferred Launch Option
- Edit the policy which you’d like to execute at the next login or reboot
- Add the “delete” script to the policy’s Script payload
- Specify the same Launch Option information as in Step No. 2
Launch Option
The following table describes the differences between using the login or the restart Launch Option:
| Launch Option | Login | Reboot |
|---|---|---|
| .plist Location | ~/Library/LaunchAgents/ | /Library/LaunchDaemons/ |
| Program Arguments | open jamfselfservice:// … "${id}" | jamf policy -event "${trigger}" |
| Run At Load | true | true |
Scripts
Add the Trigger Policy at Login or Reboot – Create script to your Jamf Pro server
- Add the
Trigger Policy at Login or Reboot - Createscript to your Jamf Pro server - Specify the following for Options > Parameter Labels
- Parameter 4:
Reverse Domain Name Notation (i.e., "com.company") - Parameter 5:
Launch Option (i.e., [ restart | login ] ) - Parameter 6:
Jamf Pro Policy Trigger (for restart) or ID (for login)
- Parameter 4:
- Click Save

Latest version available on GitHub.
#!/bin/bash
####################################################################################################
#
# ABOUT
#
# Execute a Jamf Pro Policy Trigger (for restart) or ID (for login) after computer reboot
# https://snelson.us/2023/04/trigger-policy-at-login-or-reboot-0-0-2/
#
####################################################################################################
#
# HISTORY
#
# Version 0.0.1, 20-Mar-2023, Dan K. Snelson (@dan-snelson)
# Based on Recon at Reboot
#
# Version 0.0.2, 13-Apr-2023, Dan K. Snelson (@dan-snelson)
# Miscellaneous updates
#
####################################################################################################
####################################################################################################
#
# Global Variables
#
####################################################################################################
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Script Version and Jamf Pro Script Parameters
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
scriptVersion="0.0.2"
export PATH=/usr/bin:/bin:/usr/sbin:/sbin
reverseDomain="${4:-"org.churchofjesuschrist"}" # Parameter 4: Reverse Domain Name Notation (i.e., "org.churchofjesuschrist")
launchOption="${5:-"login"}" # Parameter 5: Launch Option (i.e., [ restart | login ] )
triggerOrID="${6:-"29"}" # Parameter 6: Jamf Pro Policy Trigger (for restart) or ID (for login)
scriptLog="/var/log/${reverseDomain}.log"
plistFilename="${reverseDomain}.${triggerOrID}.plist"
loggedInUser=$( /bin/echo "show State:/Users/ConsoleUser" | /usr/sbin/scutil | /usr/bin/awk '/Name :/ { print $3 }' )
####################################################################################################
#
# Pre-flight Checks
#
####################################################################################################
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Client-side Logging
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
if [[ ! -f "${scriptLog}" ]]; then
touch "${scriptLog}"
fi
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Client-side Script Logging Function
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
function updateScriptLog() {
echo -e "$( date +%Y-%m-%d\ %H:%M:%S ) - ${1}" | tee -a "${scriptLog}"
}
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Logging Preamble
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
updateScriptLog "\n\n###\n# Trigger Policy at Login or Reboot: Create (${scriptVersion})\n###\n"
updateScriptLog "PRE-FLIGHT CHECK: Initiating …"
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Confirm script is running as root
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
if [[ $(id -u) -ne 0 ]]; then
updateScriptLog "PRE-FLIGHT CHECK: This script must be run as root; exiting."
exit 1
fi
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Complete
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
updateScriptLog "PRE-FLIGHT CHECK: Complete"
####################################################################################################
#
# Program
#
####################################################################################################
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Set LaunchDaemon or LaunchAgent based on launchOption
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
case ${launchOption} in
"login" )
# Create User's LaunchAgents directory as required
if [[ ! -d "/Users/${loggedInUser}/Library/LaunchAgents/" ]]; then
updateScriptLog "Create '/Users/${loggedInUser}/Library/LaunchAgents/' …"
mkdir -pv "/Users/${loggedInUser}/Library/LaunchAgents"
chown -v "${loggedInUser}":staff "/Users/${loggedInUser}/Library/LaunchAgents"
chmod -v 755 "/Users/${loggedInUser}/Library/LaunchAgents"
fi
# Create LaunchAgent to call Jamf Pro Policy
updateScriptLog "Create LaunchAgent to call Jamf Pro Policy: ${triggerOrID} ..."
/bin/echo "<?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>Enabled</key>
<true/>
<key>EnableTransactions</key>
<true/>
<key>Label</key>
<string>${reverseDomain}.${triggerOrID}</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/open</string>
<string>jamfselfservice://content?entity=policy&id=${triggerOrID}&action=execute</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>" > "/Users/${loggedInUser}/Library/LaunchAgents/${plistFilename}"
# Set the permission on the file
updateScriptLog "Set permissions on launchd plist ..."
chown "${loggedInUser}":staff "/Users/${loggedInUser}/Library/LaunchAgents/${plistFilename}"
chmod 644 "/Users/${loggedInUser}/Library/LaunchAgents/${plistFilename}"
;;
"restart" | * )
# Create LaunchDaemon to call Jamf Pro Policy Trigger
updateScriptLog "Create LaunchDaemon to call Jamf Pro Policy Trigger: ${triggerOrID} ..."
/bin/echo "<?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>Label</key>
<string>${reverseDomain}.${triggerOrID}</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/jamf/bin/jamf</string>
<string>policy</string>
<string>-event</string>
<string>${triggerOrID}</string>
<string>-verbose</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>" > "/Library/LaunchDaemons/${plistFilename}"
# Set the permissions on LaunchDaemon
updateScriptLog "Setting permissions on '/Library/LaunchDaemons/${plistFilename}' ..."
chown root:wheel "/Library/LaunchDaemons/${plistFilename}"
chmod 644 "/Library/LaunchDaemons/${plistFilename}"
updateScriptLog "Set permissions on '/Library/LaunchDaemons/${plistFilename}'"
;;
esac
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Exit
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
updateScriptLog "So long!"
exit 0
Add the Trigger Policy at Login or Reboot – Delete script to your Jamf Pro server
- Add the
Trigger Policy at Login or Reboot - Deletescript to your Jamf Pro server - Specify the following for Options > Parameter Labels
- Parameter 4:
Reverse Domain Name Notation (i.e., "com.company") - Parameter 5:
Launch Option (i.e., [ restart | login ] ) - Parameter 6:
Jamf Pro Policy Trigger (for restart) or ID (for login)
- Parameter 4:
- Click Save

Latest version available on GitHub.
#!/bin/bash
####################################################################################################
#
# ABOUT
#
# Delete the Jamf Pro Policy Trigger (for restart) or ID (for login) after computer reboot
# https://snelson.us/2023/04/trigger-policy-at-login-or-reboot-0-0-2/
#
####################################################################################################
#
# HISTORY
#
# Version 0.0.1, 20-Mar-2023, Dan K. Snelson (@dan-snelson)
# Based on Recon at Reboot
#
# Version 0.0.2, 13-Apr-2023, Dan K. Snelson (@dan-snelson)
# Miscellaneous updates
#
####################################################################################################
####################################################################################################
#
# Global Variables
#
####################################################################################################
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Script Version and Jamf Pro Script Parameters
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
scriptVersion="0.0.2"
export PATH=/usr/bin:/bin:/usr/sbin:/sbin
reverseDomain="${4:-"org.churchofjesuschrist"}" # Parameter 4: Reverse Domain Name Notation (i.e., "org.churchofjesuschrist")
launchOption="${5:-"login"}" # Parameter 5: Launch Option (i.e., [ restart | login ] )
triggerOrID="${6:-"29"}" # Parameter 6: Jamf Pro Policy Trigger (for restart) or ID (for login)
scriptLog="/var/log/${reverseDomain}.log"
plistFilename="${reverseDomain}.${triggerOrID}.plist"
loggedInUser=$( /bin/echo "show State:/Users/ConsoleUser" | /usr/sbin/scutil | /usr/bin/awk '/Name :/ { print $3 }' )
loggedInUserID=$( /usr/bin/id -u "${loggedInUser}" )
####################################################################################################
#
# Pre-flight Checks
#
####################################################################################################
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Client-side Logging
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
if [[ ! -f "${scriptLog}" ]]; then
touch "${scriptLog}"
fi
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Client-side Script Logging Function
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
function updateScriptLog() {
echo -e "$( date +%Y-%m-%d\ %H:%M:%S ) - ${1}" | tee -a "${scriptLog}"
}
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Logging Preamble
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
updateScriptLog "\n\n###\n# Trigger Policy at Login or Reboot: Delete (${scriptVersion})\n###\n"
updateScriptLog "PRE-FLIGHT CHECK: Initiating …"
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Confirm script is running as root
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
if [[ $(id -u) -ne 0 ]]; then
updateScriptLog "PRE-FLIGHT CHECK: This script must be run as root; exiting."
exit 1
fi
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Complete
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
updateScriptLog "PRE-FLIGHT CHECK: Complete"
####################################################################################################
#
# Functions
#
####################################################################################################
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Run command as logged-in user (thanks, @scriptingosx!)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
function runAsUser() {
updateScriptLog "Run \"$@\" as \"$loggedInUserID\" … "
launchctl asuser "$loggedInUserID" sudo -u "$loggedInUser" "$@"
}
####################################################################################################
#
# Program
#
####################################################################################################
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Delete LaunchDaemon or LaunchAgent based on launchOption
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
case ${launchOption} in
"login" )
if [[ -f "/Users/${loggedInUser}/Library/LaunchAgents/${plistFilename}" ]]; then
# Unload LaunchAgent
updateScriptLog "Unload ${plistFilename} … "
runAsUser launchctl bootout gui/"${loggedInUserID}" "/Users/${loggedInUser}/Library/LaunchAgents/${plistFilename}" 2>&1
# Remove LaunchAgent
updateScriptLog "Remove ${plistFilename} … "
rm -f "/Users/${loggedInUser}/Library/LaunchAgents/${plistFilename}" 2>&1
updateScriptLog "Removed ${plistFilename}"
else
updateScriptLog "The file '/Users/${loggedInUser}/Library/LaunchAgents/${plistFilename}' was NOT found"
fi
;;
"restart" | * )
if [[ -f "/Library/LaunchDaemons/${plistFilename}" ]]; then
# Unload LaunchAgent
updateScriptLog "Unload ${plistFilename} … "
launchctl bootout system "/Library/LaunchDaemons/${plistFilename}" 2>&1
# Remove LaunchAgent
updateScriptLog "Remove ${plistFilename} … "
rm -f "/Library/LaunchDaemons/${plistFilename}" 2>&1
updateScriptLog "Removed ${plistFilename}"
else
updateScriptLog "The file '/Library/LaunchDaemons/${plistFilename}' was NOT found"
fi
;;
esac
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Exit
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
updateScriptLog "Goodbye!"
exit 0
Referenced Posts
The following posts are referenced in the screencast: