Hi @nelsoni
I kept getting this error.
Script result:
mismatched tag at line 10, column 2, byte 404:
<p>You can get technical details <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">here</a>.<br>
Please continue your visit at our <a href="/">home page</a>.
</p>
=^
</body>
</html>
at /System/Library/Perl/Extras/5.30/darwin-thread-multi-2level/XML/Parser.pm line 187.
<html>
<head>
<title>Status page</title>
</head>
<body style="font-family: sans-serif;">
<p style="font-size: 1.2em;font-weight: bold;margin: 1em 0px;">Unauthorized</p>
<p>The request requires user authentication</p>
<p>You can get technical details <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">here</a>.<br>
Please continue your visit at our <a href="/">home page</a>.
</p>
</body>
</html>
Any clues?
Pretty sure I was getting that when I forgot to add my API credentials.
@nelsoni
Strange, i did add them. I can try again.
@nelsoni
What permissions have you given your API user?
My API user is full admin for the purposes of testing, so as to eliminate any potential errors I would run into. Did you add the URL of your Jamf instance? that may also what causes the error. Also make sure you remove the salted passphrase portions of the script and just use a plain text username and password for testing purposes.
ill try and see what i can find ^^Thanks for the tips!
I'd be interested to see if the success rate improves now that Jamf have added the "InstallASAP" key in Jamf Pro 10.29.0.
@jtrant , No improvement.. The InstallASAP
key doesn't immediately restart the device.. And of course shutdown -r
is only restarting but the update isn't being applied....
This is my attempt to update from 11.3.1 to 11.4
Edit.. As I'm sitting here typing.. It just rebooted out of nowhere using the Install
key, and installed the update.. Lovely
I think the only way I can get this to work in a policy is by using a script with an API script similar to the script @nelsoni posted above.
Otherwrise the only option is manually pushing an 'InstallForceRestart' command using a mass action which is not scaleable. The other issue is what gets installed with this command when there are multiple updates pending. That's not documented and appears to be the luck of the draw.
Not sure if I am missing something, but when I attempt to use the "InstallASAP" key in my script, it still errors out saying it is an invalid key. I confirmed I am on 10.29.2
Anyone else experiencing this?
@nelsoni I believe you need to use 'InstallForceRestart'. 'InstallASAP' will trigger a restart countdown notification.
"InstallForceRestart" results in the same error. I have posted my script here, maybe someone can shed some light on what I am missing.
#!/bin/sh
#API login info
apiuser="Temp User"
apipass="Temp Password"
jamfProURL="https://ORG.jamfcloud.com"
#Grab serial number, OS Version of computer and CPU Type
SerialNumber=$(system_profiler SPHardwareDataType | grep 'Serial Number (system)' | awk '{print $NF}')
macOSVersion=$(sw_vers -productVersion)
arch=$( /usr/bin/arch )
#Check macOS Major
CheckIt=$(echo $macOSVersion | cut -d . -f 1)
#Set xpath option based on macOS major version
if [[ "$CheckIt" == "11" ]]
then
xpath="xpath -e"
else
xpath="xpath"
fi
function scheduleOSUpdateViaAPI() {
#Grab the computers JSS ID
jamfProCompID=$( /usr/bin/curl -s -u ${apiuser}:${apipass} ${jamfProURL}/JSSResource/computers/serialnumber/${SerialNumber}/subset/general | $xpath "/computer/general/id/text()" )
#Initiate the InstallForceRestart key (Download and install the update, and restart computers after installation), To allow for updates without user interaction on target computers with Apple silicon, Bootstrap Token for the computers must be escrowed with Jamf Pro.
/usr/bin/curl -s -X POST -H "Content-Type: text/xml" -u ${apiuser}:${apipass} ${jamfProURL}/JSSResource/computercommands/command/ScheduleOSUpdate/action/InstallForceRestart/id/${jamfProCompID}
}
if [[ "$arch" == "arm64" ]]; then
scheduleOSUpdateViaAPI
else
/usr/sbin/softwareupdate --install --all --include-config-data --restart --force
fi
exit 0
This looks good to me, I even had Jamf PS look over it during a recent engagement and they said the same. I haven't had a chance to try it out just yet, though. We only upgraded to 10.29.2 two days ago.
I tested the action keys some more and found that pretty much every key resulted in an error. This was on both an M1 and Intel Mac.
I finally just tried the GUI MDM button on the Macs management tab and that worked, but I was logged into the Mac at the time.
@nelsoni where did you see that?

The Mac has to be DEP enrolled and supervised for the MDM command to be an option.

@nelsoni
I tried the GUI, but usually get an error of like ”unsupported os” if i recall. Not sure if there are some bugs with early Big Sur versions.
I know supervision is a requirement, but I have a non-DEP M1 Mac and have the "Download and Install Updates" button available under the 'Management' tab.
I don't think this is quite the same as triggering a mass action from a search, as this workflow specifically shows the "Download and install the update, and restart computers after installation" option:

Update:
The way I'm managing this is using a custom script that was being used prior to Big Sur, which allows the users to delay softwareUpdates up to 8hours or "Install Now".. If they choose the install now button, it will kick off a policy -event that will run the API script Install
key above. The dialog states along the lines of update is installing and will reboot after install(this takes up to about 7 minutes)
If they defer, once the timelimit is up, it will again, kick off the API script. This has been successfully tested and working
@JustDeWon
Cool idea! Are you able to share that script?
@fredrik.virding Sure, I took out alot in this script to make it the bare minimum. Just adjust for your environment.. This is the delay that I run prior to the above API script..
#!/bin/sh
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# SET THE VARIABLES
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
## Path of branded icon
icon="/path/to/icon"
## Set the delayed restart .plist
DelayedRestartD="/Library/LaunchDaemons/com.company.rebootdelay.plist"
## Set the delayed restart warning .plist
WarningRestartD="/Library/LaunchDaemons/com.company.rebootwarning.plist"
## Set the delayed cleanup LaunchDaemon
CleanupDelayedD="/Library/LaunchDaemons/com.company.cleanupDelayedRestart.plist"
## Set the delayed restart Script
WarningRestartScript="/path/to/rebootwarning.sh"
## Set the delayed cleanup restart Script
CleanupRestartScript="/path/to/cleanupDelayedRestart.sh"
## Get the logged in user
currentUser=$(scutil <<< "show State:/Users/ConsoleUser" | awk -F': ' '/[[:space:]]+Name[[:space:]]:/ { if ( $2 != "loginwindow" ) { print $2 }}')
## Raw unix time in seconds of last boot up
lastBootTime=$(sysctl kern.boottime | awk -F'[ |,]' '{print $5}')
## Current time in unix seconds
currentTime=$(date +"%s")
## Calculation of difference between boot and current time ( total time up in seconds )
upTimeRaw=$((currentTime-lastBootTime))
## Calculation of uptime in minutes total ( uptime in seconds div by 60 )
upTimeMin=$((upTimeRaw/60))
## Calculation of uptime in hours total ( uptime in minutes div by 60 )
upTimeHours=$((upTimeMin/60))
## Calculation of uptime in whole days total ( uptime in hours div by 24 )
upTimeDays=$((upTimeHours/24))
## Total minutes up in whole days ( [uptime in whole days x 24] x 60 minutes )
minusMinutes=$((((upTimeDays*24))*60))
## Calculation of remaining minutes to subtract
remainingMin=$((upTimeMin-minusMinutes))
## Calculation of remaining hours to subtract
remainingHrs=$((remainingMin/60))
minusHours=$((upTimeHours-remainingHrs))
## Figure out minutes value for uptime display
total1=$((upTimeDays*24*60))
total2=$((remainingHrs*60))
minusMinutes2=$((total1+total2))
remainingMinFin=$((upTimeMin-minusMinutes2))
UptimeThreshold1alt=$((UptimeThreshold1-1))
UptimeThreshold2alt=$((UptimeThreshold2-1))
#Set the log path
logpath="/path/to/logpath/"
#Ensure logFile can be written
if [ ! -d "${logpath}" ]; then
mkdir "${logpath}"
fi
##Create the logFile
logFile="${logpath}"restartTimer.log
#Check for the company icon & if not install it
if [ ! -e "$icon" ]; then
echo $(date) "company branding icon not installed.. installing" >> $logFile
jamf policy -event customTrigger
else
echo $(date) "Company branding icon is installed" >> $logFile
fi
## Determine if computer is sitting at login screen and no one is logged in. If so, run software update.
if [[ "$currentUser" == "root" ]]; then
echo $(date) "No one is currently logged in, rebooting seat." >> $logFile
jamf policy -event customAPITrigger
exit 0
else
#Check if user is logged in, if so, proceed with dialog prompts..
if [[ ! "$currentUser" == "root" ]]; then
echo $(date) "$currentUser is logged in. Proceeding with dialog prompts.." >> $logFile
fi
fi
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# FUNCTIONS
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
jamfHelper()
{
/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper
-windowType hud
-title "input title here"
-heading "Input heading here"
-description "A software update has been downloaded on your workstation and will reboot after install.
You can select to have the update installed now or delay for up to 8 hours.
Your machine will countdown 5 minutes prior to installation if you choose to delay.
Please make selection below."
-icon "$icon"
-alignDescription "left"
-alignCountdown "right"
-lockHUD
-button1 "Delay"
-button2 "Install Now"
-showDelayOptions "900, 1800, 3600, 28800" # 8 hours
}
# variables
result=$(jamfHelper)
delayint=$(echo "$result" | /usr/bin/sed 's/.$//')
warndelayint=$(expr $delayint - 300)
defercal=$(($(/bin/date +%s) + delayint))
hour=$(/bin/date -j -f "%s" "$defercal" "+%H")
minute=$(/bin/date -j -f "%s" "$defercal" "+%M")
warndefercal=$(($(/bin/date +%s) + warndelayint))
warnhour=$(/bin/date -j -f "%s" "$warndefercal" "+%H")
warnminute=$(/bin/date -j -f "%s" "$warndefercal" "+%M")
## Create LaunchDaemon from jamfHelper delay output
delay()
{
/bin/cat <<EOF > "$DelayedRestartD"
<?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.company.rebootdelay</string>
<key>ProgramArguments</key>
<array>
<string>reboot</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>$hour</integer>
<key>Minute</key>
<integer>$minute</integer>
</dict>
</dict>
</plist>
EOF
echo $(date) "The timed delay has been selected by the user. Applying permissions.." >> $logFile
/usr/sbin/chown root:wheel "$DelayedRestartD"
/bin/chmod 644 "$DelayedRestartD"
echo $(date) "8 hour permissions has been applied".. >> $logFile
}
warndelay()
{
/bin/cat <<EOF > "$WarningRestartD"
<?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.company.rebootwarning</string>
<key>ProgramArguments</key>
<array>
<string>sh</string>
<string>/path/to/rebootwarning.sh</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>$warnhour</integer>
<key>Minute</key>
<integer>$warnminute</integer>
</dict>
</dict>
</plist>
EOF
# set ownership on delaywarning launch daemon
/usr/sbin/chown root:wheel "$WarningRestartD"
/bin/chmod 644 "$WarningRestartD"
}
warnScript()
{
/bin/cat <<EOF > "$WarningRestartScript"
#!/bin/bash
PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/local/sbin
#Set the log path
logpath="/path/to/logpath/"
#Ensure logFile can be written
if [ ! -d "${logpath}" ]; then
mkdir "${logpath}"
fi
##Create the logFile
logFile="${logpath}"restartWarning.log
#Start the jamfHelper dialog
/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper
-windowType hud
-title "input title here"
-heading "input heading here"
-description "The selected delay is complete. The update will install in 5 minutes. Please save your work"
-timeout 300
-countdown
-lockHUD
-icon "$icon"
jamf policy -event customAPITrigger
EOF
## Make reboot warning script executable
/usr/sbin/chown root:admin "$WarningRestartScript"
/bin/chmod 755 "$WarningRestartScript"
/bin/chmod +x "$WarningRestartScript"
}
cleanUp()
{
/bin/cat << EOF > "$CleanupRestartScript"
#!/bin/bash
PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/local/sbin
#Set the log path
logpath="/path/to/logpath/"
#Ensure logFile can be written
if [ ! -d "${logpath}" ]; then
mkdir "${logpath}"
fi
##Create the logFile
logFile="${logpath}"restartCleanup.log
## First Run Script to remove the installer.
echo $(date) "Cleaning up files, phase 1.." >> $logFile
/bin/rm -f /Library/LaunchDaemons/com.company.rebootdelay.plist
/bin/rm -f /Library/LaunchDaemons/com.company.rebootwarning.plist
/bin/rm -f /path/to/rebootwarning.sh
launchctl remove com.company.rebootdelay
launchctl remove com.company.rebootwarning
/bin/sleep 2
## Update Device Inventory
echo $(date) "Running recon to update inventory" >> $logFile
jamf recon
## Remove LaunchDaemon
echo $(date) "Removing cleanup LaunchDaemon and script"
/bin/rm -f /Library/LaunchDaemons/com.company.cleanupDelayedRestart.plist
## Remove cleanup Script
/bin/rm -f /path/to/cleanupDelayedRestart.sh
echo "Cleaning Up Phase 2 complete.." >> $logFile
exit 0
EOF
## Make cleanup delayed restart script executable
/usr/sbin/chown root:admin "$CleanupRestartScript"
/bin/chmod 755 "$CleanupRestartScript"
/bin/chmod +x "$CleanupRestartScript"
## Create LaunchDaemon
cat << EOF > "$CleanupDelayedD"
<?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.company.cleanupDelayedRestart</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>"$CleanupRestartScript"</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
EOF
##Set the permission on the file just made.
/usr/sbin/chown root:wheel "$CleanupDelayedD"
/bin/chmod 644 "$CleanupDelayedD"
}
finalPrep()
{
# unload launchd
#launchctl unload /Library/LaunchDaemons/org.company.rebootdelay.plist
#launchctl unload /Library/LaunchDaemons/org.company.rebootwarning.plist
#load launchd
launchctl load /Library/LaunchDaemons/com.company.rebootdelay.plist
launchctl load /Library/LaunchDaemons/com.company.rebootwarning.plist
echo $(date) "8 hour delay LaunchDaemon has been loaded.." >> $logFile
}
## Select action based on user input
case "$result" in
*1 ) delay
warndelay
warnScript
finalPrep
cleanUp
;;
*2 ) echo $(date) "Updating Inventory" >> $logFile
jamf recon
echo $(date) "Rebooting seat.." >> $logFile
jamf policy -event customTrigger
;;
esac
exit 0
Based upon users response the above API script runs from a policy trigger. The only changes I made on the above script is encrypting the password for the API user..
Was that script written from scratch by yourself ?
@JustDeWon have you tried that script in conjuction with multiple OS's and hardware?
I have run the API call to an M1 + Big Sur machine (mac mini) and while the command goes through and i see a pending command under Management, it actually doesnt do the install nor reboot.
Same goes for if i click the Software Update in the Management tab of the same machine.
Im curious what your API call is and what hardware and software you have tested against.
thanks!
@beeboo , I run my script prior to running @nelsoni script he posted above.. I am using the Install
key in the API script. I'm only using the API script for macOS Big Sur(Intel/M1).. My script kicks off the -policy event
that calls the API script based upon a user's selection to Delay or Install Now.. The only edit I made to his script was encrypt the api password..
#!/bin/sh
#############################################################################################
# - The purpose of this script is to allow softwareUpdates for macOS Big Sur on Intel/M1's
# - This script will resolve the need for user input during software updates
# - The password will be Encrypted for security reasons
#
# - Created by: Ian Nelson
# - Modified: DeWon Farris 6/3/2020
# - Version: 1.1 #Encrypted API password
#############################################################################################
# Decrypt the API password
function DecryptString() {
echo "${1}" | /usr/bin/openssl enc -aes256 -d -a -A -S "${2}" -k "${3}"
}
#API login info
apiuser="APIUser"
apipass=$(DecryptString $4 'string.goes.here' 'key.goes.here')
jamfProURL="https://company.url.here"
#Grab serial number and OS Version of computer
SerialNumber=$(system_profiler SPHardwareDataType | grep 'Serial Number (system)' | awk '{print $NF}')
macOSVersion=$(sw_vers -productVersion)
#Check macOS Major
CheckIt=$(echo $macOSVersion | cut -d . -f 1)
#Set xpath option based on macOS major version
if [[ "$CheckIt" == "11" ]]
then
xpath="xpath -e"
else
xpath="xpath"
fi
## Curl API to get to install software Updates ASPAP after user chooses to delay or install now
jamfProCompID=$( /usr/bin/curl -s -u ${apiuser}:${apipass} ${jamfProURL}/JSSResource/computers/serialnumber/${SerialNumber}/subset/general | $xpath "/computer/general/id/text()" )
echo $jamfProCompID
/usr/bin/curl -s -X POST -H "Content-Type: text/xml" -u ${apiuser}:${apipass} ${jamfProURL}/JSSResource/computercommands/command/ScheduleOSUpdate/action/installASAP/id/${jamfProCompID}
exit 0