Getting Started

Endpoints

Patch Management

Vulnerability Management

Software Deployment & IT Assets

Automation & Remote Desktop

Real-Time Reports & Alerts

Account Access & Management

SSO Authentication

Security Concerns

Need Help?

Action1 5 Documentation 5 Using Common.sh File

Using Common.sh File

The common.sh file should not be changed. Include it in the ZIP archive together with the application setup and install.sh prepared for your app setup type.

#!/bin/bash
export PATH=$PATH:/sbin:/usr/sbin

# common functions #

function print_revision() {
    local revision="1.3"
    msg=$(printf 'common.sh script revision: %s\n' "${revision}")
    log -m "$msg" -n "INFO"
    return 0
}


function validate_params() {
    # -m "install"|"upgrade"
    # -p "/Applications"|"custom_path"
    # -f "/Firefox.app", for upgrade only
    # -s "error|kill|ignore", test process, default="kill" and stop deployment
    # -v "ERR|WARN|INFO|DBG|DBG2", logging level. Default="INFO"
    # -b "app.new.build.number"

    local msg
    local OPTIND
    local OPTSTRING="m:p:f:s:v:b:"
    local OPTERR=0
    local err_code=1
    local def_log_level="INFO"
    if [[ -z "$log_level" ]]; then
        log_level="$def_log_level"    # global variable
    fi

    while getopts ${OPTSTRING} opt; do
        case ${opt} in
            m)  m="${OPTARG}" ;;
            p)  p="${OPTARG}" ;;
            f)  f="${OPTARG}" ;;
            s)  s="${OPTARG}" ;;
            v)  v="${OPTARG}" ;;
            b)  b="${OPTARG}" ;;
        esac

        if [[ "${OPTARG}" == "-"* ]]; then
            msg=$(printf 'Argument %s has invalid value "%s". Exit code: %s.' "-${opt}" "${OPTARG}" "$err_code")
            echo "$msg" >&2; log -m "$msg" -n "ERR"
            return $err_code
        fi
    done

    if [[ $OPTIND -eq 1 || -z "$m" ]]; then
        msg=$(printf 'Mandatory option "-m" is not specified. Exit code: %s.' "$err_code")
        echo "$msg" >&2; log -m "$msg" -n "ERR"
        return $err_code
    fi

    if [[ "$m" != "install" && "$m" != "upgrade" ]]; then
        msg=$(printf 'Unknown deployment mode was specified as parameter value here: -m "%s"' "$m")
        echo "$msg" >&2; log -m "$msg" -n "ERR"
        return $err_code
    fi

    if [[ -z "$f" && "$m" == 'upgrade' ]]; then
        #msg=$(printf 'Argument %s is empty. Exit code %s.' "-f" "$err_code")
        msg=$(printf 'Target folder name was not specified for application update. Exit code: %s.' "$err_code")
        echo "$msg" >&2; log -m "$msg" -n "ERR"
        return $err_code
    fi

    p=${p:-"/Applications"}           # default value = "/Applications
    p=$(echo "$p" | sed 's/\/$//')    # remove last '/' if specified
    s=${s:-"kill"}                    # default value = "kill"
    v=${v:-"$def_log_level"}          # default value = $def_log_level ("INFO")

    return 0
}


function attach_dmg() {
    local setup_file="$1"
    local root_dmg_folder="$2"
    local app_folder="$3"
    local mount_point="$4"
    local exit_code=0
    local err_code=1
    local msg

    msg=$(printf 'params: setup_file="%s" root_dmg_folder="%s" app_folder="%s" mount_point="%s"' "$1" "$2" "$3" "$4")
    log -m "$msg" -n "DBG2"

    if [[ -z "$mount_point" || "$mount_point" == "." || "$mount_point" == "/" || "$mount_point" == "./" ]]; then
        msg=$(printf 'Using folder "%s" as a mount point for "%s" is not allowed. Exit code: %s.' "$mount_point" "$setup_file" "$err_code")
        echo "$msg" >&2; log -m "$msg" -n "ERR"
        return $err_code
    fi

    if [[ ! -d "$mount_point" ]]; then
        msg=$(echo mkdir "$mount_point")
        log -m "$msg" -n "INFO"
        mkdir "$mount_point"
        exit_code=$?
        if [[ $exit_code -ne 0 || ! -d "$mount_point" ]]; then
            msg=$(printf 'Failed to create a mount folder for "%s". Exit code: %s.' "$mount_point" "$exit_code")
            echo "$msg" >&2; log -m "$msg" -n "ERR"
            return $exit_code
        fi
    fi

    if [[ ! -f "$setup_file" ]]; then
        exit_code=1
        msg=$(printf 'Setup file "%s" does not exist. Exit code: %s.' "$setup_file" "$exit_code")
        echo "$msg" >&2; log -m "$msg" -n "ERR"
        return $exit_code
    fi

    # detach all images attached to mount_point, just in case
    detach_dmg "$mount_point"

    # attach dmg
    msg=$(echo hdiutil attach "$setup_file" -mountroot "$mount_point" -nobrowse -readonly -quiet)
    log -m "$msg" -n "INFO"
    hdiutil attach "$setup_file" -mountroot "$mount_point" -nobrowse -readonly -quiet
    exit_code=$?
    if [[ $exit_code -ne 0 ]]; then
        msg=$(printf 'Failed to mount dmg file "%s" to "%s". Exit code: %s' "$setup_file" "$mount_point" "$exit_code")
        echo "$msg" >&2; log -m "$msg" -n "ERR"
        return $exit_code
    fi

    if [[ ! -d "$root_dmg_folder" ]]; then
        exit_code=1
        msg=$(printf 'Mounted root folder "%s" does not exist. Exit code: %s' "$root_dmg_folder" "$exit_code")
        echo "$msg" >&2; log -m "$msg" -n "ERR"
        return $exit_code
    fi

    if [[ ! -d "$app_folder" ]]; then
        exit_code=1
        msg=$(printf 'Mounted app folder "%s" does not exist. Exit code: %s' "$app_folder" "$exit_code")
        echo "$msg" >&2; log -m "$msg" -n "ERR"
        return $exit_code
    fi
    return 0
}


function detach_dmg() {
    local root_dmg_folder="$1"
    local msg
    msg=$(printf 'params: root_dmg_folder="%s"' "$root_dmg_folder")
    log -m "$msg" -n "DBG2"

    local images=$(hdiutil info | grep "$(pwd)/local_mnt" | awk '{print $1}')
    if [[ ! -d "$root_dmg_folder" || -z "$images" ]]; then
        log -m "nothing to detach" -n "DBG"
        return 0
    fi

    # detach all dmg images in root folder
    log -m "$(printf 'unmount all dmg images mounted to "%s"' "$root_dmg_folder")" -n "DBG"
    log -m "attached images:" -n "DBG"
    log -m "$(hdiutil info | grep "$(pwd)/local_mnt")" -n "DBG"

    while IFS= read -r line; do
        log -m "$(printf 'hdiutil detach %s -force > /dev/null 2>&1' "$line")" -n "DBG"
        hdiutil detach "${line}" -force > /dev/null 2>&1
    done <<< "$images"     local images_left=$(hdiutil info | grep "$(pwd)/local_mnt" | awk '{print $1}')     if [[ -z "$images_left" ]]; then         log -m "all mounted dmg images have been successfully unmounted." -n "INFO"     else         log -m "failed to unmount images:" -n "WARN"         log -m "$images_left" -n "WARN"     fi     return 0 } function copy_app_folder() {     local source_app_path="$1"     local target_path="$2"     local app_folder="$3"     local deploy_mode="$4"     local msg; local owner     msg=$(printf 'params: source_app_path="%s" target_path="%s" app_folder="%s" deploy_mode="%s"' `     `"$source_app_path" "$target_path" "$app_folder" "$deploy_mode")     log -m "$msg" -n "DBG2"     local exit_code=0     local err_code=1     if [[ "$deploy_mode" == 'install' ]]; then         dest_folder="$target_path"                                # example for install (deploy software): dest_folder="/Applications"     else         dest_folder="${target_path}/${app_folder}"                  # example for upgrade (deploy update): dest_folder="/Applications/Firefox.app"     fi     # validate params     validate_dmg_deploy_params "$source_app_path" || return $?     validate_common_deploy_params "$target_path" "$app_folder" "$deploy_mode" "$dest_folder" || return $?     # get owner of target folder     if [[ "$deploy_mode" == 'upgrade' ]]; then         get_owner "$dest_folder" && owner="$result" || return $?     else         owner="root:wheel"     fi     # check if destination folder already exists but mode=install,     # f.e. Firefox ESR installation on EP with Firefox ("stable") installed. They have the same folder names     if [[ "$deploy_mode" == 'install' ]]; then         test_dest_folder "${dest_folder}/${app_folder}" || return $?     fi     # copy new app version folder     local sp="$source_app_path"     [[ "$deploy_mode" == "upgrade" ]] && sp="${source_app_path}/"     tmpf="${dest_folder}.tmp"     if [[ -d "$dest_folder" && "$deploy_mode" == 'upgrade' ]]; then         msg=$(printf 'mv "%s" "%s"' "$dest_folder" "$tmpf"); log -m "$msg" -n "INFO"         mv "$dest_folder" "$tmpf"     # backup/rename current version folder before upgrade     fi     msg=$(printf 'rsync -a "%s" "%s"' "$sp" "$dest_folder"); log -m "$msg" -n "INFO"     rsync -a "$sp" "${dest_folder}"     exit_code=$?     if [[ $exit_code -ne 0 || ! -d "$dest_folder" ]]; then         msg=$(printf 'Failed to copy the source path "%s" to "%s". Exit code: %s' "$source_app_path" "$dest_folder" "$exit_code")         echo "$msg" >&2; log -m "$msg" -n "ERR"
        if [[ "$deploy_mode" == 'upgrade' ]]; then
            msg=$(printf 'start renaming the "%s" folder back to "%s"' "$tmpf" "$dest_folder"); log -m "$msg" -n "INFO"
            [[ -d "$dest_folder" ]] && rm -rf "$dest_folder"                                # remove new version folder if exists
            [[ ! -d "$dest_folder" && -d "$tmpf" ]] && mv "$tmpf" "$dest_folder"            # restore current version folder if upgrade failed and new version folder is removed
        fi
        return $exit_code
    fi
    if [[ -d "$tmpf" && "$deploy_mode" == 'upgrade' ]]; then
        msg=$(printf 'rm -rf "%s"' "$tmpf"); log -m "$msg" -n "INFO"
        rm -rf "$tmpf"                                                                      # remove backup folder
    fi

    # set owner to app destination folder
    set_owner "${target_path}/${app_folder}" "$owner" || return $?

    return $exit_code
}


function test_dest_folder() {
    local folder="$1"
    local msg
    err_code=1
    msg=$(printf 'params: folder="%s"' "$1"); log -m "$msg" -n "DBG2"
    if [[ -d "$folder" ]]; then
        exit_code=$err_code
        msg=$(printf 'Destination folder "%s" already exists. Exit code: %s' "$folder" "$exit_code")
        echo "$msg" >&2; log -m "$msg" -n "ERR"
    fi
    return $exit_code
}


function get_owner() {
    local folder="$1"
    result=""

    local usr; local grp; local msg
    err_code=1

    msg=$(printf 'params: folder="%s"' "$folder")
    log -m "$msg" -n "DBG2"

    usr=$(stat -f '%Su' "$folder")
    grp=$(stat -f '%Sg' "$folder")

    msg=$(printf 'owner:group="%s"' "$usr:$grp")
    log -m "$msg" -n "INFO"

    if [[ -z "$usr" ]]; then
        msg=$(printf 'Failed to retrieve the owner of "%s" folder. The "user" field is empty. Exit code: %s' "$folder" "$err_code")
        echo "$msg" >&2; log -m "$msg" -n "ERR"
        exit_code=$err_code
    fi

    if [[ -z "$grp" ]]; then
        msg=$(printf 'Failed to retrieve the owner of "%s" folder. The "group" field is empty. Exit code: %s' "$folder" "$err_code")
        echo "$msg" >&2; log -m "$msg" -n "ERR"
        exit_code=$err_code
    fi

    # test user is deleted
    userID=$(id -u "$usr" 2>/dev/null)
    if [[ $? -eq 0 ]]; then
        result="$usr:$grp"
    else
        result="root:wheel"
        msg=$(printf 'id: "%s": no such user. owner:group="%s"' "$usr" "$result")
        log -m "$msg" -n "INFO"
    fi

    log -m "return $exit_code" -n "DBG2"
    return $exit_code
}


function set_owner() {
    local folder="$1"
    local owner="$2"
    local msg

    msg=$(printf 'params: folder="%s" owner="%s"' "$folder" "$owner"); log -m "$msg" -n "DBG2"
    msg=$(printf 'setting "%s" as the owner for "%s" folder' "$owner" "$folder"); log -m "$msg" -n "INFO"

    log -m "$(printf 'chown -R "%s" "%s"' "$owner" "$folder")" -n "INFO"
    chown -R "$owner" "$folder"
    local exit_code=$?
    if [[ $exit_code -ne 0 ]]; then
        msg=$(printf 'Failed to set "%s" as the owner for folder "%s". Exit code: %s' "$owner" "$folder" "$exit_code")
        echo "$msg" >&2; log -m "$msg" -n "ERR"
        # log -m "$(echo rm -rf "$folder")" -n "DBG"
        # rm -rf "$folder"
    fi
    log -m "return $exit_code" -n "DBG2"
    return $exit_code
}


function get_setup_by_ext() {
    local ext="$1"
    local search_paths=(".")
    local filename=''; local msg=''; local exit_code=0; result=""

    msg=$(printf 'params: ext="%s"' "$ext")
    log -m "$msg" -n "DBG2"

    for p in "${search_paths[@]}"; do
        filename=$(ls -1 "$p" | grep -i "\.$ext$" | head -n 1 )
        if [[ "$filename" != "" ]]; then
            break
        fi
    done

    if [[ "$filename" == "" ]]; then
        exit_code=1
        msg=$(printf 'Setup file "*.%s" was not found. Exit code: %s' "$ext" "$exit_code")
        echo "$msg" >&2; log -m "$msg" -n "ERR"
    else
        log -m "Setup file: ${filename}" -n "INFO"
        result="$filename"
        exit_code=0
    fi
    log -m "return $exit_code" -n "DBG2"
    return $exit_code
}


function unzip_archive() {
    local zipfile="$1"
    local dest_fold="$2"
    local exit_code=0; local msg

    msg=$(printf 'params: zipfile="%s" dest_fold="%s"' "$zipfile" "$dest_fold")
    log -m "$msg" -n "DBG2"

    local file_ext=$(echo "$zipfile" | grep -i "\.zip$")
    if [[ -z "$file_ext" ]]; then
        exit_code=1
        msg=$(printf 'File "%s" is not a zip archive. Exit code: %s' "$zipfile" "$exit_code")
        echo "$msg" >&2; log -m "$msg" -n "ERR"
        return $exit_code
    fi

    if [[ ! -f "$zipfile" ]]; then
        exit_code=1
        msg=$(printf 'File "%s" does not exist. Exit code: %s' "$zipfile" "$exit_code")
        echo "$msg" >&2; log -m "$msg" -n "ERR"
        return $exit_code
    fi

    # [[ ! -d "$dest_fold" ]] && mkdir "$dest_fold"
    # unzip -qq -o "$zipfile" -d "$dest_fold"

    log -m "$(echo ditto -x -k "$zipfile" "$dest_fold")" -n "INFO"
    ditto -x -k "$zipfile" "$dest_fold"
    exit_code=$?
    if [[ $exit_code -ne 0 ]]; then
        msg=$(printf 'Failed to unzip file "%s". Exit code: %s.' "$zipfile" "$exit_code")
        echo "$msg" >&2; log -m "$msg" -n "ERR"
    else
        msg=$(printf 'The content of "%s" file is successfully extracted to "%s"' "$zipfile" "$dest_fold")
        log -m "$msg" -n "INFO"
    fi
    log -m "return $exit_code" -n "DBG2"
    return $exit_code
}


function test_process() {
    local last_param="${!#}"
    local all_params=( "$@" )
    local exit_code=0; local procs; local msg

	if [[ $last_param = type:* ]]; then
		proc_names=("${all_params[@]:0:$#-1}")
    else
        proc_names=("${all_params[@]}")
	fi

    if [[ -z ${proc_names[*]} || ${#proc_names[@]} -eq 0 ]]; then
      log -m "$(printf 'The proc_names array is empty.')" -n "INFO"
      return 0
    fi

    for p in "${proc_names[@]}"; do
        log -m "$(printf 'checking that the "%s" process does not exist.' "$p")" -n "INFO"
        log -m "$(printf 'pgrep -i "%s"' "$p")" -n "DBG"
		[[ $last_param = type:pid ]] && procs=$(ps -o pid= -p "$p") || procs=$(pgrep -d "," -i "$p")

        if [[ -n $procs ]]; then
            exit_code=1
            msg=$(printf 'Cannot proceed while the process "%s" (pid: %s) is running.\nClose the process and try deploying again.\n' "$p" "$procs")
            echo "$msg" >&2; log -m "$msg" -n "ERR"
        else
            log -m "found: 0" -n "INFO"
        fi
    done
    log -m "return $exit_code" -n "DBG2"
    return $exit_code
}


function kill_process() {
	local last_param="${!#}"
    local all_params=( "$@" )
    local msg; local proc_found; local procs; local log_lv

	if [[ $last_param = type:* ]]; then
		proc_names=("${all_params[@]:0:$#-1}")
    else
        proc_names=("${all_params[@]}")
	fi

    if [[ -z ${proc_names[*]} || ${#proc_names[@]} -eq 0 ]]; then
      log -m "$(printf 'The proc_names array is empty.')" -n "INFO"
      return 0
    fi

    local timeout=$((60))  # Total duration to monitor in seconds (1 minute)
    local sleep_interval=1  # Interval between checks in seconds
    local iter=$((timeout / sleep_interval))

    for p in "${proc_names[@]}"; do
		[[ $last_param = type:pid ]] && procs=$(ps -o pid= -p "$p") || procs=$(pgrep -d "," -i "$p")

        if [[ -n "$procs" ]]; then
            log -m "$(printf 'found: "%s"' "$procs")" -n "$log_lv" -n "INFO"

			if [[ $last_param = type:pid ]]; then
                log -m "$(printf 'kill -SIGTERM "%s"' "$p")" -n "INFO"
				kill -SIGTERM "$p"
    		else
                log -m "$(printf 'pkill -SIGTERM -i "%s"' "$p")" -n "INFO"
				pkill -SIGTERM -i "$p"
    		fi
        fi
    done

    local i=0
    while [[ $i -lt $iter ]]; do
        proc_found="false"
        if [[ $i -eq 0 ]]; then log_lv="INFO"; else log_lv="DBG"; fi
        for p in "${proc_names[@]}"; do
            log -m "$(printf 'checking that the "%s" process does not exist.' "$p")" -n "$log_lv"

			[[ $last_param = type:pid ]] && procs=$(ps -o pid= -p "$p") || procs=$(pgrep -d "," -i "$p")

            if [[ -n "$procs" ]]; then
                log -m "$(printf 'Cannot proceed while the process "%s" (pid: %s) is running. Waiting for process termination.' "$p" "$procs")" -n "$log_lv"
                proc_found="true"
            else
                log -m "found: 0" -n "$log_lv"
            fi
        done

        if [[ "$proc_found" == "true" ]]; then
            log -m "$(printf 'testing for running process(es) every %s second(s). Timeout=%s seconds.' "$sleep_interval" "$timeout")" -n "$log_lv"
        else
            break
        fi

        sleep $sleep_interval
        i=$((i + 1))
    done

    if [[ "$proc_found" == "true" ]]; then
        for p in "${proc_names[@]}"; do
            if [[ $last_param = type:pid ]]; then
                log -m "$(printf 'kill -SIGKILL "%s"' "$p")" -n "INFO"
				kill -SIGKILL "$p" # hard kill
    		else
                log -m "$(printf 'pkill -SIGKILL -i "%s"' "$p")" -n "INFO"
				pkill -SIGKILL -i "$p" # hard kill
    		fi
        done
    fi
    return 0
}


function hardkill_process() {
    local proc_names=( "$@" )
    local msg; local proc_found; local procs; local log_lv

    for p in "${proc_names[@]}"; do
        procs=$(pgrep -l -d "," -i "$p")
        if [[ -n "$procs" ]]; then
            log -m "$(printf 'found: "%s"' "$procs")" -n "$log_lv" -n "INFO"
            log -m "$(printf 'pkill -SIGKILL -i "%s"' "$p")" -n "INFO"
            pkill -SIGKILL -i "$p" # hard kill
        fi
    done
    return 0
}


function test_binary_arch() {
    local file_path="$1"
    local err_code=1; local msg

    log -m "$(printf 'params: file_path="%s"' "$1")" -n "DBG2"
    if [[ ! -f "$file_path" ]]; then
        msg=$(printf 'Setup binary file "%s" was not found after unpacking the downloaded ZIP. Exit code %s' "$file_path" "$err_code")
        echo "$msg" 1>&2; log -m "$msg" -n "ERR"
        return $err_code
    fi

    local mac_arch=$(arch);
    log -m "$(printf 'mac_arch="%s"' "$mac_arch")" -n "INFO"

    local file_arch=$(file "$file_path" | head -n 1)
    log -m "$(printf 'file_arch="%s"' "$file_arch")" -n "INFO"
    local test_universal=$(echo "$file_arch" | grep -i "universal")
    if [[ -n "$test_universal" ]]; then return 0; fi

    local test_file_arm64=$(file "$file_path" | grep -i "arm64")  # Apple silicon
    local test_file_x64=$(file "$file_path" | grep -i "x86_64")   # Intel CPU
    local test_failed=0
    log -m "$(printf 'arm64="%s", x86_64="%s"' "$test_file_arm64" "$test_file_x64")" -n "DBG"

    if [[ -z "$test_file_arm64" && -z "$test_file_x64" ]]; then
        msg=$(printf 'Unable to detect the application target architecture. Setup binary path: "%s". Exit code: %s' "$file_path" "$err_code")
        echo "$msg" >&2; log -m "$msg" -n "ERR"
        return $err_code
    fi

    # test x86_64 binary on arm64
    if [[ "$mac_arch" == "arm64" && -z "$test_file_arm64" ]]; then test_failed=1; fi

    # test arm64 binary on x86_64
    if [[ -z "$test_file_x64" && -n "$(echo "$mac_arch" | grep -E -i 'i386|x86_64')" ]]; then test_failed=1; fi

    if [[ $test_failed -eq 1 ]]; then
        msg=$(printf 'Application is not supported on this Mac. Exit code: %s' "$err_code")
        echo "$msg" >&2; log -m "$msg" -n "ERR"
        return $err_code
    fi
    return 0
}


function get_loglevel() {
    local level="$1"
    local lv=''
    case "$level" in
        "ERR") lv=1 ;;
        "WARN") lv=2 ;;
        "INFO") lv=3 ;;
        "DBG") lv=4 ;;
        "DBG2") lv=5 ;;
        *) lv=3 ;;
    esac
    echo $lv
    return 0
}


function log() {
    # -m "message"
    # -n "message level: DBG|ERR|WARN|INFO, Default=INFO

    local OPTIND
    local OPTSTRING="m:n:"
    local OPTERR=0
    local mesg_out
    # $action_log - global variable for full log output to stdout on script exit
    # log_level is a global variable

    while getopts ${OPTSTRING} opt; do
        case ${opt} in
            m) local mesg="${OPTARG}" ;;
            n) local msg_lev="${OPTARG}" ;;
        esac
    done

    [[ -z "$log_level" ]] && log_level="INFO"

    llevel=$(get_loglevel "$log_level")
    mlevel=$(get_loglevel "$msg_lev")

    if [[ $mlevel -le $llevel ]]; then
        local time=$(date "+%Y/%m/%d %H:%M:%S%z")
        mesg_out=$(printf '%s [%-4s]: %s(): %s\n' "$time" "$msg_lev" "${FUNCNAME[1]}" "$mesg")
        action1_log=$(printf '%s\n%s' "$action1_log" "$mesg_out")
    fi
    return 0
}


function log_started() {
    local script_name="$1"
    local script_opts="$2"
    local msg=$(printf 'bash %s %s' "$script_name" "$script_opts")
    log -m "$msg" -n "INFO"
    print_revision
    print_diskspace
}


function log_finished() {
    local script_name="$1"
    local ex_code=$2
    local msg=$(printf '"%s" script is finished. Exit code: %s.' "$script_name" "$ex_code")
    log -m "$msg" -n "INFO"
}


function validate_common_deploy_params() {
    local target_path="$1"
    local app_folder="$2"
    local deploy_mode="$3"
    local dest_folder="$4"

    msg=$(printf 'params: target_path="%s" app_folder="%s" deploy_mode="%s" dest_folder="%s"' "$target_path" "$app_folder" "$deploy_mode" "$dest_folder")
    log -m "$msg" -n "DBG2"

    # test the app folder name is specified. Required as a separate value for set_owner() f.e.
    if [[ -z "$app_folder" ]]; then
        msg=$(printf 'Application folder name was not specified.')
        echo "$msg" >&2; log -m "$msg" -n "ERR"
        return $err_code
    fi

    # test the destination folder is exist
    if ! [[ -d "$dest_folder" ]]; then
        msg=$(printf 'Destination folder "%s" does not exist. Exit code %s' "$dest_folder" "$err_code")
        echo "$msg" >&2; log -m "$msg" -n "ERR"
        return $err_code
    fi

    if [[ -z "$app_folder" ]]; then
	msg=$(printf 'Target folder name was not specified for application update. Exit code: %s.' "-f" "$err_code")
        echo "$msg" >&2; log -m "$msg" -n "ERR"
        return $err_code
    fi
    return 0
}


function validate_pkg_deploy_params() {
    local pkg_file="$1"
    local choices_xml="$2"
    # local err_code=1

    log -m "$(printf 'params: pkg_file="%s" choices_xml="%s"' "$pkg_file" "$choices_xml")" -n "DBG2"

    # test the setup file exists
    if ! [[ -f "$pkg_file" ]]; then
        msg=$(printf 'Setup file "%s" does not exist. Exit code: %s' "$pkg_file" "$err_code")
        echo "$msg" >&2; log -m "$msg" -n "ERR"
        return $err_code
    fi

    [[ -z "$choices_xml" ]] && return 0

    # test the choices_xml file exists
    if ! [[ -f "$choices_xml" ]]; then
        msg=$(printf 'Choices xml file "%s" does not exist. Exit code: %s' "$choices_xml" "$err_code")
        echo "$msg" >&2; log -m "$msg" -n "ERR"
        return $err_code
    fi
   
    return 0
}


function validate_dmg_deploy_params() {
    local src_folder="$1"
    # local err_code=1

    log -m "$(printf 'params: src_folder="%s"' "$src_folder")" -n "DBG2"

    # test the setup file exists
    if [[ ! -d "$src_folder" ]]; then
        msg=$(printf 'Source folder "%s" does not exist. Exit code: %s' "$src_folder" "$err_code")
        echo "$msg" >&2; log -m "$msg" -n "ERR"
        return $err_code
    fi
    return 0
}


function deploy_pkg() {
    local pkg_file="$1"
    local target_path="$2"
    local app_folder="$3"
    local deploy_mode="$4"
    local choices_xml="$5"

    local err_code=1  # default error code
    local exit_code; local dest_folder

    msg=$(printf 'params: pkg_file="%s" target_path="%s" app_folder_name="%s" deploy_mode="%s" choices_xml="%s"' "$pkg_file" "$target_path" "$app_folder_name" "$deploy_mode" "$choices_xml")
    log -m "$msg" -n "DBG2"

    if [[ "$deploy_mode" == 'install' ]]; then
        dest_folder="$target_path"                              # example for install (deploy software): dest_folder="/Applications"
    else
        dest_folder="$target_path/$app_folder"                  # example for upgrade (deploy update): dest_folder="/Applications/Firefox.app"
    fi

    # validate params
    validate_pkg_deploy_params "$pkg_file" "$choices_xml" || return $?
    validate_common_deploy_params "$target_path" "$app_folder" "$deploy_mode" "$dest_folder" || return $?

    msg=$(printf 'software installation has been started.\nInstalling "%s" to "%s"' "$pkg_file" "$dest_folder")
    log -m "$msg" -n "INFO"
    
    local deploy_result
    if [[ -z "$choices_xml" ]]; then
        deploy_result=$(installer -pkg "$pkg_file" -target "$dest_folder" 2>&1)
        exit_code=$?
    else 
        deploy_result=$(installer -pkg "$pkg_file" -applyChoiceChangesXML "$choices_xml" -target "$dest_folder" 2>&1)
        exit_code=$?
    fi

    log -m "$deploy_result" -n "INFO"
    log -m "$exit_code" -n "DBG2"

    if [[ $exit_code -ne 0 ]]; then
        msg=$(printf 'Installation failed. Exit code: %s' "$exit_code")
        echo "$msg" >&2; log -m "$msg" -n "ERR"
        return $exit_code
    fi

    return $exit_code
}


function print_diskspace() {
    disksize_Kb=$(df -k / | awk 'NR==2 {print $2}')
    disksize_Gb=$(echo "scale=2; $disksize_Kb / 1024 / 1024" | bc)
    freespace_Kb=$(df -k / | awk 'NR==2 {print $4}')
    freespace_Gb=$(echo "scale=2; $freespace_Kb / 1024 / 1024" | bc)
    msg=$(printf '%s GB of %s GB disk space is currently free.\n' "${freespace_Gb}" "${disksize_Gb}")
    log -m "$msg" -n "INFO"
    return 0
}


function finally_dmg() {
    local dmg_folder="$1"
    local exit_code="$2"
    log -m 'finalizing deployment...' -n "INFO"
    detach_dmg "$dmg_folder"
    log_finished "$inv_script" "$exit_code"
    echo "$action1_log"   # send script log to the agent log file
    unset LC_NUMERIC
}


function finally_zip() {
    local exit_code="$1"
    log -m 'finalizing deployment...' -n "INFO"
     # local appf="$2"
     # rm -rf "./$appf"
     # msg=$(printf 'folder "%s" removed' "./$appf"); log -m "$msg" -n "DBG"
     # log -m "$msg" -n "INFO"
    log_finished "$inv_script" "$exit_code"
    echo "$action1_log"   # send script log to the agent log file
    unset LC_NUMERIC
}


function finally_pkg() {
    local exit_code="$1"
    log -m 'finalizing deployment...' -n "INFO"
    log_finished "$inv_script" "$exit_code"
    echo "$action1_log"  # send script log to the agent log file
    unset LC_NUMERIC
}

NOTE: MacOS is case-sensitive, so make sure you copy and paste all names correctly.