Slack OS X app auto-install/update deployment method / script

owen_hael
New Contributor III

Hi all,

Wanted to share a small script I've been using to install/update the OS X Slack app using the JSS. Found App Store app deployment considerations to be an unnecessary pain for the deployment but could not deploy a flat package with an Apple ID locked Slack app because users would not be able to update. Inspired by the recent App Store downtime

I am far from a pro developer. Happy to get any input.

https://github.com/opragel/install_latest_slack_osx_app

30 REPLIES 30

KyleGDG
New Contributor

Thanks Owen! I've been finding it a pain as well to deploy slack in a non-app store dependent mode that is easy to update without having to get your end user to sign in every time. I'll give this a try.

owen_hael
New Contributor III

Great! That's exactly why I wrote it. Let me know if you run into issues with the script, I've been using it to install Slack during deployment for the last five months.

KyleGDG
New Contributor

Hey Owen,

This is fantastic, you've really helped me out. I am starting to have some funny issues however. Are you scripting a "quit" before and an "open" after? I'm getting some strange error messages when I do so not sure if you have experienced the same thing..

Sometimes during the quit, sometimes during the open I'll get this:
Running script Open Slack...
Script exit code: 1
Script result: LSOpenURLsWithRole() failed with error -10810 for the file /Applications/Slack.app.

nbaumgart
New Contributor

Thanks for this! Works great!

owen_hael
New Contributor III

@KyleGDG @nbaumgart

Glad the two of you were able to use the script, I do recommend using device-based VPP to deploy Slack when possible however. The script has worked well enough, but I've seen an issue or two with automatic updates when deploying this way that I haven't taken the time to investigate yet. Re-running the script / deleting Slack from Applications and then downloading again works fine, but it's not ideal.

Let me know if issues come up.

michaelherrick
New Contributor III

Hi Everyone,

I've been happily using this Slack installation script for months now but it suddenly stopped working in the past few weeks. When I run the script locally on a Mac, it works fine, but when I push it through Casper (either as part of my deployment workflow or from a Self-Service item), it fails with a "Cannot Install Item" error . The logs in the JSS aren't very helpful, they only read:

[STEP 1 of 4]
Executing Policy Install Slack
[STEP 2 of 4]
[STEP 3 of 4]
[STEP 4 of 4]

Is there another place to look for more detailed logs about how the script ran in the Casper context?

Thanks,

-Mike

bwiessner
Contributor II

I am seeing this error now that Slack 3.0.0 is out -

/usr/bin/unzip -o -q "$slackZipPath" -d "$SLACK_UNZIP_DIRECTORY"
[/tmp/Slack-3.0.0.dmg]
End-of-central-directory signature not found. Either this file is not
a zipfile, or it constitutes one disk of a multi-part archive. In the
latter case the central directory and zipfile comment will be found on
the last disk(s) of this archive.
unzip: cannot find zipfile directory in one of /tmp/Slack-3.0.0.dmg or
/tmp/Slack-3.0.0.dmg.zip, and cannot find /tmp/Slack-3.0.0.dmg.ZIP, period.
rm -rf "$slackZipPath"

mhinsz
New Contributor III

I believe Slack changed their downloader from a .zip to a .dmg.

This should replace any .zip functionality you have in the script:

# Downloads the Slack Update
/usr/bin/curl --retry 3 -L "https://slack.com/ssb/download-osx" -o "/tmp/Slack.dmg"

# Mount the .dmg 
hdiutil attach /tmp/Slack.dmg -nobrowse

#Print existing verion
slackVersion=$(defaults read /Applications/Slack.app/Contents/Info.plist CFBundleShortVersionString)
printf "Removing Slack version %s
" "$slackVersion"

# Remove the existing Application
rm -rf /Applications/Slack.app

# Copy the updated .app into place
cp -R /Volumes/Slack/Slack.app /Applications

# unmount the .dmg
hdiutil unmount /Volumes/Slack

nimitz
New Contributor II

@mhinsz Can you expand on this? I'm a noob and can't figure out what to replace.

bwiessner
Contributor II

Slack also now changed their mount dmg name to "Slack420921516146465233" so the unmount name might need to have some logic behind it as well . I will be posting a fork for this shortly.

@nimitz slack changed from a .zip download file type to a .dmg - so instead of unzipping now you need to mount the dmg and then copy the app from the Volume/Slack420921516146465233 to the Applications folder.

bwiessner
Contributor II

@mhinsz Slack changed the dmg mount volume name scheme - take a look at my fork below - happy hunting!

bwiessner
Contributor II

Here is my fork of @owen.pragel Slack script installer - cheers

[https://github.com/bwiessner/install_latest_slack_osx_app](link URL)

FritzsCorner
Contributor III

@bwiessner

Looks like your embedded link points back to this JAMFNation discussion when you click on it. Here is the link to your GitHub

https://github.com/bwiessner/install_latest_slack_osx_app

Thanks for sharing this!

IQ-MacAdmin
New Contributor II

This script works well. Thanks for posting and sharing.

ryan_er
New Contributor II

Script works! Thanks for sharing!

AndrewFromIT
New Contributor

Just wanted to say this script is still working and works great!

Also just looking at it is actually quite inspirational in terms of how as a JAMF admin you can make software policies that don't have to be manually re-packaged and updated because they stay good for so long.

Only question is @owen.pragel where did you find out the site you used to query the version numbers? I couldn't figure out where you found that json link.

I hope you're still around to answer that question!

jrserapio
Contributor

Looks like Slack has changed their wrapper method from DMG to Zip. May run into issues if DMG is hard coded. Trying to work on a script which will check for what kind of file was downloaded and then either mount dmg then move or unzip to /Applications.

anverhousseini
Contributor II

This would be the easiest way, you don't have to care about the file extension:

#!/bin/bash

install_slack() {
  download=$(curl -s "https://downloads.slack-edge.com/mac_releases/releases.json" | jq -r '.[].url' | tail -1)
  echo "=> Download from '${download}'"
  curl -s "${download}" -o "/tmp/$(basename ${download})"
  echo "=> Install '$(basename ${download})' to '/Applications/Slack.app'"
  ditto -x -k "/tmp/$(basename ${download})" "/Applications/"
  rm -f "/tmp/$(basename ${download})"
}

if [ ! -s "/Applications/Slack.app" ]; then
  echo "=> Slack.app is not installed"
  install_slack
elif [ ! $(defaults read "/Applications/Slack.app/Contents/Info.plist" CFBundleShortVersionString) = $(curl -s "https://downloads.slack-edge.com/mac_releases/releases.json" | jq -r '.[].version' | tail -1) ]; then
  echo "=> Slack.app is installed but not current"
  install_slack
else
  echo "=> Slack.app is installed and current"
fi

You will need to have jq installed: https://stedolan.github.io/jq/

jrserapio
Contributor

Thanks @anverhousseini. Trying to get something going without installing the jq as a dependancy.

shaquir
Contributor III

I recreated the Slack install script without the jq dependency. Feel free to check it out Here

shaquir
Contributor III

It just came to my attention that

"https://downloads.slack-edge.com/mac_releases/releases.json will soon be removed due to a change in [Slack's] updating process. They can hit our update endpoint, that URL looks something like:
https://slack.com/desktop/update?id=<any-random-string>&platform=darwin&os_version=<their-os-version>&app_version=<their-app-version>&channel=<their-channel>
They can find an example in their browser.log. Search for a line with the text MacUpdater: Direct feed URL and copy the URL from there."

I will be working to rewrite the Slack version pull this weekend, but the quickest fix would be to change

currentSlackVersion="4.0.0"

shaquir
Contributor III

To address Slacks removal of their releases.json link, I have updated the currentSlackVersion variable to:
Edited 8/1/19 to point to RSS link

currentSlackVersion=$(/usr/bin/curl -sL 'https://slack.com/release-notes/mac/rss' | grep -o "Slack-[0-9].[0-9].[0-9]"  | cut -c 7-11 | head -n 1)

The latest version of my Slack install/update script can be found here:

https://github.com/shaquir/ShellScript/blob/master/installSlack.sh

Heavy_D
Contributor III

Question?

What if you currently have the latest version of Slack which at the moment is 4.1.2 if they are working on something in Slack would this script check the version and just exit if it's the latest version?

I want to add it as part of my check-in's but don't want to interrupt any of our Developers in the processes.

rmgmedia
New Contributor III

Would this work on Catalina?

Heavy_D
Contributor III

@rmgmedia

I been using the following script that up to this point has been working for me not sure if it has changed for Catalina.

#!/usr/bin/env bash

################################################################################
# Custom Functions (These functions may need to be edited for specific apps)
################################################################################
extract_latest_version(){
  # used to extract the latest version from a website.
  # /usr/bin/grep -Eo '^.{4}$' can be used to extract a version number of a specific length.
  # generic extraction code: perl -pe 'if(($_)=/([0-9]+([.][0-9]+)+)/){$_.="
"}' | /usr/bin/sort -Vu | /usr/bin/tail -n 1
  perl -pe 'if(($_)=/([0-9]+([.][0-9]+)+)/){$_.="
"}' | /usr/bin/sort -Vu | /usr/bin/grep -Eo '^.{5}$' | /usr/bin/tail -n 1
}

################################################################################
# Fuctions (DO NOT EDIT THE BELOW FUNCTIONS, EXCEPT FOR MAIN)
################################################################################
message(){
  # description of what function does.
  local description='Displays a message to the customer if the defined application is running. Allow the customer to cancel if needed.'

  # define local variables.
  local message="${1}"
  local title='IT Support'
  local jamfHelper='/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper'
  local cancelMessage='User chose to cancel the update the process.'

  # data validation.
  [[ -z "${applicationName}" ]] && applicationName='Application'
  [[ -z "${applicationState}" ]] && applicationState=0
  [[ -z "${message}" ]] && message='Press OK to continue'

  # display dialog box message if application is running, otherwise continue silently.
  if [[ -e "${jamfHelper}" && "${applicationState}" -eq 1 ]]; then
    if ! "${jamfHelper}" 
    -windowType hud 
    -title "${title}" 
    -heading "${applicationName} Update" 
    -button1 'OK' 
    -button2 'Cancel' 
    -description "${message}" 
    -defaultButton 1 
    -lockHUD &>/dev/null
    then
      printf '%s
' "ERROR: ${cancelMessage}" 1>&2
      exit 1
    fi
  fi
}

error(){
  # description of what function does.
  local description='Displays an error message to the customer if the defined application is running. Otherwise prints to STDERR.'

  # declare local variables.
  local errorMessage="${1}"
  local title='Block.one IT Support'
  local jamfHelper='/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper'
  local defaultMessage='Update Failed.'

  # data validation
  [[ -z "${applicationName}" ]] && applicationName='Application'
  [[ -z "${applicationState}" ]] && applicationState=0
  [[ -z "${errorMessage}" ]] && errorMessage='something went wrong. update failed.'

  # display error message to customer only if application is running, otherwise print to STDERR.
  if [[ -e "${jamfHelper}" && "${applicationState}" -eq 1 ]]; then
    "${jamfHelper}" 
    -windowType hud 
    -title "${title}" 
    -heading "${applicationName} Update" 
    -button1 'OK' 
    -description "${defaultMessage}" 
    -defaultButton 1 
    -lockHUD &>/dev/null &
    printf '%s
' "ERROR: ${errorMessage}" 1>&2
    exit 1
  else
    printf '%s
' "ERROR: ${errorMessage}" 1>&2
    exit 1
  fi
}

get_installed_version(){
  # description of what function does.
  local description='Read and return version information from the Info.plist of the defined application.(aka: obtain version information for currently installed application.)'

  # define local variables.
  local applicationPath="${1}"
  local installedVersion

  # if the application path is defined and is a directory attempt to read and return version information
  [[ -z "${applicationPath}" || ! -d "${applicationPath}" ]] && error 'application not installed or path undefined.'
  installedVersion="$( /usr/bin/defaults read "${applicationPath}"/Contents/Info CFBundleShortVersionString 2> /dev/null )" || error 'could not detect installed version.'
  [[ -z "${installedVersion}" ]] && error 'installed version undefined.'

  # return installed version.
  printf '%s
' "${installedVersion}"
}

get_latest_version(){
  # description of what function does.
  local description='Extracts latest version information from a URL.'

  # define local variables.
  local latestVersionUrl="${1}"
  local data
  local latestVersion

  # attempt to extract version information from URL and return value.
  [[ -z "${latestVersionUrl}" ]] && error 'URL to search for latest version undefined.'
  data="$( /usr/bin/curl -sLJ "${latestVersionUrl}" )" || error 'failed to download version data.'
  [[ -z "${data}" ]] && error 'failed to download version data.'
  latestVersion="$( printf '%s
' "${data}" | extract_latest_version )" || error 'failed to extract latest version.'
  [[ -z "${latestVersion}" ]] && error 'latest version undefined.'

  # return latest version.
  printf '%s
' "${latestVersion}"
}

compare_versions(){
  # description of what function does.
  local description='Determines if the installed and latest versions are equal or not.'

  # define local variables.
  local latestVersion="${1}"
  local installedVersion="${2}"

  # data validation.
  [[ -z "${latestVersion}" ]] && error 'latest version undefined.'
  [[ -z "${installedVersion}" ]] && error 'installed version undefined.'

  # use the sort commands built-in ability to sort version numbers.
  if [[ "$( printf '%s
' "${latestVersion}" "${installedVersion}" | /usr/bin/sort -V | /usr/bin/head -n 1 )" != "${latestVersion}" ]]; then
    printf '%s
' 'application needs to be updated.'
  elif [[ "${latestVersion}" != "${installedVersion}" ]]; then
    error 'installed version is newer. latest version URL may need to be updated.'
  else
    printf '%s
' 'application is on the latest version.'
    exit 0
  fi
}

download(){
  # description of what function does.
  local description='Downloads a file from a given URL to a temporary directory and returns the full path to the download.'

  # define local variables.
  local dlURL="${1}"
  dlDir=''
  local dlName
  local productVer
  local userAgent
  downloadPath=''

  # if the download URL was provided. Build the effective URL (this helps if the given URL redirects to a specific download URL.)
  [[ -z "${dlURL}" ]] && error 'download url undefined.'
  dlURL="$( /usr/bin/curl "${dlURL}" -s -L -I -o /dev/null -w '%{url_effective}' )" || error 'failed to determine effective URL.'

  # create temporary directory for the download.
  dlDir="$( /usr/bin/mktemp -d 2> /dev/null )" || error 'failed to create temporary download directory.'
  [[ ! -d "${dlDir}" ]] && error 'temporary download directory does not exist.'
  export dlDir

  # build user agent for curl.
  productVer="$( /usr/bin/sw_vers -productVersion | /usr/bin/tr '.' '_' )" || error 'could not detect product version needed for user agent.'
  [[ -z "${productVer}" ]] && error 'product version undefined'
  userAgent='Mozilla/5.0 (Macintosh; Intel Mac OS X '"${productVer})"' AppleWebKit/535.6.2 (KHTML, like Gecko) Version/5.2 Safari/535.6.2'

  # change the present working directory to the temporary download directory and attempt download.
  cd "${dlDir}" || error 'could not change pwd to temporary download directory.'
  dlName="$( /usr/bin/curl -sLJO -A "${userAgent}" -w "%{filename_effective}" --retry 10 "${dlURL}" )" || error 'failed to download latest version.'
  [[ -z "${dlName}" ]] && error 'download filename undefined.'
  downloadPath="${dlDir}/${dlName}"
  [[ ! -e "${downloadPath}" ]] && error 'download filename undefined. can not locate download.'

  # export full path to the downloaded file including extension.
  export downloadPath
}

detect_running(){
  # description of what function does.
  local description='Detect if the defined application is currently running. Export the application state so it is available globally.'

  # define variables.
  applicationState=0

  # determine if application is running and return result.
  # 1 = running , 0 = not running.
  if /usr/bin/pgrep -q "${applicationName}"; then
    applicationState=1
    export applicationState
  else
    export applicationState
  fi
}

kill_running(){
  # description of what function does.
  local description='If the defined application is running. Give customer option to close it or cancel the update process.'

  # notify customer and get input. attempt killing application if it is running and customer has agreed.
  message "${applicationName} needs to be updated. The application will close if you continue."
  if [[ "${applicationState}" -eq 1 ]]; then
    /usr/bin/pkill -9 "${applicationName}" &>/dev/null
  fi
}

uninstall(){
  # description of what function does.
  local description='Uninstalls the defined application.'

  # define local variables.
  local applicationPath="${1}"

  # data validation.
  [[ ! -d "${applicationPath}" ]] && error 'app path undefined or not a directory.'

  # attempt uninstall.
  /bin/mv "${applicationPath}" "${applicationPath}.old" &> /dev/null || error "failed to uninstall application."
  sleep 2
}

install(){
  # description of what function does.
  local description='Determines what kind of installer the download is. Attempts install accordingly.'

  # determine download installer type. (dmg, pkg, zip)
  if [[ "$( printf '%s
' "${downloadPath}" | /usr/bin/grep -c '.dmg$' )" -eq 1 ]]; then
    install_dmg
  elif [[ "$( printf '%s
' "${downloadPath}" | /usr/bin/grep -c '.pkg$' )" -eq 1 ]]; then
    install_pkg
  elif [[ "$( printf '%s
' "${downloadPath}" | /usr/bin/grep -c '.zip$' )" -eq 1 ]]; then
    install_zip
  else
    error 'could not detect install type.'
  fi
}

install_pkg(){
  # description of what function does.
  local description='Silently install pkg.'

  # define local variables.
  local pkg="${1}"

  if [[ -z "${pkg}" ]]; then
    pkg="${downloadPath}"
  fi

  # use installer command line tool to silently install pkg.
  /usr/sbin/installer -allowUntrusted -pkg "${pkg}" -target / &> /dev/null || error 'failed to install latest version pkg.'
}

install_dmg(){
  # description of what function does.
  local description='Silently install dmg.'

  # define variables.
  mnt=''
  local dmg="${1}"
  local app
  local pkg


  if [[ -z "${dmg}" ]]; then
    dmg="${downloadPath}"
  fi

  # create temporary mount directory for dmg and export path if exists.
  mnt="$( /usr/bin/mktemp -d 2> /dev/null )" || error 'failed to create temporary mount point for dmg.'
  [[ ! -d "${mnt}" ]] && error 'failed to verify temporary mount point for dmg exists.'
  export mnt

  # silently attach the dmg download to the temporary mount directory and determine what it contains (app or pkg)
  sleep 2
  /usr/bin/hdiutil attach "${dmg}" -quiet -nobrowse -mountpoint ${mnt} &> /dev/null || error 'failed to mount dmg.'
  app="$( /bin/ls "${mnt}" | /usr/bin/grep '.app$' | head -n 1 )"
  pkg="$( /bin/ls "${mnt}" | /usr/bin/grep '.pkg$' | head -n 1 )"

  # attempt install based on contents of dmg.
  if [[ ! -z "${app}" && -e "${mnt}/${app}" ]]; then
    cp -Rf "${mnt}/${app}" '/Applications' &> /dev/null || error "failed to copy the latest version to the applications directory."
  elif [[ ! -z "${pkg}" && -e "${mnt}/${pkg}" ]]; then
    install_pkg "${mnt}/${pkg}"
  else
    error 'could not detect installation type in mounted dmg.'
  fi
}

install_zip(){

  # define variables.
  uz=''
  local app
  local pkg
  local dmg

  # create temporary unzip directory and export globally if exists.
  uz="$( /usr/bin/mktemp -d 2> /dev/null )" || error 'failed to create temporary unzip directory for zip.'
  [[ ! -d "${uz}" ]] && error 'failed to verify temporary unzip directory exists.'
  export uz

  # unzip zip file and determine installer type. (app, pkg, dmg)
  /usr/bin/unzip "${downloadPath}" -d "${uz}" &> /dev/null || error 'failed to unzip download.'
  app="$( /bin/ls ${uz} | /usr/bin/grep '.app$' | head -n 1 )"
  pkg="$( /bin/ls ${uz} | /usr/bin/grep '.pkg$' | head -n 1 )"
  dmg="$( /bin/ls ${uz} | /usr/bin/grep '.dmg$' | head -n 1 )"

  # attempt install based on contents of zip file.
  if [[ ! -z "${app}" && -e "${uz}/${app}" ]]; then
    cp -Rf "${uz}/${app}" '/Applications' &> /dev/null || error "failed to copy the latest version to the applications directory."
  elif [[ ! -z "${pkg}" && -e "${uz}/${pkg}" ]]; then
    install_pkg "${uz}/${pkg}"
  elif [[ ! -z "${dmg}" && -e "${uz}/${dmg}" ]]; then
    install_dmg "${uz}/${dmg}"
  else
    error 'could not detect installation type in unzipped download.'
  fi
}

cleanup(){
  # description of what function does.
  local description='Removes temporary items created during the download and installation processes.'

  local applicationPath="/Applications/${applicationName}.app"

  # if a temporary mount directory has been created, force unmount and remove the directory.
  if [[ -d "${mnt}" ]]; then
    /usr/bin/hdiutil detach -force -quiet "${mnt}"
    /sbin/umount -f "${mnt}" &> /dev/null
    /bin/rm -rf "${mnt}" &> /dev/null
  fi

  # if temporary unzip directory exists, remove it.
  if [[ -d "${uz}" ]]; then
    /bin/rm -rf "${uz}" &> /dev/null
  fi

  # if the defined application does not exist restore the original to the apps directory.
  if [[ ! -d "${applicationPath}" ]]; then
    printf '%s
' 'Update failed. Restoring original application...'
    /bin/mv "${applicationPath}.old" "${applicationPath}" &> /dev/null
  elif [[ -d "${applicationPath}.old" ]]; then
    /bin/rm -rf "${applicationPath}.old" &> /dev/null
  fi

  # if a temporary download directory has been created. remove it.
  if [[ -d "${dlDir}" ]]; then
    /bin/rm -rf "${dlDir}" &> /dev/null
  fi
}

main(){

  # declare local variables
  applicationName='Slack'
  local latestVersionUrl='https://slack.com/downloads/mac'
  local latestDownloadUrl='https://slack.com/ssb/download-osx'
  local applicationPath="/Applications/${applicationName}.app"
  local installedVersion
  local latestVersion

  # ensure cleanup runs on exit or error.
  trap cleanup EXIT ERR

  # export global variables
  export applicationName

  # determine if the application needs to be updated.
  installedVersion="$( get_installed_version "${applicationPath}" )" || exit 1
  latestVersion="$( get_latest_version "${latestVersionUrl}" )" || exit 1
  compare_versions "${latestVersion}" "${installedVersion}"

  # download latest version of the application and export full path to the temporary download location for the cleanup function.
  download "${latestDownloadUrl}"

  # determine if application is running and notify customer before attempting kill.
  detect_running
  kill_running

  # uninstall the application if neeeded for the update.
  uninstall "${applicationPath}"

  # install latest version of the application.
  install
  message 'Update Successful'
  exit 0
}
main "$@"

rmgmedia
New Contributor III

Thanks @JarvisUno I will test it out soon.

MacJunior
Contributor III

Anybody has a script to install and download the latest version of Slack?

Dalmatian
Contributor

Any script for M1 chip laptop

radarfirst
New Contributor II

This script works for me. It is based on the Google Chrome DL and Install script found here: https://www.jamf.com/jamf-nation/discussions/20894/install-latest-version-of-google-chrome-without-r...

#!/bin/sh
dmgfile="Slack.dmg"
volname="Slack.app"
logfile="/Library/Logs/SlackInstallScript.log"

url='https://slack.com/ssb/download-osx'

/bin/echo "--" >> ${logfile}
/bin/echo "`date`: Downloading latest version." >> ${logfile}
/usr/bin/curl -L -o /tmp/${dmgfile} "${url}" >/dev/null 2>&1
/bin/echo "`date`: Mounting installer disk image." >> ${logfile}
/usr/bin/hdiutil attach /tmp/${dmgfile} -nobrowse -quiet
/bin/echo "`date`: Installing..." >> ${logfile}
ditto -rsrc "/Volumes/${volname}/Slack.app" "/Applications/Slack.app"
/bin/sleep 10
/bin/echo "`date`: Unmounting installer disk image." >> ${logfile}
/usr/bin/hdiutil detach $(/bin/df | /usr/bin/grep "${volname}" | awk '{print $1}') -quiet
/bin/sleep 10
/bin/echo "`date`: Deleting disk image." >> ${logfile}
/bin/rm /tmp/"${dmgfile}"

exit 0

sgoetz
Contributor

Hey All,

Thought I would post this to help others. I found the links for the 3 different installers:

Intel = curl "https://slack.com/ssb/download-osx" -s -L -I -o /dev/null -w '%{url_effective}'
Silicon = curl "https://slack.com/ssb/download-osx-silicon" -s -L -I -o /dev/null -w '%{url_effective}'
Universal = curl "https://slack.com/ssb/download-osx-universal" -s -L -I -o /dev/null -w '%{url_effective}'

Article pulled from: https://slack.com/help/articles/360035635174-Deploy-Slack-for-macOS

Hope this helps

Shawn OG