Booting to macOS Recovery or Diagnostics via Jamf Pro’s Self Service
One of the advantages provided by Jamf Pro’s Self Service is that you can use it to provide easy access to tools for your users or helpdesk folks. One such tool could be a script which helps folks boot to their Macs to one of the following Apple support services:
- Diagnostics / Apple Hardware Test: https://support.apple.com/HT202731
- macOS Recovery: https://support.apple.com/HT201314
For more details, please see below the jump.
This script sets one of four boot arguments and passes it to NVRAM:
- RecoveryModeDisk – Boots to the Recovery volume on your local boot drive
- RecoveryModeNetwork – Boots to Internet Recovery
- DiagsModeDisk – Boots to the Diagnostics or Apple Hardware Test volume on your local boot drive.
- DiagsModeNetwork – Boots to Internet Diagnostics or Apple Hardware Test
Note: If booting to macOS Recovery, this script will set the logged-in account to have admin privileges. This is because, on Macs equipped with T2 security chips, an admin account is needed to be able to access the macOS Utilities tools in the Recovery environment.
To set it up for use with Jamf Pro, do the following:
1. Add the script to Jamf Pro.
2. Create a new Self Service policy.
3. Select the script as part of the policy.
4. Set the policy’s Execution Frequency to Ongoing.
5. Set target scope as desired.
Note: While this script was written with Jamf Pro’s Self Service in mind, it should be adaptable with either no alteration or minor edits to other Self Service tools which can run scripts.
Once the policy is set up in Jamf Pro, it should look like this when run.
When the Mac restarts, it should boot to Diagnostics or macOS Recovery (depending on your choices.)
The script is available below. It is also available from the following location on GitHub:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Enables the Mac to boot into the following: | |
# | |
# * Recovery | |
# * Internet Recovery | |
# * Diagnostics | |
# * Internet Diagnostics | |
# | |
# For information about Diagnostics: https://support.apple.com/HT202731 | |
# For information about Recovery: https://support.apple.com/HT201314 | |
# some variables we have to declare first | |
exitCode=0 | |
# This is our logging function. It logs to syslog and also prints the | |
# log message to STDOUT to make sure, it appears in the Jamf Pro policy log. | |
log() | |
{ | |
local errorMessage="$1" | |
echo "$errorMessage" | |
/usr/bin/logger "SetBootMode: $errorMessage" | |
} | |
# Here we have a function that allows us to display a message box | |
# to the user. If Jamf Self Service is installed on the machine, we | |
# use its icon, otherwise we check for the existence of the system's | |
# AlertNoteIcon and use this, if available. If none of those icons | |
# are available, we use a generic one. | |
displayDialog() | |
{ | |
local dialogMessage="$1" | |
local dialogButtons="$2" | |
local defaultButton="$3" | |
local dialogIcon="$4" | |
local dialogGiveUp="$5" | |
# set up our buttons and make sure we have at least | |
# an OK button if nothing else has been specified | |
if [[ -z "$dialogButtons" ]]; then | |
dialogButtons="\"OK\"" | |
else | |
dialogButtons=$(echo "$dialogButtons" | /usr/bin/sed -e 's/^/"/' -e 's/$/"/' -e 's/,/","/g') | |
fi | |
# if not otherwise specified, the last button is enabled | |
local allButtons=$(echo "$dialogButtons" | /usr/bin/awk -F"," '{print NF-1}') | |
local lastButton=$(($allButtons + 1)) | |
if [[ ! "$defaultButton" =~ ^[0-9]+$ || $defaultButton -le 0 || $defaultButton -gt $lastButton ]]; then defaultButton="$lastButton"; fi | |
# we set the dialog timeout to 0 if not otherwise specified | |
if [[ ! "$dialogGiveUp" =~ ^[0-9]+$ ]]; then dialogGiveUp=0; fi | |
# make sure double quotation marks are properly escaped | |
dialogMessage=$(echo "$dialogMessage" | /usr/bin/sed -e 's/"/\\\"/g') | |
# get the currently logged-in user | |
currentUser=$(/bin/ls -l /dev/console | /usr/bin/awk '{ print $3 }') | |
# set the dialog's icon | |
if [[ ! "$dialogIcon" =~ ^[0-9]+$ || "$dialogIcon" -gt 2 ]]; then | |
local selfServicePath="/Applications/Self Service.app" | |
local iconName=$(/usr/bin/defaults read "/Applications/Self Service.app/Contents/Info" CFBundleIconFile 2>/dev/null) | |
dialogIcon="1" | |
if [[ -n "$iconName" && -r "$selfServicePath/Contents/Resources/$iconName.icns" ]]; then | |
dialogIcon="alias POSIX file \"$selfServicePath/Contents/Resources/$iconName.icns\"" | |
elif [[ -r "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/AlertNoteIcon.icns" ]]; then | |
dialogIcon="alias POSIX file \"/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/AlertNoteIcon.icns\"" | |
fi | |
fi | |
buttonPressed=$(/usr/bin/sudo -u "$currentUser" /usr/bin/osascript << EOF | |
tell application "System Events" | |
try | |
activate | |
display dialog "$dialogMessage" with icon $dialogIcon buttons {$dialogButtons} default button $defaultButton giving up after $dialogGiveUp | |
return (button returned of the result) | |
end try | |
end tell | |
EOF | |
) | |
echo "$buttonPressed" | |
} | |
# this script must be run with root privileges | |
if [[ "$(/usr/bin/id -u)" -eq 0 ]]; then | |
# bootMode is a 2-bit binary value defined as follows: | |
# the most significant bit (MSB) specifies the actual boot mode. "Recovery" is 0 and "Diagnostics" is 1. | |
# the least significant bit (LSB) specifies the boot method. 0 means "local" and 1 means "Internet". | |
# so "01" would set the boot mode to "Internet Recovery" and "10" would boot local diagnostics. | |
bootMode= | |
# to set the MSB (as explained above) we ask the users if the | |
# Mac should be started in recovery or diagnostics mode and | |
# then set the MSB accordingly. | |
buttonPressed=$(displayDialog "Would you like to restart your Mac in recovery or diagnostics mode?" "Cancel,Diagnostics,Recovery" "" "" "60") | |
if [[ "$buttonPressed" = "Recovery" ]]; then | |
bootMode="0" | |
elif [[ "$buttonPressed" = "Diagnostics" ]]; then | |
bootMode="1" | |
fi | |
# we go ahead, if the user did not click "cancel" in the previous dialog … | |
if [[ -n "$bootMode" ]]; then | |
# … and ask the user if the Mac should be booted from the local | |
# disk or from Internet. So we can set our LSB now. | |
buttonPressed=$(displayDialog "Would you like to boot from your Mac's local disk or from the Internet?" "Cancel,Internet,Local" "" "" "60") | |
if [[ "$buttonPressed" = "Local" ]]; then | |
bootMode="${bootMode}0" | |
elif [[ "$buttonPressed" = "Internet" ]]; then | |
bootMode="${bootMode}1" | |
fi | |
# we convert our binary number into an integer and | |
# select the actual argument for the nvram command | |
bootArg= | |
case "$((2#$bootMode))" in | |
0) | |
bootArg="RecoveryModeDisk" | |
;; | |
1) | |
bootArg="RecoveryModeNetwork" | |
;; | |
2) | |
bootArg="DiagsModeDisk" | |
;; | |
3) | |
bootArg="DiagsModeNetwork" | |
;; | |
esac | |
if [[ -n "$bootArg" ]]; then | |
# we ask the user to restart the Mac | |
buttonPressed=$(displayDialog "Please click \"Restart\" to restart your Mac." "Cancel,Restart" "" "" "60") | |
if [[ "$buttonPressed" = "Restart" ]]; then | |
# the user clicked the "Restart" button so we use | |
# the nvram tool to set the boot options … | |
log "Setting boot mode to \"$bootArg\"" | |
/usr/sbin/nvram "internet-recovery-mode=$bootArg" | |
# if the user want to boot into recovery mode, we have to | |
# make sure the user has admin rights | |
if [[ "$bootArg" =~ ^Recovery ]]; then | |
isNotAdmin=$(/usr/bin/dsmemberutil checkmembership -U "$currentUser" -G admin | /usr/bin/grep -i "is not") | |
if [[ -n "$isNotAdmin" ]]; then | |
/usr/sbin/dseditgroup -o edit -a "$currentUser" -t user admin | |
fi | |
# make sure the Recovery environment is aware the user | |
# has admin rights, by updating the preboot volume | |
/usr/sbin/diskutil apfs updatepreboot / >/dev/null 2>&1 | |
fi | |
# … and restart the machine a few seconds after | |
# this script exited | |
(/bin/sleep 5 && /sbin/shutdown -r now) & | |
fi | |
fi | |
fi | |
else | |
log "ERROR! You must be root in order to run this script!" | |
exitCode=1 | |
fi | |
exit $exitCode |
Great!
Will this even work if the efi firmwarepassword is enabled? Or will it by-passed if set boot args are set via nvram?
+1 Hinrich, I would also be very interested to know if it works with Efi set. Thanks.
Just tested it and it looks like it asks for a firmware password. 😦
will the elevation of the permissions reset after the user logs back in?
No.
Fantasic Rich. But why should Jamf Pro people have all the fun? I adapted it for Munki Self-Service and added it to my collection of (now 24) items.
https://github.com/precursorca/Munki-SelfService-On-Demand
@wicker i have adopted this method from Rich and slightly modified for the diagnostic part the firmwarepassword it temporariliy removed, before restart/shutdown to diagnostics a LaunchDaemon is being created which unloads and removes itself after booting up again and it triggers an policy to enable the firmwarepassword directly after the mac comes online
I have one doubt is there any option to remember the current network password and name to automate for the internet recovery instant of tying user name and password for the network.
can you add the policy for grab the current internet-connected SSID and password and put to while asking./
Does this work on Silicon Macs as well or Intel only?
Because of the changes Apple made to booting to Recovery on Apple Silicon, this only works on Intel.