Big Sur Deployment Scripts

dbarker
New Contributor

Has anyone started putting together a workflow to update endpoints to Big Sur that they can share?

15 REPLIES 15

Cayde-6
Release Candidate Programs Tester

Not yet but my plan is below

  1. Obtain the Install Big Sur app via VPP and distribute to ask devices.

  2. Smart group looking for that application

  3. Self Service policy scoped to that smart group with a script.

  4. Script executes the startosinstall binary of the app on step 1

AJPinto
Honored Contributor II

We use the instructions found here https://github.com/kc9wwh/macOSUpgrade. I am working on adapting it to Big Sur right now, all the arguments are the same so it should not be too hard. The same script worked fine for Mojave and Catalina, I put variables to use JAMF parameters so I could use the same script for multiple OS versions in different policies. (famous last words)

onsMart
New Contributor II

I've changed the script @AJPinto mentions to this.
It's a bit dirty, but it works (for now).
There is a separate policy to download and install a DMG with the Big Sur installer from JCDS.

#!/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
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Added loop for macOS version 11 to equal InstallerVersionMajor 16
#
# Updated "validate_free_space" to work with Big Sur – freeSpace
# Added "Print :APFSContainerFree"
# /usr/libexec/PlistBuddy -c "Print :APFSContainerFree" /dev/stdin <<< "$diskInfoPlist" 2>/dev/null || /usr/libexec/PlistBuddy -c "Print :FreeSpace" /dev/stdin <<< "$diskInfoPlist" 2>/dev/null || /usr/libexec/PlistBuddy -c "Print :AvailableSpace" /dev/stdin <<< "$diskInfoPlist" 2>/dev/null
# 
# Updated loopCout to work with Stub/Lite installer – Stub/Lite installer does not specify a macOS version.
# Check to see if the installer version matches, or if the installer does not have InstallInfo.plist.
# if [ "$currentInstallerVersion" = "$installerVersion" ] || [[ ! -e "$OSInstaller/Contents/SharedSupport/InstallInfo.plist" ]]; then
#
# Updated variable "currentUser"
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# USER VARIABLES
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

## Specify path to OS installer – Use Parameter 4 in the JSS, or specify here.
## Parameter Label: Path to the macOS installer
## Example: /Applications/Install macOS High Sierra.app
OSInstaller="$4"

## Version of Installer OS. Use Parameter 5 in the JSS, or specify here.
## Parameter Label: Version of macOS
## 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
installerVersion="$5"
OSVersion=$( /bin/echo "$installerVersion" | /usr/bin/awk -F. '{print $1}' )
if [[ "$OSVersion" = 11 ]]; then
    installerVersionMajor="16"
else
    installerVersionMajor=$( /bin/echo "$installerVersion" | /usr/bin/awk -F. '{print $2}' )
fi
installerVersionMinor=$( /bin/echo "$installerVersion" | /usr/bin/awk -F. '{print $3}' )


## Custom Trigger used for download – Use Parameter 6 in the JSS, or specify here.
## Parameter Label: Download Policy Trigger
## 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 or SharedSupport.dmg (11.0+) – Use Parameter 7 in the JSS.
## Parameter Label: installESD Checksum (optional)
## 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.
## Parameter Label: Upgrade or Erase (0 or 1)
eraseInstall="$8"
if [ "$eraseInstall" != "1" ]; then eraseInstall=0 ; fi
# macOS Installer 10.13.3 or ealier set 0 to it.
if [ "$installerVersionMajor${installerVersionMinor:=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.
## Parameter Label: Full Screen or Dialog Box (0 or 1)
userDialog="$9"
if [ "$userDialog" != "1" ]; then userDialog=0 ; fi

# Control for auth reboot execution.
if [ "$installerVersionMajor" -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="Your computer will reboot in 5-10 minutes and begin the upgrade.
This process will take approximately 30-60 minutes."

## Description to be used prior to downloading the OS installer
dldescription="We need to download $macOSname to your computer. This can 
take up to 2 hours depending on the speed of your internet connection. The installation 
will continue automatically once the download is completed."

## 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="90"

## 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"

## The startossinstall log file path
osinstallLogfile="/var/log/startosinstall.log"

## caffeinatePID
caffeinatePID=""

## The startossinstall command option array
declare -a startosinstallOptions=()

## Determine binary name
binaryNameForOSInstallerSetup=$([ "$installerVersionMajor" -ge 11 ] && /bin/echo "osinstallersetupd" || /bin/echo "osinstallersetupplaind")

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# 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"
}

downloadInstaller() {
    /bin/echo "Downloading macOS Installer..."
    /Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper 
        -windowType hud -windowPosition $dlPosition -title "$title" -alignHeading center -alignDescription left -description "$dldescription" 
        -lockHUD -icon "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/Sync.icns" -iconSize 100 &
    ## Capture PID for Jamf Helper HUD
    jamfHUDPID=$!
    ## Run policy to cache installer
    /usr/local/jamf/bin/jamf policy -event "$download_trigger"
    ## Kill Jamf Helper HUD post download
    kill_process "$jamfHUDPID"
}

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() {
    local installerMajor diskInfoPlist freeSpace requiredDiskSpaceSizeGB installerPath installerSizeBytes

    installerMajor="$1"
    installerPath="$2"

    diskInfoPlist=$(/usr/sbin/diskutil info -plist /)
    ## 10.13.4 or later, diskutil info command output changes key from 'AvailableSpace' to 'Free Space' about disk space.
    ## 10.15.0 or later, diskutil info command output changes key from 'APFSContainerFree' to 'Free Space' about disk space.
    freeSpace=$(
    /usr/libexec/PlistBuddy -c "Print :APFSContainerFree" /dev/stdin <<< "$diskInfoPlist" 2>/dev/null || /usr/libexec/PlistBuddy -c "Print :FreeSpace" /dev/stdin <<< "$diskInfoPlist" 2>/dev/null || /usr/libexec/PlistBuddy -c "Print :AvailableSpace" /dev/stdin <<< "$diskInfoPlist" 2>/dev/null
    )

    ## The free space calculation also includes the installer, so it is excluded.
    if [ -e "$installerPath" ]; then
        installerSizeBytes=$(/usr/bin/du -s "$installerPath" | /usr/bin/awk '{print $1}' | /usr/bin/xargs)
        freeSpace=$((freeSpace + installerSizeBytes))
    fi

    ## Check if free space > 15GB (install 10.13), 20GB (install 10.14+) or 35GB (install 11.0)
    if [[ "$installerMajor" -ge 16 ]]; then
        requiredDiskSpaceSizeGB=35
    elif [[ "$installerMajor" -ge 14 ]]; then
        requiredDiskSpaceSizeGB=20
    else
        requiredDiskSpaceSizeGB=15
    fi
#   requiredDiskSpaceSizeGB=$([ "$installerMajor" -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
        if [[ "$installerVersionMajor" -ge 16 ]]; then
            osChecksum=$( /sbin/md5 -q "$OSInstaller/Contents/SharedSupport/SharedSupport.dmg" )
        else
            osChecksum=$( /sbin/md5 -q "$OSInstaller/Contents/SharedSupport/InstallESD.dmg" )
        fi
        if [ "$osChecksum" = "$installESDChecksum" ]; then
            /bin/echo "Checksum: Valid"
            validChecksum=1
            return
        else
            /bin/echo "Checksum: Not Valid"
            /bin/echo "Beginning new download 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() {
    if [ -n "$caffeinatePID" ]; then
      kill_process "$caffeinatePID"
    fi
    ## Remove Script
    /bin/rm -f "$finishOSInstallScriptFilePath"
    /bin/rm -f "$osinstallersetupdDaemonSettingsFilePath"
    /bin/rm -f "$osinstallersetupdAgentSettingsFilePath"
    exit "$1"
}

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# SYSTEM CHECKS
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

## If previous processes remain for some reason, the installation will freeze, so kill it.
killingProcesses=("caffeinate" "startosinstall" "$binaryNameForOSInstallerSetup")
for processName in "${killingProcesses[@]}"; do
    [ -z "$processName" ] && continue
    /bin/echo "Killing $processName processes."
    /usr/bin/killall "$processName" 2>&1 || true
done

## Caffeinate
/usr/bin/caffeinate -dis &
caffeinatePID=$!

##Get Current User
currentUser=$(/bin/echo 'show State:/Users/ConsoleUser' | /usr/sbin/scutil | /usr/bin/awk '/Name / { print $3 }')

## Check if FileVault Enabled
fvStatus=$( /usr/bin/fdesetup status | /usr/bin/head -1 )

## Run system requirement checks
validate_power_status
validate_free_space "$installerVersionMajor" "$OSInstaller"

## 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

## Check for existing OS installer
loopCount=0
while [ "$loopCount" -lt 3 ]; do
    if [ -e "$OSInstaller" ]; then
        /bin/echo "$OSInstaller found, checking version."
        if [[ "$installerVersionMajor" -ge 16 ]]; then
            currentInstallerVersion=$(/usr/libexec/PlistBuddy -c "print DTPlatformVersion" "$OSInstaller/Contents/Info.plist")
        else
            currentInstallerVersion=$(/usr/libexec/PlistBuddy -c 'Print :"System Image Info":version' "$OSInstaller/Contents/SharedSupport/InstallInfo.plist")
        fi
        /bin/echo "Found macOS installer for version $currentInstallerVersion."
        ## Check to see if the installer version matches, or if the installer does not have InstallInfo.plist.
        if [ "$currentInstallerVersion" = "$installerVersion" ] || [[ ! -e "$OSInstaller/Contents/SharedSupport/InstallInfo.plist" ]]; then
            /bin/echo "Installer found, version matches. Verifying checksum..."
            verifyChecksum
        else
            ## Delete old version.
            /bin/echo "Installer found, but old. Deleting..."
            /bin/rm -rf "$OSInstaller"
            /bin/sleep 2
            downloadInstaller
        fi
        if [ "$validChecksum" -eq 1 ]; then
            unsuccessfulDownload=0
            break
        fi
    else
        downloadInstaller
    fi
    unsuccessfulDownload=1
    ((loopCount++))
done

if [ "$unsuccessfulDownload" -eq 1 ]; then
    /bin/echo "macOS Installer Downloaded 3 Times - Checksum is Not Valid"
    /bin/echo "Prompting user for error and exiting..."
    /Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType utility -title "$title" -icon "$errorIcon" -heading "Error Downloading $macOSname" -description "We were unable to prepare your computer for $macOSname. Please contact the IT Support Center." -iconSize 100 -button1 "OK" -defaultButton 1
    cleanExit 0
fi

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# CREATE FIRST BOOT SCRIPT
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

## Because the parent bash script creates a new bash script using HEREDOC ( this <<EOF >that ),
## use a backslash before the dollar sign to avoid evaluating the variable (or command) as part of creating the script.

/bin/mkdir -p /usr/local/jamfps

/bin/cat << EOF > "$finishOSInstallScriptFilePath"
#!/bin/bash
## First Run Script to remove the installer.
## Wait until /var/db/.AppleUpgrade disappears
while [ -e /var/db/.AppleUpgrade ];
do
    echo "$(date "+%a %h %d %H:%M:%S"): Waiting for /var/db/.AppleUpgrade to disappear." >> /usr/local/jamfps/firstbootupgrade.log
    sleep 60
done

## Wait until the upgrade process completes
INSTALLER_PROGRESS_PROCESS=$(pgrep -l "Installer Progress")
until [ "$INSTALLER_PROGRESS_PROCESS" = "" ];
do
    echo "$(date "+%a %h %d %H:%M:%S"): Waiting for Installer Progress to complete." >> /usr/local/jamfps/firstbootupgrade.log
    sleep 60
    INSTALLER_PROGRESS_PROCESS=$(pgrep -l "Installer Progress")
done
## Clean up files
/bin/rm -fr "$OSInstaller"
## 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
    /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/$binaryNameForOSInstallerSetup</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
jamfHelperPID=""
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

echo "jamfHelper Started"

## 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

echo "LaunchAgent Started"

## Set required startosinstall options
startosinstallOptions+=(
"--agreetolicense"
"--nointeraction"
"--pidtosignal $jamfHelperPID"
)

echo "startOS Options set"

## Set version specific startosinstall options
if [ "$installerVersionMajor" -lt 14 ]; then
    # This variable may have space. Therefore, escape value with duble quotation
    startosinstallOptions+=("--applicationpath "$OSInstaller"")
fi

if [ "$installerVersionMajor" -gt 14 ]; then
    # The --forcequitapps option will force Self Service to quit, which prevents Self Service from cancelling a restart
    startosinstallOptions+=("--forcequitapps")
fi

## Check if eraseInstall is Enabled
if [ "$eraseInstall" -eq 1 ]; then
    startosinstallOptions+=("--eraseinstall")
    /bin/echo "Script is configured for Erase and Install of macOS."
fi

echo "All set, starting upgrade"

## Begin Upgrade
startosinstallCommand=""$OSInstaller/Contents/Resources/startosinstall" ${startosinstallOptions[*]} >> $osinstallLogfile 2>&1 &"
/bin/echo "Running a command as '$startosinstallCommand'..."
eval "$startosinstallCommand"

/bin/sleep 3

exit 0

aarongrant
New Contributor II

Great Script! What if I already have the macOS on the device and just want it to upgrade or erase and install? Do I just comment download_trigger="$6"

onsMart
New Contributor II

This script works with the following script options in Jamf.
The script check for a local installer first and if found it skips triggering the download policy.
The selection to either Upgrade or Erase and install can be made in the options as well.
All credits for the script go to Joshua Roskos | Jamf. I've only adapted it to my needs

b11601cf11ce4698b100b8b4e6f2fbd8
742cd54038f14ce2b8042c46b7b56038

mconners
Valued Contributor

@aarongrant did you ever navigate around the download piece with this script? I ask because I am stumbling over it as well. In the past, I would copy the install.app down to the local client, then run the script to begin the install process. Even with the installer on the local test client I have, it is still trying to download a copy. Perhaps it is a versioning thing. I have seen the install call itself 10.16.1 and I have also seen elsewhere it is called, 11.0.1.

aarongrant
New Contributor II

Yes, @mconners. Hence, the reason why I posted the question. I will try @onsMart solution and see what happens.

geoff_widdowson
Contributor II

FYI the above scrtipt has been updated at
https://github.com/kc9wwh/macOSUpgrade/blob/master/macOSUpgrade.sh

sumitjha
New Contributor II

Getting 702 error at 69% and it's taking too much time approx 50 mins to reach 69%. I ran twice but same error while reaching at 69%. Don't know what the cause is. ed02ab87e5df4cea9b39115e61290c54

cjavallas
New Contributor

I also have the issue where the script just keeps redownloading the Big Sur installer... I see the package come down into /Library/Application Support/JAMF/Downloads then the package I created with MegaPKGr.zsh populates in the Applications folder but then deletes it and redownloads.... Can't seem to find out why

sdagley
Esteemed Contributor II

@cjavallas Be aware that Install macOS Big Sur app for both versions 11.2.1 and 11.2.2 reports the version of Big Sur that will be installed as 11.2, so if you're using the macOSUpgrade script you need to use 11.2 as the OS version otherwise it will reject the 11.2.1.and 11.2.2 installers (I have not checked the 11.2.3 installer yet)

user-NOQkvCrTvc
New Contributor II

Hi

Thanks for the script, looks amazing. I can't seem to find the line, If package not found ? Where does it download the package from, Or do I need another policy "download installer trigger" ? What needs to be in the policy ?

Thanks

michaelprice
New Contributor III

[deleted - answered my own question]

JminD
New Contributor II

Just wanted to update that none of these options worked for me. Tried using the updated script here: https://github.com/kc9wwh/macOSUpgrade/blob/master/macOSUpgrade.sh
But when it runs on user's machine's it says "The item is no longer available."

#!/bin/bash
#Parameters:
    #$4 is the number of deferrals to give the user.
    #$5 is the name of the app, shown in logs and used to uniquely name the deferral message.
    #$6 is the custom trigger of a policy that will cache the macOS Installer if it isn't found. Not strictly necessary, but the script will fail out if it can't find the installer.
    #$7 is the path to the macOS installer .app. ie. "/Applications/Install macOS High Sierra.app"
    #$8 is the major version the policy should be upgrading the machine to, used to bail on a machine that's already at or above this version. ie. "14" for Mojave, "13" for High Sierra, "12" for Sierra, etc.

#Configuration & Parameters
initialDeferralCount="${4}"                                                                                       #How many deferrals to give the user.
OS="${5}"                                                                                                     #For logging and naming the deferral counter file.
cachePolicy="${6}"                                                                                                #Jamf policy trigger for getting a macOS installer .app
installerPath="${7}"                                                                                          #Path to the macOS Installer .app file
targetVersion="${8}"                                                                                          #Major version to upgrade to. Just used to determine if the script should exit to prevent re-running an upgrade.
iconPath="/private/etc/Logo.icns"                                                                             #Path to the icon to use on the deferral prompt. If file is missing or not specified, an alternate non-icon'd window will be shown.
deferralTitle=""                                                                                              #Optional. Shown in the title bar of the deferral prompt.
deferralHeader="Upgrade to macOS $OS"
deferralMessage="An upgrade to macOS $OS is ready and available. 

Do you want to install this upgrade now or ask again later?

Note a restart is required immediately to finish the upgrade and typically takes under an hour. 

If you have any questions or concerns, please reach out to TechOps at itsupport@snagajob.com"               #Dialog shown on the deferral prompt.
noDeferralMessage="An upgrade to macOS $OS is ready and required.

Note a restart is required immediately to finish the upgrade and typically takes under an hour.

Please save all work and then click Upgrade when ready. 

If you have any questions or concerns, please reach out to TechOps at itsupport@snagajob.com"                                                       #Dialog shown on the no deferral prompt.
######################################################################################################################################################

check_version()
{
    majorVersion="$( sw_vers -productVersion | cut -d . -f 2 )"
    if [[ "$majorVersion" -ge "$targetVersion" ]];
        then
            echo "Already at or macOS major version $targetVersion"
            jamf recon
            exit 0
        else
            check_installer
    fi
}


check_installer()
{
    if [ -e "${installerPath}" ];
        then
            echo "$OS installer found."
            check_for_users
        else
            echo "$OS installer not found."
            jamf policy -event $cachePolicy
            if [ -e "${installerPath}" ];
                then
                    echo "$OS installer found after caching from Jamf."
                    check_for_users
                else
                    echo "$OS installer not found after attempting to cache from Jamf. Check policy and connectivity to DP."
                    exit 1
            fi
    fi
}


#Check if anyone is logged on
check_for_users()
{
    user="$( stat -f%Su /dev/console )"
    if [[ $( who | awk '{ print $2 }' | grep console ) ]]; 
        then
            dnd_Check
        else
            echo "Nobody is logged on, starting the macOS upgrade...."
            run_installer
    fi
}


#See if one-off DND is enabled for the logged in user.
dnd_Check()
{
    dndFile="$( ls /Users/$user/Library/Preferences/ByHost/com.apple.notificationcenterui.*.plist )"          #Plist to read for DND detection.

    #Some users don't have this plist file. It's generated the first time you do anything in notification center, so catch it here
    if [[ ! -f $dndFile ]];
        then
            echo "$user doesn't have a notification preference file. Usually that's because they've never used notification center."           #This also means they could not have set DND, so just run the check.
            check_deferrals
    fi

    #Check for one-off DND
    echo "Checking $user's DND status."
    dndOneOff="$( plutil -p $dndFile | grep -i -w "doNotDisturb" | awk '{print $3}' )"
        if [[ $dndOneOff == "1" ]];
            then
                echo "One-Off DND is enabled, stopping now."
                exit 0
            else
                echo "One-Off DND isn't enabled."
                check_deferrals
        fi
}


check_deferrals()
{
    deferralFolder="/Library/Application Support/JAMF/Deferrals"
    deferralFile="${deferralFolder}/${OS}_deferralCount"

    ## Create the deferral folder if missing.
    if [ ! -d "$deferralFolder" ]; then
        echo "Initial Deferral folder isn't present. Creating it..."
        mkdir "$deferralFolder"
    fi

    ## Create or read the deferral counter file.
    if [ -f "$deferralFile" ];
        then
            deferralsLeft=$(cat "${deferralFile}")
        else
            echo "No Deferral Counter file found. Creating it with initial value of ${initialDeferralCount}..."
            echo "$initialDeferralCount" > "$deferralFile"
            deferralsLeft="$initialDeferralCount"
    fi

    ## If the user has deferrals left, give them the prompt.
    if [[ "$deferralsLeft" -ge 1 ]]; 
        then
            display_deferral_prompt
    fi

    ## If no deferrals are left, just start the installer.
    if [[ "$deferralsLeft" -eq 0 ]]; 
        then
            echo "0 deferrals remaining."
            display_noDeferral_prompt
    fi
}


display_deferral_prompt()
{
    helper="/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper"                                   #The jamfHelper binary
    helperDeferralsLeft=\n\n"You have $deferralsLeft deferral(s) remaining."
    helperDescription="$( echo -e "$deferralMessage$helperDeferralsLeft" )"                                                 #Echo the description to preserve formatting when we call it during the jamfHelper message.
    echo "Showing the deferral prompt."

    if [[ -f $iconPath ]];
        then
            helperReturn=$( "$helper" -windowType hud -icon "$iconPath" -lockHUD -heading "$deferralHeader" -alignHeading center -description "$helperDescription" -alignDescription center -button2 "Upgrade" -button1 "Defer")
        else
            #The specified logo file does NOT exist, run the exact same patch command without the icon switch.
            helperReturn=$( "$helper" -windowType hud -lockHUD -heading "$deferralHeader" -alignHeading center -description "$helperDescription" -alignDescription center -button2 "Upgrade" -button1 "Defer")
    fi

    if [[ $helperReturn == "239" ]];
        then
            echo "User force-closed the prompt. Relaunching."
            display_deferral_prompt
    fi

    if [[ $helperReturn == "2" ]];
        then
            echo "User chose to upgrade."
            run_installer
        else
            echo "User deferred. Next check will be at the execution frequency interval defined in this policy."
            newDeferralCount=$((deferralsLeft-1))
            echo "$newDeferralCount deferrals remaining."   
            echo "$newDeferralCount" > "$deferralFile"
            exit 0
    fi
}


display_noDeferral_prompt()
{
    helper="/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper"                                   #The jamfHelper binary
    helperDeferralsLeft=\n\n"You have $deferralsLeft deferral(s) remaining."
    helperDescription="$( echo -e "$noDeferralMessage$helperDeferralsLeft" )"                                               #Echo the description to preserve formatting when we call it during the jamfHelper message.
    echo "Showing the deferral prompt."

    if [[ -f $iconPath ]];
        then
            helperReturn=$( "$helper" -windowType hud -icon "$iconPath" -lockHUD -title "$deferralTitle" -heading "$deferralHeader" -alignHeading center -description "$helperDescription" -alignDescription center -button2 "Upgrade" -button1 "Defer" -timeout 60)
        else
            #The specified logo file does NOT exist, run the exact same patch command without the icon switch.
            helperReturn=$( "$helper" -windowType hud -lockHUD -title "$deferralTitle" -heading "$deferralHeader" -alignHeading center -description "$helperDescription" -alignDescription center -button2 "Upgrade" -button1 "Upgrade" -timeout 60)
    fi

    if [[ $helperReturn == "239" ]];
        then
            echo "User force-closed the prompt. Resistance is futile."
            display_noDeferral_prompt
    fi

    if [[ $helperReturn == "0" ]];
        then
            echo "User hit defer with no deferrals remaining, or the window timed out and will be reset into the foreground and center."
            display_noDeferral_prompt
    fi

    if [[ $helperReturn == "2" ]];
        then
            echo "User chose to upgrade."
            run_installer
        else
            echo "Jamf Helper exit code: $helperReturn"
            exit 1
    fi
}


run_installer()
{
    #--nointeraction bypasses the license agreement and makes it run silently and auto apply to the boot drive.
    #--reboot delay isn't strictly necessary, but the client will frequently reboot before sending the log back to Jamf if it isn't delayed.
    #Specifying volume is not a requirement as of 10.12.0+
    echo "Starting macOS installer"
    "${installerPath}/Contents/Resources/startosinstall" --rebootdelay 5 --nointeraction &&
    exit $?
}


#Start by checking the current OS version isn't what we are trying to upgrade to.
check_version

!/bin/sh

```
#!/bin/sh

```

But turning of the deferral portion and just having it run the install didn't work either. I may just be doing everything wrong but it's been a long month of trying to stitch something together that works. Also the VPP option wouldn't work for my company either. For some reason user's keep getting the admin credential prompt for the install and a majority of user's are not local admins on their machines.

AJPinto
Honored Contributor II

Boiling it all down to what is needed, its just 2 things. You need install macOS Big Sur on the Mac, and you need to run a single terminal command (providing user interaction is not desired). The rest of the scripts are just fluff and safety measures. Get something basic working then go from there.

Step 1: Get Big Sur on the Mac. Since VPP is out, you can do this via CLI with the fetch full installer command, or deploy the installer via JAMF. I prefer to deploy via JAMF for version control. Due to the size and cryptographic nature of Big Sur you need to package it as a DMG with composer if you want JAMF to push Big Sur's installer. How install macOS Big Sur.app gets on the mac does not matter, but it must be there.

Step 2: Verify Big Sur is on the target device, and nothing is removing it. Some of these complex scripts have cleanups that remove the installers, make sure there is none of that.

Step 3: Make terminal do the needful. The command below should kick off the installer and run it with no interaction providing macOS Big Sur is cached on the Macs and in /Applications. Adjust the path as needed.

#!/bin/sh
sudo /Applications/Install macOS Big Sur.app/Contents/Resources/startosinstall --agreetolicense --forcequitapps --nointeraction