diff --git a/CHANGELOG.md b/CHANGELOG.md index 1aaf739..19fad69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,13 @@ -## v0.3 - 2020-07- +## v0.4 - 2020-10-19 + +- you can now set script variables as an argument in the form `VARIABLE=value`. More detail on this in the README file, 'Configuration from Arguments.' (#26, #50, #72, and #73) +- change `downloadFromGit` to match file types better (#58) +- implemented a workaround for changed behavior of `xpath` in Big Sur (#80) +- added an option `prompt_user_the_kill` to `BLOCKING_PROCESS_ACTION` which will kill the process after the third unsuccessful attempt to quit (#78, thanks Patrick Atoon @raptor399) +- added several new labels for total of 116 + + +## v0.3 - 2020-07-23 - added several new labels for total of 98 - removed the powershell labels, since the installer is not notarized @@ -16,4 +25,4 @@ improved logging - several new applications: count increased from 62 in 0.1 to 87 in 0.2 -## v0.1 - 2020-05-12 \ No newline at end of file +## v0.1 - 2020-05-12 diff --git a/Installomator.sh b/Installomator.sh index 4e84dda..04505f6 100755 --- a/Installomator.sh +++ b/Installomator.sh @@ -8,8 +8,8 @@ # inspired by the download scripts from William Smith and Sander Schram # with additional ideas and contribution from Isaac Ordonez, Mann consulting -VERSION='0.3' -VERSIONDATE='20200609' +VERSION='0.4' +VERSIONDATE='20201019' export PATH=/usr/bin:/bin:/usr/sbin:/sbin @@ -35,6 +35,9 @@ BLOCKING_PROCESS_ACTION=prompt_user # - 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 +# - prompt_user_then_kill +# show a user dialog for each blocking process found, +# attempt to quit two times, kill the process finally # - kill kill process without prompting or giving the user a chance to save @@ -113,9 +116,54 @@ BLOCKING_PROCESS_ACTION=prompt_user # -# MARK: functions to help with getting data +# MARK: Functions -# Logging +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 + printlog "################## End Installomator \n\n" + exit "$1" +} + +runAsUser() { + if [[ $currentUser != "loginwindow" ]]; then + uid=$(id -u "$currentUser") + launchctl asuser $uid sudo -u $currentUser "$@" + 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\")" +} + +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(){ @@ -147,7 +195,7 @@ downloadURLFromGit() { # $1 git user name, $2 git repo name | awk -F '"' "/browser_download_url/ && /$archiveName/ { print \$4 }") else downloadURL=$(curl --silent --fail "https://api.github.com/repos/$gitusername/$gitreponame/releases/latest" \ - | awk -F '"' "/browser_download_url/ && /$filetype/ { print \$4 }") + | awk -F '"' "/browser_download_url/ && /$filetype\"/ { print \$4 }") fi if [ -z "$downloadURL" ]; then printlog "could not retrieve download URL for $gitusername/$gitreponame" @@ -159,6 +207,312 @@ downloadURLFromGit() { # $1 git user name, $2 git repo name } +xpath() { + # the xpath tool changes in Big Sur and now requires the `-e` option + if [[ $(sw_vers -buildVersion) > "20A" ]]; then + /usr/bin/xpath -e $@ + else + /usr/bin/xpath $@ + fi +} + + +getAppVersion() { + # get all apps matching name + applist=$(mdfind "kind:application $appName" -0 ) + if [[ $applist = "" ]]; then + printlog "Spotlight not returning any app, trying manually in /Applications." + if [[ -d "/Applications/$appName" ]]; then + applist="/Applications/$appName" + fi + 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 ) + printlog "found app at $installedAppPath, version $appversion" + 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..3}; do + countedProcesses=0 + for x in ${blockingProcesses}; do + if pgrep -xq "$x"; then + printlog "found blocking process $x" + + case $BLOCKING_PROCESS_ACTION in + kill) + printlog "killing process $x" + pkill $x + ;; + 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 = 3 && $BLOCKING_PROCESS_ACTION = "prompt_user_then_kill" ]]; then + printlog "killing process $x" + pkill $x + else + printlog "telling app $x to quit" + runAsUser osascript -e "tell app \"$x\" to quit" + fi + fi + ;; + silent_fail) + cleanupAndExit 12 "blocking process '$x' found, aborting" + ;; + esac + + countedProcesses=$((countedProcesses + 1)) + fi + done + + if [[ $countedProcesses -eq 0 ]]; then + # no blocking processes, exit the loop early + break + else + # give the user a bit of time to quit apps + printlog "waiting 30 seconds for processes to quit" + sleep 30 + fi + done + + if [[ $countedProcesses -ne 0 ]]; then + cleanupAndExit 11 "could not quit all processes, aborting..." + fi + + printlog "no more blocking processes, continue with update" +} + +installAppWithPath() { # $1: path to app to install in $targetDir + 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: $teamID (expected: $expectedTeamID )" + + if [ "$expectedTeamID" != "$teamID" ]; then + cleanupAndExit 5 "Team IDs do not match" + fi + + # check for root + if [ "$(whoami)" != "root" ]; then + # not running as root + if [ "$DEBUG" -eq 0 ]; then + cleanupAndExit 6 "not running as root, exiting" + fi + + printlog "DEBUG enabled, skipping copy and chown steps" + return 0 + fi + + # 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 + +} + +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 + + # 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 + printlog "not running as root, exiting" + cleanupAndExit 6 + 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 starting with $name and ending with 'pkg' + findfiles=$(find "$tmpDir" -iname "*.pkg" -maxdepth 1 ) + 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 +} + +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 +} + + # MARK: check minimal macOS requirement autoload is-at-least @@ -167,17 +521,28 @@ if ! is-at-least 10.14 $(sw_vers -productVersion); then exit 98 fi -# MARK: get the label +# MARK: argument parsing if [[ $# -eq 0 ]]; then - grep -E '^[a-z0-9\-]*(\)|\|\\)$' "$0" | tr -d ')|\' | grep -v -E '^broken' | grep -v -E '^(longversion|version)$' | sort + grep -E '^[a-z0-9\-]*(\)|\|\\)$' "$0" | tr -d ')|\' | grep -v -E '^(broken.*|longversion|version|valuesfromarguments)$' | sort exit 0 -elif [[ $# -gt 3 ]]; then - # jamf uses $4 for the first custom parameter +elif [[ $1 == "/" ]]; then + # jamf uses sends '/' as the first argument printlog "shifting arguments for Jamf" shift 3 fi -label=${1:?"no label provided"} +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 printlog "################## Start Installomator" printlog "################## $label" @@ -441,13 +806,13 @@ webexteams) downloadURL="https://binaries.webex.com/WebexTeamsDesktop-MACOS-Gold/WebexTeams.dmg" expectedTeamID="DE8Y96K9QP" ;; -#citrixworkspace) - # credit: Erik Stam (@erikstam) - #name="Citrix Workspace" - #type="pkgInDmg" - #downloadURL="https://downloads.citrix.com/17596/CitrixWorkspaceApp.dmg?__gda__=1588183500_fc68033aef7d6d163d8b8309b964f1de" - #expectedTeamID="S272Y5R93J" - #;; +citrixworkspace) + #credit: Erik Stam (@erikstam) and #Philipp on MacAdmins Slack + name="Citrix Workspace" + type="pkgInDmg" + downloadURL="https:"$(curl -s -L "https://www.citrix.com/downloads/workspace-app/mac/workspace-app-for-mac-latest.html#ctx-dl-eula-external" | grep "dmg?" | sed "s/.*rel=.\(.*\)..id=.*/\1/") + expectedTeamID="S272Y5R93J" + ;; privileges) # credit: Erik Stam (@erikstam) name="Privileges" @@ -640,8 +1005,8 @@ brave) # credit: @securitygeneration name="Brave Browser" type="dmg" - downloadURL="https://laptop-updates.brave.com/latest/osx" - expectedTeamID="9BNSXJN65R" + downloadURL=$(curl --location --fail --silent "https://updates.bravesoftware.com/sparkle/Brave-Browser/stable/appcast.xml" | xpath '//rss/channel/item[1]/enclosure/@url' 2>/dev/null | cut -d '"' -f 2) + expectedTeamID="KL8N8XSYF4" ;; umbrellaroamingclient) # credit: Tadayuki Onishi (@kenchan0130) @@ -765,9 +1130,9 @@ r) ;; 8x8) # credit: #D-A-James from MacAdmins Slack and Isaac Ordonez, Mann consulting (@mannconsulting) - name="8x8 - Virtual Office" + name="8x8 Work" type="dmg" - downloadURL=$(curl -fs https://support.8x8.com/cloud-phone-service/voice/virtual-office-desktop/download-virtual-office-desktop | grep -m 1 -o "http.*VOD.*.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") expectedTeamID="FC967L3QRG" ;; egnyte) @@ -778,6 +1143,132 @@ egnyte) expectedTeamID="FELUD555VC" blockingProcesses=( NONE ) ;; +camtasia) + name="Camtasia 2020" + type="dmg" + downloadURL=https://download.techsmith.com/camtasiamac/releases/Camtasia.dmg + expectedTeamID="7TQL462TU8" + ;; +snagit2020) + # credit: Isaac Ordonez, Mann consulting (@mannconsulting) + name="Snagit 2020" + type="dmg" + downloadURL="https://download.techsmith.com/snagitmac/releases/Snagit.dmg" + expectedTeamID="7TQL462TU8" + ;; +virtualbox) + # credit: AP Orlebeke (@apizz) + name="VirtualBox" + type="pkgInDmg" + pkgName="VirtualBox.pkg" + downloadURL=$(curl -fs "https://www.virtualbox.org/wiki/Downloads" \ + | awk -F '"' "/OSX.dmg/ { print \$4 }") + expectedTeamID="VB5E2TV963" + ;; +detectxswift) + # credit: AP Orlebeke (@apizz) + name="DetectX Swift" + type="zip" + downloadURL="https://s3.amazonaws.com/sqwarq.com/PublicZips/DetectX_Swift.app.zip" + expectedTeamID="MAJ5XBJSG3" + ;; +autopkgr) + # credit: AP Orlebeke (@apizz) + 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 }") + expectedTeamID="JVY2ZR6SEF" + ;; +airserver) + # credit: AP Orlebeke (@apizz) + name="AirServer" + type="dmg" + downloadURL="https://www.airserver.com/download/mac/latest" + expectedTeamID="6C755KS5W3" + ;; +vscodium) + # credit: AP Orlebeke (@apizz) + name="VSCodium" + type="dmg" + downloadURL=$(curl -fs "https://api.github.com/repos/VSCodium/vscodium/releases/latest" \ + | awk -F '"' "/browser_download_url/ && /dmg/ && ! /sig/ && ! /CLI/ && ! /sha256/ { print \$4 }") + expectedTeamID="C7S3ZQ2B8V" + appName="VSCodium.app" + blockingProcesses=( Electron ) + ;; +keepassxc) + # credit: Patrick Atoon (@raptor399) + name="KeePassXC" + type="dmg" + downloadURL="$(downloadURLFromGit keepassxreboot keepassxc)" + expectedTeamID="G2S7P7J672" + ;; +alfred) + # credit: AP Orlebeke (@apizz) + name="Alfred" + type="dmg" + downloadURL=$(curl -fs https://www.alfredapp.com | awk -F '"' "/dmg/ {print \$2}" | head -1) + appName="Alfred 4.app" + expectedTeamID="XZZXE9SED4" + ;; +istatmenus) + # credit: AP Orlebeke (@apizz) + name="iStat Menus" + type="zip" + downloadURL="https://download.bjango.com/istatmenus/" + expectedTeamID="Y93TK974AT" + blockingProcesses=( "iStat Menus" "iStatMenusAgent" "iStat Menus Status" ) + ;; +sizeup) + # credit: AP Orlebeke (@apizz) + name="SizeUp" + type="zip" + downloadURL="https://www.irradiatedsoftware.com/download/SizeUp.zip" + expectedTeamID="GVZ7RF955D" + ;; +tunnelblick) + name="Tunnelblick" + type="dmg" + downloadURL=$(downloadURLFromGit TunnelBlick Tunnelblick ) + expectedTeamID="Z2SG5H3HC8" + ;; +yubikeymanagerqt) + # credit: Tadayuki Onishi (@kenchan0130) + name="YubiKey Manager GUI" + type="pkg" + downloadURL="https://developers.yubico.com/yubikey-manager-qt/Releases/$(curl -sfL https://api.github.com/repos/Yubico/yubikey-manager-qt/releases/latest | awk -F '"' '/"tag_name"/ { print $4 }')-mac.pkg" + expectedTeamID="LQA3CS5MM7" + ;; +skitch) + # credit: Isaac Ordonez, Mann consulting (@mannconsulting) + name="Skitch" + type="zip" + downloadURL=$(curl -fs -A "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1 Safari/605.1.15" https://evernote.com/products/skitch | grep -o "https://.*zip") + expectedTeamID="J8RPQ294UB" + Company="Evernote" + ;; +dialpad) + # credit: @ehosaka + name="Dialpad" + type="dmg" + downloadURL="https://storage.googleapis.com/dialpad_native/osx/Dialpad.dmg" + expectedTeamID="9V29MQSZ9M" + ;; +amazonworkspaces) + # credit: Isaac Ordonez, Mann consulting (@mannconsulting) + name="Workspaces" + type="pkg" + downloadURL="https://d2td7dqidlhjx7.cloudfront.net/prod/global/osx/WorkSpaces.pkg" + expectedTeamID="94KV3E626L" + ;; +apparency) + name="Apparency" + type="dmg" + downloadURL="https://www.mothersruin.com/software/downloads/Apparency.dmg" + expectedTeamID="936EB786NH" + ;; + # MARK: add new labels above here @@ -948,6 +1439,27 @@ microsoftdefenderatp) updateToolArguments=( --install --apps WDAV00 ) ;; +# this description is so you can provide all variables as arguments +# it will only check if the required variables are setting +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 + ;; + # these descriptions exist for testing and are intentionally broken brokendownloadurl) @@ -975,341 +1487,8 @@ brokenteamid) ;; esac -# 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 - printlog "################## End Installomator \n\n" - exit "$1" -} - -runAsUser() { - if [[ $currentUser != "loginwindow" ]]; then - uid=$(id -u "$currentUser") - launchctl asuser $uid sudo -u $currentUser "$@" - 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\")" -} - -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 -} - - -getAppVersion() { - # get all apps matching name - applist=$(mdfind "kind:application $appName" -0 ) - if [[ $applist = "" ]]; then - printlog "Spotlight not returning any app, trying manually in /Applications." - if [[ -d "/Applications/$appName" ]]; then - applist="/Applications/$appName" - fi - 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 ) - printlog "found app at $installedAppPath, version $appversion" - 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..3}; do - countedProcesses=0 - for x in ${blockingProcesses}; do - if pgrep -xq "$x"; then - printlog "found blocking process $x" - - case $BLOCKING_PROCESS_ACTION in - kill) - printlog "killing process $x" - pkill $x - ;; - prompt_user) - 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 - runAsUser osascript -e "tell app \"$x\" to quit" - fi - ;; - silent_fail) - cleanupAndExit 12 "blocking process '$x' found, aborting" - ;; - esac - - countedProcesses=$((countedProcesses + 1)) - fi - done - - if [[ $countedProcesses -eq 0 ]]; then - # no blocking processes, exit the loop early - break - else - # give the user a bit of time to quit apps - printlog "waiting 30 seconds for processes to quit" - sleep 30 - fi - done - - if [[ $countedProcesses -ne 0 ]]; then - cleanupAndExit 11 "could not quit all processes, aborting..." - fi - - printlog "no more blocking processes, continue with update" -} - -installAppWithPath() { # $1: path to app to install in $targetDir - 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: $teamID (expected: $expectedTeamID )" - - if [ "$expectedTeamID" != "$teamID" ]; then - cleanupAndExit 5 "Team IDs do not match" - fi - - # check for root - if [ "$(whoami)" != "root" ]; then - # not running as root - if [ "$DEBUG" -eq 0 ]; then - cleanupAndExit 6 "not running as root, exiting" - fi - - printlog "DEBUG enabled, skipping copy and chown steps" - return 0 - fi - - # 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 - -} - -mountDMG() { - # mount the dmg - printlog "Mounting $tmpDir/$archiveName" - # always pipe 'Y\n' in case the dmg requires an agreement - if ! dmgmount=$(printlog '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 - - # 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 - printlog "not running as root, exiting" - cleanupAndExit 6 - 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" - 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 starting with $name and ending with 'pkg' - findfiles=$(find "$tmpDir" -iname "*.pkg" -maxdepth 1 ) - 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 -} - -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 -} - - - -# MARK: main code starts here +# MARK: application download and installation starts here # MARK: extract info from data diff --git a/Labels.txt b/Labels.txt index 0ac878f..b6433dc 100644 --- a/Labels.txt +++ b/Labels.txt @@ -3,20 +3,29 @@ adobereaderdc adobereaderdc-install adobereaderdc-update +airserver +alfred +amazonworkspaces +apparency appcleaner aquaskk atom autodmg +autopkgr aviatrix bbedit bettertouchtool boxdrive brave +camtasia +citrixworkspace code42 coderunner cyberduck depnotify desktoppr +detectxswift +dialpad discord docker dropbox @@ -33,11 +42,13 @@ googlejapaneseinput grandperspective handbrake icons +istatmenus iterm2 jamfmigrator jamfpppcutility jamfreenroller karabinerelements +keepassxc krisp malwarebytes microsoftautoupdate @@ -74,7 +85,10 @@ royaltsx santa sfsymbols signal +sizeup +skitch slack +snagit2020 sonos sonoss1 sonoss2 @@ -88,11 +102,15 @@ textmate things torbrowser tunnelbear +tunnelblick umbrellaroamingclient +virtualbox visualstudiocode vlc +vscodium webexmeetings webexteams whatsapp wwdcformac +yubikeymanagerqt zoom diff --git a/README.md b/README.md index 5cc5a36..8fa11a5 100644 --- a/README.md +++ b/README.md @@ -305,6 +305,27 @@ Depending on the application or pkg there are a few more variables you can or ne - `updateToolRunAsCurrentUser`: When this variable is set (any value), `$updateTool` will be run as the current user. Default is unset and +### Configuration from Arguments + +You can provide a configuration variable, such as `DEBUG` or `NOTIFY` as an argument in the form `VAR=value`. For example: + +``` +./Installomator.sh desktoppr DEBUG=0 NOTIFY=silent +``` + +Providing variables this way will override any variables set in the script. + +You can even provide _all_ the variables necessary for download and installation. Of course, without a label the argument parsing will fail, so I created a special label `valuesfromarguments` which only checks if the four required values are present: + +``` +./Installomator.sh name=desktoppr type=pkg downloadURL=https://github.com/scriptingosx/desktoppr/releases/download/v0.3/desktoppr-0.3.pkg expectedTeamID=JME5BW3F3R valuesfromarguments +``` + +The order of the variables and label is not relevant. But, when you provide more than one label, all but the _last_ label will be ignored. + +Providing all the variables this way might be useful for certain downloads that have a customized URL for each vendor/customer (like customized TeamView or Watchman Monitoring) or are local downloads. + + ## Frequently Asked Questions ### What if the latest version of the app is already installed?