Force a Computer Restart to Install macOS Updates

Valued Contributor

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. 



# Server connection information

# Determine Serial Number
serialNumber=$(system_profiler SPHardwareDataType | awk '/Serial Number/{print $4}')

	# 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}"



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


105 REPLIES 105

Contributor II

You misunderstood me :) 

I am using your script but other then a warning (which will stay in view for 30-60 minutes) there is no warning when the restart actually takes place. So it is still a restart without a short notice warning. 


Valued Contributor

Yep, nothing you can do about that. That's the main downfall of MDM commands in my opinion. Word is, Apple is release more features to make the update process better though. So stay hopeful.



I'm finding most of these scripts are performing a serial number search to find the computer id, which will be the bit that taxes the server, I haven't found where it is stored yet normally, but I have noticed that 'jamf recon' outputs the computer id, so I realised if I capture that and store it, we can skip the S/N search, grab the stored computer id, and get right on with sending the push command.

Valued Contributor

@wakco , when you have a moment, please post your process. Any help improving the script is much appreciated. Thanks!

Step 1: Grab the computer_id from a recon.

Like most of us we need to have Jamf getting inventory updates from computers. We have a large number of laptops, which causes a common issue of network errors being reported if we use the standard basic inventory update policy, as users on laptops often disconnect their laptop from the network in the middle of the inventory update. I resolved this by using the hidden command jamf scheduledTask to create a launchd plist in /Library/LaunchDaemons that would trigger the jamf recon similar to a check-in, so the logs of network errors end up in the computers system log instead of causing jamf to log failed inventory update attempts, when a laptop happens to be disconnected during an inventory update. I realised I could change this to a script that could capture the jamf recon output and store it somewhere i.e.


# Create script
# This is a simple script to perform an inventory update, and save the computer id
RECON=\"\$(/usr/local/bin/jamf recon -randomDelaySeconds 450)\"
if [[ \"\$RECON\" = *\"computer_id\"* ]]; then
echo \"\$RECON\" | grep computer_id | grep -o '[0-9]\\+' > '/Library/Application Support/JAMF/computer_id'
# Give it execute permissions
chmod ugo+x "$SCRIPTFILE"
# Make it a scheduled task, running once a day (1440 Minutes = 24 hours)
/usr/local/bin/jamf scheduledTask -command "$SCRIPTFILE" -name recon -runAtLoad -minute '*/1440/'
# Let jamf know it is all done
ps -axj | grep jamf
ls -l /Library/LaunchDaemons/com.jamfsoftware.task.recon.plist


This would cause an inventory update once a day, and within 10 minutes of a computer starting up (450 seconds is about 7 minutes 30 seconds). and leaves the Jamf Computer ID stored in /Library/Application Support/JAMF/computer_id, as captured from the last line of jamf recon which tends to look like <computer_id>1234</computer_id> where 1234 is the Jamf Computer ID.


Step 2: Used the stored computer_id to send a push command. 



deviceID=$(curl -s -H "Accept: text/xml" -H "Authorization: Bearer ${token}" ${URL}/JSSResource/computers/serialnumber/"$serialNumber" | xmllint --xpath '/computer/general/id/text()' -)




deviceID=$(cat '/Library/Application Support/JAMF/computer_id')


And removing the Serial Number collection.


I am actually doing more than that, I'm checking the file exists, and forcing an inventory update to capture it again if it isn't there, but this provides the basic difference.


- Richard

Valued Contributor

Fancy stuff man. I like it.

New Contributor

Was playing around with this and granting the account read access to Computers, it outputs some information that's questionable. management_password, list of users, software detected, etc


Not the fastest to get ID. But it won't expose more information than what's needed.

deviceID=$(jamf recon | grep "computer_id" | xmllint --xpath '/computer_id/text()' -)
Account just has this permission: Send Computer Remote Command to Download and Install macOS Update

Contributor II

From going through this thread, are 3 policies needed to deploy the update? From what I'm reading it looks like one is needed for the update script, Part A and Part B?

Valued Contributor

Only two policies are needed. One to load the launch daemon and the other to run the script.

Contributor II

Sorry for the ignorance on this, very new to Jamf. I'm just creating part A(daemon) and then part B for the script?

Valued Contributor

Correct, part A is creating and loading a launch daemon that then runs the script in part B.

Contributor II

I must be doing something wrong, because Part A shows installed, but it's not kicking off B. 

Valued Contributor

Ensure that you've configured the parameter for part A and that the custom trigger in part B matches the trigger in part A. Are you familiar with positional parameters / custom triggers in Jamf?

Contributor II

I'm familiar with custom triggers, but not so much regarding positional parameters. I have the trigger in part B set to updateOS.

Contributor II

Also, if the user hit's start now and then locks there Mac, will the process finish or does it need to be logged on to?

Contributor II

Sorry for all the questions. Is it possible to set part B to deploy without any user interaction? Trying to be more like our SCCM side of things. We typically send out communications informing users of updates that are coming at night. If possible, it would cool to set this to deploy at night without any user interaction.

Valued Contributor

If you don't want to notify the user first, you can send a mass action command via Jamf. More info below: 

Updating macOS Using a Mass Action - Managing macOS Updates | Jamf

Contributor II

That was hit miss when we tested it.  Your script worked, so I think we'll roll with that and just have to have some user interaction.

Valued Contributor

That's very strange because my script is basically an MDM Command in API form. If you don't want any interaction you can copy my original function above and just trow it in a policy.

Contributor II

When running either the Part B or your original function, does the user have to be logged on for the update complete?

Valued Contributor

Yes sir.

New Contributor II

Hi @bwoods - I've noticed on my test Mac when rebooting the machine the osupdate.plist file disappears from tmp folder. The file is still in the tmp folder when testing with logging the user off which is great.

Would you know a solution on how we can make the file not disappear after a reboot? Thank you

Valued Contributor

You need to change the path in the Launch Daemon to:



Valued Contributor

All of the highlighted paths below will need to be changed.



Valued Contributor II

just FYI you need to change ${osFullName} to $osFullName in the variables otherwise you will get 403 Access Denied when trying to edit the script

Contributor II

I'm still having such a hard time for updates to deploy. We have a user that we deployed the script to a couple of weeks ago and under software updates, it's waiting to install, but it's prompting for a username and password and his logon info doesn't work. Is this something related to maybe the securetoken?