05-16-2022 12:34 PM - edited 06-23-2022 08:47 AM
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
Posted on 05-16-2022 12:53 PM
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.
05-27-2022 09:43 AM - edited 05-27-2022 09:44 AM
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"
Posted on 07-30-2022 03:08 PM
I ran the above script on one of our M1's and it says completed with the below details. The user's MacBook was locked.
Script result: Serial number: XYVH2K0FVH
% 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 905 0 --:--:-- --:--:-- --:--:-- 922 Auth Token: { "token" : "eyJhbGciOiJIUzI1NiJ9.eyJhdXRoZW50aWNhdGVkLWFwcCI6IkdFTkVSSUMiLCJhdXRoZW50aWNhdGlvbi10eXBlIjoiSlNTIiwiZ3JvdXBzIjpbXSwic3ViamVjdC10eXBlIjoiSlNTX1VTRVJfSUQiLCJ0b2tlbi11dWlkIjoiNmQwNDQwMmQtZjgyOC00Mzk2LWEwNjItNjRkOWQ2NjU4ZTJmIiwibGRhcC1zZXJ2ZXItaWQiOi0xLCJzdWIiOiIxIiwiZXhwIjoxNjU5MjIwMTkxfQ.IMGqlh89pAIIGNOI0Fnn4bKKLkZwZb__G6b4p1nH6Z0", "expires" : "2022-07-30T22:29:51.103Z" } Token: eyJhbGciOiJIUzI1NiJ9.eyJhdXRoZW50aWNhdGVkLWFwcCI6IkdFTkVSSUMiLCJhdXRoZW50aWNhdGlvbi10eXBlIjoiSlNTIiwiZ3JvdXBzIjpbXSwic3ViamVjdC10eXBlIjoiSlNTX1VTRVJfSUQiLCJ0b2tlbi11dWlkIjoiNmQwNDQwMmQtZjgyOC00Mzk2LWEwNjItNjRkOWQ2NjU4ZTJmIiwibGRhcC1zZXJ2ZXItaWQiOi0xLCJzdWIiOiIxIiwiZXhwIjoxNjU5MjIwMTkxfQ.IMGqlh89pAIIGNOI0Fnn4bKKLkZwZb__G6b4p1nH6Z0 Device ID: 107 % 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 224 100 168 539 404 --:--:-- --:--:-- --:--:-- 982 { "responses" : [ { "id" : "e84835e9-7ee3-4884-9c01-2a032512cbfb", "href" : "https://lplfinancial.jamfcloud.com/lplfinancial/api/v1/mdm/commands?uuids=e84835e9-7ee3-4884-9c01-2a..." } ], "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 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 100 391 0 391 0 0 854 0 --:--:-- --:--:-- --:--:-- 884 { "token" : "eyJhbGciOiJIUzI1NiJ9.eyJhdXRoZW50aWNhdGVkLWFwcCI6IkdFTkVSSUMiLCJhdXRoZW50aWNhdGlvbi10eXBlIjoiSlNTIiwiZ3JvdXBzIjpbXSwic3ViamVjdC10eXBlIjoiSlNTX1VTRVJfSUQiLCJ0b2tlbi11dWlkIjoiNmQwNDQwMmQtZjgyOC00Mzk2LWEwNjItNjRkOWQ2NjU4ZTJmIiwibGRhcC1zZXJ2ZXItaWQiOi0xLCJzdWIiOiIxIiwiZXhwIjoxNjU5MjIwMTkzfQ.cpkwPxLRneGh3uI9THNzys7rl8cHjFnEEmu8sTevuoA", "expires" : "2022-07-30T22:29:53.276Z" }
Posted on 05-16-2022 12:57 PM
I will def be trying this out as soon as possible. Awesome work @bwoods !!
05-16-2022 01:02 PM - edited 05-16-2022 01:51 PM
Thanks @nwagner! Let me know how your testing goes.
07-10-2022 07:53 PM - edited 07-10-2022 08:05 PM
Hi @bwoods. Firstly, thank you for your work on this. I tested your script with a Jamf admin account in the Part B policy and seems to work really well! My question, do you know what privileges are needed for a non administrator Jamf account specifically to able to run this task?
Cheers!
Posted on 07-11-2022 03:36 AM
@moussabl Here are the JAMF account permissions I use for this script and it works for me.
Account
Access Level - Full Access
Privilege Set - Custom
Privileges (everything is unchecked except for the following):
JAMF Pro Server Objects - Computers - Create+Read
JAMF Pro Server Actions - Send Computer Remote Command to Download and Install macOS Update - Checked
Posted on 07-24-2022 07:49 PM
Awesome, Thank you @stutz
Posted on 05-16-2022 01:22 PM
@bwoods Thanks for posting the example. Since Jamf discourages calling the API from an arbitrary endpoint, and recommends it's better used from an administrator's system you might consider creating a script that uses the API to get the members of a Static or Smart Group as the target machines for an update, and then makes the send-updates endpoint API call to force the update for those machines.
Posted on 05-16-2022 01:28 PM
@sdagley , good to know. I thought that wasn't an issue with the new bearer token feature. That's definitely something worth looking into. Thanks again.
Posted on 05-16-2022 02:20 PM
Bearer token auth is an improvement if you're making repeated API calls, but it does still require some exposure of the credentials used to request the token.
Posted on 05-16-2022 03:06 PM
Your APIUser, what privileges does the account require?
Posted on 05-17-2022 06:28 AM
Posted on 05-18-2022 01:32 PM
This function is going to be so helpful.
Out of curiosity, are you checking before running to see if the update is already installed? I know Jamf inventory is not always the most reliable. In the past we've checked by running softwareupdate -l and checking for the phrase "found the following new or updated software" in the results. I think it still works on 11+, but with how Apple is treating that command I should probably try to get the MDM command version of that same idea up and running.
Posted on 05-18-2022 07:40 PM
@McAwesome I downgrade my test machine OS before I run this. This currently works reliably to update M1's running any version of Monterey, but I'm currently working on getting full OS Upgrades working. For instance, upgrading from Big Sur to Monterey.
Posted on 05-18-2022 07:46 PM
Right what I'm meaning is a way to make sure the function needs to be called at all. This kind of a function is likely to be used after some kind of pop up warning given to the end user. No sense showing them a countdown if they're already up to date.
Posted on 05-18-2022 09:04 PM
Ah okay, I see what you're saying. Jamf Pro doesn't always show the correct OS version, so I need to check that the reported version is actually the version on the client.
Posted on 05-20-2022 10:14 PM
These couple of functions may help you add some safety checks in.
batteryCheck(){
powerType=$(pmset -g batt | head -n 1 | cut -c19- | rev | cut -c 2- | rev)
if [[ "$powerType" == "Battery Power" ]]; then
echo "Machine is on Battery Power."
bat=$(pmset -g batt | grep 'InternalBattery' | awk '{print $3}' | tr -d '%'';')
if (( $bat > 60 )); then
echo "Battery Level OK, Continuing Update..."
else
echo "Battery Level Insufficient for this update"
return 1
fi
else
echo "Machine is on AC Power."
fi
}
bootstrapTokenCheck(){
bootstrap=$(profiles status -type bootstraptoken)
if [[ $bootstrap == *"escrowed to server: YES"* ]]; then
echo "Bootstrap escrowed"
else
echo "Bootstrap not escrowed. Cannot update with MDM commands."
return 1
fi
}
freeSpaceRequirement(){
freespace=$(df -h -m | grep -m 1 /System/Volumes/Data | awk '{print $4}')
if [[ $freespace -lt 15000 ]]; then
echo "Insufficient free space"
return 1
else
echo "Machine has at least 15 GBs of free space"
fi
}
verifyUpdateNeeded(){
# Check for available updates
availableUpdates=`/usr/libexec/mdmclient AvailableOSUpdates`
# Updates available
if [[ "$availableUpdates" == *"=== OS Update Item ==="* ]]; then
echo "Updates Available"
# Updates not available
else
echo "No Updates Available"
return 1
fi
}
# Verifying update is needed and requirements have been met.
verifyUpdateNeeded || exit 0
bootstrapTokenCheck || exit 0
freeSpaceRequirement || exit 0
batteryCheck || exit 0
Also if I'm not mistaken you can streamline your update command so you don't have to fiddle with it as much next time around. If you don't specify a specific version, it will download the newest one based on device eligibility.
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}\"],\"skipVersionVerification\":false,\"applyMajorUpdate\":false,\"updateAction\":\"DOWNLOAD_AND_INSTALL\",\"forceRestart\":true}"
Posted on 05-23-2022 06:19 AM
Nice work. Thank you for helping me improve this.
Posted on 09-02-2022 05:46 AM
can the bootstrap token check be skipped if not using mobile accounts? or is it a requirement that would stop it working?
Posted on 09-02-2022 06:47 AM
@tkimpton The account type doesn't matter, a bootstrap token is required for any MDM triggered updates
Posted on 05-21-2022 07:58 AM
Thanks again
Testing also seeing how the schema\parameters of the post on the mdm command can mirror this check box "Include major updates, if available"
Posted on 05-23-2022 06:18 AM
@Gary_R , I found that full OS Upgrades can be completed with the configuration you posted above, but it doesn't work if you specify a version. I've only been able to do this with the Jamf Pro GUI though. Can't seem to get it working with the API.
05-23-2022 02:37 PM - edited 05-23-2022 02:37 PM
ok I'll keep poking around.
Posted on 05-18-2022 03:30 PM
Hmm, I can't seem to get this working. Here is my output:
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 395 0 395 0 0 578 0 --:--:-- --:--:-- --:--:-- 583
eyJhbGciOiJIUzI1NiJ9.eyJhdXRoZW50aWNhdGVkLWFwcCI6IkdFTkVSSUMiLCJhdXRoZW50aWNhdGlvbi10eXBlIjoiSlNTIiwiZ3JvdXBzIjpbXSwic3ViamVjdC10eXBlIjoiSlNTX1VTRVJfSUQiLCJ0b2tlbi11dWlkIjoiZWNkNzkyMTctMDYzMy00YjcxLWFmMjItOGE5YzViYTljZjhkIiwibGRhcC1zZXJ2ZXItaWQiOi0xLCJzdWIiOiI2NSIsImV4cCI6MTY1MjkxNDE4MH0.etverG0GbjaH6XAgPtdYYtVLlIJtYdBOg-dOds4uh_0
-:1: parser error : StartTag: invalid element name
<!doctype html><html lang="en"><head><title>HTTP Status 404 – Not Found</title
^
-:1: parser error : Extra content at the end of the document
<!doctype html><html lang="en"><head><title>HTTP Status 404 – Not Found</title
^
% 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 372 0 206 100 166 415 334 --:--:-- --:--:-- --:--:-- 767
{
"httpStatus" : 400,
"errors" : [ {
"code" : "INVALID_ID",
"description" : "id field must be string of positive numeric value or -1",
"id" : "",
"field" : "deviceIds[0]"
} ]
} % Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
100 395 0 395 0 0 782 0 --:--:-- --:--:-- --:--:-- 796
{
"token" : "eyJhbGciOiJIUzI1NiJ9.eyJhdXRoZW50aWNhdGVkLWFwcCI6IkdFTkVSSUMiLCJhdXRoZW50aWNhdGlvbi10eXBlIjoiSlNTIiwiZ3JvdXBzIjpbXSwic3ViamVjdC10eXBlIjoiSlNTX1VTRVJfSUQiLCJ0b2tlbi11dWlkIjoiZWNkNzkyMTctMDYzMy00YjcxLWFmMjItOGE5YzViYTljZjhkIiwibGRhcC1zZXJ2ZXItaWQiOi0xLCJzdWIiOiI2NSIsImV4cCI6MTY1MjkxNDE4Mn0.FQmrKKDv02zotLXD-T2l7nefutUZPvNvLvYsfYLwUW4",
"expires" : "2022-05-18T22:49:42.198Z"
}
05-18-2022 07:25 PM - edited 05-18-2022 07:51 PM
@markopolo it looks like your device id isn't being generated for some reason. Run my function below to test recovering the device id. Refer to the highlighted sections as they determine how the device id is generated.
It looks like you are generating a token properly though.
#!/bin/bash
# Server connection information
URL=""
username=""
password=""
# 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)
# 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 "My Device ID is ${deviceID}"
}
initializeSoftwareUpdate
Posted on 05-19-2022 04:05 PM
Thanks, I wish I was better at this :). I do appreciate the help...
So if I reduce the script to this:
#!/bin/bash
# Server connection information
URL="xxxxxxxxxxx"
username="xxxxxxxxxxx"
password="xxxxxxxxxxx"
# Determine Serial Number
serialNumber=$(system_profiler SPHardwareDataType | awk '/Serial Number/{print $4}')
echo ${serialNumber}
## Generate new auth token
token=$(curl -X POST -u "${username}:${password}" -s ${URL}/api/v1/auth/token | plutil -extract token raw –)
# 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()' -)
This is what I get:
D0Q9P63VFM
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 45 100 45 0 0 94 0 --:--:-- --:--:-- --:--:-- 97
-:10: parser error : Opening and ending tag mismatch: br line 8 and p
</p>
^
-:11: parser error : Opening and ending tag mismatch: p line 8 and body
</body>
^
-:12: parser error : Opening and ending tag mismatch: body line 5 and html
</html>
^
-:12: parser error : Premature end of data in tag html line 1
</html>
^
And if I use a "curl -s -X" instead of a "curl -s -H" I get this:
D0Q9P63VFM
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 45 100 45 0 0 94 0 --:--:-- --:--:-- --:--:-- 96
-:1: parser error : StartTag: invalid element name
<!doctype html><html lang="en"><head><title>HTTP Status 400 – Bad Request</**bleep**
^
-:1: parser error : Extra content at the end of the document
<!doctype html><html lang="en"><head><title>HTTP Status 400 – Bad Request</**bleep**
^
Not sure what I'm doing wrong. Could there be something screwy with the API on my on-prem JSS?
05-20-2022 07:20 AM - edited 05-20-2022 07:36 AM
@markopolo , you need the variables below to properly generate a bearer token. Otherwise your API call will fail. First practice generating a token, then try to get the device id. I suggest running this in a code editor like "CoderRunner" first.
Basically, there is a difference between the "authToken" and "token" variables. The "authToken" must be generated first, but contains unnecessary information like the expiration date. The "token" variable gets rid of the unnecessary information and can be used to properly make the API call.
I would also test running my original code above with your api account, then testing with your admin account. If you get different results, it's a permissions issue.
# 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)
Posted on 05-20-2022 12:36 PM
Thanks, I actually got it working last night (tried so many things I don't remember what fixed it). Here is the final script I'm using:
#!/bin/bash
# Server connection information
URL="xxxxxxxx"
username="xxxxxxxx"
password="xxxxxxxx"
# Determine Serial Number
serialNumber=$(system_profiler SPHardwareDataType | awk '/Serial Number/{print $4}')
echo "Serial number: ${serialNumber}"
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: ${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 "Device ID: ${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.4\",\"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
Thanks for your help, I really appreciate it!
Posted on 08-01-2022 07:06 AM
For this to work, does the user have to be logged on. I tested it on one our users, but the device was locked and it never updated.
05-18-2022 08:06 PM - edited 05-18-2022 08:06 PM
This is really solid. Thanks for your work on this.
As an FYI unless you want to maneuver around password complexity I experienced issues the % symbols in the API account and passing that from the script. I created a new account without it.
Posted on 05-18-2022 08:08 PM
Thank you. You can use single quotes to get around the password issue as well.
Posted on 05-25-2022 06:59 AM
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.
05-25-2022 08:41 AM - edited 05-25-2022 08:46 AM
@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.
Posted on 05-26-2022 03:02 AM
@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!
Posted on 05-31-2022 10:41 AM
@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.
05-26-2022 09:07 AM - edited 05-26-2022 09:08 AM
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?
Posted on 05-26-2022 09:28 AM
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.
Posted on 05-27-2022 09:05 AM
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.