Force a Computer Restart to Install macOS Updates

bwoods
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.

bwoods_0-1652729152198.png

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

 

114 REPLIES 114

rblaas
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. 

 

bwoods
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.

 

wakco
Contributor III

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.

bwoods
Valued Contributor

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

wakco
Contributor III

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.

 

#!/bin/sh
# Create script
SCRIPTFILE="/Library/Scripts/InventoryScript"
MYSCRIPT="#!/bin/sh
# 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'
fi
"
echo "$MYSCRIPT" > "$SCRIPTFILE"
# 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 "$SCRIPTFILE"
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. 

Replacing:

 

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

 

With:

 

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

bwoods
Valued Contributor

Fancy stuff man. I like it.

claw
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

SMR1
Contributor III

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?

bwoods
Valued Contributor

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

SMR1
Contributor III

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

bwoods
Valued Contributor

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

SMR1
Contributor III

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

bwoods
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?

SMR1
Contributor III

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

SMR1
Contributor III

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?

SMR1
Contributor III

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.

bwoods
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

SMR1
Contributor III

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.

bwoods
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.

SMR1
Contributor III

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

bwoods
Valued Contributor

Yes sir.

moussabl
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

bwoods
Valued Contributor

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

/Library/LaunchDaemons

 

bwoods
Valued Contributor

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

bwoods_0-1662667295896.png

 

tkimpton
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

SMR1
Contributor III

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?

MatG
Contributor III

I've got good results with this, better than Mass MDM Commands.

MatG
Contributor III

@bwoods Really nice work, does this work for Ventura?

Is the syntax where you specify the version as below for 13.1 13.1.0 or 13.1

"version\":\"12.4\"


 

bwoods
Valued Contributor

@MatG I haven't tested this in quite a while. I switched to using Nudge. Some people are telling me that it's not working for Ventura.

Maik
New Contributor II

 Hey  

Did you have try with an Updates Script with the new Priorty MDM Key for forcing Updates?

priority
string

Priority can only be configured on macOS 12.3 and above, for minor updates only. Any version below 12.3 is always Low and cannot be changed until prerequisites are met. When qualified, if not explicitly set, priority will default to High

HIGHLOW

 

kcadm
New Contributor II

Warning: Deprecated

Well made. But unfortunately the function is no longer usable. :(

Screenshot 2024-01-09 at 16.23.32.png

@bwoods do you have an alternate to above?

thanks @kcadm I was able to still use for macOS13. not sure if this doesnt work within macOS14

tkimpton
Valued Contributor II

look to do Declarative Software updates via jamf

tegus232
Contributor

We have that enabled in Sandbox and have seen issues since the Software Update is in Beta

tkimpton
Valued Contributor II

yeah there is an Apple bug. make sure devices are on 14.2 or later