In light of the new "Force a Computer Restart to Install macOS Updates" feature in Jamf Pro 10.38.0, I've decided to create a bash function that should make life easier for admins that want to force updates on M1 machines.
Please refer to the screenshot below for more information on this feature.

New to bearer tokens? Don't worry about it, I've already done the work for you. Simply fill in your api account data and let the function take care of the rest.
#!/bin/bash
# Server connection information
URL="https://url.jamfcloud.com"
username="apiusername"
password="apipassword"
# Determine Serial Number
serialNumber=$(system_profiler SPHardwareDataType | awk '/Serial Number/{print $4}')
initializeSoftwareUpdate(){
# create base64-encoded credentials
encodedCredentials=$( printf "${username}:${password}" | /usr/bin/iconv -t ISO-8859-1 | /usr/bin/base64 -i - )
# Generate new auth token
authToken=$( curl -X POST "${URL}/api/v1/auth/token" -H "accept: application/json" -H "Authorization: Basic ${encodedCredentials}" )
# parse authToken for token, omit expiration
token=$(/usr/bin/awk -F \\" 'NR==2{print $4}' <<< "$authToken" | /usr/bin/xargs)
echo ${token}
# Determine Jamf Pro device id
deviceID=$(curl -s -H "Accept: text/xml" -H "Authorization: Bearer ${token}" ${URL}/JSSResource/computers/serialnumber/"$serialNumber" | xmllint --xpath '/computer/general/id/text()' -)
echo ${deviceID}
# Execute software update
curl -X POST "${URL}/api/v1/macos-managed-software-updates/send-updates" -H "accept: application/json" -H "Authorization: Bearer ${token}" -H "Content-Type: application/json" -d "{\\"deviceIds\\":[\\"${deviceID}\\"],\\"maxDeferrals\\":0,\\"version\\":\\"12.3.1\\",\\"skipVersionVerification\\":true,\\"applyMajorUpdate\\":true,\\"updateAction\\":\\"DOWNLOAD_AND_INSTALL\\",\\"forceRestart\\":true}"
# Invalidate existing token and generate new token
curl -X POST "${URL}/api/v1/auth/keep-alive" -H "accept: application/json" -H "Authorization: Bearer ${token}"
}
initializeSoftwareUpdate
Upload this script in combination with a user interaction / jamfhelper dialog policy to start forcing updates again!!!
Lessons Learned 06/01/2022:
1. The update can take an extremely long time to kick off. I'm talking 1-2 hours +
2. While the Jamf Pro GUI can do full OS upgrades, it doesn't seem to be supported in the API.
Lessons Learned 06/23/2022:
1. I cannot recommend putting this into production. While my jamf helper script does guide the user through the update, The targeted device does not restart in a reasonable time.
2. At this time, the best options for Monterey updates and upgrades seems to be using Nudge or the startosinstall executable that comes packaged with macOS installers: Solved: Re: macOS installer script not working for Apple S... - Jamf Nation Community - 249859
Hey @bwoods, thanks for sharing this. I am just testing now and it seems great!
I have version part so that machines just get the latest available update. I have found that the process is a bit abrupt though, Have you found anyway to warn users that their Mac is about to reboot to start the update?
I use a user interaction to let them know that updates are required but because the update takes a good while to download (especially 12.3.1 to 12.4) the fact that it just restarts without another warning is a bit unfriendly.
Be great to know what you, or others, have done to make this a better experience for users.
@awginger , I use User Interaction in combination with jamf helper/launch daemon prompts to notify them about the reboot. The last prompt basically tells them that the computer will randomly reboot.
If Apple just fixed some basic UX prompts this would be perfect.



@awginger , I use User Interaction in combination with jamf helper/launch daemon prompts to notify them about the reboot. The last prompt basically tells them that the computer will randomly reboot.
If Apple just fixed some basic UX prompts this would be perfect.



@bwoods I was thinking along the same lines and have put a Jamf helper message like yours into the workflow. I think it will be something that we need to communicate to users as the policy we use now (using user interaction) warns them that they have 5mins to restart but the 'download and install' option doesn't appear to give us this flexibility. It would be good if Jamf or Apple give us some more user friendly ways to do this in the future.
Great work on the script!
@awginger , I use User Interaction in combination with jamf helper/launch daemon prompts to notify them about the reboot. The last prompt basically tells them that the computer will randomly reboot.
If Apple just fixed some basic UX prompts this would be perfect.



What do you have setup for your jamfhelper window to popup like that and give a countdown and remaining deferral attempts? Could you share it?
What do you have setup for your jamfhelper window to popup like that and give a countdown and remaining deferral attempts? Could you share it?
I'm not them, but in my test version here's what I used for the prompt when I was testing with it
# JamfHelper window options
heading="macOS Software Update"
description=$( echo "Your computer has mandatory macOS updates it needs to install.\\n\\nThese updates could take up to an hour to install.")
timeout="3600"
canCancel=$(echo "$7" | tr '[:upper:]' '[:lower:]')
style="hud"
button1="Start Now"
icon="/Applications/Software Center.app/Contents/Resources/AppIcon.icns"
currentUser=$(who | grep console | grep -v _mbsetupuser)
countdown(){
# Gets current time. Script is logging how many seconds the prompt was displayed for.
# Primarily used for record keeping.
promptTime=`date +%s`
returned=`/Library/Application\\ Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper \\
-windowType $style \\
-title "Software Center" \\
-heading "$heading" \\
-description "$description" \\
-icon "$icon" \\
-button1 "$button1" \\
-button2 "Later" \\
-cancelButton 2 \\
-defaultButton 1 \\
-lockHUD \\
-alighDescription left \\
-timeout $timeout -countdown`
readTime=$(($(date +%s) - $promptTime))
# Log how quickly the user made a selection.
echo "Countdown window was up for $(($readTime/60)) minutes $(($readTime%60)) seconds."
}
countdownCancelCheck(){
# They started the policy
if [[ $returned -eq 0 ]]; then
echo "User has chosen to start the update now."
elif [[ $returned -eq 2 ]]; then
echo "User has cancelled for now."
return 1
else
echo "Unknown error. JamfHelper may have been force quit."
echo "Assuming user meant to cancel."
return 1
fi
}
countdownForced(){
# Gets current time. Script is logging how many seconds the prompt was displayed for.
# Primarily used for record keeping.
promptTime=`date +%s`
/Library/Application\\ Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper \\
-windowType $style \\
-title "Software Center" \\
-heading "$heading" \\
-description "$description" \\
-icon "$icon" \\
-button1 "$button1" \\
-defaultButton 1 \\
-lockHUD \\
-alighDescription left \\
-timeout $timeout -countdown
readTime=$(($(date +%s) - $promptTime))
# Log how quickly the user made a selection.
echo "Countdown window was up for $(($readTime/60)) minutes $(($readTime%60)) seconds."
}
curtainPuller(){
/Library/Application\\ Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper \\
-windowType fs \\
-title "Software Update" \\
-heading "$heading" \\
-description "$description" \\
-icon "$icon" >/dev/null 2>&1 &
}
if [ "$currentUser" == "" ]; then
echo "No one signed in. Proceeding with update."
elif [ "$canCancel" == "yes" ]; then
echo "$currentUser is signed in and allowed to cancel"
countdown
countdownCancelCheck || exit 0
else
echo "$currentUser is signed in and update is mandatory"
countdownForced
fi
curtainPuller
These functions give some flexibility on allowing the user to cancel (set by $7) and also pulling up a full screen window so they don't keep working while the update is installing. I also posted a couple safety check functions earlier that can help avoid unneeded interruptions to the end user if they either already updated or can't actually update.
I'm not them, but in my test version here's what I used for the prompt when I was testing with it
# JamfHelper window options
heading="macOS Software Update"
description=$( echo "Your computer has mandatory macOS updates it needs to install.\\n\\nThese updates could take up to an hour to install.")
timeout="3600"
canCancel=$(echo "$7" | tr '[:upper:]' '[:lower:]')
style="hud"
button1="Start Now"
icon="/Applications/Software Center.app/Contents/Resources/AppIcon.icns"
currentUser=$(who | grep console | grep -v _mbsetupuser)
countdown(){
# Gets current time. Script is logging how many seconds the prompt was displayed for.
# Primarily used for record keeping.
promptTime=`date +%s`
returned=`/Library/Application\\ Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper \\
-windowType $style \\
-title "Software Center" \\
-heading "$heading" \\
-description "$description" \\
-icon "$icon" \\
-button1 "$button1" \\
-button2 "Later" \\
-cancelButton 2 \\
-defaultButton 1 \\
-lockHUD \\
-alighDescription left \\
-timeout $timeout -countdown`
readTime=$(($(date +%s) - $promptTime))
# Log how quickly the user made a selection.
echo "Countdown window was up for $(($readTime/60)) minutes $(($readTime%60)) seconds."
}
countdownCancelCheck(){
# They started the policy
if [[ $returned -eq 0 ]]; then
echo "User has chosen to start the update now."
elif [[ $returned -eq 2 ]]; then
echo "User has cancelled for now."
return 1
else
echo "Unknown error. JamfHelper may have been force quit."
echo "Assuming user meant to cancel."
return 1
fi
}
countdownForced(){
# Gets current time. Script is logging how many seconds the prompt was displayed for.
# Primarily used for record keeping.
promptTime=`date +%s`
/Library/Application\\ Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper \\
-windowType $style \\
-title "Software Center" \\
-heading "$heading" \\
-description "$description" \\
-icon "$icon" \\
-button1 "$button1" \\
-defaultButton 1 \\
-lockHUD \\
-alighDescription left \\
-timeout $timeout -countdown
readTime=$(($(date +%s) - $promptTime))
# Log how quickly the user made a selection.
echo "Countdown window was up for $(($readTime/60)) minutes $(($readTime%60)) seconds."
}
curtainPuller(){
/Library/Application\\ Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper \\
-windowType fs \\
-title "Software Update" \\
-heading "$heading" \\
-description "$description" \\
-icon "$icon" >/dev/null 2>&1 &
}
if [ "$currentUser" == "" ]; then
echo "No one signed in. Proceeding with update."
elif [ "$canCancel" == "yes" ]; then
echo "$currentUser is signed in and allowed to cancel"
countdown
countdownCancelCheck || exit 0
else
echo "$currentUser is signed in and update is mandatory"
countdownForced
fi
curtainPuller
These functions give some flexibility on allowing the user to cancel (set by $7) and also pulling up a full screen window so they don't keep working while the update is installing. I also posted a couple safety check functions earlier that can help avoid unneeded interruptions to the end user if they either already updated or can't actually update.
Hey McAwesome,
Thanks this is very helpful. I'm looking more for the solution that bwoods implemented though where its just a small window.
I like how he has three different popups: The deferral on and then the one sayings it's started.
Honestly i'd like an exact copy of his script.
@awginger , I use User Interaction in combination with jamf helper/launch daemon prompts to notify them about the reboot. The last prompt basically tells them that the computer will randomly reboot.
If Apple just fixed some basic UX prompts this would be perfect.



Hey @bwoods would you be able to share your jamfhelper script you wrote?
One note, the section below can mostly likely be turned into a variable or parameter to edit the version number on the fly in the future. Otherwise, have at it. Help me improve this thing.

I seem to be running this script on machines that need an update and it does not successfully update the machines:
Here is the output from one:
[STEP 1 of 5]
Executing Policy Self Service: Install macOS Updates (M1)
[STEP 2 of 5]
Running script MacOS updater script (M1)...
Script exit code: 0
Script result: % Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
100 392 0 392 0 0 2230 0 --:--:-- --:--:-- --:--:-- 2333
eyJhbGciOiJIUzI1NiJ9.eyJhdXRoZW50aWNhdGVkLWFwcCI6IkdFTkVSSUMiLCJhdXRoZW50aWNhdGlvbi10eXBlIjoiSlNTIiwiZ3JvdXBzIjpbXSwic3ViamVjdC10eXBlIjoiSlNTX1VTRVJfSUQiLCJ0b2tlbi11dWlkIjoiNTBkYmEwNmYtZjdiOC00YzE3LWFkYjUtZjg2MjM5MmVhMDYwIiwibGRhcC1zZXJ2ZXItaWQiOi0xLCJzdWIiOiIxOSIsImV4cCI6MTY1MzY3MTAxNX0.RqAJD2DksSq7N2Du9-taFtvtD9wTBVdrZpXOd4fbIcY
388
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
100 394 0 224 100 170 1399 1062 --:--:-- --:--:-- --:--:-- 2662
{
"responses" : [ {
"id" : "c93d3095-125d-42a3-a58c-94f54aef980e",
"href" : "https://mycompany.jamfcloud.com/mycompany/api/v1/mdm/commands?uuids=c93d3095-125d-42a3-a58c-94f54aef980e"
} ],
"errors" : [ ]
} % Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
100 391 0 391 0 0 3715 0 --:--:-- --:--:-- --:--:-- 4296
{
"token" : "eyJhbGciOiJIUzI1NiJ9.eyJhdXRoZW50aWNhdGVkLWFwcCI6IkdFTkVSSUMiLCJhdXRoZW50aWNhdGlvbi10eXBlIjoiSlNTIiwiZ3JvdXBzIjpbXSwic3ViamVjdC10eXBlIjoiSlNTX1VTRVJfSUQiLCJ0b2tlbi11dWlkIjoiNTBkYmEwNmYtZjdiOC00YzE3LWFkYjUtZjg2MjM5MmVhMDYwIiwibGRhcC1zZXJ2ZXItaWQiOi0xLCJzdWIiOiIxOSIsImV4cCI6MTY1MzY3MTAxNn0.nDEK6XdTEm5BtXYIse0o53Oob3lo8SPT9_N5p0lu7Fo",
"expires" : "2022-05-27T17:03:36.42Z"
Hey @bwoods would you be able to share your jamfhelper script you wrote?
@MPL , Below is the first part of my jamf helper process. It is a launch daemon that runs a custom policy. The deferral interval can be changed to what ever you want with the built in parameter.
#!/bin/bash
deferInterval="$4"
createLaunchDaemon(){
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>com.os.update</string>
<key>UserName</key>
<string>root</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/jamf/bin/jamf</string>
<string>policy</string>
<string>-event</string>
<string>updateOS</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>StartInterval</key>
<integer>${deferInterval}</integer>
</dict>
</plist>" > /tmp/osUpdate.plist
sudo chown root:wheel /tmp/osUpdate.plist
sudo chmod 755 /tmp/osUpdate.plist
sudo launchctl load /tmp/osUpdate.plist
}
createLaunchDaemon
Hey @bwoods would you be able to share your jamfhelper script you wrote?
@MPL Below is the second part of my jamf helper process. It's basically a set of dialogs that guides the user through the updates. logs are created to track how many times the user has deferred the prompt. Simply create a custom policy with the trigger "updateOS". The launch daemon in part A will run this at the desired time interval.
#!/bin/bash
# Server connection information
URL="https://url.jamfcloud.com"
username="$4"
password="$5"
# Operatitng system information
osVersion="$6"
osName="$7"
osFullName="$7 "${osVersion}""
serialNumber=$(system_profiler SPHardwareDataType | awk '/Serial Number/{print $4}')
# Log information
currentUser=$( scutil <<< "show State:/Users/ConsoleUser" | awk '/Name
&& ! /loginwindow/ { print $3 }' )
logFolder="/Users/"${currentUser}"/logs/"
deferlog="${logFolder}/deferlog.txt"
lastDeferralCount=$(cat "${deferlog}")
# Jamf helper information
jamfHelper="/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper"
description1="${osFullName} is now available. Select 'Start Now' to initialize the update. Select 'Defer' to complete the update at another time.
Once the update is initiated, it may take 25-60 minutes for the computer to restart.
Remaining Deferral Attempts: 3"
description2="${osFullName} is now available. Select 'Start Now' to initialize the update. Select 'Defer' to complete the update at another time.
Once the update is initiated, it may take 25-60 minutes for the computer to restart.
Remaining Deferral Attempts: 2"
description3="${osFullName} is now available. Select 'Start Now' to initialize the software update. Select 'Defer' to initialize the update at another time.
Once the update is initiated, it may take 25-60 minutes for the computer to restart.
Remaining Deferral Attempts: 1"
finalDescription="The maximum deferral limit has been reached. Select 'Start Now' to initialize the ${osFullName} update. Otherwise, use the remaining time to sync any unsaved changes to OneDrive.
Remaining Deferral Attempts: 0"
waitDescription="Processing software update. Please wait for the computer to restart. Ensure that the power adapter is connected. This process may take awhile."
timeout="$8"
########Functions###########
initializeSoftwareUpdate(){
# create base64-encoded credentials
encodedCredentials=$( printf "${username}:${password}" | /usr/bin/iconv -t ISO-8859-1 | /usr/bin/base64 -i - )
# Generate new auth token
authToken=$( curl -X POST "${URL}/api/v1/auth/token" -H "accept: application/json" -H "Authorization: Basic ${encodedCredentials}" )
# parse authToken for token, omit expiration
token=$(/usr/bin/awk -F \\" 'NR==2{print $4}' <<< "$authToken" | /usr/bin/xargs)
echo ${token}
# Determine Jamf Pro device id
deviceID=$(curl -s -H "Accept: text/xml" -H "Authorization: Bearer ${token}" ${URL}/JSSResource/computers/serialnumber/"$serialNumber" | xmllint --xpath '/computer/general/id/text()' -)
echo ${deviceID}
# Execute software update
curl -X POST "${URL}/api/v1/macos-managed-software-updates/send-updates" -H "accept: application/json" -H "Authorization: Bearer ${token}" -H "Content-Type: application/json" -d "{\\"deviceIds\\":[\\"${deviceID}\\"],\\"maxDeferrals\\":0,\\"version\\":\\"${osVersion}\\",\\"skipVersionVerification\\":true,\\"applyMajorUpdate\\":true,\\"updateAction\\":\\"DOWNLOAD_AND_INSTALL\\",\\"forceRestart\\":true}"
# Invalidate existing token and generate new token
curl -X POST "${URL}/api/v1/auth/keep-alive" -H "accept: application/json" -H "Authorization: Bearer ${token}"
}
pleaseWait(){
buttonClicked=$("$jamfHelper" -windowType hud -lockHUD -windowPosition "lr" -title "Software Update" -description "$waitDescription" -alignDescription "Left")
}
firstPrompt(){
# First Prompt
buttonClicked=$("$jamfHelper" -windowType hud -lockHUD -windowPosition "lr" -title "Software Update" -description "${description1}" -alignDescription "Left" -button1 "Start Now" -button2 "Defer")
echo $buttonClicked
if [[ $buttonClicked == 0 ]]; then
echo "User clicked Start Now"
echo "Initializing Software Update"
rm -rf "${logFolder}"
launchctl unload /private/tmp/osUpdate.plist
rm -rf /private/tmp/osUpdate.plist
initializeSoftwareUpdate
pleaseWait
elif [[ $buttonClicked == 2 ]]; then
# Declare what the user clicked
echo "User Clicked Defer"
# Set deferralCount to 0
deferralCount="1"
#Append current number of deferrals to deferlog.txt
echo $deferralCount > $deferlog
exit 0
fi
}
secondPrompt(){
# Second Prompt
buttonClicked=$("$jamfHelper" -windowType hud -lockHUD -windowPosition "lr" -title "Software Update" -description "${description2}" -alignDescription "Left" -icon -button1 "Start Now" -button2 "Defer")
echo $buttonClicked
if [[ $buttonClicked == 0 ]]; then
echo "User clicked Start Now"
echo "Initializing Software Update"
rm -rf "${logFolder}"
launchctl unload /private/tmp/osUpdate.plist
rm -rf /private/tmp/osUpdate.plist
initializeSoftwareUpdate
pleaseWait
elif [[ $buttonClicked == 2 ]]; then
echo "User Clicked Defer"
# Set deferralCount to 0
deferralCount="2"
#Append current number of deferrals to deferlog.txt
echo $deferralCount > $deferlog
exit 0
fi
}
thirdPrompt(){
# Third Prompt
buttonClicked=$("$jamfHelper" -windowType hud -lockHUD -windowPosition "lr" -title "Software Update" -description "${description3}" -alignDescription "Left" -button1 "Start Now" -button2 "Defer")
echo $buttonClicked
if [[ $buttonClicked == 0 ]]; then
echo "User clicked Start Now"
echo "Initializing Software Update"
rm -rf "${logFolder}"
launchctl unload /private/tmp/osUpdate.plist
rm -rf /private/tmp/osUpdate.plist
initializeSoftwareUpdate
pleaseWait
elif [[ $buttonClicked == 2 ]]; then
echo "User Clicked Defer"
# Set deferralCount to 0
deferralCount="3"
#Append current number of deferrals to deferlog.txt
echo $deferralCount > $deferlog
exit 0
fi
}
finalPrompt(){
buttonClicked=$("$jamfHelper" -windowType hud -lockHUD -windowPosition "lr" -title "Software Update" -description "$finalDescription" -alignDescription "Left" -button1 "Start Now" -timeout $timeout -countdown -alignCountdown "Center")
if [[ $buttonClicked == 0 ]]; then
echo "User clicked Start Now"
echo "Initializing Software Update"
rm -rf "${logFolder}"
launchctl unload /private/tmp/osUpdate.plist
rm -rf /private/tmp/osUpdate.plist
initializeSoftwareUpdate
pleaseWait
fi
}
# First Prompt
if [[ ! -f $deferlog ]]; then
echo "Initializing deferral process"
# Create a logs folder
mkdir $logFolder
# Create a deferlog.txt to track deferral count
touch "$deferlog"
# Set deferralCount to 0
deferralCount="1"
#Append current number of deferrals to deferlog.txt
echo $deferralCount > $deferlog
# Initialize First Prompt
firstPrompt
# Second Prompt
elif [[ ${lastDeferralCount} = "1" ]]; then
echo "Initializing Second Prompt"
secondPrompt
# Third Prompt
elif [[ ${lastDeferralCount} = "2" ]]; then
echo "Initializing Third Prompt"
thirdPrompt
elif [[ ${lastDeferralCount} = "3" ]]; then
echo "Initializing Final Prompt"
finalPrompt
fi
exit 0 ## Success
exit 1 ## Failure
Hey @bwoods would you be able to share your jamfhelper script you wrote?
@MPL , Below is a screenshot of my parameters for the second part of my jamf helper process. You'll want to increase the timeout for production. I use 30 seconds for testing.

@MPL , Below is a screenshot of my parameters for the second part of my jamf helper process. You'll want to increase the timeout for production. I use 30 seconds for testing.

@bwoods Thank you for all this! So both of these are attached to different policies?
Also, does this work with Intel devices too?
@bwoods Thank you for all this! So both of these are attached to different policies?
Also, does this work with Intel devices too?
You'll need two policies for this to work. Part A needs a policy with recurring check-in trigger to place the daemon. Part B needs a policy with a custom trigger. This also works on intel.
@bwoods I was thinking along the same lines and have put a Jamf helper message like yours into the workflow. I think it will be something that we need to communicate to users as the policy we use now (using user interaction) warns them that they have 5mins to restart but the 'download and install' option doesn't appear to give us this flexibility. It would be good if Jamf or Apple give us some more user friendly ways to do this in the future.
Great work on the script!
@awginger Apple included the max deferral limit, but it doesn't force the reboot after the limit is reached. They just need to do that last thing to make all of our lives easier.
You'll need two policies for this to work. Part A needs a policy with recurring check-in trigger to place the daemon. Part B needs a policy with a custom trigger. This also works on intel.
Thanks! I split it up into two policies a bit ago exactly the way you said above.
It's been about an hour since the script ran and the software update began "processing" and it hasn't moved since then. It did prompt me for a restart (I exited the window) but no forced restart as of yet.
I'll wait a bit longer to see if anything happens.
In addition, do you know if this script works fine with Intel devices? Or only with M1's?
Thanks! I split it up into two policies a bit ago exactly the way you said above.
It's been about an hour since the script ran and the software update began "processing" and it hasn't moved since then. It did prompt me for a restart (I exited the window) but no forced restart as of yet.
I'll wait a bit longer to see if anything happens.
In addition, do you know if this script works fine with Intel devices? Or only with M1's?
Yeah, It can take a long time. It's basically all up to APNS, but you can check the computer record to see if the management action is pending. Search your computer>Management Tab>You should see something like scheduleOSUpdate or availableOSUdate. It works on Intel machines too.
Yeah, It can take a long time. It's basically all up to APNS, but you can check the computer record to see if the management action is pending. Search your computer>Management Tab>You should see something like scheduleOSUpdate or availableOSUdate. It works on Intel machines too.
Just checked the computer>Management Tab and didn't see any pending/failed commands :(
Just checked the computer>Management Tab and didn't see any pending/failed commands :(
It'll only show as Pending if the command hasn't been sent yet. Since you're running this on the machine, it likely won't have Jamf's typically 10 minute hold before the command is sent. You should check computer > History > Management History instead.
Also note that if you haven't already run the "Download and Notify" MDM command it will likely be in the process of downloading the updates before it reboots for installing them.
It'll only show as Pending if the command hasn't been sent yet. Since you're running this on the machine, it likely won't have Jamf's typically 10 minute hold before the command is sent. You should check computer > History > Management History instead.
Also note that if you haven't already run the "Download and Notify" MDM command it will likely be in the process of downloading the updates before it reboots for installing them.
Gotcha. I checked the Computer > History > Management History area and do see both "ScheduleOSUpdate" and "AvailableOSUpdates" completed about an hour and a half ago. The "Software Update" window on the machine itself is still present saying it's processing the software update.
I believe this machine already had the updates downloaded to it and just needed to be restarted to complete everything prior to this policy being implemented/running.
Gotcha. I checked the Computer > History > Management History area and do see both "ScheduleOSUpdate" and "AvailableOSUpdates" completed about an hour and a half ago. The "Software Update" window on the machine itself is still present saying it's processing the software update.
I believe this machine already had the updates downloaded to it and just needed to be restarted to complete everything prior to this policy being implemented/running.
I don't believe it takes updates that have already been downloaded into account. It just downloads them again and install from there...but yeah it can take quite a while.
@bwoods I see that this script specifically calls out a macOS version to update to. Does this script also install non-macOS point release updates (ex: Command Line Tools, Xcode etc...)?
@bwoods I see that this script specifically calls out a macOS version to update to. Does this script also install non-macOS point release updates (ex: Command Line Tools, Xcode etc...)?
@stutz , I haven't tested that yet.
Yeah, It can take a long time. It's basically all up to APNS, but you can check the computer record to see if the management action is pending. Search your computer>Management Tab>You should see something like scheduleOSUpdate or availableOSUdate. It works on Intel machines too.
Update - So I let the system sit overnight and it never actually restarted by itself.
In the top left it says, "Restarting Your Computer. Your computer needs to restart to install updates." and gives me a button to click to restart but it never forcefully restarted.
- Any idea why that might be?
Another thing, when I tested the deferral by clicking "Defer" the update window popped up immediately after saying I have 2 deferrals left. I tried to setup deferrals in the policy itself in JAMF but they didn't seem to sync together. Do you know why this is?
- Edit: Might be because I have the policy set to "Ongoing" hehe. My mistake!


Update - So I let the system sit overnight and it never actually restarted by itself.
In the top left it says, "Restarting Your Computer. Your computer needs to restart to install updates." and gives me a button to click to restart but it never forcefully restarted.
- Any idea why that might be?
Another thing, when I tested the deferral by clicking "Defer" the update window popped up immediately after saying I have 2 deferrals left. I tried to setup deferrals in the policy itself in JAMF but they didn't seem to sync together. Do you know why this is?
- Edit: Might be because I have the policy set to "Ongoing" hehe. My mistake!


A tad confused on how to setup the deferrals actually.
Do i need to setup deferrals in the actual policy itself too? If not, should the policy be set to ongoing or? Or just select "Defer" on the window that pops up from the script and it'll take care of the rest?
Apologies - not too familiar with scripting and whatnot.
A tad confused on how to setup the deferrals actually.
Do i need to setup deferrals in the actual policy itself too? If not, should the policy be set to ongoing or? Or just select "Defer" on the window that pops up from the script and it'll take care of the rest?
Apologies - not too familiar with scripting and whatnot.
I've hardcoded the script for 3 deferrals after user interaction. You can change the deferInterval in Part A with parameter $4. You can change the time out in part B with parameter $8. You can't edit much else, this is specifically what my org requested. You'd have to tinker with the script in Part B to change the description etc...
Below I have the daemon calling the script every 30 seconds. You most likely need to extend this interval.

I've hardcoded the script for 3 deferrals after user interaction. You can change the deferInterval in Part A with parameter $4. You can change the time out in part B with parameter $8. You can't edit much else, this is specifically what my org requested. You'd have to tinker with the script in Part B to change the description etc...
Below I have the daemon calling the script every 30 seconds. You most likely need to extend this interval.

Thanks for all your help and your amazing script!
We got it working on our end for now!