mirror of
https://github.com/PurpleComputing/mdmscripts.git
synced 2026-02-03 14:13:27 +00:00
1831 lines
67 KiB
Bash
1831 lines
67 KiB
Bash
|
||
#!/bin/sh
|
||
#####################################################################################################
|
||
#
|
||
# ABOUT THIS PROGRAM
|
||
#
|
||
# NAME
|
||
# hpclick.sh -- Installs HPClick
|
||
#
|
||
# SYNOPSIS
|
||
# sudo hpclick.sh
|
||
#
|
||
####################################################################################################
|
||
#
|
||
#
|
||
####################################################################################################
|
||
# Script to download and install AccountEdge Network Edition.
|
||
#
|
||
#!/bin/zsh
|
||
label="hpclicknp" # if no label is sent to the script, this will be used
|
||
|
||
# Installomator
|
||
#
|
||
# Downloads and installs Applications
|
||
# 2020-2021 Installomator
|
||
#
|
||
# inspired by the download scripts from William Smith and Sander Schram
|
||
#
|
||
# Contributers:
|
||
# Armin Briegel - @scriptingosx
|
||
# Isaac Ordonez - @issacatmann
|
||
# Søren Theilgaard - @Theile
|
||
# Adam Codega - @acodega
|
||
#
|
||
# with contributions from many others
|
||
|
||
export PATH=/usr/bin:/bin:/usr/sbin:/sbin
|
||
|
||
# NOTE: adjust these variables:
|
||
|
||
# set to 0 for production, 1 or 2 for debugging
|
||
# while debugging, items will be downloaded to the parent directory of this script
|
||
# also no actual installation will be performed
|
||
# debug mode 1 will download to the directory the script is run in, but will not check the version
|
||
# debug mode 2 will download to the temp directory, check for blocking processes, check the version, but will not install anything or remove the current version
|
||
DEBUG=0
|
||
|
||
# notify behavior
|
||
NOTIFY=success
|
||
# options:
|
||
# - success notify the user on success
|
||
# - silent no notifications
|
||
# - all all notifications (great for Self Service installation)
|
||
|
||
# behavior when blocking processes are found
|
||
BLOCKING_PROCESS_ACTION=tell_user
|
||
# options:
|
||
# - ignore continue even when blocking processes are found
|
||
# - quit app will be told to quit nicely if running
|
||
# - quit_kill told to quit twice, then it will be killed
|
||
# Could be great for service apps if they do not respawn
|
||
# - silent_fail exit script without prompt or installation
|
||
# - prompt_user show a user dialog for each blocking process found,
|
||
# user can choose "Quit and Update" or "Not Now".
|
||
# When "Quit and Update" is chosen, blocking process
|
||
# will be told to quit. Installomator will wait 30 seconds
|
||
# before checking again in case Save dialogs etc are being responded to.
|
||
# Installomator will abort if quitting after three tries does not succeed.
|
||
# "Not Now" will exit Installomator.
|
||
# - prompt_user_then_kill
|
||
# show a user dialog for each blocking process found,
|
||
# user can choose "Quit and Update" or "Not Now".
|
||
# When "Quit and Update" is chosen, blocking process
|
||
# will be terminated. Installomator will abort if terminating
|
||
# after two tries does not succeed. "Not Now" will exit Installomator.
|
||
# - prompt_user_loop
|
||
# Like prompt-user, but clicking "Not Now", will just wait an hour,
|
||
# and then it will ask again.
|
||
# WARNING! It might block the MDM agent on the machine, as
|
||
# the script will not exit, it will pause until the hour has passed,
|
||
# possibly blocking for other management actions in this time.
|
||
# - tell_user User will be showed a notification about the important update,
|
||
# but user is only allowed to Quit and Continue, and then we
|
||
# ask the app to quit. This is default.
|
||
# - tell_user_then_kill
|
||
# User will be showed a notification about the important update,
|
||
# but user is only allowed to Quit and Continue. If the quitting fails,
|
||
# the blocking processes will be terminated.
|
||
# - kill kill process without prompting or giving the user a chance to save
|
||
|
||
|
||
# logo-icon used in dialog boxes if app is blocking
|
||
LOGO=appstore
|
||
# options:
|
||
# - appstore Icon is Apple App Store (default)
|
||
# - jamf JAMF Pro
|
||
# - mosyleb Mosyle Business
|
||
# - mosylem Mosyle Manager (Education)
|
||
# - addigy Addigy
|
||
# - microsoft Microsoft Endpoint Manager (Intune)
|
||
# - ws1 Workspace ONE (AirWatch)
|
||
# path can also be set in the command call, and if file exists, it will be used.
|
||
# Like 'LOGO="/System/Applications/App\ Store.app/Contents/Resources/AppIcon.icns"'
|
||
# (spaces have to be escaped).
|
||
|
||
|
||
# App Store apps handling
|
||
IGNORE_APP_STORE_APPS=no
|
||
# options:
|
||
# - no If the installed app is from App Store (which include VPP installed apps)
|
||
# it will not be touched, no matter its version (default)
|
||
# - yes Replace App Store (and VPP) version of the app and handle future
|
||
# updates using Installomator, even if latest version.
|
||
# Shouldn’t give any problems for the user in most cases.
|
||
# Known bad example: Slack will lose all settings.
|
||
|
||
# Owner of copied apps
|
||
SYSTEMOWNER=0
|
||
# options:
|
||
# - 0 Current user will be owner of copied apps, just like if they
|
||
# installed it themselves (default).
|
||
# - 1 root:wheel will be set on the copied app.
|
||
# Useful for shared machines.
|
||
|
||
# install behavior
|
||
INSTALL=""
|
||
# options:
|
||
# - When not set, the software will only be installed
|
||
# if it is newer/different in version
|
||
# - force Install even if it’s the same version
|
||
|
||
|
||
# Re-opening of closed app
|
||
REOPEN="yes"
|
||
# options:
|
||
# - yes App will be reopened if it was closed
|
||
# - no App not reopened
|
||
|
||
# Only let Installomator return the name of the label
|
||
# RETURN_LABEL_NAME=0
|
||
# options:
|
||
# - 1 Installomator will return the name of the label and exit, so last line of
|
||
# output will be that name. When Installomator is locally installed and we
|
||
# use DEPNotify, then DEPNotify can present a more nice name to the user,
|
||
# instead of just the label name.
|
||
|
||
|
||
# Interrupt Do Not Disturb (DND) full screen apps
|
||
INTERRUPT_DND="yes"
|
||
# options:
|
||
# - yes Script will run without checking for DND full screen apps.
|
||
# - no Script will exit when an active DND full screen app is detected.
|
||
|
||
# Comma separated list of app names to ignore when evaluating DND
|
||
IGNORE_DND_APPS=""
|
||
# example that will ignore browsers when evaluating DND:
|
||
# IGNORE_DND_APPS="firefox,Google Chrome,Safari,Microsoft Edge,Opera,Amphetamine,caffeinate"
|
||
|
||
|
||
# Swift Dialog integration
|
||
|
||
# These variables will allow Installomator to communicate progress with Swift Dialog
|
||
# https://github.com/bartreardon/swiftDialog
|
||
|
||
# This requires Swift Dialog 2.11.2 or higher.
|
||
|
||
DIALOG_CMD_FILE=""
|
||
# When this variable is set, Installomator will write Swift Dialog commands to this path.
|
||
# Installomator will not launch Swift Dialog. The process calling Installomator will have
|
||
# launch and configure Swift Dialog to listen to this file.
|
||
# See `MDM/swiftdialog_example.sh` for an example.
|
||
|
||
DIALOG_LIST_ITEM_NAME=""
|
||
# When this variable is set, progress for downloads and installs will be sent to this
|
||
# listitem.
|
||
# When the variable is unset, progress will be sent to Swift Dialog's main progress bar.
|
||
|
||
|
||
|
||
# NOTE: How labels work
|
||
|
||
# Each workflow label needs to be listed in the case statement below.
|
||
# for each label these variables can be set:
|
||
#
|
||
# - name: (required)
|
||
# Name of the installed app.
|
||
# This is used to derive many of the other variables.
|
||
#
|
||
# - type: (required)
|
||
# The type of the installation. Possible values:
|
||
# - dmg
|
||
# - pkg
|
||
# - zip
|
||
# - tbz
|
||
# - pkgInDmg
|
||
# - pkgInZip
|
||
# - appInDmgInZip
|
||
# - updateronly This last one is for labels that should only run an updateTool (see below)
|
||
#
|
||
# - packageID: (optional)
|
||
# The package ID of a pkg
|
||
# If given, will be used to find the version of installed software, instead of searching for an app.
|
||
# Usefull if a pkg does not install an app.
|
||
# See label installomator_st
|
||
#
|
||
# - downloadURL: (required)
|
||
# URL to download the dmg.
|
||
# Can be generated with a series of commands (see BBEdit for an example).
|
||
#
|
||
# - curlOptions: (array, optional)
|
||
# Options to the curl command, needed for curl to be able to download the software.
|
||
# Usually used for adding extra headers that some servers need in order to serve the file.
|
||
# curlOptions=( -H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Safari/605.1.15" )
|
||
# (See “mocha”-labels, for examples on labels, and buildLabel.sh for header-examples.)
|
||
#
|
||
# - appNewVersion: (optional)
|
||
# Version of the downloaded software.
|
||
# If given, it will be compared to the installed version, to see if the download is different.
|
||
# It does not check for newer or not, only different.
|
||
#
|
||
# - versionKey: (optional)
|
||
# How we get version number from app. Possible values:
|
||
# - CFBundleShortVersionString
|
||
# - CFBundleVersion
|
||
# Not all software titles uses fields the same.
|
||
# See Opera label.
|
||
#
|
||
# - appCustomVersion(){}: (optional function)
|
||
# This function can be added to your label, if a specific custom
|
||
# mechanism hs to be used for getting the installed version.
|
||
# See labels zulujdk11, zulujdk13, zulujdk15
|
||
#
|
||
# - expectedTeamID: (required)
|
||
# 10-digit developer team ID.
|
||
# Obtain the team ID by running:
|
||
#
|
||
# - Applications (in dmgs or zips)
|
||
# spctl -a -vv /Applications/BBEdit.app
|
||
#
|
||
# - Pkgs
|
||
# spctl -a -vv -t install ~/Downloads/desktoppr-0.2.pkg
|
||
#
|
||
# The team ID is the ten-digit ID at the end of the line starting with 'origin='
|
||
#
|
||
# - archiveName: (optional)
|
||
# The name of the downloaded file.
|
||
# When not given the archiveName is derived from the $name.
|
||
# Note: This has to be defined BEFORE calling downloadURLFromGit or
|
||
# versionFromGit functions in the label.
|
||
#
|
||
# - appName: (optional)
|
||
# File name of the app bundle in the dmg to verify and copy (include .app).
|
||
# When not given, the appName is derived from the $name.
|
||
#
|
||
# - targetDir: (optional)
|
||
# dmg or zip:
|
||
# Applications will be copied to this directory.
|
||
# Default value is '/Applications' for dmg and zip installations.
|
||
# pkg:
|
||
# targetDir is used as the install-location. Default is '/'.
|
||
#
|
||
# - blockingProcesses: (optional)
|
||
# Array of process names that will block the installation or update.
|
||
# If no blockingProcesses array is given the default will be:
|
||
# blockingProcesses=( $name )
|
||
# When a package contains multiple applications, _all_ should be listed, e.g:
|
||
# blockingProcesses=( "Keynote" "Pages" "Numbers" )
|
||
# When a workflow has no blocking processes, use
|
||
# blockingProcesses=( NONE )
|
||
#
|
||
# - pkgName: (optional, only used for pkgInDmg, dmgInZip, and appInDmgInZip)
|
||
# File name or path to the pkg/dmg file _inside_ the dmg or zip.
|
||
# When not given the pkgName is derived from the $name
|
||
#
|
||
# - updateTool:
|
||
# - updateToolArguments:
|
||
# When Installomator detects an existing installation of the application,
|
||
# and the updateTool variable is set
|
||
# $updateTool $updateArguments
|
||
# Will be run instead of of downloading and installing a complete new version.
|
||
# Use this when the updateTool does differential and optimized downloads.
|
||
# e.g. msupdate on various Microsoft labels
|
||
#
|
||
# - updateToolRunAsCurrentUser:
|
||
# When this variable is set (any value), $updateTool will be run as the current user.
|
||
#
|
||
# - CLIInstaller:
|
||
# - CLIArguments:
|
||
# If the downloaded dmg is an installer that we can call using CLI, we can
|
||
# use these two variables for what to call.
|
||
# We need to define `name` for the installed app (to be version checked), as well as
|
||
# `installerTool` for the installer app (if named differently than `name`. Installomator
|
||
# will add the path to the folder/disk image with the binary, and it will be called like this:
|
||
# $CLIInstaller $CLIArguments
|
||
# For most installations `CLIInstaller` should contain the `installerTool` for the CLI call
|
||
# (if it’s the same).
|
||
# We can support a whole range of other software titles by implementing this.
|
||
# See label adobecreativeclouddesktop
|
||
#
|
||
# - installerTool:
|
||
# Introduced as part of `CLIInstaller`. If the installer in the DMG or ZIP is named
|
||
# differently than the installed app, then this variable can be used to name the
|
||
# installer that should be located after mounting/expanding the downloaded archive.
|
||
# See label adobecreativeclouddesktop
|
||
#
|
||
### Logging
|
||
# Logging behavior
|
||
LOGGING="INFO"
|
||
# options:
|
||
# - DEBUG Everything is logged
|
||
# - INFO (default) normal logging level
|
||
# - WARN only warning
|
||
# - ERROR only errors
|
||
# - REQ ????
|
||
|
||
# MDM profile name
|
||
MDMProfileName=""
|
||
# options:
|
||
# - MDM Profile Addigy has this name on the profile
|
||
# - Mosyle Corporation MDM Mosyle uses this name on the profile
|
||
# From the LOGO variable we can know if Addigy og Mosyle is used, so if that variable
|
||
# is either of these, and this variable is empty, then we will auto detect this.
|
||
|
||
# Datadog logging used
|
||
datadogAPI=""
|
||
# Simply add your own API key for this in order to have logs sent to Datadog
|
||
# See more here: https://www.datadoghq.com/product/log-management/
|
||
|
||
# Log Date format used when parsing logs for debugging, this is the default used by
|
||
# install.log, override this in the case statements if you need something custom per
|
||
# application (See adobeillustrator). Using stadard GNU Date formatting.
|
||
LogDateFormat="%Y-%m-%d %H:%M:%S"
|
||
|
||
# Get the start time for parsing install.log if we fail.
|
||
starttime=$(date "+$LogDateFormat")
|
||
|
||
# Check if we have rosetta installed
|
||
if [[ $(/usr/bin/arch) == "arm64" ]]; then
|
||
if ! arch -x86_64 /usr/bin/true >/dev/null 2>&1; then # pgrep oahd >/dev/null 2>&1
|
||
rosetta2=no
|
||
fi
|
||
fi
|
||
VERSION="10.0beta2"
|
||
VERSIONDATE="2022-09-02"
|
||
|
||
# MARK: Functions
|
||
|
||
cleanupAndExit() { # $1 = exit code, $2 message, $3 level
|
||
if [ -n "$dmgmount" ]; then
|
||
# unmount disk image
|
||
printlog "Unmounting $dmgmount" DEBUG
|
||
unmountingOut=$(hdiutil detach "$dmgmount" 2>&1)
|
||
printlog "Debugging enabled, Unmounting output was:\n$unmountingOut" DEBUG
|
||
fi
|
||
if [ "$DEBUG" -ne 1 ]; then
|
||
# remove the temporary working directory when done (only if DEBUG is not used)
|
||
printlog "Deleting $tmpDir" DEBUG
|
||
deleteTmpOut=$(rm -Rfv "$tmpDir")
|
||
printlog "Debugging enabled, Deleting tmpDir output was:\n$deleteTmpOut" DEBUG
|
||
fi
|
||
|
||
|
||
# If we closed any processes, reopen the app again
|
||
reopenClosedProcess
|
||
if [[ -n $2 && $1 -ne 0 ]]; then
|
||
printlog "ERROR: $2" $3
|
||
updateDialog "fail" "Error ($1; $2)"
|
||
else
|
||
printlog "$2" $3
|
||
updateDialog "success" ""
|
||
fi
|
||
printlog "################## End Installomator, exit code $1 \n" REQ
|
||
|
||
# if label is wrong and we wanted name of the label, then return ##################
|
||
if [[ $RETURN_LABEL_NAME -eq 1 ]]; then
|
||
1=0 # If only label name should be returned we exit without any errors
|
||
echo "#"
|
||
fi
|
||
exit "$1"
|
||
}
|
||
|
||
runAsUser() {
|
||
if [[ $currentUser != "loginwindow" ]]; then
|
||
uid=$(id -u "$currentUser")
|
||
launchctl asuser $uid sudo -u $currentUser "$@"
|
||
fi
|
||
}
|
||
|
||
reloadAsUser() {
|
||
if [[ $currentUser != "loginwindow" ]]; then
|
||
uid=$(id -u "$currentUser")
|
||
su - $currentUser -c "${@}"
|
||
fi
|
||
}
|
||
|
||
displaydialog() { # $1: message $2: title
|
||
message=${1:-"Message"}
|
||
title=${2:-"Installomator"}
|
||
runAsUser osascript -e "button returned of (display dialog \"$message\" with title \"$title\" buttons {\"Not Now\", \"Quit and Update\"} default button \"Quit and Update\" with icon POSIX file \"$LOGO\")"
|
||
}
|
||
|
||
displaydialogContinue() { # $1: message $2: title
|
||
message=${1:-"Message"}
|
||
title=${2:-"Installomator"}
|
||
runAsUser osascript -e "button returned of (display dialog \"$message\" with title \"$title\" buttons {\"Quit and Update\"} default button \"Quit and Update\" with icon POSIX file \"$LOGO\")"
|
||
}
|
||
|
||
displaynotification() { # $1: message $2: title
|
||
message=${1:-"Message"}
|
||
title=${2:-"Notification"}
|
||
manageaction="/Library/Application Support/JAMF/bin/Management Action.app/Contents/MacOS/Management Action"
|
||
hubcli="/usr/local/bin/hubcli"
|
||
|
||
if [[ -x "$manageaction" ]]; then
|
||
"$manageaction" -message "$message" -title "$title"
|
||
elif [[ -x "$hubcli" ]]; then
|
||
"$hubcli" notify -t "$title" -i "$message" -c "Dismiss"
|
||
else
|
||
runAsUser osascript -e "display notification \"$message\" with title \"$title\""
|
||
fi
|
||
}
|
||
|
||
printlog(){
|
||
[ -z "$2" ] && 2=INFO
|
||
log_message=$1
|
||
log_priority=$2
|
||
timestamp=$(date +%F\ %T)
|
||
|
||
# Check to make sure that the log isn't the same as the last, if it is then don't log and increment a timer.
|
||
if [[ ${log_message} == ${previous_log_message} ]]; then
|
||
let logrepeat=$logrepeat+1
|
||
return
|
||
fi
|
||
previous_log_message=$log_message
|
||
|
||
# Once we finally stop getting duplicate logs output the number of times we got a duplicate.
|
||
if [[ $logrepeat -gt 1 ]];then
|
||
echo "$timestamp" : "${log_priority} : $label : Last Log repeated ${logrepeat} times" | tee -a $log_location
|
||
|
||
if [[ ! -z $datadogAPI ]]; then
|
||
curl -s -X POST https://http-intake.logs.datadoghq.com/v1/input -H "Content-Type: text/plain" -H "DD-API-KEY: $datadogAPI" -d "${log_priority} : $mdmURL : $APPLICATION : $VERSION : $SESSION : Last Log repeated ${logrepeat} times" > /dev/null
|
||
fi
|
||
logrepeat=0
|
||
fi
|
||
|
||
# If the datadogAPI key value is set and our logging level is greater than or equal to our set level
|
||
# then post to Datadog's HTTPs endpoint.
|
||
if [[ -n $datadogAPI && ${levels[$log_priority]} -ge ${levels[$datadogLoggingLevel]} ]]; then
|
||
while IFS= read -r logmessage; do
|
||
curl -s -X POST https://http-intake.logs.datadoghq.com/v1/input -H "Content-Type: text/plain" -H "DD-API-KEY: $datadogAPI" -d "${log_priority} : $mdmURL : Installomator-${label} : ${VERSIONDATE//-/} : $SESSION : ${logmessage}" > /dev/null
|
||
done <<< "$log_message"
|
||
fi
|
||
|
||
# Extra spaces
|
||
space_char=""
|
||
if [[ ${#log_priority} -eq 3 ]]; then
|
||
space_char=" "
|
||
elif [[ ${#log_priority} -eq 4 ]]; then
|
||
space_char=" "
|
||
fi
|
||
# If our logging level is greaterthan or equal to our set level then output locally.
|
||
if [[ ${levels[$log_priority]} -ge ${levels[$LOGGING]} ]]; then
|
||
while IFS= read -r logmessage; do
|
||
if [[ "$(whoami)" == "root" ]]; then
|
||
echo "$timestamp" : "${log_priority}${space_char} : $label : ${logmessage}" | tee -a $log_location
|
||
else
|
||
echo "$timestamp" : "${log_priority}${space_char} : $label : ${logmessage}"
|
||
fi
|
||
done <<< "$log_message"
|
||
fi
|
||
}
|
||
|
||
# Used to remove dupplicate lines in large log output,
|
||
# for example from msupdate command after it finishes running.
|
||
deduplicatelogs() {
|
||
loginput=${1:-"Log"}
|
||
logoutput=""
|
||
# Read each line of the incoming log individually, match it with the previous.
|
||
# If it matches increment logrepeate then skip to the next line.
|
||
while read log; do
|
||
if [[ $log == $previous_log ]];then
|
||
let logrepeat=$logrepeat+1
|
||
continue
|
||
fi
|
||
|
||
previous_log="$log"
|
||
if [[ $logrepeat -gt 1 ]];then
|
||
logoutput+="Last Log repeated ${logrepeat} times\n"
|
||
logrepeat=0
|
||
fi
|
||
|
||
logoutput+="$log\n"
|
||
done <<< "$loginput"
|
||
}
|
||
|
||
# will get the latest release download from a github repo
|
||
downloadURLFromGit() { # $1 git user name, $2 git repo name
|
||
gitusername=${1?:"no git user name"}
|
||
gitreponame=${2?:"no git repo name"}
|
||
|
||
if [[ $type == "pkgInDmg" ]]; then
|
||
filetype="dmg"
|
||
elif [[ $type == "pkgInZip" ]]; then
|
||
filetype="zip"
|
||
else
|
||
filetype=$type
|
||
fi
|
||
|
||
if [ -n "$archiveName" ]; then
|
||
#downloadURL=$(curl -L --silent --fail "https://api.github.com/repos/$gitusername/$gitreponame/releases/latest" | awk -F '"' "/browser_download_url/ && /$archiveName\"/ { print \$4; exit }")
|
||
downloadURL=https://github.com$(curl -sfL "https://github.com/$gitusername/$gitreponame/releases/latest" | tr '"' "\n" | grep -i "^/.*\/releases\/download\/.*$archiveName" | head -1)
|
||
else
|
||
#downloadURL=$(curl -L --silent --fail "https://api.github.com/repos/$gitusername/$gitreponame/releases/latest" | awk -F '"' "/browser_download_url/ && /$filetype\"/ { print \$4; exit }")
|
||
downloadURL=https://github.com$(curl -sfL "https://github.com/$gitusername/$gitreponame/releases/latest" | tr '"' "\n" | grep -i "^/.*\/releases\/download\/.*\.$filetype" | head -1)
|
||
fi
|
||
if [ -z "$downloadURL" ]; then
|
||
cleanupAndExit 14 "could not retrieve download URL for $gitusername/$gitreponame" ERROR
|
||
else
|
||
echo "$downloadURL"
|
||
return 0
|
||
fi
|
||
}
|
||
|
||
versionFromGit() {
|
||
# credit: Søren Theilgaard (@theilgaard)
|
||
# $1 git user name, $2 git repo name
|
||
gitusername=${1?:"no git user name"}
|
||
gitreponame=${2?:"no git repo name"}
|
||
|
||
#appNewVersion=$(curl -L --silent --fail "https://api.github.com/repos/$gitusername/$gitreponame/releases/latest" | grep tag_name | cut -d '"' -f 4 | sed 's/[^0-9\.]//g')
|
||
appNewVersion=$(curl -sLI "https://github.com/$gitusername/$gitreponame/releases/latest" | grep -i "^location" | tr "/" "\n" | tail -1 | sed 's/[^0-9\.]//g')
|
||
if [ -z "$appNewVersion" ]; then
|
||
printlog "could not retrieve version number for $gitusername/$gitreponame" WARN
|
||
appNewVersion=""
|
||
else
|
||
echo "$appNewVersion"
|
||
return 0
|
||
fi
|
||
}
|
||
|
||
|
||
# Handling of differences in xpath between Catalina and Big Sur
|
||
xpath() {
|
||
# the xpath tool changes in Big Sur and now requires the `-e` option
|
||
if [[ $(sw_vers -buildVersion) > "20A" ]]; then
|
||
/usr/bin/xpath -e $@
|
||
# alternative: switch to xmllint (which is not perl)
|
||
#xmllint --xpath $@ -
|
||
else
|
||
/usr/bin/xpath $@
|
||
fi
|
||
}
|
||
|
||
# from @Pico: https://macadmins.slack.com/archives/CGXNNJXJ9/p1652222365989229?thread_ts=1651786411.413349&cid=CGXNNJXJ9
|
||
getJSONValue() {
|
||
# $1: JSON string OR file path to parse (tested to work with up to 1GB string and 2GB file).
|
||
# $2: JSON key path to look up (using dot or bracket notation).
|
||
printf '%s' "$1" | /usr/bin/osascript -l 'JavaScript' \
|
||
-e "let json = $.NSString.alloc.initWithDataEncoding($.NSFileHandle.fileHandleWithStandardInput.readDataToEndOfFile$(/usr/bin/uname -r | /usr/bin/awk -F '.' '($1 > 18) { print "AndReturnError(ObjC.wrap())" }'), $.NSUTF8StringEncoding)" \
|
||
-e 'if ($.NSFileManager.defaultManager.fileExistsAtPath(json)) json = $.NSString.stringWithContentsOfFileEncodingError(json, $.NSUTF8StringEncoding, ObjC.wrap())' \
|
||
-e "const value = JSON.parse(json.js)$([ -n "${2%%[.[]*}" ] && echo '.')$2" \
|
||
-e 'if (typeof value === "object") { JSON.stringify(value, null, 4) } else { value }'
|
||
}
|
||
|
||
getAppVersion() {
|
||
# modified by: Søren Theilgaard (@theilgaard) and Isaac Ordonez
|
||
|
||
# If label contain function appCustomVersion, we use that and return
|
||
if type 'appCustomVersion' 2>/dev/null | grep -q 'function'; then
|
||
appversion=$(appCustomVersion)
|
||
printlog "Custom App Version detection is used, found $appversion"
|
||
return
|
||
fi
|
||
|
||
# pkgs contains a version number, then we don't have to search for an app
|
||
if [[ $packageID != "" ]]; then
|
||
appversion="$(pkgutil --pkg-info-plist ${packageID} 2>/dev/null | grep -A 1 pkg-version | tail -1 | sed -E 's/.*>([0-9.]*)<.*/\1/g')"
|
||
if [[ $appversion != "" ]]; then
|
||
printlog "found packageID $packageID installed, version $appversion"
|
||
updateDetected="YES"
|
||
return
|
||
else
|
||
printlog "No version found using packageID $packageID"
|
||
fi
|
||
fi
|
||
|
||
# get app in targetDir, /Applications, or /Applications/Utilities
|
||
if [[ -d "$targetDir/$appName" ]]; then
|
||
applist="$targetDir/$appName"
|
||
elif [[ -d "/Applications/$appName" ]]; then
|
||
applist="/Applications/$appName"
|
||
# if [[ $type =~ '^(dmg|zip|tbz|app.*)$' ]]; then
|
||
# targetDir="/Applications"
|
||
# fi
|
||
elif [[ -d "/Applications/Utilities/$appName" ]]; then
|
||
applist="/Applications/Utilities/$appName"
|
||
# if [[ $type =~ '^(dmg|zip|tbz|app.*)$' ]]; then
|
||
# targetDir="/Applications/Utilities"
|
||
# fi
|
||
else
|
||
# applist=$(mdfind "kind:application $appName" -0 )
|
||
printlog "name: $name, appName: $appName"
|
||
applist=$(mdfind "kind:application AND name:$name" -0 )
|
||
# printlog "App(s) found: ${applist}" DEBUG
|
||
# applist=$(mdfind "kind:application AND name:$appName" -0 )
|
||
fi
|
||
if [[ -z $applist ]]; then
|
||
printlog "No previous app found" WARN
|
||
else
|
||
printlog "App(s) found: ${applist}" INFO
|
||
fi
|
||
# if [[ $type =~ '^(dmg|zip|tbz|app.*)$' ]]; then
|
||
# printlog "targetDir for installation: $targetDir" INFO
|
||
# fi
|
||
|
||
appPathArray=( ${(0)applist} )
|
||
|
||
if [[ ${#appPathArray} -gt 0 ]]; then
|
||
filteredAppPaths=( ${(M)appPathArray:#${targetDir}*} )
|
||
if [[ ${#filteredAppPaths} -eq 1 ]]; then
|
||
installedAppPath=$filteredAppPaths[1]
|
||
#appversion=$(mdls -name kMDItemVersion -raw $installedAppPath )
|
||
appversion=$(defaults read $installedAppPath/Contents/Info.plist $versionKey) #Not dependant on Spotlight indexing
|
||
printlog "found app at $installedAppPath, version $appversion, on versionKey $versionKey"
|
||
updateDetected="YES"
|
||
# Is current app from App Store
|
||
if [[ -d "$installedAppPath"/Contents/_MASReceipt ]];then
|
||
printlog "Installed $appName is from App Store, use “IGNORE_APP_STORE_APPS=yes” to replace."
|
||
if [[ $IGNORE_APP_STORE_APPS == "yes" ]]; then
|
||
printlog "Replacing App Store apps, no matter the version" WARN
|
||
appversion=0
|
||
else
|
||
cleanupAndExit 23 "App previously installed from App Store, and we respect that" ERROR
|
||
fi
|
||
fi
|
||
else
|
||
printlog "could not determine location of $appName" WARN
|
||
fi
|
||
else
|
||
printlog "could not find $appName" WARN
|
||
fi
|
||
}
|
||
|
||
checkRunningProcesses() {
|
||
# don't check in DEBUG mode 1
|
||
if [[ $DEBUG -eq 1 ]]; then
|
||
printlog "DEBUG mode 1, not checking for blocking processes" DEBUG
|
||
return
|
||
fi
|
||
|
||
# try at most 3 times
|
||
for i in {1..4}; do
|
||
countedProcesses=0
|
||
for x in ${blockingProcesses}; do
|
||
if pgrep -xq "$x"; then
|
||
printlog "found blocking process $x"
|
||
appClosed=1
|
||
|
||
case $BLOCKING_PROCESS_ACTION in
|
||
quit|quit_kill)
|
||
printlog "telling app $x to quit"
|
||
runAsUser osascript -e "tell app \"$x\" to quit"
|
||
if [[ $i > 2 && $BLOCKING_PROCESS_ACTION = "quit_kill" ]]; then
|
||
printlog "Changing BLOCKING_PROCESS_ACTION to kill"
|
||
BLOCKING_PROCESS_ACTION=kill
|
||
else
|
||
# give the user a bit of time to quit apps
|
||
printlog "waiting 30 seconds for processes to quit"
|
||
sleep 30
|
||
fi
|
||
;;
|
||
kill)
|
||
printlog "killing process $x"
|
||
pkill $x
|
||
sleep 5
|
||
;;
|
||
prompt_user|prompt_user_then_kill)
|
||
button=$(displaydialog "Quit “$x” to continue updating? (Leave this dialogue if you want to activate this update later)." "The application “$x” needs to be updated.")
|
||
if [[ $button = "Not Now" ]]; then
|
||
cleanupAndExit 10 "user aborted update" ERROR
|
||
else
|
||
if [[ $i > 2 && $BLOCKING_PROCESS_ACTION = "prompt_user_then_kill" ]]; then
|
||
printlog "Changing BLOCKING_PROCESS_ACTION to kill"
|
||
BLOCKING_PROCESS_ACTION=kill
|
||
else
|
||
printlog "telling app $x to quit"
|
||
runAsUser osascript -e "tell app \"$x\" to quit"
|
||
# give the user a bit of time to quit apps
|
||
printlog "waiting 30 seconds for processes to quit"
|
||
sleep 30
|
||
fi
|
||
fi
|
||
;;
|
||
prompt_user_loop)
|
||
button=$(displaydialog "Quit “$x” to continue updating? (Click “Not Now” to be asked in 1 hour, or leave this open until you are ready)." "The application “$x” needs to be updated.")
|
||
if [[ $button = "Not Now" ]]; then
|
||
if [[ $i < 2 ]]; then
|
||
printlog "user wants to wait an hour"
|
||
sleep 3600 # 3600 seconds is an hour
|
||
else
|
||
printlog "change of BLOCKING_PROCESS_ACTION to tell_user"
|
||
BLOCKING_PROCESS_ACTION=tell_user
|
||
fi
|
||
else
|
||
printlog "telling app $x to quit"
|
||
runAsUser osascript -e "tell app \"$x\" to quit"
|
||
# give the user a bit of time to quit apps
|
||
printlog "waiting 30 seconds for processes to quit"
|
||
sleep 30
|
||
fi
|
||
;;
|
||
tell_user|tell_user_then_kill)
|
||
button=$(displaydialogContinue "Quit “$x” to continue updating? (This is an important update). Wait for notification of update before launching app again." "The application “$x” needs to be updated.")
|
||
printlog "telling app $x to quit"
|
||
runAsUser osascript -e "tell app \"$x\" to quit"
|
||
# give the user a bit of time to quit apps
|
||
printlog "waiting 30 seconds for processes to quit"
|
||
sleep 30
|
||
if [[ $i > 1 && $BLOCKING_PROCESS_ACTION = tell_user_then_kill ]]; then
|
||
printlog "Changing BLOCKING_PROCESS_ACTION to kill"
|
||
BLOCKING_PROCESS_ACTION=kill
|
||
fi
|
||
;;
|
||
silent_fail)
|
||
cleanupAndExit 12 "blocking process '$x' found, aborting" ERROR
|
||
;;
|
||
esac
|
||
|
||
countedProcesses=$((countedProcesses + 1))
|
||
fi
|
||
done
|
||
|
||
done
|
||
|
||
if [[ $countedProcesses -ne 0 ]]; then
|
||
cleanupAndExit 11 "could not quit all processes, aborting..." ERROR
|
||
fi
|
||
|
||
printlog "no more blocking processes, continue with update" REQ
|
||
}
|
||
|
||
reopenClosedProcess() {
|
||
# If Installomator closed any processes, let's get the app opened again
|
||
# credit: Søren Theilgaard (@theilgaard)
|
||
|
||
# don't reopen if REOPEN is not "yes"
|
||
if [[ $REOPEN != yes ]]; then
|
||
printlog "REOPEN=no, not reopening anything"
|
||
return
|
||
fi
|
||
|
||
# don't reopen in DEBUG mode 1
|
||
if [[ $DEBUG -eq 1 ]]; then
|
||
printlog "DEBUG mode 1, not reopening anything" DEBUG
|
||
return
|
||
fi
|
||
|
||
if [[ $appClosed == 1 ]]; then
|
||
printlog "Telling app $appName to open"
|
||
#runAsUser osascript -e "tell app \"$appName\" to open"
|
||
#runAsUser open -a "${appName}"
|
||
reloadAsUser "open -a \"${appName}\""
|
||
#reloadAsUser "open \"${(0)applist}\""
|
||
processuser=$(ps aux | grep -i "${appName}" | grep -vi "grep" | awk '{print $1}')
|
||
printlog "Reopened ${appName} as $processuser"
|
||
else
|
||
printlog "App not closed, so no reopen." INFO
|
||
fi
|
||
}
|
||
|
||
installAppWithPath() { # $1: path to app to install in $targetDir
|
||
# modified by: Søren Theilgaard (@theilgaard)
|
||
appPath=${1?:"no path to app"}
|
||
|
||
# check if app exists
|
||
if [ ! -e "$appPath" ]; then
|
||
cleanupAndExit 8 "could not find: $appPath" ERROR
|
||
fi
|
||
|
||
# verify with spctl
|
||
printlog "Verifying: $appPath" INFO
|
||
updateDialog "wait" "Verifying..."
|
||
printlog "App size: $(du -sh "$appPath")" DEBUG
|
||
appVerify=$(spctl -a -vv "$appPath" 2>&1 )
|
||
appVerifyStatus=$(echo $?)
|
||
teamID=$(echo $appVerify | awk '/origin=/ {print $NF }' | tr -d '()' )
|
||
deduplicatelogs "$appVerify"
|
||
|
||
if [[ $appVerifyStatus -ne 0 ]] ; then
|
||
#if ! teamID=$(spctl -a -vv "$appPath" 2>&1 | awk '/origin=/ {print $NF }' | tr -d '()' ); then
|
||
cleanupAndExit 4 "Error verifying $appPath error:\n$logoutput" ERROR
|
||
fi
|
||
printlog "Debugging enabled, App Verification output was:\n$logoutput" DEBUG
|
||
printlog "Team ID matching: $teamID (expected: $expectedTeamID )" INFO
|
||
|
||
if [ "$expectedTeamID" != "$teamID" ]; then
|
||
cleanupAndExit 5 "Team IDs do not match" ERROR
|
||
fi
|
||
|
||
# app versioncheck
|
||
appNewVersion=$(defaults read $appPath/Contents/Info.plist $versionKey)
|
||
if [[ -n $appNewVersion && $appversion == $appNewVersion ]]; then
|
||
printlog "Downloaded version of $name is $appNewVersion on versionKey $versionKey, same as installed."
|
||
if [[ $INSTALL != "force" ]]; then
|
||
message="$name, version $appNewVersion, is the latest version."
|
||
if [[ $currentUser != "loginwindow" && $NOTIFY == "all" ]]; then
|
||
printlog "notifying"
|
||
displaynotification "$message" "No update for $name!"
|
||
fi
|
||
cleanupAndExit 0 "No new version to install" REG
|
||
else
|
||
printlog "Using force to install anyway."
|
||
fi
|
||
elif [[ -z $appversion ]]; then
|
||
printlog "Installing $name version $appNewVersion on versionKey $versionKey."
|
||
else
|
||
printlog "Downloaded version of $name is $appNewVersion on versionKey $versionKey (replacing version $appversion)."
|
||
fi
|
||
|
||
# macOS versioncheck
|
||
minimumOSversion=$(defaults read $appPath/Contents/Info.plist LSMinimumSystemVersion 2>/dev/null )
|
||
if [[ -n $minimumOSversion && $minimumOSversion =~ '[0-9.]*' ]]; then
|
||
printlog "App has LSMinimumSystemVersion: $minimumOSversion"
|
||
if ! is-at-least $minimumOSversion $installedOSversion; then
|
||
printlog "App requires higher System Version than installed: $installedOSversion"
|
||
message="Cannot install $name, version $appNewVersion, as it is not compatible with the running system version."
|
||
if [[ $currentUser != "loginwindow" && $NOTIFY == "all" ]]; then
|
||
printlog "notifying"
|
||
displaynotification "$message" "Error updating $name!"
|
||
fi
|
||
cleanupAndExit 15 "Installed macOS is too old for this app." ERROR
|
||
fi
|
||
fi
|
||
|
||
# skip install for DEBUG 1
|
||
if [ "$DEBUG" -eq 1 ]; then
|
||
printlog "DEBUG mode 1 enabled, skipping remove, copy and chown steps" DEBUG
|
||
return 0
|
||
fi
|
||
|
||
# skip install for DEBUG 2
|
||
if [ "$DEBUG" -eq 2 ]; then
|
||
printlog "DEBUG mode 2 enabled, not installing anything, exiting" DEBUG
|
||
cleanupAndExit 0
|
||
fi
|
||
|
||
# Test if variable CLIInstaller is set
|
||
if [[ -z $CLIInstaller ]]; then
|
||
|
||
# remove existing application
|
||
if [ -e "$targetDir/$appName" ]; then
|
||
printlog "Removing existing $targetDir/$appName" WARN
|
||
deleteAppOut=$(rm -Rfv "$targetDir/$appName" 2>&1)
|
||
tempName="$targetDir/$appName"
|
||
tempNameLength=$((${#tempName} + 10))
|
||
deleteAppOut=$(echo $deleteAppOut | cut -c 1-$tempNameLength)
|
||
deduplicatelogs "$deleteAppOut"
|
||
printlog "Debugging enabled, App removing output was:\n$logoutput" DEBUG
|
||
fi
|
||
|
||
# copy app to /Applications
|
||
printlog "Copy $appPath to $targetDir"
|
||
copyAppOut=$(ditto -v "$appPath" "$targetDir/$appName" 2>&1)
|
||
copyAppStatus=$(echo $?)
|
||
deduplicatelogs "$copyAppOut"
|
||
printlog "Debugging enabled, App copy output was:\n$logoutput" DEBUG
|
||
if [[ $copyAppStatus -ne 0 ]] ; then
|
||
#if ! ditto "$appPath" "$targetDir/$appName"; then
|
||
cleanupAndExit 7 "Error while copying:\n$logoutput" ERROR
|
||
fi
|
||
|
||
# set ownership to current user
|
||
if [[ "$currentUser" != "loginwindow" && $SYSTEMOWNER -ne 1 ]]; then
|
||
printlog "Changing owner to $currentUser" WARN
|
||
chown -R "$currentUser" "$targetDir/$appName"
|
||
else
|
||
printlog "No user logged in or SYSTEMOWNER=1, setting owner to root:wheel" WARN
|
||
chown -R root:wheel "$targetDir/$appName"
|
||
fi
|
||
|
||
elif [[ ! -z $CLIInstaller ]]; then
|
||
mountname=$(dirname $appPath)
|
||
printlog "CLIInstaller exists, running installer command $mountname/$CLIInstaller $CLIArguments" INFO
|
||
|
||
CLIoutput=$("$mountname/$CLIInstaller" "${CLIArguments[@]}" 2>&1)
|
||
CLIstatus=$(echo $?)
|
||
deduplicatelogs "$CLIoutput"
|
||
|
||
if [ $CLIstatus -ne 0 ] ; then
|
||
cleanupAndExit 16 "Error installing $mountname/$CLIInstaller $CLIArguments error:\n$logoutput" ERROR
|
||
else
|
||
printlog "Succesfully ran $mountname/$CLIInstaller $CLIArguments" INFO
|
||
fi
|
||
printlog "Debugging enabled, update tool output was:\n$logoutput" DEBUG
|
||
fi
|
||
|
||
}
|
||
|
||
mountDMG() {
|
||
# mount the dmg
|
||
printlog "Mounting $tmpDir/$archiveName"
|
||
# always pipe 'Y\n' in case the dmg requires an agreement
|
||
dmgmountOut=$(echo 'Y'$'\n' | hdiutil attach "$tmpDir/$archiveName" -nobrowse -readonly )
|
||
dmgmountStatus=$(echo $?)
|
||
dmgmount=$(echo $dmgmountOut | tail -n 1 | cut -c 54- )
|
||
deduplicatelogs "$dmgmountOut"
|
||
|
||
if [[ $dmgmountStatus -ne 0 ]] ; then
|
||
#if ! dmgmount=$(echo 'Y'$'\n' | hdiutil attach "$tmpDir/$archiveName" -nobrowse -readonly | tail -n 1 | cut -c 54- ); then
|
||
cleanupAndExit 3 "Error mounting $tmpDir/$archiveName error:\n$logoutput" ERROR
|
||
fi
|
||
if [[ ! -e $dmgmount ]]; then
|
||
cleanupAndExit 3 "Error accessing mountpoint for $tmpDir/$archiveName error:\n$logoutput" ERROR
|
||
fi
|
||
printlog "Debugging enabled, dmgmount output was:\n$logoutput" DEBUG
|
||
|
||
printlog "Mounted: $dmgmount" INFO
|
||
}
|
||
|
||
installFromDMG() {
|
||
mountDMG
|
||
installAppWithPath "$dmgmount/$appName"
|
||
}
|
||
|
||
installFromPKG() {
|
||
# verify with spctl
|
||
printlog "Verifying: $archiveName"
|
||
updateDialog "wait" "Verifying..."
|
||
printlog "File list: $(ls -lh "$archiveName")" DEBUG
|
||
printlog "File type: $(file "$archiveName")" DEBUG
|
||
spctlOut=$(spctl -a -vv -t install "$archiveName" 2>&1 )
|
||
spctlStatus=$(echo $?)
|
||
printlog "spctlOut is $spctlOut" DEBUG
|
||
|
||
teamID=$(echo $spctlOut | awk -F '(' '/origin=/ {print $2 }' | tr -d '()' )
|
||
# Apple signed software has no teamID, grab entire origin instead
|
||
if [[ -z $teamID ]]; then
|
||
teamID=$(echo $spctlOut | awk -F '=' '/origin=/ {print $NF }')
|
||
fi
|
||
|
||
deduplicatelogs "$spctlOut"
|
||
|
||
if [[ $spctlStatus -ne 0 ]] ; then
|
||
#if ! spctlout=$(spctl -a -vv -t install "$archiveName" 2>&1 ); then
|
||
cleanupAndExit 4 "Error verifying $archiveName error:\n$logoutput" ERROR
|
||
fi
|
||
|
||
# Apple signed software has no teamID, grab entire origin instead
|
||
if [[ -z $teamID ]]; then
|
||
teamID=$(echo $spctlout | awk -F '=' '/origin=/ {print $NF }')
|
||
fi
|
||
|
||
printlog "Team ID: $teamID (expected: $expectedTeamID )"
|
||
|
||
if [ "$expectedTeamID" != "$teamID" ]; then
|
||
cleanupAndExit 5 "Team IDs do not match!" ERROR
|
||
fi
|
||
|
||
# Check version of pkg to be installed if packageID is set
|
||
if [[ $packageID != "" && $appversion != "" ]]; then
|
||
printlog "Checking package version."
|
||
baseArchiveName=$(basename $archiveName)
|
||
expandedPkg="$tmpDir/${baseArchiveName}_pkg"
|
||
pkgutil --expand "$archiveName" "$expandedPkg"
|
||
appNewVersion=$(cat "$expandedPkg"/Distribution | xpath 'string(//installer-gui-script/pkg-ref[@id][@version]/@version)' 2>/dev/null )
|
||
rm -r "$expandedPkg"
|
||
printlog "Downloaded package $packageID version $appNewVersion"
|
||
if [[ $appversion == $appNewVersion ]]; then
|
||
printlog "Downloaded version of $name is the same as installed."
|
||
if [[ $INSTALL != "force" ]]; then
|
||
message="$name, version $appNewVersion, is the latest version."
|
||
if [[ $currentUser != "loginwindow" && $NOTIFY == "all" ]]; then
|
||
printlog "notifying"
|
||
displaynotification "$message" "No update for $name!"
|
||
fi
|
||
cleanupAndExit 0 "No new version to install" REQ
|
||
else
|
||
printlog "Using force to install anyway."
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
# skip install for DEBUG 1
|
||
if [ "$DEBUG" -eq 1 ]; then
|
||
printlog "DEBUG enabled, skipping installation" DEBUG
|
||
return 0
|
||
fi
|
||
|
||
# skip install for DEBUG 2
|
||
if [ "$DEBUG" -eq 2 ]; then
|
||
cleanupAndExit 0 "DEBUG mode 2 enabled, exiting" DEBUG
|
||
fi
|
||
|
||
# install pkg
|
||
printlog "Installing $archiveName to $targetDir"
|
||
|
||
if [[ $DIALOG_CMD_FILE != "" ]]; then
|
||
# pipe
|
||
pipe="$tmpDir/installpipe"
|
||
# initialise named pipe for installer output
|
||
initNamedPipe create $pipe
|
||
|
||
# run the pipe read in the background
|
||
readPKGInstallPipe $pipe "$DIALOG_CMD_FILE" & installPipePID=$!
|
||
printlog "listening to output of installer with pipe $pipe and command file $DIALOG_CMD_FILE on PID $installPipePID" DEBUG
|
||
|
||
pkgInstall=$(installer -verboseR -pkg "$archiveName" -tgt "$targetDir" 2>&1 | tee $pipe)
|
||
pkgInstallStatus=$pipestatus[1]
|
||
# because we are tee-ing the output, we want the pipe status of the first command in the chain, not the most recent one
|
||
killProcess $installPipePID
|
||
|
||
else
|
||
pkgInstall=$(installer -verbose -dumplog -pkg "$archiveName" -tgt "$targetDir" 2>&1)
|
||
pkgInstallStatus=$(echo $?)
|
||
fi
|
||
|
||
|
||
|
||
sleep 1
|
||
pkgEndTime=$(date "+$LogDateFormat")
|
||
pkgInstall+=$(echo "\nOutput of /var/log/install.log below this line.\n")
|
||
pkgInstall+=$(echo "----------------------------------------------------------\n")
|
||
pkgInstall+=$(awk -v "b=$starttime" -v "e=$pkgEndTime" -F ',' '$1 >= b && $1 <= e' /var/log/install.log)
|
||
deduplicatelogs "$pkgInstall"
|
||
|
||
if [[ $pkgInstallStatus -ne 0 ]] && [[ $logoutput == *"requires Rosetta 2"* ]] && [[ $rosetta2 == no ]]; then
|
||
printlog "Package requires Rosetta 2, Installing Rosetta 2 and Installing Package" INFO
|
||
/usr/sbin/softwareupdate --install-rosetta --agree-to-license
|
||
rosetta2=yes
|
||
installFromPKG
|
||
fi
|
||
|
||
if [[ $pkginstallstatus -ne 0 ]] ; then
|
||
#if ! installer -pkg "$archiveName" -tgt "$targetDir" ; then
|
||
cleanupAndExit 9 "Error installing $archiveName error:\n$logoutput" ERROR
|
||
fi
|
||
printlog "Debugging enabled, installer output was:\n$logoutput" DEBUG
|
||
}
|
||
|
||
installFromZIP() {
|
||
# unzip the archive
|
||
printlog "Unzipping $archiveName"
|
||
|
||
# tar -xf "$archiveName"
|
||
|
||
# note: when you expand a zip using tar in Mojave the expanded
|
||
# app will never pass the spctl check
|
||
|
||
# unzip -o -qq "$archiveName"
|
||
|
||
# note: githubdesktop fails spctl verification when expanded
|
||
# with unzip
|
||
|
||
ditto -x -k "$archiveName" "$tmpDir"
|
||
installAppWithPath "$tmpDir/$appName"
|
||
}
|
||
|
||
installFromTBZ() {
|
||
# unzip the archive
|
||
printlog "Unzipping $archiveName"
|
||
tar -xf "$archiveName"
|
||
installAppWithPath "$tmpDir/$appName"
|
||
}
|
||
|
||
installPkgInDmg() {
|
||
mountDMG
|
||
# locate pkg in dmg
|
||
if [[ -z $pkgName ]]; then
|
||
# find first file ending with 'pkg'
|
||
findfiles=$(find "$dmgmount" -iname "*.pkg" -type f -maxdepth 1 )
|
||
printlog "Found pkg(s):\n$findfiles" DEBUG
|
||
filearray=( ${(f)findfiles} )
|
||
if [[ ${#filearray} -eq 0 ]]; then
|
||
cleanupAndExit 20 "couldn't find pkg in dmg $archiveName" ERROR
|
||
fi
|
||
archiveName="${filearray[1]}"
|
||
else
|
||
if [[ -s "$dmgmount/$pkgName" ]] ; then # was: $tmpDir
|
||
archiveName="$dmgmount/$pkgName"
|
||
else
|
||
# try searching for pkg
|
||
findfiles=$(find "$dmgmount" -iname "$pkgName") # was: $tmpDir
|
||
printlog "Found pkg(s):\n$findfiles" DEBUG
|
||
filearray=( ${(f)findfiles} )
|
||
if [[ ${#filearray} -eq 0 ]]; then
|
||
cleanupAndExit 20 "couldn't find pkg “$pkgName” in dmg $archiveName" ERROR
|
||
fi
|
||
# it is now safe to overwrite archiveName for installFromPKG
|
||
archiveName="${filearray[1]}"
|
||
fi
|
||
fi
|
||
printlog "found pkg: $archiveName"
|
||
|
||
# installFromPkgs
|
||
installFromPKG
|
||
}
|
||
|
||
installPkgInZip() {
|
||
# unzip the archive
|
||
printlog "Unzipping $archiveName"
|
||
tar -xf "$archiveName"
|
||
|
||
# locate pkg in zip
|
||
if [[ -z $pkgName ]]; then
|
||
# find first file ending with 'pkg'
|
||
findfiles=$(find "$tmpDir" -iname "*.pkg" -type f -maxdepth 2 )
|
||
printlog "Found pkg(s):\n$findfiles" DEBUG
|
||
filearray=( ${(f)findfiles} )
|
||
if [[ ${#filearray} -eq 0 ]]; then
|
||
cleanupAndExit 21 "couldn't find pkg in zip $archiveName" ERROR
|
||
fi
|
||
# it is now safe to overwrite archiveName for installFromPKG
|
||
archiveName="${filearray[1]}"
|
||
printlog "found pkg: $archiveName"
|
||
else
|
||
if [[ -s "$tmpDir/$pkgName" ]]; then
|
||
archiveName="$tmpDir/$pkgName"
|
||
else
|
||
# try searching for pkg
|
||
findfiles=$(find "$tmpDir" -iname "$pkgName")
|
||
filearray=( ${(f)findfiles} )
|
||
if [[ ${#filearray} -eq 0 ]]; then
|
||
cleanupAndExit 21 "couldn't find pkg “$pkgName” in zip $archiveName" ERROR
|
||
fi
|
||
# it is now safe to overwrite archiveName for installFromPKG
|
||
archiveName="${filearray[1]}"
|
||
printlog "found pkg: $archiveName"
|
||
fi
|
||
fi
|
||
|
||
# installFromPkgs
|
||
installFromPKG
|
||
}
|
||
|
||
installAppInDmgInZip() {
|
||
# unzip the archive
|
||
printlog "Unzipping $archiveName"
|
||
tar -xf "$archiveName"
|
||
|
||
# locate dmg in zip
|
||
if [[ -z $pkgName ]]; then
|
||
# find first file ending with 'dmg'
|
||
findfiles=$(find "$tmpDir" -iname "*.dmg" -maxdepth 2 )
|
||
filearray=( ${(f)findfiles} )
|
||
if [[ ${#filearray} -eq 0 ]]; then
|
||
cleanupAndExit 22 "couldn't find dmg in zip $archiveName" ERROR
|
||
fi
|
||
archiveName="$(basename ${filearray[1]})"
|
||
# it is now safe to overwrite archiveName for installFromDMG
|
||
printlog "found dmg: $tmpDir/$archiveName"
|
||
else
|
||
# it is now safe to overwrite archiveName for installFromDMG
|
||
archiveName="$pkgName"
|
||
fi
|
||
|
||
# installFromDMG, DMG expected to include an app (will not work with pkg)
|
||
installFromDMG
|
||
}
|
||
|
||
runUpdateTool() {
|
||
printlog "Function called: runUpdateTool"
|
||
if [[ -x $updateTool ]]; then
|
||
printlog "running $updateTool $updateToolArguments"
|
||
if [[ -n $updateToolRunAsCurrentUser ]]; then
|
||
updateOutput=$(runAsUser $updateTool ${updateToolArguments} 2>&1)
|
||
updateStatus=$(echo $?)
|
||
else
|
||
updateOutput=$($updateTool ${updateToolArguments} 2>&1)
|
||
updateStatus=$(echo $?)
|
||
fi
|
||
sleep 1
|
||
updateEndTime=$(date "+$updateToolLogDateFormat")
|
||
deduplicatelogs $updateOutput
|
||
if [[ -n $updateToolLog ]]; then
|
||
updateOutput+=$(echo "Output of Installer log of $updateToolLog below this line.\n")
|
||
updateOutput+=$(echo "----------------------------------------------------------\n")
|
||
updateOutput+=$(awk -v "b=$updatestarttime" -v "e=$updateEndTime" -F ',' '$1 >= b && $1 <= e' $updateToolLog)
|
||
fi
|
||
|
||
if [[ $updateStatus -ne 0 ]]; then
|
||
printlog "Error running $updateTool, Procceding with normal installation. Exit Status: $updateStatus Error:\n$logoutput" WARN
|
||
return 1
|
||
if [[ $type == updateronly ]]; then
|
||
cleanupAndExit 77 "No Download URL Set, this is an update only application and the updater failed" ERROR
|
||
fi
|
||
elif [[ $updateStatus -eq 0 ]]; then
|
||
printlog "Debugging enabled, update tool output was:\n$logoutput" DEBUG
|
||
fi
|
||
else
|
||
printlog "couldn't find $updateTool, continuing normally" WARN
|
||
return 1
|
||
fi
|
||
return 0
|
||
}
|
||
|
||
finishing() {
|
||
printlog "Finishing..."
|
||
|
||
sleep 3 # wait a moment to let spotlight catch up
|
||
getAppVersion
|
||
|
||
if [[ -z $appversion ]]; then
|
||
message="Installed $name"
|
||
else
|
||
message="Installed $name, version $appversion"
|
||
fi
|
||
|
||
printlog "$message" REQ
|
||
|
||
if [[ $currentUser != "loginwindow" && ( $NOTIFY == "success" || $NOTIFY == "all" ) ]]; then
|
||
printlog "notifying"
|
||
if [[ $updateDetected == "YES" ]]; then
|
||
displaynotification "$message" "$name update complete!"
|
||
else
|
||
displaynotification "$message" "$name installation complete!"
|
||
fi
|
||
fi
|
||
}
|
||
|
||
# Detect if there is an app actively making a display sleep assertion, e.g.
|
||
# KeyNote, PowerPoint, Zoom, or Webex.
|
||
# See: https://developer.apple.com/documentation/iokit/iopmlib_h/iopmassertiontypes
|
||
hasDisplaySleepAssertion() {
|
||
# Get the names of all apps with active display sleep assertions
|
||
local apps="$(/usr/bin/pmset -g assertions | /usr/bin/awk '/NoDisplaySleepAssertion | PreventUserIdleDisplaySleep/ && match($0,/\(.+\)/) && ! /coreaudiod/ {gsub(/^.*\(/,"",$0); gsub(/\).*$/,"",$0); print};')"
|
||
|
||
if [[ ! "${apps}" ]]; then
|
||
# No display sleep assertions detected
|
||
return 1
|
||
fi
|
||
|
||
# Create an array of apps that need to be ignored
|
||
local ignore_array=("${(@s/,/)IGNORE_DND_APPS}")
|
||
|
||
for app in ${(f)apps}; do
|
||
if (( ! ${ignore_array[(Ie)${app}]} )); then
|
||
# Relevant app with display sleep assertion detected
|
||
printlog "Display sleep assertion detected by ${app}."
|
||
return 0
|
||
fi
|
||
done
|
||
|
||
# No relevant display sleep assertion detected
|
||
return 1
|
||
}
|
||
|
||
initNamedPipe() {
|
||
# create or delete a named pipe
|
||
# commands are "create" or "delete"
|
||
|
||
local cmd=$1
|
||
local pipe=$2
|
||
case $cmd in
|
||
"create")
|
||
if [[ -e $pipe ]]; then
|
||
rm $pipe
|
||
fi
|
||
# make named pipe
|
||
mkfifo -m 644 $pipe
|
||
;;
|
||
"delete")
|
||
# clean up
|
||
rm $pipe
|
||
;;
|
||
*)
|
||
;;
|
||
esac
|
||
}
|
||
|
||
readDownloadPipe() {
|
||
# reads from a previously created named pipe
|
||
# output from curl with --progress-bar. % downloaded is read in and then sent to the specified log file
|
||
local pipe=$1
|
||
local log=${2:-$DIALOG_CMD_FILE}
|
||
# set up read from pipe
|
||
while IFS= read -k 1 -u 0 char; do
|
||
if [[ $char =~ [0-9] ]]; then
|
||
keep=1
|
||
fi
|
||
|
||
if [[ $char == % ]]; then
|
||
updateDialog $progress "Downloading..."
|
||
progress=""
|
||
keep=0
|
||
fi
|
||
|
||
if [[ $keep == 1 ]]; then
|
||
progress="$progress$char"
|
||
fi
|
||
done < $pipe
|
||
}
|
||
|
||
readPKGInstallPipe() {
|
||
# reads from a previously created named pipe
|
||
# output from installer with -verboseR. % install status is read in and then sent to the specified log file
|
||
local pipe=$1
|
||
local log=${2:-$DIALOG_CMD_FILE}
|
||
local appname=${3:-$name}
|
||
|
||
while read -k 1 -u 0 char; do
|
||
if [[ $char == % ]]; then
|
||
keep=1
|
||
fi
|
||
if [[ $char =~ [0-9] && $keep == 1 ]]; then
|
||
progress="$progress$char"
|
||
fi
|
||
if [[ $char == . && $keep == 1 ]]; then
|
||
updateDialog $progress "Installing..."
|
||
progress=""
|
||
keep=0
|
||
fi
|
||
done < $pipe
|
||
}
|
||
|
||
killProcess() {
|
||
# will silently kill the specified PID
|
||
builtin kill $1 2>/dev/null
|
||
}
|
||
|
||
updateDialog() {
|
||
local state=$1
|
||
local message=$2
|
||
local listitem=${3:-$DIALOG_LIST_ITEM_NAME}
|
||
local cmd_file=${4:-$DIALOG_CMD_FILE}
|
||
local progress=""
|
||
|
||
if [[ $state =~ '^[0-9]' \
|
||
|| $state == "reset" \
|
||
|| $state == "increment" \
|
||
|| $state == "complete" \
|
||
|| $state == "indeterminate" ]]; then
|
||
progress=$state
|
||
fi
|
||
|
||
# when to cmdfile is set, do nothing
|
||
if [[ $cmd_file == "" ]]; then
|
||
return
|
||
fi
|
||
|
||
if [[ $listitem == "" ]]; then
|
||
# no listitem set, update main progress bar and progress text
|
||
if [[ $progress != "" ]]; then
|
||
echo "progress: $progress" >> $cmd_file
|
||
fi
|
||
if [[ $message != "" ]]; then
|
||
echo "progresstext: $name - $message" >> $cmd_file
|
||
fi
|
||
else
|
||
# list item has a value, so we update the progress and text in the list
|
||
if [[ $progress != "" ]]; then
|
||
echo "listitem: title: $listitem, statustext: $message, progress: $progress" >> $cmd_file
|
||
else
|
||
echo "listitem: title: $listitem, statustext: $message, status: $state" >> $cmd_file
|
||
fi
|
||
fi
|
||
}
|
||
|
||
|
||
|
||
# MARK: argument parsing
|
||
if [[ $# -eq 0 ]]; then
|
||
if [[ -z $label ]]; then # check if label is set inside script
|
||
printlog "no label provided, printing labels" REQ
|
||
grep -E '^[a-z0-9\_-]*(\)|\|\\)$' "$0" | tr -d ')|\' | grep -v -E '^(broken.*|longversion|version|valuesfromarguments)$' | sort
|
||
#grep -E '^[a-z0-9\_-]*(\)|\|\\)$' "${labelFile}" | tr -d ')|\' | grep -v -E '^(broken.*|longversion|version|valuesfromarguments)$' | sort
|
||
exit 0
|
||
fi
|
||
elif [[ $1 == "/" ]]; then
|
||
# jamf uses sends '/' as the first argument
|
||
printlog "shifting arguments for Jamf" REQ
|
||
shift 3
|
||
fi
|
||
|
||
while [[ -n $1 ]]; do
|
||
if [[ $1 =~ ".*\=.*" ]]; then
|
||
# if an argument contains an = character, send it to eval
|
||
printlog "setting variable from argument $1" INFO
|
||
eval $1
|
||
else
|
||
# assume it's a label
|
||
label=$1
|
||
fi
|
||
# shift to next argument
|
||
shift 1
|
||
done
|
||
|
||
# lowercase the label
|
||
label=${label:l}
|
||
|
||
# separate check for 'version' in order to print plain version number without any other information
|
||
if [[ $label == "version" ]]; then
|
||
echo "$VERSION"
|
||
exit 0
|
||
fi
|
||
|
||
# MARK: Logging
|
||
log_location="/private/var/log/Installomator.log"
|
||
|
||
# Check if we're in debug mode, if so then set logging to DEBUG, otherwise default to INFO
|
||
# if no log level is specified.
|
||
if [[ $DEBUG -ne 0 ]]; then
|
||
LOGGING=DEBUG
|
||
elif [[ -z $LOGGING ]]; then
|
||
LOGGING=INFO
|
||
datadogLoggingLevel=INFO
|
||
fi
|
||
|
||
# Associate logging levels with a numerical value so that we are able to identify what
|
||
# should be removed. For example if the LOGGING=ERROR only printlog statements with the
|
||
# level REQ and ERROR will be displayed. LOGGING=DEBUG will show all printlog statements.
|
||
# If a printlog statement has no level set it's automatically assigned INFO.
|
||
|
||
declare -A levels=(DEBUG 0 INFO 1 WARN 2 ERROR 3 REQ 4)
|
||
|
||
# If we are able to detect an MDM URL (Jamf Pro) or another identifier for a customer/instance we grab it here, this is useful if we're centrally logging multiple MDM instances.
|
||
if [[ -f /Library/Preferences/com.jamfsoftware.jamf.plist ]]; then
|
||
mdmURL=$(defaults read /Library/Preferences/com.jamfsoftware.jamf.plist jss_url)
|
||
elif [[ -n "$MDMProfileName" ]]; then
|
||
mdmURL=$(sudo profiles show | grep -A3 "$MDMProfileName" | sed -n -e 's/^.*organization: //p')
|
||
else
|
||
mdmURL="Unknown"
|
||
fi
|
||
|
||
# Generate a session key for this run, this is useful to idenify streams when we're centrally logging.
|
||
SESSION=$RANDOM
|
||
|
||
# Mark: START
|
||
printlog "################## Start Installomator v. $VERSION, date $VERSIONDATE" REQ
|
||
printlog "################## Version: $VERSION" INFO
|
||
printlog "################## Date: $VERSIONDATE" INFO
|
||
printlog "################## $label" INFO
|
||
|
||
# Check for DEBUG mode
|
||
if [[ $DEBUG -gt 0 ]]; then
|
||
printlog "DEBUG mode $DEBUG enabled." DEBUG
|
||
fi
|
||
|
||
# How we get version number from app
|
||
if [[ -z $versionKey ]]; then
|
||
versionKey="CFBundleShortVersionString"
|
||
fi
|
||
|
||
# get current user
|
||
currentUser=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }')
|
||
|
||
# MARK: check for root
|
||
if [[ "$(whoami)" != "root" && "$DEBUG" -eq 0 ]]; then
|
||
# not running as root
|
||
cleanupAndExit 6 "not running as root, exiting" ERROR
|
||
fi
|
||
|
||
|
||
# check Swift Dialog presence and version
|
||
DIALOG_CMD="/usr/local/bin/dialog"
|
||
|
||
if [[ ! -x $DIALOG_CMD ]]; then
|
||
# Swift Dialog is not installed, clear cmd file variable to ignore
|
||
printlog "SwiftDialog is not installed, clear cmd file var"
|
||
DIALOG_CMD_FILE=""
|
||
fi
|
||
|
||
# MARK: labels in case statement
|
||
case $label in
|
||
longversion)
|
||
# print the script version
|
||
printlog "Installomater: version $VERSION ($VERSIONDATE)" REQ
|
||
exit 0
|
||
;;
|
||
valuesfromarguments)
|
||
if [[ -z $name ]]; then
|
||
printlog "need to provide 'name'" ERROR
|
||
exit 1
|
||
fi
|
||
if [[ -z $type ]]; then
|
||
printlog "need to provide 'type'" ERROR
|
||
exit 1
|
||
fi
|
||
if [[ -z $downloadURL ]]; then
|
||
printlog "need to provide 'downloadURL'" ERROR
|
||
exit 1
|
||
fi
|
||
if [[ -z $expectedTeamID ]]; then
|
||
printlog "need to provide 'expectedTeamID'" ERROR
|
||
exit 1
|
||
fi
|
||
;;
|
||
|
||
# label descriptions start here
|
||
hpclicknp)
|
||
name="HP Click"
|
||
type="dmg"
|
||
downloadURL="https://ftp.hp.com/pub/softlib/software13/hpdesignjetclick/HPClick-3.5.263.dmg"
|
||
expectedTeamID="6HB5Y2QTA3"
|
||
;;
|
||
*)
|
||
# unknown label
|
||
#printlog "unknown label $label"
|
||
cleanupAndExit 1 "unknown label $label" ERROR
|
||
;;
|
||
esac
|
||
|
||
# Are we only asked to return label name
|
||
if [[ $RETURN_LABEL_NAME -eq 1 ]]; then
|
||
printlog "Only returning label name." REQ
|
||
printlog "$name"
|
||
echo "$name"
|
||
exit
|
||
fi
|
||
|
||
# MARK: application download and installation starts here
|
||
|
||
# Debug output of all variables in a label
|
||
printlog "name=${name}" DEBUG
|
||
printlog "appName=${appName}" DEBUG
|
||
printlog "type=${type}" DEBUG
|
||
printlog "archiveName=${archiveName}" DEBUG
|
||
printlog "downloadURL=${downloadURL}" DEBUG
|
||
printlog "curlOptions=${curlOptions}" DEBUG
|
||
printlog "appNewVersion=${appNewVersion}" DEBUG
|
||
printlog "appCustomVersion function: $(if type 'appCustomVersion' 2>/dev/null | grep -q 'function'; then echo "Defined. ${appCustomVersion}"; else; echo "Not defined"; fi)" DEBUG
|
||
printlog "versionKey=${versionKey}" DEBUG
|
||
printlog "packageID=${packageID}" DEBUG
|
||
printlog "pkgName=${pkgName}" DEBUG
|
||
printlog "choiceChangesXML=${choiceChangesXML}" DEBUG
|
||
printlog "expectedTeamID=${expectedTeamID}" DEBUG
|
||
printlog "blockingProcesses=${blockingProcesses}" DEBUG
|
||
printlog "installerTool=${installerTool}" DEBUG
|
||
printlog "CLIInstaller=${CLIInstaller}" DEBUG
|
||
printlog "CLIArguments=${CLIArguments}" DEBUG
|
||
printlog "updateTool=${updateTool}" DEBUG
|
||
printlog "updateToolArguments=${updateToolArguments}" DEBUG
|
||
printlog "updateToolRunAsCurrentUser=${updateToolRunAsCurrentUser}" DEBUG
|
||
#printlog "Company=${Company}" DEBUG # Not used
|
||
|
||
if [[ ${INTERRUPT_DND} = "no" ]]; then
|
||
# Check if a fullscreen app is active
|
||
if hasDisplaySleepAssertion; then
|
||
cleanupAndExit 24 "active display sleep assertion detected, aborting" ERROR
|
||
fi
|
||
fi
|
||
|
||
printlog "BLOCKING_PROCESS_ACTION=${BLOCKING_PROCESS_ACTION}"
|
||
printlog "NOTIFY=${NOTIFY}"
|
||
printlog "LOGGING=${LOGGING}"
|
||
|
||
# Finding LOGO to use in dialogs
|
||
case $LOGO in
|
||
appstore)
|
||
# Apple App Store on Mac
|
||
if [[ $(sw_vers -buildVersion) > "19" ]]; then
|
||
LOGO="/System/Applications/App Store.app/Contents/Resources/AppIcon.icns"
|
||
else
|
||
LOGO="/Applications/App Store.app/Contents/Resources/AppIcon.icns"
|
||
fi
|
||
;;
|
||
jamf)
|
||
# Jamf Pro
|
||
LOGO="/Library/Application Support/JAMF/Jamf.app/Contents/Resources/AppIcon.icns"
|
||
;;
|
||
mosyleb)
|
||
# Mosyle Business
|
||
LOGO="/Applications/Self-Service.app/Contents/Resources/AppIcon.icns"
|
||
if [[ -z $MDMProfileName ]]; then; MDMProfileName="Mosyle Corporation MDM"; fi
|
||
;;
|
||
mosylem)
|
||
# Mosyle Manager (education)
|
||
LOGO="/Applications/Manager.app/Contents/Resources/AppIcon.icns"
|
||
if [[ -z $MDMProfileName ]]; then; MDMProfileName="Mosyle Corporation MDM"; fi
|
||
;;
|
||
addigy)
|
||
# Addigy
|
||
LOGO="/Library/Addigy/macmanage/MacManage.app/Contents/Resources/atom.icns"
|
||
if [[ -z $MDMProfileName ]]; then; MDMProfileName="MDM Profile"; fi
|
||
;;
|
||
microsoft)
|
||
# Microsoft Endpoint Manager (Intune)
|
||
LOGO="/Library/Intune/Microsoft Intune Agent.app/Contents/Resources/AppIcon.icns"
|
||
if [[ -z $MDMProfileName ]]; then; MDMProfileName="Management Profile"; fi
|
||
;;
|
||
ws1)
|
||
# Workspace ONE (AirWatch)
|
||
LOGO="/Applications/Workspace ONE Intelligent Hub.app/Contents/Resources/AppIcon.icns"
|
||
if [[ -z $MDMProfileName ]]; then; MDMProfileName="Device Manager"; fi
|
||
;;
|
||
esac
|
||
if [[ ! -a "${LOGO}" ]]; then
|
||
if [[ $(sw_vers -buildVersion) > "19" ]]; then
|
||
LOGO="/System/Applications/App Store.app/Contents/Resources/AppIcon.icns"
|
||
else
|
||
LOGO="/Applications/App Store.app/Contents/Resources/AppIcon.icns"
|
||
fi
|
||
fi
|
||
printlog "LOGO=${LOGO}" INFO
|
||
|
||
printlog "Label type: $type" INFO
|
||
|
||
# MARK: extract info from data
|
||
if [ -z "$archiveName" ]; then
|
||
case $type in
|
||
dmg|pkg|zip|tbz|bz2)
|
||
archiveName="${name}.$type"
|
||
;;
|
||
pkgInDmg)
|
||
archiveName="${name}.dmg"
|
||
;;
|
||
*InZip)
|
||
archiveName="${name}.zip"
|
||
;;
|
||
updateronly)
|
||
;;
|
||
*)
|
||
printlog "Cannot handle type $type"
|
||
cleanupAndExit 99
|
||
;;
|
||
esac
|
||
fi
|
||
printlog "archiveName: $archiveName" INFO
|
||
|
||
if [ -z "$appName" ]; then
|
||
# when not given derive from name
|
||
appName="$name.app"
|
||
fi
|
||
|
||
if [ -z "$targetDir" ]; then
|
||
case $type in
|
||
dmg|zip|tbz|bz2|app*)
|
||
targetDir="/Applications"
|
||
;;
|
||
pkg*)
|
||
targetDir="/"
|
||
;;
|
||
updateronly)
|
||
;;
|
||
*)
|
||
cleanupAndExit 99 "Cannot handle type $type" ERROR
|
||
;;
|
||
esac
|
||
fi
|
||
|
||
if [[ -z $blockingProcesses ]]; then
|
||
printlog "no blocking processes defined, using $name as default" INFO
|
||
blockingProcesses=( $name )
|
||
fi
|
||
|
||
# MARK: determine tmp dir
|
||
if [ "$DEBUG" -eq 1 ]; then
|
||
# for debugging use script dir as working directory
|
||
tmpDir=$(dirname "$0")
|
||
else
|
||
# create temporary working directory
|
||
tmpDir=$(mktemp -d )
|
||
fi
|
||
|
||
# MARK: change directory to temporary working directory
|
||
printlog "Changing directory to $tmpDir" DEBUG
|
||
if ! cd "$tmpDir"; then
|
||
cleanupAndExit 13 "error changing directory $tmpDir" ERROR
|
||
fi
|
||
|
||
# MARK: get installed version
|
||
getAppVersion
|
||
printlog "appversion: $appversion"
|
||
|
||
# MARK: Exit if new version is the same as installed version (appNewVersion specified)
|
||
if [[ "$type" != "updateronly" && ($INSTALL == "force" || $IGNORE_APP_STORE_APPS == "yes") ]]; then
|
||
printlog "Label is not of type “updateronly”, and it’s set to use force to install or ignoring app store apps, so not using updateTool."
|
||
updateTool=""
|
||
fi
|
||
if [[ -n $appNewVersion ]]; then
|
||
printlog "Latest version of $name is $appNewVersion"
|
||
if [[ $appversion == $appNewVersion ]]; then
|
||
if [[ $DEBUG -ne 1 ]]; then
|
||
printlog "There is no newer version available."
|
||
if [[ $INSTALL != "force" ]]; then
|
||
message="$name, version $appNewVersion, is the latest version."
|
||
if [[ $currentUser != "loginwindow" && $NOTIFY == "all" ]]; then
|
||
printlog "notifying"
|
||
displaynotification "$message" "No update for $name!"
|
||
fi
|
||
cleanupAndExit 0 "No newer version." REQ
|
||
fi
|
||
else
|
||
printlog "DEBUG mode 1 enabled, not exiting, but there is no new version of app." WARN
|
||
fi
|
||
fi
|
||
else
|
||
printlog "Latest version not specified."
|
||
fi
|
||
|
||
# MARK: check if this is an Update and we can use updateTool
|
||
if [[ (-n $appversion && -n "$updateTool") || "$type" == "updateronly" ]]; then
|
||
printlog "appversion & updateTool"
|
||
updateDialog "wait" "Updating..."
|
||
|
||
if [[ $DEBUG -ne 1 ]]; then
|
||
if runUpdateTool; then
|
||
finishing
|
||
cleanupAndExit 0 "updateTool has run" REQ
|
||
elif [[ $type == "updateronly" ]];then
|
||
cleanupAndExit 0 "type is $type so we end here." REQ
|
||
fi # otherwise continue
|
||
else
|
||
printlog "DEBUG mode 1 enabled, not running update tool" WARN
|
||
fi
|
||
fi
|
||
|
||
# MARK: download the archive
|
||
if [ -f "$archiveName" ] && [ "$DEBUG" -eq 1 ]; then
|
||
printlog "$archiveName exists and DEBUG mode 1 enabled, skipping download"
|
||
else
|
||
# download
|
||
printlog "Downloading $downloadURL to $archiveName" REQ
|
||
if [[ $currentUser != "loginwindow" && $NOTIFY == "all" ]]; then
|
||
printlog "notifying"
|
||
if [[ $updateDetected == "YES" ]]; then
|
||
displaynotification "Downloading $name update" "Download in progress …"
|
||
else
|
||
displaynotification "Downloading new $name" "Download in progress …"
|
||
fi
|
||
fi
|
||
|
||
if [[ $DIALOG_CMD_FILE != "" ]]; then
|
||
# pipe
|
||
pipe="$tmpDir/downloadpipe"
|
||
# initialise named pipe for curl output
|
||
initNamedPipe create $pipe
|
||
|
||
# run the pipe read in the background
|
||
readDownloadPipe $pipe "$DIALOG_CMD_FILE" & downloadPipePID=$!
|
||
printlog "listening to output of curl with pipe $pipe and command file $DIALOG_CMD_FILE on PID $downloadPipePID" DEBUG
|
||
|
||
curlDownload=$(curl -fL -# --show-error ${curlOptions} "$downloadURL" -o "$archiveName" 2>&1 | tee $pipe)
|
||
# because we are tee-ing the output, we want the pipe status of the first command in the chain, not the most recent one
|
||
curlDownloadStatus=$(echo $pipestatus[1])
|
||
killProcess $downloadPipePID
|
||
|
||
else
|
||
printlog "No Dialog connection, just download" DEBUG
|
||
curlDownload=$(curl -v -fsL --show-error ${curlOptions} "$downloadURL" -o "$archiveName" 2>&1)
|
||
curlDownloadStatus=$(echo $?)
|
||
fi
|
||
|
||
deduplicatelogs "$curlDownload"
|
||
if [[ $curlDownloadStatus -ne 0 ]]; then
|
||
#if ! curl --location --fail --silent "$downloadURL" -o "$archiveName"; then
|
||
printlog "error downloading $downloadURL" ERROR
|
||
message="$name update/installation failed. This will be logged, so IT can follow up."
|
||
if [[ $currentUser != "loginwindow" && $NOTIFY == "all" ]]; then
|
||
printlog "notifying"
|
||
if [[ $updateDetected == "YES" ]]; then
|
||
displaynotification "$message" "Error updating $name"
|
||
else
|
||
displaynotification "$message" "Error installing $name"
|
||
fi
|
||
fi
|
||
printlog "File list: $(ls -lh "$archiveName")" ERROR
|
||
printlog "File type: $(file "$archiveName")" ERROR
|
||
cleanupAndExit 2 "Error downloading $downloadURL error:\n$logoutput" ERROR
|
||
fi
|
||
printlog "File list: $(ls -lh "$archiveName")" DEBUG
|
||
printlog "File type: $(file "$archiveName")" DEBUG
|
||
printlog "curl output was:\n$logoutput" DEBUG
|
||
fi
|
||
|
||
# MARK: when user is logged in, and app is running, prompt user to quit app
|
||
if [[ $BLOCKING_PROCESS_ACTION == "ignore" ]]; then
|
||
printlog "ignoring blocking processes"
|
||
else
|
||
if [[ $currentUser != "loginwindow" ]]; then
|
||
if [[ ${#blockingProcesses} -gt 0 ]]; then
|
||
if [[ ${blockingProcesses[1]} != "NONE" ]]; then
|
||
checkRunningProcesses
|
||
fi
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
# MARK: install the download
|
||
printlog "Installing $name" REQ
|
||
if [[ $currentUser != "loginwindow" && $NOTIFY == "all" ]]; then
|
||
printlog "notifying"
|
||
if [[ $updateDetected == "YES" ]]; then
|
||
displaynotification "Updating $name" "Installation in progress …"
|
||
updateDialog "wait" "Updating..."
|
||
else
|
||
displaynotification "Installing $name" "Installation in progress …"
|
||
updateDialog "wait" "Installing..."
|
||
fi
|
||
fi
|
||
|
||
if [ -n "$installerTool" ]; then
|
||
# installerTool defined, and we use that for installation
|
||
printlog "installerTool used: $installerTool" REQ
|
||
appName="$installerTool"
|
||
fi
|
||
|
||
case $type in
|
||
dmg)
|
||
installFromDMG
|
||
;;
|
||
pkg)
|
||
installFromPKG
|
||
;;
|
||
zip)
|
||
installFromZIP
|
||
;;
|
||
tbz|bz2)
|
||
installFromTBZ
|
||
;;
|
||
pkgInDmg)
|
||
installPkgInDmg
|
||
;;
|
||
pkgInZip)
|
||
installPkgInZip
|
||
;;
|
||
appInDmgInZip)
|
||
installAppInDmgInZip
|
||
;;
|
||
*)
|
||
cleanupAndExit 99 "Cannot handle type $type" ERROR
|
||
;;
|
||
esac
|
||
|
||
updateDialog "wait" "Finishing..."
|
||
|
||
# MARK: Finishing — print installed application location and version
|
||
finishing
|
||
|
||
# all done!
|
||
cleanupAndExit 0 "All done!" REQ
|