#!/bin/zsh
label="" # 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 for debugging
# while debugging, items will be downloaded to the parent directory of this script
# also no actual installation will be performed
DEBUG=1
# 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
# abort after three attempts to quit
# (only if user accepts to quit the apps, otherwise
# the update is cancelled).
# - prompt_user_then_kill
# show a user dialog for each blocking process found,
# attempt to quit two times, kill the process finally
# - 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 scripts gets stuct in waiting 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.
# - tell_user_then_kill
# Show dialog 2 times, and if the quitting fails, the
# blocking processes will be killed.
# - 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
# 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 installed app is from App Store (which include VPP installed apps)
# it will not be touched, no matter it's version (default)
# - yes Replace App Store (and VPP) version of 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 loose all settings.
# install behavior
INSTALL=""
# options:
# - When not set, 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 wil be reopened if it was closed
# - no App not reopened
# 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 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).
#
# - appNewVersion: (optional)
# Version of the downloaded software.
# If given, it will be compared to installed version, to see if 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 of 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 actually 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 that `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
#
VERSION="8.0"
VERSIONDATE="2021-11-23"
# MARK: Functions
cleanupAndExit() { # $1 = exit code, $2 message
if [[ -n $2 && $1 -ne 0 ]]; then
printlog "ERROR: $2"
fi
if [ "$DEBUG" -eq 0 ]; then
# remove the temporary working directory when done
printlog "Deleting $tmpDir"
rm -Rf "$tmpDir"
fi
if [ -n "$dmgmount" ]; then
# unmount disk image
printlog "Unmounting $dmgmount"
hdiutil detach "$dmgmount"
fi
# If we closed any processes, reopen the app again
reopenClosedProcess
printlog "################## End Installomator, exit code $1 \n\n"
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"
if [[ -x "$manageaction" ]]; then
"$manageaction" -message "$message" -title "$title"
else
runAsUser osascript -e "display notification \"$message\" with title \"$title\""
fi
}
# MARK: Logging
log_location="/private/var/log/Installomator.log"
printlog(){
timestamp=$(date +%F\ %T)
if [[ "$(whoami)" == "root" ]]; then
echo "$timestamp" "$label" "$1" | tee -a $log_location
else
echo "$timestamp" "$label" "$1"
fi
}
# 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 --silent --fail "https://api.github.com/repos/$gitusername/$gitreponame/releases/latest" \
| awk -F '"' "/browser_download_url/ && /$archiveName\"/ { print \$4; exit }")
else
downloadURL=$(curl --silent --fail "https://api.github.com/repos/$gitusername/$gitreponame/releases/latest" \
| awk -F '"' "/browser_download_url/ && /$filetype\"/ { print \$4; exit }")
fi
if [ -z "$downloadURL" ]; then
cleanupAndExit 9 "could not retrieve download URL for $gitusername/$gitreponame"
#exit 9
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 --silent --fail "https://api.github.com/repos/$gitusername/$gitreponame/releases/latest" | grep tag_name | cut -d '"' -f 4 | sed 's/[^0-9\.]//g')
if [ -z "$appNewVersion" ]; then
printlog "could not retrieve version number for $gitusername/$gitreponame"
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
}
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 /Applications, or /Applications/Utilities, or find using Spotlight
if [[ -d "/Applications/$appName" ]]; then
applist="/Applications/$appName"
elif [[ -d "/Applications/Utilities/$appName" ]]; then
applist="/Applications/Utilities/$appName"
else
applist=$(mdfind "kind:application $appName" -0 )
fi
if [[ -z applist ]]; then
printlog "No previous app found"
else
printlog "App(s) found: ${applist}"
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"
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"
appversion=0
else
cleanupAndExit 1 "App previously installed from App Store, and we respect that"
fi
fi
else
printlog "could not determine location of $appName"
fi
else
printlog "could not find $appName"
fi
}
checkRunningProcesses() {
# don't check in DEBUG mode
if [[ $DEBUG -ne 0 ]]; then
printlog "DEBUG mode, not checking for blocking processes"
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"
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"
;;
esac
countedProcesses=$((countedProcesses + 1))
fi
done
done
if [[ $countedProcesses -ne 0 ]]; then
cleanupAndExit 11 "could not quit all processes, aborting..."
fi
printlog "no more blocking processes, continue with update"
}
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
if [[ $DEBUG -ne 0 ]]; then
printlog "DEBUG mode, not reopening anything"
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."
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"
fi
# verify with spctl
printlog "Verifying: $appPath"
if ! teamID=$(spctl -a -vv "$appPath" 2>&1 | awk '/origin=/ {print $NF }' | tr -d '()' ); then
cleanupAndExit 4 "Error verifying $appPath"
fi
printlog "Team ID matching: $teamID (expected: $expectedTeamID )"
if [ "$expectedTeamID" != "$teamID" ]; then
cleanupAndExit 5 "Team IDs do not match"
fi
# versioncheck
# credit: Søren Theilgaard (@theilgaard)
appNewVersion=$(defaults read $appPath/Contents/Info.plist $versionKey)
if [[ -n $appNewVersion && $appversion == $appNewVersion ]]; then
printlog "Downloaded version of $name is $appNewVersion, 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"
else
printlog "Using force to install anyway."
fi
else
printlog "Downloaded version of $name is $appNewVersion (replacing version $appversion)."
fi
# skip install for DEBUG
if [ "$DEBUG" -ne 0 ]; then
printlog "DEBUG enabled, skipping remove, copy and chown steps"
return 0
fi
# check for root
if [ "$(whoami)" != "root" ]; then
# not running as root
cleanupAndExit 6 "not running as root, exiting"
fi
# Test if variable CLIInstaller is set
if [[ -z $CLIInstaller ]]; then
# remove existing application
if [ -e "$targetDir/$appName" ]; then
printlog "Removing existing $targetDir/$appName"
rm -Rf "$targetDir/$appName"
fi
# copy app to /Applications
printlog "Copy $appPath to $targetDir"
if ! ditto "$appPath" "$targetDir/$appName"; then
cleanupAndExit 7 "Error while copying"
fi
# set ownership to current user
if [ "$currentUser" != "loginwindow" ]; then
printlog "Changing owner to $currentUser"
chown -R "$currentUser" "$targetDir/$appName"
else
printlog "No user logged in, not changing user"
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 $?)
logoutput="$CLIoutput" # dedupliatelogs "$CLIoutput"
if [ $CLIstatus -ne 0 ] ; then
cleanupAndExit 3 "Error installing $mountname/$CLIInstaller $CLIArguments error:\n$logoutput" #ERROR
else
printlog "Succesfully ran $mountname/$CLIInstaller $CLIArguments"
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
if ! dmgmount=$(echo 'Y'$'\n' | hdiutil attach "$tmpDir/$archiveName" -nobrowse -readonly | tail -n 1 | cut -c 54- ); then
cleanupAndExit 3 "Error mounting $tmpDir/$archiveName"
fi
if [[ ! -e $dmgmount ]]; then
printlog "Error mounting $tmpDir/$archiveName"
cleanupAndExit 3
fi
printlog "Mounted: $dmgmount"
}
installFromDMG() {
mountDMG
installAppWithPath "$dmgmount/$appName"
}
installFromPKG() {
# verify with spctl
printlog "Verifying: $archiveName"
if ! spctlout=$(spctl -a -vv -t install "$archiveName" 2>&1 ); then
printlog "Error verifying $archiveName"
cleanupAndExit 4
fi
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
printlog "Team ID: $teamID (expected: $expectedTeamID )"
if [ "$expectedTeamID" != "$teamID" ]; then
printlog "Team IDs do not match!"
cleanupAndExit 5
fi
# Check version of pkg to be installed if packageID is set
if [[ $packageID != "" && $appversion != "" ]]; then
printlog "Checking package version."
pkgutil --expand "$archiveName" "$archiveName"_pkg
#printlog "$(cat "$archiveName"_pkg/Distribution | xpath '//installer-gui-script/pkg-ref[@id][@version]' 2>/dev/null)"
appNewVersion=$(cat "$archiveName"_pkg/Distribution | xpath '//installer-gui-script/pkg-ref[@id][@version]' 2>/dev/null | grep -i "$packageID" | tr ' ' '\n' | grep -i version | cut -d \" -f 2) #sed -E 's/.*\"([0-9.]*)\".*/\1/g'
rm -r "$archiveName"_pkg
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"
else
printlog "Using force to install anyway."
fi
fi
fi
# skip install for DEBUG
if [ "$DEBUG" -ne 0 ]; then
printlog "DEBUG enabled, skipping installation"
return 0
fi
# check for root
if [ "$(whoami)" != "root" ]; then
# not running as root
cleanupAndExit 6 "not running as root, exiting"
fi
# install pkg
printlog "Installing $archiveName to $targetDir"
if ! installer -pkg "$archiveName" -tgt "$targetDir" ; then
printlog "error installing $archiveName"
cleanupAndExit 9
fi
}
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" -maxdepth 1 )
filearray=( ${(f)findfiles} )
if [[ ${#filearray} -eq 0 ]]; then
cleanupAndExit 20 "couldn't find pkg in dmg $archiveName"
fi
archiveName="${filearray[1]}"
printlog "found pkg: $archiveName"
else
# it is now safe to overwrite archiveName for installFromPKG
archiveName="$dmgmount/$pkgName"
fi
# 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" -maxdepth 2 )
filearray=( ${(f)findfiles} )
if [[ ${#filearray} -eq 0 ]]; then
cleanupAndExit 20 "couldn't find pkg in zip $archiveName"
fi
archiveName="${filearray[1]}"
# it is now safe to overwrite archiveName for installFromPKG
printlog "found pkg: $archiveName"
else
# it is now safe to overwrite archiveName for installFromPKG
archiveName="$tmpDir/$pkgName"
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 20 "couldn't find dmg in zip $archiveName"
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
runAsUser $updateTool ${updateToolArguments}
else
$updateTool ${updateToolArguments}
fi
if [[ $? -ne 0 ]]; then
cleanupAndExit 15 "Error running $updateTool"
fi
else
printlog "couldn't find $updateTool, continuing normally"
return 1
fi
return 0
}
finishing() {
printlog "Finishing..."
sleep 10 # 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"
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
}
# MARK: check minimal macOS requirement
autoload is-at-least
if ! is-at-least 10.14 $(sw_vers -productVersion); then
printlog "Installomator requires at least macOS 10.14 Mojave."
exit 98
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"
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"
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"
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
printlog "################## Start Installomator v. $VERSION"
printlog "################## $label"
# Check for DEBUG mode
if [[ $DEBUG -gt 0 ]]; then
printlog "DEBUG mode $DEBUG enabled."
fi
# How we get version number from app
# (alternative is "CFBundleVersion", that can be used in labels)
versionKey="CFBundleShortVersionString"
# get current user
currentUser=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }')
# MARK: labels in case statement
case $label in
longversion)
# print the script version
printlog "Installomater: version $VERSION ($VERSIONDATE)"
exit 0
;;
valuesfromarguments)
if [[ -z $name ]]; then
printlog "need to provide 'name'"
exit 1
fi
if [[ -z $type ]]; then
printlog "need to provide 'type'"
exit 1
fi
if [[ -z $downloadURL ]]; then
printlog "need to provide 'downloadURL'"
exit 1
fi
if [[ -z $expectedTeamID ]]; then
printlog "need to provide 'expectedTeamID'"
exit 1
fi
;;
# label descriptions start here
1password7)
name="1Password 7"
type="pkg"
downloadURL="https://app-updates.agilebits.com/download/OPM7"
appNewVersion=$( curl -fsIL "${downloadURL}" | grep -i "^location" | awk '{print $2}' | sed -E 's/.*\/[0-9a-zA-Z]*-([0-9.]*)\..*/\1/g' )
expectedTeamID="2BUA8C4S2C"
blockingProcesses=( "1Password Extension Helper" "1Password 7" "1Password (Safari)" "1PasswordNativeMessageHost" "1PasswordSafariAppExtension" )
#forcefulQuit=YES
;;
4kvideodownloader)
name="4K Video Downloader"
type="dmg"
downloadURL="$(curl -fsL "https://www.4kdownload.com/products/product-videodownloader" | grep -E -o "https:\/\/dl\.4kdownload\.com\/app\/4kvideodownloader_.*?.dmg\?source=website" | head -1)"
appNewVersion=$(echo "${downloadURL}" | sed -E 's/.*\/[0-9a-zA-Z]*_([0-9.]*)\.dmg.*/\1/g')
versionKey="CFBundleVersion"
expectedTeamID="GHQ37VJF83"
;;
8x8)
# credit: #D-A-James from MacAdmins Slack and Isaac Ordonez, Mann consulting (@mannconsulting)
name="8x8 Work"
type="dmg"
downloadURL=$(curl -fs -L https://support.8x8.com/cloud-phone-service/voice/work-desktop/download-8x8-work-for-desktop | grep -m 1 -o "https.*dmg" | sed 's/\"//' | awk '{print $1}')
# As for appNewVersion, it needs to be checked for newer version than 7.2.4
appNewVersion=$(curl -fs -L https://support.8x8.com/cloud-phone-service/voice/work-desktop/download-8x8-work-for-desktop | grep -m 1 -o "https.*dmg" | sed 's/\"//' | awk '{print $1}' | sed -E 's/.*-v([0-9\.]*)[-\.]*.*/\1/' )
expectedTeamID="FC967L3QRG"
;;
abstract)
name="Abstract"
type="zip"
downloadURL="https://api.goabstract.com/releases/latest/download"
appNewVersion=$( curl -fsIL "${downloadURL}" | grep -i "^location" | awk '{print $2}' | sed -E 's/.*\/[a-zA-Z]*-([0-9.]*)\..*/\1/g' )
expectedTeamID="77MZLZE47D"
;;
adobebrackets)
name="Brackets"
type="dmg"
downloadURL=$(downloadURLFromGit adobe brackets )
appNewVersion=$(versionFromGit adobe brackets )
expectedTeamID="JQ525L2MZD"
;;
adobeconnect)
# credit: Oh4sh0 https://github.com/Oh4sh0
# Comment by Søren: I do not know this software.
# Looks like it's an Adobe installer in an app, so it will probably not work
name="AdobeConnectInstaller"
type="dmg"
downloadURL="http://www.adobe.com/go/ConnectSetupMac"
appNewVersion=$(curl -fs https://helpx.adobe.com/adobe-connect/connect-downloads-updates.html | grep "Mac" | grep version | head -1 | sed -E 's/.*\(version ([0-9\.]*),.*/\1/g')
expectedTeamID="JQ525L2MZD"
;;
adobecreativeclouddesktop)
name="Adobe Creative Cloud"
#appName="Install.app"
type="dmg"
if [[ $(arch) == "arm64" ]]; then
downloadURL=$(curl -fs "https://helpx.adobe.com/download-install/kb/creative-cloud-desktop-app-download.html" | grep -o "https*.*macarm64.*dmg" | cut -d '"' -f1 | head -1)
elif [[ $(arch) == "i386" ]]; then
downloadURL=$(curl -fs "https://helpx.adobe.com/download-install/kb/creative-cloud-desktop-app-download.html" | grep -o "https*.*osx10.*dmg" | cut -d '"' -f1 | head -1)
fi
#downloadURL=$(curl -fs "https://helpx.adobe.com/download-install/kb/creative-cloud-desktop-app-download.html" | grep -o "https*.*dmg" | head -1)
appNewVersion=$(curl -fs "https://helpx.adobe.com/creative-cloud/release-note/cc-release-notes.html" | grep "mandatory" | head -1 | grep -o "Version *.* released" | cut -d " " -f2)
installerTool="Install.app"
CLIInstaller="Install.app/Contents/MacOS/Install"
CLIArguments=(--mode=silent)
expectedTeamID="JQ525L2MZD"
Company="Adobe"
;;
adobereaderdc-update)
name="Adobe Acrobat Reader DC"
type="pkgInDmg"
downloadURL=$(adobecurrent=`curl --fail --silent https://armmf.adobe.com/arm-manifests/mac/AcrobatDC/reader/current_version.txt | tr -d '.'` && echo http://ardownload.adobe.com/pub/adobe/reader/mac/AcrobatDC/"$adobecurrent"/AcroRdrDCUpd"$adobecurrent"_MUI.dmg)
appNewVersion=$(curl -s https://armmf.adobe.com/arm-manifests/mac/AcrobatDC/reader/current_version.txt)
#appNewVersion=$(curl -s -H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15)" https://get.adobe.com/reader/ | grep ">Version" | sed -E 's/.*Version 20([0-9.]*)<.*/\1/g') # credit: Søren Theilgaard (@theilgaard)
expectedTeamID="JQ525L2MZD"
blockingProcesses=( "AdobeReader" )
;;
adobereaderdc|\
adobereaderdc-install)
name="Adobe Acrobat Reader DC"
type="pkgInDmg"
packageID="com.adobe.acrobat.DC.reader.app.pkg.MUI"
downloadURL=$(curl --silent --fail -H "Sec-Fetch-Site: same-origin" -H "Accept-Encoding: gzip, deflate, br" -H "Accept-Language: en-US;q=0.9,en;q=0.8" -H "DNT: 1" -H "Sec-Fetch-Mode: cors" -H "X-Requested-With: XMLHttpRequest" -H "Referer: https://get.adobe.com/reader/enterprise/" -H "Accept: */*" "https://get.adobe.com/reader/webservices/json/standalone/?platform_type=Macintosh&platform_dist=OSX&platform_arch=x86-32&language=English&eventname=readerotherversions" | grep -Eo '"download_url":.*?[^\]",' | head -n 1 | cut -d \" -f 4)
appNewVersion=$(curl -s https://armmf.adobe.com/arm-manifests/mac/AcrobatDC/reader/current_version.txt)
#appNewVersion=$(curl -s -H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15)" https://get.adobe.com/reader/ | grep ">Version" | sed -E 's/.*Version 20([0-9.]*)<.*/\1/g') # credit: Søren Theilgaard (@theilgaard)
expectedTeamID="JQ525L2MZD"
blockingProcesses=( "AdobeReader" )
;;
aircall)
# credit: @kris-anderson
name="Aircall"
type="dmg"
downloadURL="https://electron.aircall.io/download/osx"
expectedTeamID="3ML357Q795"
;;
airserver)
# credit: AP Orlebeke (@apizz)
name="AirServer"
type="dmg"
downloadURL="https://www.airserver.com/download/mac/latest"
appNewVersion=$(curl -fsIL "${downloadURL}" | grep -i "location" | sed -E 's/.*\/[a-zA-Z]*-([0-9.]*)\..*/\1/g')
expectedTeamID="6C755KS5W3"
;;
airtame)
name="Airtame"
type="dmg"
downloadURL="$(curl -fs https://airtame.com/download/ | grep -i platform=mac | head -1 | grep -o -i -E "https.*" | cut -d '"' -f1)"
appNewVersion="$(curl -fsIL "${downloadURL}" | grep -i ^location | sed -E 's/.*\/[a-zA-Z]*-([0-9.]*)\..*/\1/g')"
expectedTeamID="4TPSP88HN2"
;;
aldente)
name="AlDente"
type="dmg"
downloadURL=$(downloadURLFromGit davidwernhart AlDente)
appNewVersion=$(versionFromGit davidwernhart AlDente)
expectedTeamID="3WVC84GB99"
;;
alephone)
name="Aleph One"
type="dmg"
downloadURL=$(downloadURLFromGit Aleph-One-Marathon alephone)
appNewVersion=$(versionFromGit Aleph-One-Marathon alephone)
expectedTeamID="E8K89CXZE7"
;;
alfred)
# credit: AP Orlebeke (@apizz)
name="Alfred"
type="dmg"
downloadURL=$(curl -fs https://www.alfredapp.com | awk -F '"' "/dmg/ {print \$2}" | head -1)
appNewVersion=$(echo "${downloadURL}" | sed -E 's/.*Alfred_([0-9.]*)_.*/\1/')
appName="Alfred 4.app"
expectedTeamID="XZZXE9SED4"
;;
alttab)
# credit: Gabe Marchan (gabemarchan.com - @darklink87)
name="AltTab"
type="zip"
downloadURL=$(downloadURLFromGit lwouis alt-tab-macos)
expectedTeamID="QXD7GW8FHY"
;;
amazonchime)
# credit: @dvsjr macadmins slack
name="Amazon Chime"
type="dmg"
downloadURL="https://clients.chime.aws/mac/latest"
appNewVersion=$( curl -fsIL "${downloadURL}" | grep -i "^location" | awk '{print $2}' | sed -E 's/.*\/[a-zA-Z.\-]*-([0-9.]*)\..*/\1/g' )
expectedTeamID="94KV3E626L"
;;
amazonworkspaces)
# credit: Isaac Ordonez, Mann consulting (@mannconsulting)
name="Workspaces"
type="pkg"
downloadURL="https://d2td7dqidlhjx7.cloudfront.net/prod/global/osx/WorkSpaces.pkg"
appNewVersion=$(curl -fs https://d2td7dqidlhjx7.cloudfront.net/prod/iad/osx/WorkSpacesAppCast_macOS_20171023.xml | grep -o "Version*.*<" | head -1 | cut -d " " -f2 | cut -d "<" -f1)
expectedTeamID="94KV3E626L"
;;
androidfiletransfer)
#credit: Sam Ess (saess-sep)
name="Android File Transfer"
type="dmg"
downloadURL="https://dl.google.com/dl/androidjumper/mtp/current/AndroidFileTransfer.dmg"
expectedTeamID="EQHXZ8M8AV"
;;
anydesk)
name="AnyDesk"
type="dmg"
downloadURL="https://download.anydesk.com/anydesk.dmg"
appNewVersion="$(curl -fs https://anydesk.com/da/downloads/mac-os | grep -i "d-block" | grep -E -o ">v[0-9.]* .*MB" | sed -E 's/.*v([0-9.]*) .*/\1/g')"
expectedTeamID="KU6W3B6JMZ"
;;
apparency)
name="Apparency"
type="dmg"
downloadURL="https://www.mothersruin.com/software/downloads/Apparency.dmg"
appNewVersion=$(curl -fs https://mothersruin.com/software/Apparency/data/ApparencyVersionInfo.plist | grep -A1 CFBundleShortVersionString | tail -1 | sed -E 's/.*>([0-9.]*)<.*/\1/g')
expectedTeamID="936EB786NH"
;;
appcleaner)
# credit: Tadayuki Onishi (@kenchan0130)
name="AppCleaner"
type="zip"
downloadURL=$(curl -fs https://freemacsoft.net/appcleaner/Updates.xml | xpath '//rss/channel/*/enclosure/@url' 2>/dev/null | tr " " "\n" | sort | tail -1 | cut -d '"' -f 2)
expectedTeamID="X85ZX835W9"
;;
applenyfonts)
name="Apple New York Font Collection"
type="pkgInDmg"
downloadURL="https://devimages-cdn.apple.com/design/resources/download/NY.dmg"
packageID="com.apple.pkg.NYFonts"
expectedTeamID="Development Update"
;;
applesfcompact)
name="San Francisco Compact"
type="pkgInDmg"
downloadURL="https://devimages-cdn.apple.com/design/resources/download/SF-Compact.dmg"
packageID="com.apple.pkg.SanFranciscoCompact"
expectedTeamID="Development Update"
;;
applesfmono)
name="San Francisco Mono"
type="pkgInDmg"
downloadURL="https://devimages-cdn.apple.com/design/resources/download/SF-Mono.dmg"
packageID="com.apple.pkg.SFMonoFonts"
expectedTeamID="Software Update"
;;
applesfpro)
name="San Francisco Pro"
type="pkgInDmg"
downloadURL="https://devimages-cdn.apple.com/design/resources/download/SF-Pro.dmg"
packageID="com.apple.pkg.SanFranciscoPro"
expectedTeamID="Development Update"
;;
applesfsymbols|\
sfsymbols)
name="SF Symbols"
type="pkgInDmg"
downloadURL=$( curl -fs "https://developer.apple.com/sf-symbols/" | grep -oe "https.*\.dmg" | head -1 )
appNewVersion=$( echo "$downloadURL" | head -1 | sed -E 's/.*SF-Symbols-([0-9.]*)\..*/\1/g')
expectedTeamID="Software Update"
;;
aquaskk)
# credit: Tadayuki Onishi (@kenchan0130)
name="aquaskk"
type="pkg"
downloadURL=$(downloadURLFromGit codefirst aquaskk)
appNewVersion=$(versionFromGit codefirst aquaskk)
expectedTeamID="FPZK4WRGW7"
;;
arq7)
name="Arq7"
type="pkg"
packageID="com.haystacksoftware.Arq"
downloadURL="https://arqbackup.com/download/arqbackup/Arq7.pkg"
appNewVersion="$(curl -fs "https://arqbackup.com" | grep -io "version .*[0-9.]*.* for macOS" | cut -d ">" -f2 | cut -d "<" -f1)"
expectedTeamID="48ZCSDVL96"
;;
asana)
# credit: Lance Stephens (@pythoninthegrass on MacAdmins Slack)
name="Asana"
type="dmg"
downloadURL="https://desktop-downloads.asana.com/darwin_x64/prod/latest/Asana.dmg"
expectedTeamID="A679L395M8"
;;
atext)
# credit: Gabe Marchan (gabemarchan.com - @darklink87)
name="aText"
type="dmg"
downloadURL="https://trankynam.com/atext/downloads/aText.dmg"
expectedTeamID="KHEMQ2FD9E"
;;
atom)
name="Atom"
type="zip"
archiveName="atom-mac.zip"
downloadURL=$(downloadURLFromGit atom atom )
appNewVersion=$(versionFromGit atom atom)
expectedTeamID="VEKTX9H2N7"
;;
audacity)
name="Audacity"
type="dmg"
downloadURL=$(downloadURLFromGit audacity audacity)
appNewVersion=$(versionFromGit audacity audacity)
expectedTeamID="AWEYX923UX"
;;
authydesktop)
name="Authy Desktop"
type="dmg"
downloadURL="https://electron.authy.com/download?channel=stable&arch=x64&platform=darwin&version=latest&product=authy"
appNewVersion="$(curl -sfL --output /dev/null -r 0-0 "${downloadURL}" --remote-header-name --remote-name -w "%{url_effective}\n" | grep -o -E '([a-zA-Z0-9\_.%-]*)\.(dmg|pkg|zip|tbz)$' | sed -E 's/.*-([0-9.]*)\.dmg/\1/g')"
expectedTeamID="9EVH78F4V4"
;;
autodmg)
# credit: Mischa van der Bent (@mischavdbent)
name="AutoDMG"
type="dmg"
downloadURL=$(downloadURLFromGit MagerValp AutoDMG)
appNewVersion=$(versionFromGit MagerValp AutoDMG)
expectedTeamID="5KQ3D3FG5H"
;;
autopkgr)
# credit: Søren Theilgaard (@theilgaard)
name="AutoPkgr"
type="dmg"
#downloadURL=$(curl -fs "https://api.github.com/repos/lindegroup/autopkgr/releases/latest" | awk -F '"' "/browser_download_url/ && /dmg/ && ! /sig/ && ! /CLI/ && ! /sha256/ { print \$4 }")
downloadURL=$(downloadURLFromGit lindegroup autopkgr)
appNewVersion=$(versionFromGit lindegroup autopkgr)
expectedTeamID="JVY2ZR6SEF"
;;
aviatrix)
# credit: Isaac Ordonez, Mann consulting (@mannconsulting)
name="Aviatrix VPN Client"
type="pkg"
downloadURL="https://s3-us-west-2.amazonaws.com/aviatrix-download/AviatrixVPNClient/AVPNC_mac.pkg"
expectedTeamID="32953Z7NBN"
;;
awscli2)
# credit: Bilal Habib (@Pro4TLZZ)
name="AWSCLI"
type="pkg"
packageID="com.amazon.aws.cli2"
downloadURL="https://awscli.amazonaws.com/AWSCLIV2.pkg"
appNewVersion=$( curl -fs "https://raw.githubusercontent.com/aws/aws-cli/v2/CHANGELOG.rst" | grep -i "CHANGELOG" -a4 | grep "[0-9.]" )
expectedTeamID="94KV3E626L"
;;
awsvpnclient)
name="AWS VPN Client"
type="pkg"
downloadURL="https://d20adtppz83p9s.cloudfront.net/OSX/latest/AWS_VPN_Client.pkg"
expectedTeamID="94KV3E626L"
appNewVersion=$(curl -is "https://beta2.communitypatch.com/jamf/v1/ba1efae22ae74a9eb4e915c31fef5dd2/patch/AWSVPNClient" | grep currentVersion | tr ',' '\n' | grep currentVersion | cut -d '"' -f 4)
;;
balenaetcher)
name="balenaEtcher"
type="dmg"
downloadURL=$(downloadURLFromGit balena-io etcher )
appNewVersion=$(versionFromGit balena-io etcher )
expectedTeamID="66H43P8FRG"
;;
balsamiqwireframes)
# credit: Gabe Marchan (gabemarchan.com - @darklink87)
name="Balsamiq Wireframes"
type="dmg"
downloadURL=https://builds.balsamiq.com/bwd/$(curl -fs "https://builds.balsamiq.com" | awk -F "
Version" | head -1 | cut -d " " -f1 | cut -d ";" -f2 | cut -d "." -f 1-3) expectedTeamID="S272Y5R93J" ;; clevershare2) name="Clevershare" type="dmg" downloadURL=$(curl -fs https://www.clevertouch.com/eu/clevershare2g | grep -i -o -E "https.*Mac.*\.dmg") appNewVersion=$( echo "${downloadURL}" | sed -E 's/.*\/[a-zA-Z-]*_Mac\.([0-9.]*)\.[0-9]*\.dmg$/\1/g' ) expectedTeamID="P76M9BE8DQ" ;; clickshare) # credit: Søren Theilgaard (@theilgaard) name="ClickShare" type="appInDmgInZip" downloadURL=https://www.barco.com$(curl -fs "https://www.barco.com/en/clickshare/app" | grep -E -o '(\/\S*Download\?FileNumber=R3306192\S*ShowDownloadPage=False)' | tail -1) expectedTeamID="P6CDJZR997" ;; closeio) name="Close.io" type="dmg" downloadURL=$(downloadURLFromGit closeio closeio-desktop-releases) appNewVersion=$(versionFromGit closeio closeio-desktop-releases) expectedTeamID="WTNQ6773UC" ;; cloudya) name="Cloudya" type="appInDmgInZip" downloadURL="$(curl -fs https://www.nfon.com/de/service/downloads | grep -i -E -o "https://cdn.cloudya.com/Cloudya-[.0-9]+-mac.zip")" appNewVersion="$(curl -fs https://www.nfon.com/de/service/downloads | grep -i -E -o "Cloudya Desktop App MAC [0-9.]*" | sed 's/^.*\ \([^ ]\{0,7\}\)$/\1/g')" expectedTeamID="X26F74J8TH" ;; code42) # credit: Isaac Ordonez, Mann consulting (@mannconsulting) name="Code42" type="pkgInDmg" downloadURL=https://download.code42.com/installs/agent/latest-mac.dmg expectedTeamID="9YV9435DHD" blockingProcesses=( NONE ) ;; coderunner) # credit: Erik Stam (@erikstam) name="CodeRunner" type="zip" downloadURL="https://coderunnerapp.com/download" expectedTeamID="R4GD98AJF9" ;; colourcontrastanalyser) name="Colour Contrast Analyser (CCA)" type="dmg" downloadURL=$(downloadURLFromGit ThePacielloGroup CCAe) appNewVersion=$(versionFromGit ThePacielloGroup CCAe) expectedTeamID="34RS4UC3M6" blockingProcesses=( NONE ) ;; cormorant) # credit: Søren Theilgaard (@theilgaard) name="Cormorant" type="zip" downloadURL=$(curl -fs https://eclecticlight.co/downloads/ | grep -i $name | grep zip | sed -E 's/.*href=\"(https.*)\">.*/\1/g') appNewVersion=$(curl -fs https://eclecticlight.co/downloads/ | grep zip | grep -o -E "$name [0-9.]*" | awk '{print $2}') expectedTeamID="QWY4LRW926" ;; craftmanager) name="CraftManager" type="zip" #downloadURL="https://craft-assets.invisionapp.com/CraftManager/production/CraftManager.zip" downloadURL="$(curl -fs https://craft-assets.invisionapp.com/CraftManager/production/appcast.xml | xpath '(//rss/channel/item/enclosure/@url)[1]' 2>/dev/null | head -1 | cut -d '"' -f 2)" appNewVersion="$(curl -fs https://craft-assets.invisionapp.com/CraftManager/production/appcast.xml | xpath '(//rss/channel/item/enclosure/@sparkle:shortVersionString)[1]' 2>/dev/null | head -1 | cut -d '"' -f 2)" expectedTeamID="VRXQSNCL5W" ;; cryptomator) name="Cryptomator" type="dmg" downloadURL=$(downloadURLFromGit cryptomator cryptomator) appNewVersion=$(versionFromGit cryptomator cryptomator) expectedTeamID="YZQJQUHA3L" ;; cyberduck) name="Cyberduck" type="zip" downloadURL=$(curl -fs https://version.cyberduck.io/changelog.rss | xpath '//rss/channel/item/enclosure/@url' 2>/dev/null | cut -d '"' -f 2 ) appNewVersion=$(curl -fs https://version.cyberduck.io/changelog.rss | xpath '//rss/channel/item/enclosure/@sparkle:shortVersionString' 2>/dev/null | cut -d '"' -f 2 ) expectedTeamID="G69SCX94XU" ;; dangerzone) # credit: Micah Lee (@micahflee) name="Dangerzone" type="dmg" downloadURL=$(curl -s https://dangerzone.rocks/ | grep https://github.com/firstlookmedia/dangerzone/releases/download | grep \.dmg | cut -d'"' -f2) expectedTeamID="P24U45L8P5" ;; darktable) # credit: Søren Theilgaard (@theilgaard) name="darktable" type="dmg" downloadURL=$(downloadURLFromGit darktable-org darktable) appNewVersion=$(versionFromGit darktable-org darktable) expectedTeamID="85Q3K4KQRY" ;; dbeaverce) name="DBeaver" type="dmg" downloadURL="https://dbeaver.io/files/dbeaver-ce-latest-macos.dmg" expectedTeamID="42B6MDKMW8" blockingProcesses=( dbeaver ) ;; debookee) name="Debookee" type="zip" downloadURL=$(curl --location --fail --silent "https://www.iwaxx.com/debookee/appcast.xml" | xpath '//rss/channel/item[1]/enclosure/@url' 2>/dev/null | cut -d '"' -f 2) expectedTeamID="AATLWWB4MZ" ;; defaultfolderx) # credit: Gabe Marchan (gabemarchan.com - @darklink87) name="Default Folder X" type="dmg" downloadURL=$(curl -fs "https://www.stclairsoft.com/cgi-bin/dl.cgi?DX" | awk -F '"' "/dmg/ {print \$4}" | head -2 | tail -1) expectedTeamID="7HK42V8R9D" ;; depnotify) name="DEPNotify" type="zip" downloadURL="https://files.nomad.menu/DEPNotify.zip" expectedTeamID="VRPY9KHGX6" targetDir="/Applications/Utilities" ;; desktoppr) name="desktoppr" type="pkg" packageID="com.scriptingosx.desktoppr" downloadURL=$(downloadURLFromGit "scriptingosx" "desktoppr") appNewVersion=$(versionFromGit "scriptingosx" "desktoppr") expectedTeamID="JME5BW3F3R" blockingProcesses=( NONE ) ;; detectxswift) # credit: AP Orlebeke (@apizz) name="DetectX Swift" type="zip" downloadURL="https://s3.amazonaws.com/sqwarq.com/PublicZips/DetectX_Swift.app.zip" appNewVersion=$(curl -fs https://s3.amazonaws.com/sqwarq.com/AppCasts/dtxswift_release_notes.html | grep Version | head -1 | sed -E 's/.*Version ([0-9.]*)\<.*/\1/') expectedTeamID="MAJ5XBJSG3" ;; devonthink) # It's a zipped dmg file, needs function installAppInDmgInZip # credit: Søren Theilgaard (@theilgaard) name="DEVONthink 3" type="appInDmgInZip" downloadURL=$( curl -fs https://www.devontechnologies.com/apps/devonthink | grep -i "download.devon" | tr '"' '\n' | tr "'" '\n' | grep -e '^https://' ) appNewVersion=$( echo ${downloadURL} | tr '/' '\n' | grep "[0-9]" | grep "[.]" | head -1 ) expectedTeamID="679S2QUWR8" ;; dialog) name="Dialog" type="pkg" packageID="au.csiro.dialogcli" downloadURL="$(downloadURLFromGit bartreardon Dialog)" appNewVersion="$(versionFromGit bartreardon Dialog)" expectedTeamID="PWA5E9TQ59" ;; dialpad) # credit: @ehosaka name="Dialpad" type="dmg" downloadURL="https://storage.googleapis.com/dialpad_native/osx/Dialpad.dmg" expectedTeamID="9V29MQSZ9M" ;; discord) name="Discord" type="dmg" downloadURL="https://discordapp.com/api/download?platform=osx" expectedTeamID="53Q6R32WPB" ;; docker) # credit: @securitygeneration name="Docker" type="dmg" #downloadURL="https://download.docker.com/mac/stable/Docker.dmg" if [[ $(arch) == arm64 ]]; then downloadURL="https://desktop.docker.com/mac/stable/arm64/Docker.dmg" elif [[ $(arch) == i386 ]]; then downloadURL="https://desktop.docker.com/mac/stable/amd64/Docker.dmg" fi appNewVersion=$(curl -ifs https://docs.docker.com/docker-for-mac/release-notes/ | grep ">Docker Desktop Community" | head -1 | sed -n -e 's/^.*Community //p' | cut -d '<' -f1) expectedTeamID="9BNSXJN65R" ;; drift) # credit Elena Ackley (@elenaelago) name="Drift" type="dmg" downloadURL="https://drift-prod-desktop-installers.s3.amazonaws.com/mac/Drift-latest.dmg" expectedTeamID="78559WUUR9" ;; dropbox) name="Dropbox" type="dmg" downloadURL="https://www.dropbox.com/download?plat=mac&full=1" expectedTeamID="G7HH3F8CAK" ;; easeusdatarecoverywizard) # credit: Søren Theilgaard (@theilgaard) name="EaseUS Data Recovery Wizard" type="dmg" downloadURL=$( curl -fsIL https://down.easeus.com/product/mac_drw_free_setup | grep -i "^location" | awk '{print $2}' | tr -d '\r\n' ) #appNewVersion="" expectedTeamID="DLLVW95FSM" ;; egnyte) # credit: #MoeMunyoki from MacAdmins Slack name="Egnyte Connect" type="pkg" downloadURL="https://egnyte-cdn.egnyte.com/egnytedrive/mac/en-us/latest/EgnyteConnectMac.pkg" expectedTeamID="FELUD555VC" blockingProcesses=( NONE ) ;; element) name="Element" type="dmg" downloadURL="https://packages.riot.im/desktop/install/macos/Element.dmg" appNewVersion=$(versionFromGit vector-im element-desktop) expectedTeamID="7J4U792NQT" ;; eraseinstall) name="EraseInstall" type="pkg" downloadURL=https://bitbucket.org$(curl -fs https://bitbucket.org/prowarehouse-nl/erase-install/downloads/ | grep pkg | cut -d'"' -f2 | head -n 1) expectedTeamID="R55HK5K86Y" ;; eshareosx) name="e-Share" type="pkg" packageID="com.ncryptedcloud.e-Share.pkg" downloadURL=https://www.ncryptedcloud.com/static/downloads/osx/$(curl -fs https://www.ncryptedcloud.com/static/downloads/osx/ | grep -o -i "href.*\".*\"" | cut -d '"' -f2) versionKey="CFBundleVersion" appNewVersion=$( echo "${downloadURL}" | sed -E 's/.*\/[a-zA-Z\-]*_([0-9.]*)\.pkg/\1/g' ) expectedTeamID="X9MBQS7DDC" ;; etrecheck) # credit: @dvsjr macadmins slack name="EtreCheckPro" type="zip" downloadURL="https://cdn.etrecheck.com/EtreCheckPro.zip" expectedTeamID="U87NE528LC" ;; evernote) name="Evernote" type="dmg" downloadURL=$(curl -fs -H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15)" "https://evernote.com/download" | grep -i ".dmg" | cut -d '"' -f2) appNewVersion=$( echo "${downloadURL}" | sed -E 's/.*\/[a-zA-Z]*-([0-9.]*)-.*/\1/g' ) expectedTeamID="Q79WDW8YH9" appName="Evernote.app" ;; exelbanstats) # credit: Søren Theilgaard (@theilgaard) name="Stats" type="dmg" downloadURL=$(downloadURLFromGit exelban stats) appNewVersion=$(versionFromGit exelban stats) expectedTeamID="RP2S87B72W" ;; exifrenamer) name="ExifRenamer" type="dmg" downloadURL="https://www.qdev.de/"$(curl -fs "https://www.qdev.de/download.php?file=ExifRenamer.dmg" | grep -o -e "URL=[a-zA-Z/]*.dmg" | cut -d "=" -f2) appNewVersion=$(curl -fs "https://www.qdev.de/?location=downloads" | grep -A1 -m1 "ExifRenamer" | tail -1 | cut -d ">" -f2 | cut -d " " -f1) expectedTeamID="MLF9FE35AM" ;; fantastical) # credit: Drew Diver (@grumpydrew on MacAdmins Slack) name="Fantastical" type="zip" downloadURL="https://flexibits.com/fantastical/download" appNewVersion=$( curl -fsIL "${downloadURL}" | grep -i "^location" | awk '{print $2}' | sed -E 's/.*\/[a-zA-Z]*_([0-9.]*)\..*/\1/g' ) expectedTeamID="85C27NK92C" ;; fastscripts) name="FastScripts" type="zip" downloadURL=$( curl -fs "https://redsweater.com/fastscripts/appcast3.php" | xpath '(//rss/channel/item/enclosure/@url)[1]' 2>/dev/null | cut -d '"' -f2 ) appNewVersion=$( curl -fs "https://redsweater.com/fastscripts/appcast3.php" | xpath '(//rss/channel/item/enclosure/@sparkle:shortVersionString)[1]' 2>/dev/null | cut -d '"' -f2 ) expectedTeamID="493CVA9A35" ;; favro) name="Favro" type="dmg" downloadURL="https://download.favro.com/FavroDesktop/macOS/x64/$(curl -fs https://download.favro.com/FavroDesktop/macOS/x64/Latest.html | cut -d ">" -f1 | cut -d "=" -f 4 | cut -d '"' -f1)" appNewVersion="$(curl -fs https://download.favro.com/FavroDesktop/macOS/x64/Latest.html | cut -d ">" -f1 | cut -d "=" -f 4 | cut -d '"' -f1 | sed -E 's/.*-([0-9.]*)\.dmg/\1/g')" expectedTeamID="PUA8Q354ZF" ;; ferdi) name="Ferdi" type="zip" if [[ $(arch) == i386 ]]; then downloadURL=$(curl --silent --fail "https://api.github.com/repos/getferdi/ferdi/releases/latest" \ | awk -F '"' "/browser_download_url/ && /mac.zip/ && ! /blockmap/ && ! /arm64-mac/ && ! /AppImage/{ print \$4 }") elif [[ $(arch) == arm64 ]]; then downloadURL=$(downloadURLFromGit getferdi ferdi ) archiveName="arm64-mac.zip" fi appNewVersion=$(versionFromGit getferdi ferdi ) expectedTeamID="B6J9X9DWFL" ;; figma) name="Figma" type="zip" if [[ $(arch) == "arm64" ]]; then downloadURL="https://desktop.figma.com/mac-arm/Figma.zip" elif [[ $(arch) == "i386" ]]; then downloadURL="https://desktop.figma.com/mac/Figma.zip" fi appNewVersion="$(curl -fsL https://desktop.figma.com/mac/RELEASE.json | awk -F '"' '{ print $8 }')" expectedTeamID="T8RA8NE3B7" ;; findanyfile) name="Find Any File" type="zip" downloadURL=$(curl -fs "https://findanyfile.app/appcast2.php" | xpath '(//rss/channel/item/enclosure/@url)[1]' 2>/dev/null | cut -d '"' -f2) appNewVersion=$(curl -fs "https://findanyfile.app/appcast2.php" | xpath '(//rss/channel/item/enclosure/@sparkle:shortVersionString)[1]' 2>/dev/null | cut -d '"' -f2) expectedTeamID="25856V4B4X" ;; firefox) name="Firefox" type="dmg" downloadURL="https://download.mozilla.org/?product=firefox-latest&os=osx&lang=en-US" appNewVersion=$(curl -fs https://www.mozilla.org/en-US/firefox/releases/ | grep '/dev/null | cut -d '"' -f 2) expectedTeamID="V85GBYB7B9" ;; gimp) name="GIMP-2.10" type="dmg" downloadURL=https://$(curl -fs https://www.gimp.org/downloads/ | grep -m 1 -o "download.*gimp-.*.dmg") appNewVersion=$(echo $downloadURL | cut -d "-" -f 2) expectedTeamID="T25BQ8HSJF" ;; githubdesktop) name="GitHub Desktop" type="zip" if [[ $(arch) == "arm64" ]]; then downloadURL="https://central.github.com/deployments/desktop/desktop/latest/darwin-arm64" elif [[ $(arch) == "i386" ]]; then downloadURL="https://central.github.com/deployments/desktop/desktop/latest/darwin" fi appNewVersion=$(curl -fsL https://central.github.com/deployments/desktop/desktop/changelog.json | awk -F '{' '/"version"/ { print $2 }' | sed -E 's/.*,\"version\":\"([0-9.]*)\".*/\1/g') expectedTeamID="VEKTX9H2N7" ;; golang) # credit: Søren Theilgaard (@theilgaard) name="GoLang" type="pkg" packageID="org.golang.go" downloadURL="$(curl -fsIL "https://golang.org$(curl -fs "https://golang.org/dl/" | grep -i "downloadBox" | grep "pkg" | tr '"' '\n' | grep "pkg")" | grep -i "^location" | awk '{print $2}' | tr -d '\r\n')" appNewVersion="$( echo "${downloadURL}" | sed -E 's/.*\/(go[0-9.]*)\..*/\1/g' )" # Version includes letters "go" expectedTeamID="EQHXZ8M8AV" blockingProcesses=( NONE ) ;; googlechrome) name="Google Chrome" type="dmg" if [[ $(arch) != "i386" ]]; then printlog "Architecture: arm64 (not i386)" downloadURL="https://dl.google.com/chrome/mac/universal/stable/GGRO/googlechrome.dmg" appNewVersion=$(curl -s https://omahaproxy.appspot.com/history | awk -F',' '/mac_arm64,stable/{print $3; exit}') else printlog "Architecture: i386" downloadURL="https://dl.google.com/chrome/mac/stable/GGRO/googlechrome.dmg" appNewVersion=$(curl -s https://omahaproxy.appspot.com/history | awk -F',' '/mac,stable/{print $3; exit}') fi expectedTeamID="EQHXZ8M8AV" ;; googlechromepkg) name="Google Chrome" type="pkg" # # Note: this url acknowledges that you accept the terms of service # https://support.google.com/chrome/a/answer/9915669 # downloadURL="https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg" expectedTeamID="EQHXZ8M8AV" updateTool="/Library/Google/GoogleSoftwareUpdate/GoogleSoftwareUpdate.bundle/Contents/Resources/GoogleSoftwareUpdateAgent.app/Contents/MacOS/GoogleSoftwareUpdateAgent" updateToolArguments=( -runMode oneshot -userInitiated YES ) updateToolRunAsCurrentUser=1 ;; googledrive|\ googledrivefilestream) # credit: Isaac Ordonez, Mann consulting (@mannconsulting) name="Google Drive File Stream" type="pkgInDmg" packageID="com.google.drivefs" downloadURL="https://dl.google.com/drive-file-stream/GoogleDriveFileStream.dmg" # downloadURL="https://dl.google.com/drive-file-stream/GoogleDrive.dmg" blockingProcesses=( "Google Docs" "Google Drive" "Google Sheets" "Google Slides" ) expectedTeamID="EQHXZ8M8AV" ;; googledrivebackupandsync) name="Backup and Sync" type="dmg" downloadURL="https://dl.google.com/drive/InstallBackupAndSync.dmg" expectedTeamID="EQHXZ8M8AV" ;; googleearth) name="Google Earth Pro" type="pkgInDmg" downloadURL="https://dl.google.com/earth/client/advanced/current/GoogleEarthProMac-Intel.dmg" expectedTeamID="EQHXZ8M8AV" ;; googlejapaneseinput) # credit: Tadayuki Onishi (@kenchan0130) name="GoogleJapaneseInput" type="pkgInDmg" pkgName="GoogleJapaneseInput.pkg" downloadURL="https://dl.google.com/japanese-ime/latest/GoogleJapaneseInput.dmg" blockingProcesses=( NONE ) expectedTeamID="EQHXZ8M8AV" ;; googlesoftwareupdate) name="Install Google Software Update" type="pkgInDmg" pkgName="Install Google Software Update.app/Contents/Resources/GSUInstall.pkg" downloadURL="https://dl.google.com/mac/install/googlesoftwareupdate.dmg" blockingProcesses=( NONE ) expectedTeamID="EQHXZ8M8AV" ;; gotomeeting) # credit: @matins name="GoToMeeting" type="dmg" downloadURL="https://link.gotomeeting.com/latest-dmg" expectedTeamID="GFNFVT632V" ;; gpgsuite) # credit: Micah Lee (@micahflee) name="GPG Suite" type="pkgInDmg" pkgName="Install.pkg" downloadURL=$(curl -s https://gpgtools.org/ | grep https://releases.gpgtools.org/GPG_Suite- | grep Download | cut -d'"' -f4) expectedTeamID="PKV8ZPD836" ;; gpgsync) # credit: Micah Lee (@micahflee) name="GPG Sync" type="pkg" downloadURL="https://github.com$(curl -s -L https://github.com/firstlookmedia/gpgsync/releases/latest | grep /firstlookmedia/gpgsync/releases/download | grep \.pkg | cut -d'"' -f2)" expectedTeamID="P24U45L8P5" ;; grandperspective) name="GrandPerspective" type="dmg" downloadURL="https://sourceforge.net/projects/grandperspectiv/files/latest/download" expectedTeamID="3Z75QZGN66" ;; grasshopper) # credit: Gabe Marchan (gabemarchan.com - @darklink87) name="Grasshopper" type="dmg" downloadURL="https://dl.grasshopper.com/Grasshopper.dmg" pkgName="Grasshopper.dmg" expectedTeamID="KD6L2PTK2Q" ;; gyazo) # credit: @matins name="Gyazo" type="dmg" appNewVersion=$(curl -is "https://formulae.brew.sh/cask/gyazo" | grep 'Current version:' | grep -o "Gyazo.*dmg" | cut -d "-" -f 2 | awk -F ".dmg" '{print $1}') downloadURL="https://files.gyazo.com/setup/Gyazo-${appNewVersion}.dmg" expectedTeamID="9647Y3B7A4" ;; gyazogif) # credit: @matins # This is identical to gyazo, but the download contains two apps on the DMG name="Gyazo GIF" type="dmg" appNewVersion=$(curl -is "https://formulae.brew.sh/cask/gyazo" | grep 'Current version:' | grep -o "Gyazo.*dmg" | cut -d "-" -f 2 | awk -F ".dmg" '{print $1}') downloadURL="https://files.gyazo.com/setup/Gyazo-${appNewVersion}.dmg" expectedTeamID="9647Y3B7A4" ;; hancock) # Credit: Bilal Habib @Pro4TLZZZ name="Hancock" type="dmg" downloadURL=$(downloadURLFromGit JeremyAgost Hancock ) appNewVersion=$(versionFromGit JeremyAgost Hancock ) expectedTeamID="SWD2B88S58" ;; handbrake) name="HandBrake" type="dmg" downloadURL=$(downloadURLFromGit HandBrake HandBrake ) appNewVersion=$(versionFromGit HandBrake HandBrake ) expectedTeamID="5X9DE89KYV" ;; hazel) # credit: Søren Theilgaard (@theilgaard) name="Hazel" type="dmg" downloadURL=$(curl -fsI https://www.noodlesoft.com/Products/Hazel/download | grep -i "^location" | awk '{print $2}' | tr -d '\r\n') appNewVersion=$(curl -fsI https://www.noodlesoft.com/Products/Hazel/download | grep -i "^location" | awk '{print $2}' | sed -E 's/.*\/[a-zA-Z]*-([0-9.]*)\..*/\1/g') expectedTeamID="86Z3GCJ4MF" ;; hpeasyadmin) # credit: Søren Theilgaard (@theilgaard) name="HP Easy Admin" type="zip" downloadURL="https://ftp.hp.com/pub/softlib/software12/HP_Quick_Start/osx/Applications/HP_Easy_Admin.app.zip" expectedTeamID="6HB5Y2QTA3" ;; hpeasystart) # credit: Søren Theilgaard (@theilgaard) name="HP Easy Start" type="zip" downloadURL="https://ftp.hp.com/pub/softlib/software12/HP_Quick_Start/osx/Applications/HP_Easy_Start.app.zip" expectedTeamID="6HB5Y2QTA3" ;; hyper) name="Hyper" type="dmg" if [[ $(arch) == i386 ]]; then archiveName="mac-x64.dmg" elif [[ $(arch) == arm64 ]]; then archiveName="mac-arm64.dmg" fi downloadURL=$(downloadURLFromGit vercel hyper ) appNewVersion=$(versionFromGit vercel hyper) expectedTeamID="JW6Y669B67" ;; icons) # credit: Mischa van der Bent (@mischavdbent) name="Icons" type="zip" downloadURL=$(downloadURLFromGit sap macOS-icon-generator ) appNewVersion=$(versionFromGit sap macOS-icon-generator ) expectedTeamID="7R5ZEU67FQ" ;; imazingprofileeditor) # Credit: Bilal Habib @Pro4TLZZ name="iMazing Profile Editor" type="dmg" downloadURL="https://downloads.imazing.com/mac/iMazing-Profile-Editor/iMazingProfileEditorMac.dmg" expectedTeamID="J5PR93692Y" ;; inkscape) # credit: Søren Theilgaard (@theilgaard) name="Inkscape" type="dmg" downloadURL="https://inkscape.org$(curl -fs https://inkscape.org$(curl -fsJL https://inkscape.org/release/ | grep "/release/" | grep en | head -n 1 | cut -d '"' -f 6)mac-os-x/dmg/dl/ | grep "click here" | cut -d '"' -f 2)" appCustomVersion() { /Applications/Inkscape.app/Contents/MacOS/inkscape --version | cut -d " " -f2 } appNewVersion=$(curl -fsJL https://inkscape.org/release/ | grep "