Upgrade To Mojave on Boot

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Posted on
07-12-2019
08:59 AM
- last edited on
03-04-2025
04:44 AM
by
kh-richa_mig
We're trying to upgrade our fleet of MacBook Airs to Mojave. We want to be able to send a command that will start the process on the boot of the device. We're NOT trying to erase and install, but to upgrade in place.
We have a script that works if the user is logged in, but we don't want to have to log in to all the user accounts to make it function as expected - we want the installer to begin from the login screen after an initial boot function. Is this possible?
This is the script we're using (it's slightly modified from the original):
!/bin/bash
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Copyright (c) 2019 Jamf. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Jamf nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY JAMF SOFTWARE, LLC "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL JAMF SOFTWARE, LLC BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# This script was designed to be used in a Self Service policy to ensure specific
requirements have been met before proceeding with an inplace upgrade of the macOS,
as well as to address changes Apple has made to the ability to complete macOS upgrades
silently.
REQUIREMENTS:
- Jamf Pro
- macOS Clients running version 10.10.5 or later
- macOS Installer 10.12.4 or later
- eraseInstall option is ONLY supported with macOS Installer 10.13.4+ and client-side macOS 10.13+
- Look over the USER VARIABLES and configure as needed.
# For more information, visit https://github.com/kc9wwh/macOSUpgrade
Written by: Joshua Roskos | Jamf
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
USER VARIABLES
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
Specify path to OS installer. Use Parameter 4 in the JSS, or specify here
Example: /Applications/Install macOS High Sierra.app
OSInstaller="$4"
Version of Installer OS. Use Parameter 5 in the JSS, or specify here.
Example Command: /usr/libexec/PlistBuddy -c 'Print :"System Image Info":version' "/Applications/Install macOS High Sierra.app/Contents/SharedSupport/InstallInfo.plist"
Example: 10.12.5
version="$5"
versionMajor=$( /bin/echo "$version" | /usr/bin/awk -F. '{print $2}' )
versionMinor=$( /bin/echo "$version" | /usr/bin/awk -F. '{print $3}' )
Custom Trigger used for download. Use Parameter 6 in the JSS, or specify here.
This should match a custom trigger for a policy that contains just the
MacOS installer. Make sure that the policy is scoped properly
to relevant computers and/or users, or else the custom trigger will
not be picked up. Use a separate policy for the script itself.
Example trigger name: download-sierra-install
download_trigger="$6"
MD5 Checksum of InstallESD.dmg
This variable is OPTIONAL
Leave the variable BLANK if you do NOT want to verify the checksum (DEFAULT)
Example Command: /sbin/md5 /Applications/Install macOS High Sierra.app/Contents/SharedSupport/InstallESD.dmg
Example MD5 Checksum: b15b9db3a90f9ae8a9df0f81741efa2b
installESDChecksum="$7"
Valid Checksum? O (Default) for false, 1 for true.
validChecksum=0
Unsuccessful Download? 0 (Default) for false, 1 for true.
unsuccessfulDownload=0
Erase & Install macOS (Factory Defaults)
Requires macOS Installer 10.13.4 or later
Disabled by default
Options: 0 = Disabled / 1 = Enabled
Use Parameter 8 in the JSS.
eraseInstall="$8"
if [ "$eraseInstall" != "1" ]; then eraseInstall=0 ; fi
macOS Installer 10.13.3 or ealier set 0 to it.
if [ "$versionMajor${versionMinor:=0}" -lt 134 ]; then
eraseInstall=0
fi
Enter 0 for Full Screen, 1 for Utility window (screenshots available on GitHub)
Full Screen by default
Use Parameter 9 in the JSS.
userDialog="$9"
if [ "$userDialog" != "1" ]; then userDialog=0 ; fi
Control for auth reboot execution.
if [ "$versionMajor" -ge 14 ]; then
# Installer of macOS 10.14 or later set cancel to auth reboot.
cancelFVAuthReboot=1
else
# Installer of macOS 10.13 or earlier try to do auth reboot.
cancelFVAuthReboot=0
fi
Title of OS
macOSname=$(/bin/echo "$OSInstaller" | /usr/bin/sed -E 's/(.)?Install(.).app/?/2/' | /usr/bin/xargs)
Title to be used for userDialog (only applies to Utility Window)
title="$macOSname Upgrade"
Heading to be used for userDialog
heading="Please wait as we prepare your computer for $macOSname..."
Title to be used for userDialog
description="This process will take approximately 5-10 minutes.
Once completed your computer will reboot and begin the upgrade."
Description to be used prior to downloading the OS installer
dldescription="We need to download $macOSname to your computer, this will
take several minutes."
Jamf Helper HUD Position if macOS Installer needs to be downloaded
Options: ul (Upper Left); ll (Lower Left); ur (Upper Right); lr (Lower Right)
Leave this variable empty for HUD to be centered on main screen
dlPosition="ul"
Icon to be used for userDialog
Default is macOS Installer logo which is included in the staged installer package
icon="$OSInstaller/Contents/Resources/InstallAssistant.icns"
First run script to remove the installers after run installer
finishOSInstallScriptFilePath="/usr/local/jamfps/finishOSInstall.sh"
Launch deamon settings for first run script to remove the installers after run installer
osinstallersetupdDaemonSettingsFilePath="/Library/LaunchDaemons/com.jamfps.cleanupOSInstall.plist"
Launch agent settings for filevault authenticated reboots
osinstallersetupdAgentSettingsFilePath="/Library/LaunchAgents/com.apple.install.osinstallersetupd.plist"
Amount of time (in seconds) to allow a user to connect to AC power before moving on
If null or 0, then the user will not have the opportunity to connect to AC power
acPowerWaitTimer="0"
Declare the sysRequirementErrors array
declare -a sysRequirementErrors
Icon to display during the AC Power warning
warnIcon="/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/AlertCautionIcon.icns"
Icon to display when errors are found
errorIcon="/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/AlertStopIcon.icns"
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
FUNCTIONS
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
kill_process() {
processPID="$1"
if /bin/ps -p "$processPID" > /dev/null ; then
/bin/kill "$processPID"
wait "$processPID" 2>/dev/null
fi
}
wait_for_ac_power() {
local jamfHelperPowerPID
jamfHelperPowerPID="$1"
# Loop for "acPowerWaitTimer" seconds until either AC Power is detected or the timer is up
/bin/echo "Waiting for AC power..."
while [[ "$acPowerWaitTimer" -gt "0" ]]; do
if /usr/bin/pmset -g ps | /usr/bin/grep "AC Power" > /dev/null ; then
/bin/echo "Power Check: OK - AC Power Detected"
kill_process "$jamfHelperPowerPID"
return
fi
sleep 1
((acPowerWaitTimer--))
done
kill_process "$jamfHelperPowerPID"
sysRequirementErrors+=("Is connected to AC power")
/bin/echo "Power Check: ERROR - No AC Power Detected"
}
validate_power_status() {
##Check if device is on battery or ac power
##If not, and our acPowerWaitTimer is above 1, allow user to connect to power for specified time period
if /usr/bin/pmset -g ps | /usr/bin/grep "AC Power" > /dev/null ; then
/bin/echo "Power Check: OK - AC Power Detected"
else
if [[ "$acPowerWaitTimer" -gt 0 ]]; then
/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType utility -title "Waiting for AC Power Connection" -icon "$warnIcon" -description "Please connect your computer to power using an AC power adapter. This process will continue once AC power is detected." &
wait_for_ac_power "$!"
else
sysRequirementErrors+=("Is connected to AC power")
/bin/echo "Power Check: ERROR - No AC Power Detected"
fi
fi
}
validate_free_space() { ##Check if free space > 15GB (10.13) or 20GB (10.14+) osMajor=$( /usr/bin/sw_vers -productVersion | /usr/bin/awk -F. '{print $2}' ) osMinor=$( /usr/bin/sw_vers -productVersion | /usr/bin/awk -F. '{print $3}' ) if [[ $osMajor -eq 12 ]] || [[ $osMajor -eq 13 && $osMinor -lt 4 ]]; then freeSpace=$( /usr/sbin/diskutil info / | /usr/bin/grep "Available Space" | /usr/bin/awk '{print $6}' | /usr/bin/cut -c 2- ) else freeSpace=$( /usr/sbin/diskutil info / | /usr/bin/grep "Free Space" | /usr/bin/awk '{print $6}' | /usr/bin/cut -c 2- ) fi
requiredDiskSpaceSizeGB=$([ "$osMajor" -ge 14 ] && /bin/echo "20" || /bin/echo "15")
if [[ ${freeSpace%.} -ge $(( requiredDiskSpaceSizeGB 1000 1000 1000 )) ]]; then
/bin/echo "Disk Check: OK - ${freeSpace%.} Bytes Free Space Detected"
else
sysRequirementErrors+=("Has at least ${requiredDiskSpaceSizeGB}GB of Free Space")
/bin/echo "Disk Check: ERROR - ${freeSpace%.} Bytes Free Space Detected"
fi
}
verifyChecksum() {
if [ -n "$installESDChecksum" ]; then
osChecksum=$( /sbin/md5 -q "$OSInstaller/Contents/SharedSupport/InstallESD.dmg" )
if [ "$osChecksum" = "$installESDChecksum" ]; then
/bin/echo "Checksum: Valid"
validChecksum=1
return
else
/bin/echo "Checksum: Not Valid"
/bin/echo "Beginning new dowload of installer"
/bin/rm -rf "$OSInstaller"
/bin/sleep 2
downloadInstaller
fi
else
##Checksum not specified as script argument, assume true
validChecksum=1
return
fi
}
cleanExit() {
kill_process "$caffeinatePID"
## Remove Script
/bin/rm -f "$finishOSInstallScriptFilePath" 2>/dev/null
/bin/rm -f "$osinstallersetupdDaemonSettingsFilePath" 2>/dev/null
/bin/rm -f "$osinstallersetupdAgentSettingsFilePath" 2>/dev/null
exit "$1"
}
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
SYSTEM CHECKS
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
Caffeinate
/usr/bin/caffeinate -dis &
caffeinatePID=$!
Get Current User
currentUser=$( /usr/bin/stat -f %Su /dev/console )
Check if FileVault Enabled
fvStatus=$( /usr/bin/fdesetup status | head -1 )
Run system requirement checks
validate_power_status
validate_free_space
Don't waste the users time, exit here if system requirements are not met
if [[ "${#sysRequirementErrors[@]}" -ge 1 ]]; then /bin/echo "Launching jamfHelper Dialog (Requirements Not Met)..." /Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType utility -title "$title" -icon "$errorIcon" -heading "Requirements Not Met" -description "We were unable to prepare your computer for $macOSname. Please ensure your computer meets the following requirements:
$( /usr/bin/printf ' • %s ' "${sysRequirementErrors[@]}" )
If you continue to experience this issue, please contact the IT Support Center." -iconSize 100 -button1 "OK" -defaultButton 1
cleanExit 1
fi
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
CREATE FIRST BOOT SCRIPT
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
/bin/mkdir -p /usr/local/jamfps
/bin/cat << EOF > "$finishOSInstallScriptFilePath"
!/bin/bash
First Run Script to remove the installer.
Clean up files
/bin/rm -fr "$OSInstaller"
/bin/sleep 2
Update Device Inventory
/usr/local/jamf/bin/jamf recon
Remove LaunchAgent and LaunchDaemon
/bin/rm -f "$osinstallersetupdAgentSettingsFilePath"
/bin/rm -f "$osinstallersetupdDaemonSettingsFilePath"
Remove Script
/bin/rm -fr /usr/local/jamfps
exit 0
EOF
/usr/sbin/chown root:admin "$finishOSInstallScriptFilePath"
/bin/chmod 755 "$finishOSInstallScriptFilePath"
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
LAUNCH DAEMON
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
/bin/cat << EOF > "$osinstallersetupdDaemonSettingsFilePath"
<?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.jamfps.cleanupOSInstall</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>$finishOSInstallScriptFilePath</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
EOF
Set the permission on the file just made.
/usr/sbin/chown root:wheel "$osinstallersetupdDaemonSettingsFilePath"
/bin/chmod 644 "$osinstallersetupdDaemonSettingsFilePath"
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
LAUNCH AGENT FOR FILEVAULT AUTHENTICATED REBOOTS
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
if [ "$cancelFVAuthReboot" -eq 0 ]; then ##Determine Program Argument if [ "$osMajor" -ge 11 ]; then progArgument="osinstallersetupd" elif [ "$osMajor" -eq 10 ]; then progArgument="osinstallersetupplaind" fi
/bin/cat << EOP > "$osinstallersetupdAgentSettingsFilePath"
<?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.apple.install.osinstallersetupd</string>
<key>LimitLoadToSessionType</key>
<string>Aqua</string>
<key>MachServices</key>
<dict>
<key>com.apple.install.osinstallersetupd</key>
<true/>
</dict>
<key>TimeOut</key>
<integer>300</integer>
<key>OnDemand</key>
<true/>
<key>ProgramArguments</key>
<array>
<string>$OSInstaller/Contents/Frameworks/OSInstallerSetup.framework/Resources/$progArgument</string>
</array>
</dict>
</plist>
EOP
##Set the permission on the file just made. /usr/sbin/chown root:wheel "$osinstallersetupdAgentSettingsFilePath" /bin/chmod 644 "$osinstallersetupdAgentSettingsFilePath"
fi
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
APPLICATION
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
Launch jamfHelper
if [ "$userDialog" -eq 0 ]; then
/bin/echo "Launching jamfHelper as FullScreen..."
/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType fs -title "" -icon "$icon" -heading "$heading" -description "$description" &
jamfHelperPID=$!
else
/bin/echo "Launching jamfHelper as Utility Window..."
/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType utility -title "$title" -icon "$icon" -heading "$heading" -description "$description" -iconSize 100 &
jamfHelperPID=$!
fi
Load LaunchAgent
if [ "$fvStatus" = "FileVault is On." ] &&
[ "$currentUser" != "root" ] &&
[ "$cancelFVAuthReboot" -eq 0 ] ; then
userID=$( /usr/bin/id -u "${currentUser}" )
/bin/launchctl bootstrap gui/"${userID}" /Library/LaunchAgents/com.apple.install.osinstallersetupd.plist
fi
Begin Upgrade
/bin/echo "Launching startosinstall..."
Check if eraseInstall is Enabled
if [ "$eraseInstall" -eq 1 ]; then
eraseopt='--eraseinstall'
/bin/echo "Script is configured for Erase and Install of macOS."
fi
osinstallLogfile="/var/log/startosinstall.log"
if [ "$versionMajor" -ge 14 ]; then
eval ""$OSInstaller/Contents/Resources/startosinstall"" "$eraseopt" --agreetolicense --nointeraction --pidtosignal "$jamfHelperPID" >> "$osinstallLogfile" 2>&1 &
else
eval ""$OSInstaller/Contents/Resources/startosinstall"" "$eraseopt" --applicationpath ""$OSInstaller"" --agreetolicense --nointeraction --pidtosignal "$jamfHelperPID" >> "$osinstallLogfile" 2>&1 &
fi
/bin/sleep 3
cleanExit 0
- Labels:
-
Jamf Pro

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Posted on 07-12-2019 09:43 AM
@micah002 Note the Read Me at https://github.com/kc9wwh/macOSUpgrade:
This workflow will not work if a user is not logged in since the startosinstall binary requires a user to be logged in. Tested with macOS 10.13.4 and you will get errors in that the process couldn't establish a connection to the WindowServer.

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Posted on 07-12-2019 11:21 AM
Hello, tested today with a 10.13.6 machine left at login window
If you create a recurring/correctly scoped policy to drop the full "Install macOS Mojave" installer within Applications folder (using a .dmg) and then - within the same policy - add a "Files and Processes / EXECUTE COMMAND
/Applications/Install macOS Mojave.app/Contents/Resources/startosinstall --agreetolicense --nointeraction
it works at login window although you then have to wait the installation to finish at first login (it says something like 15 mins)*
This is the same issue that used to happen with High Sierra upgrades done the same way, I am quite sure if you leave the computer on for a while (say a couple of hours), it should complete by itself but have not tested yet with Mojave
Hope it helps
Ciao
Carlo
PS: Might also work sending the one liner with ARD to the target if Mojave installer is already there but again jave not tested it

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Posted on 07-12-2019 11:21 AM

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Posted on 07-12-2019 11:26 AM
@carlo.anselmi If you're using FileVault 2 encryption there's no way to do an authenticated restart if no user is logged in, which is probably why startosinstall
wants a logged in user.

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Posted on 07-12-2019 11:37 AM
Oh yup, that’s absolutely right
I took for granted target was not encrypted
Thank you!
