01-19-2023 05:32 PM - edited 01-22-2023 07:56 PM
Hi all,
Just wanted to share something I had played around with over the past couple of MacBook runups using DEPNotify,
The goal: Obtain a nice looking, accurate status update via the 'softwareupdate' command when running MacOS updates via DEPNotify during a laptop runup.
The goal was achieved and can be viewed here in testing: https://youtu.be/XGodJCYb2vc
Issue: The first thing I ran into when redirecting the output to the 'depnotify.log' was the age old Mac/Unix 'Control Character' CR (carriage return, CR, \r, ^M) https://en.wikipedia.org/wiki/Control_character#In_ASCII
When reading the redirected output of 'softwareupdate --fetch-full-installer --full-installer-version 13.1' it would contain carriage returns at the end of each line
STF21-ICT-PSASSMANN:~ psassmannshausen$ cat -v testLog.txt
Scanning for 13.1 installer
^MInstalling: 22.0%^M
^M
^M
^M
^M
^M
^M
Scanning for 13.1 installer
^MInstalling: 54.0%^MInstalling: 55.0%^MInstalling: 56.0%^MInstalling: 57.0%^MInstalling: 59.0%^MInstalling: 60.0%^MInstalling: 61.0%^MInstalling: 62.0%^MInstalling: 63.0%^MInstalling: 64.0%^MInstalling: 65.0%^MInstalling: 67.0%^MInstalling: 68.0%^MInstalling: 69.0%^MInstalling: 70.0%^MInstalling: 71.0%^MInstalling: 72.0%^MInstalling: 74.0%^MInstalling: 75.0%^MInstalling: 76.0%^MInstalling: 77.0%^MInstalling: 78.0%^MInstalling: 79.0%^MInstalling: 80.0%^MInstalling: 82.0%^MInstalling: 83.0%^MInstalling: 84.0%^MInstalling: 85.0%
A simple 'while read line' would not be able to handle the ^M single character...
Setting IFS (great write up reference here: https://bash.cyberciti.biz/guide/$IFS ) was no help either..
Knowing that I create the CR in terminal using CTRL + v then CTRL + m,
I could use the 'verbatim input' in older Nano versions to insert the single CR ^M using the Meta key ESC + v then CTRL + m (reference: https://superuser.com/questions/433903/how-do-i-type-carriage-return#:~:text=To%20insert%20a%20carri...
This did work, not via IFS but rather inserting the CR in as a delimiter in the 'read' syntax... without using cat -v the script would interpret the CR/^M as:
If using cat -v it would look like this:
while read -r -d '^M' line
do echo "Status: ${line}" >> ${DEP_NOTIFY_LOG}
However the script would need to uploaded via Jamf pro to ensure the verbatim ^M was maintained..
Version 2:
As the previous verbatim input would break occasionally I was able to find this article very useful https://docstore.mik.ua/orelly/unix/upt/ch45_35.htm and based on the example
escseq=`echo 'ea[w' | tr 'eaw' '\033\001\027'`
I was able to write:
eval $(echo m=M | tr 'M' '\015')
Storing the CR in the variable $m then adding this as the delimiter in the read -d '$m' line..
Below is the script in full, there are obvious parameters that will need to be modified and there is no check for the DEPNotify installer but if anyone would like any help I'm here, this is simply something to play with and the named pipe was the easiest way to keep the flow going to the depnotifylog.
I have since moved to an SMB download version using the same method that has incread the speed 10 fold via a local JSS distro (on a network segment) so if anyone is interested in this type of setup id be happy to help.
#!/bin/bash
### Written via Peter Sassmannshausen
### Version 4.1 trillion ;)
DEP_NOTIFY_LOG="/var/tmp/depnotify.log"
rm -f ${DEP_NOTIFY_LOG}
echo "Status: Looking for newest MacOS update now... please allow a minute or so" >> "$DEP_NOTIFY_LOG"
echo "Command: Video: https://imgur.com/I3T3K9m.mp4" >> "$DEP_NOTIFY_LOG"
open ${DEP_NOTIFY_LOG}
open -a "DEPNotify"
### CHECK AVAILABLE DOWNLOADS
GET_OS_UPDATE=$(softwareupdate --list-full-installer | sed -n 3p)
echo "Status: $GET_OS_UPDATE" >> "$DEP_NOTIFY_LOG"
sleep 1
TRIM_OS_VERSION=${GET_OS_UPDATE#*Version: }
echo "$TRIM_OS_VERSION"
OS_BUILD=${TRIM_OS_VERSION%%, Size*}
### GET THE MAJOR AND MINOR VERSIONS:
majorVersion=$(echo "$OS_BUILD" | cut -d "." -f 1)
minorVersion=$(echo "$OS_BUILD" | cut -d "." -f 2)
### GET THE NAME:
TRIM_OS_NAME=${GET_OS_UPDATE#*Title: }
echo "$TRIM_OS_NAME"
OS_NAME=${TRIM_OS_NAME%%, Version:*}
echo "Status: Newest compatible OS available for your Mac is MacOS ${OS_NAME}, Build: ${OS_BUILD}..." >> "$DEP_NOTIFY_LOG"
sleep 1
#echo "Status: Latest Major Available OS Version is: ${OS_NAME}, ${majorVersion}" >> "$DEP_NOTIFY_LOG"
#sleep 1
#echo "Status: Latest Minor Available OS Version is: ${OS_NAME}, ${majorVersion}.${minorVersion}" >> "$DEP_NOTIFY_LOG"
### CURRENT MACOS VERSION:
installerFileName=$(ls /Applications/ | grep "Install ${OS_NAME}")
macOSVersion=$(sw_vers -productVersion)
macOSMajor=$(sw_vers -productVersion | cut -d '.' -f1)
macOSMinor=$(sw_vers -productVersion | cut -d '.' -f2)
macOSName=$(awk '/SOFTWARE LICENSE AGREEMENT FOR macOS/' '/System/Library/CoreServices/Setup Assistant.app/Contents/Resources/en.lproj/OSXSoftwareLicense.rtf' | awk -F 'macOS ' '{print $NF}' | awk '{print substr($0, 0, length($0)-1)}')
runInstall () {
rm /tmp/hpipe
mkfifo /tmp/hpipe
chmod 777 /tmp/hpipe
softwareupdate --fetch-full-installer --full-installer-version "${1}" > /tmp/hpipe &
echo "Command: Determinate: 100" >> ${DEP_NOTIFY_LOG}
eval $(echo m=M | tr 'M' '\015')
while read -r -d "$m" line
do echo "Status: ${line}" >> ${DEP_NOTIFY_LOG}
done < /tmp/hpipe
rm /tmp/hpipe
echo "Status: ${line}..." >> "$DEP_NOTIFY_LOG"
}
### CHECK PRIOR TO DOWNLOAD:
if [[ ${majorVersion} -gt ${macOSMajor} ]] && [[ ${installerFileName} == "" ]]; then
echo "Status: You currently have ${macOSName} ${macOSVersion}, you will now be ugraded to the most compatible OS: ${OS_NAME} ${OS_BUILD}" >> "$DEP_NOTIFY_LOG";
runInstall "${OS_BUILD}"
elif [[ ${majorVersion} -eq ${macOSMajor} ]] && [[ ${macOSMinor} -lt ${minorVersion} ]]; then
echo "Status: You currently have ${macOSName} ${macOSVersion}, you will now be ugraded to the most compatible OS: ${OS_NAME} ${OS_BUILD}" >> "$DEP_NOTIFY_LOG";
runInstall "${OS_BUILD}"
elif [[ ${majorVersion} -gt ${macOSMajor} ]] && [[ ${installerFileName} != "" ]]; then
echo "Status: Looks like you already have ${installerFileName} sitting in the Apllications folder... quiting now" >> "$DEP_NOTIFY_LOG"
sleep 2
echo "Status: Quiting Download Process Now..." >> "$DEP_NOTIFY_LOG"
sleep 2
echo "Command: Quit" >> "$DEP_NOTIFY_LOG"
sleep 2
exit 0
else
echo "Status: Oh dear..., looks like your computer cannot upgrade past ${macOSName}, version:${macOSMajor}..." >> "$DEP_NOTIFY_LOG";
sleep 2
echo "Status: Quiting Download Process Now..." >> "$DEP_NOTIFY_LOG"
sleep 2
echo "Command: Quit" >> "$DEP_NOTIFY_LOG"
sleep 2
exit 0
fi
sleep 2
echo "Status: Quiting Download Process Now..." >> "$DEP_NOTIFY_LOG"
sleep 2
echo "Command: Quit" >> "$DEP_NOTIFY_LOG"
sleep 2
exit 0
As always I'd love to know how your MacOS updates are done either during runup or on the fly, and help is always appreciated, I did review S.U.P.E.R.M.A.N and whilst one of the most elegant Bash scripts of ever seen (still learning from it.. for a long time yet) it didn't have the GUI feel I was going for my use case and techs here.
Regards
Pete
Posted on 01-20-2023 12:41 PM
Incredible work!
Posted on 03-09-2023 12:18 PM
This is very interesting. I really like what you've done here. We typically handle our OS updates via Nudge. This is a good way to integrate update install at deployment though.
Posted on 01-05-2024 03:25 PM
Indeed Nudge or Super are great for user interaction and you are spot on this is used for the run up of new or formatted devices in conjunction with our existing DEP process. At this point we don’t need to check for disk space or power as these are are taken care of already.
Lice to know what others are using in there DEP to ensure the MacOS is up to date
Cheers