Add a GUI To Your JAMF Pro Workflows Using Bash and Swift Dialog

Introduction

As you navigate your Jamf Pro journey, you might find the need for a graphical user interface (GUI) to streamline your workflows. I faced a similar challenge with smaller workflows that required time to execute, where providing users with feedback on the process was essential. To address this, I’ve developed two scripts that allow you to run up to four or eight policies while utilizing Swift Dialog to display the status of each policy’s progress.

I’m not going to cover how to set this up in JAMF Pro. I’ll leave that up to the reader.

Script #1: Run up to 4 policies

The first script runs up to 4 Jamf policies. Very easy to use. The script comments define how script parameters 4 to 11 are used.

#!/bin/bash
# 
# Basic workflow with GUI status updates
#
# This script runs a list of up to 4 Jamf policies and displays their statuses
# to the user. The gui is only displayed if a user is logged in.
#
# DEPENDENCIES  
# SwiftDialog - https://github.com/swiftDialog/swiftDialog
#
# SCRIPT ARGUMENTS
# 4
# Jamf Policy Custom Trigger #1 (Optional)
# 5
# Display Label for Policy #1 (Optional)
# 6
# Jamf Policy Custom Trigger #2 (Optional)
# 7
# Display Label for Policy #2 (Optional)
# 8
# Jamf Policy Custom Trigger #3 (Optional)
# 9
# Display Label for Policy #3 (Optional)
# 10
# Jamf Policy Custom Trigger #4 (Optional)
# 11
# Display Label for Policy #4 (Optional)
#
# HISTORY
# 20240610 RC Initial Release
#

#
# Settings
#

# User Configurable Settings
DIALOG_TITLE="Task Progress"
DIALOG_MESSAGE="Please wait while we complete the tasks below."
DIALOG_ICON="info"    # icon type or path to icon  
DIALOG_WIDTH=800      # width of the window
DIALOG_HEIGHT=350     # height of the window
DIALOG_ONTOP=1        # set to 1 to keep the window on top.
DIALOG_BLURSCREEN=0   # set to 1 to blur the screen behind the window.
JAMF_RETRIES=3        # Max retries to run for a failed Jamf command.

# Script Settings
DIALOG_LOG="/var/tmp/myDialog.log"
DIALOG="/usr/local/bin/dialog"
JAMF_CMD="/usr/local/bin/jamf"
SCRIPT_EXIT_FLAG=0

#
# Functions
#

# Function - Send command to dialog
# $1 = Command to send.
dialog_cmd() {
  echo "${1}"
  [ -f "${DIALOG_LOG}" ] && echo "${1}" >> "${DIALOG_LOG}"
}

# Function - Add list item
# $1 = Policy Trigger
# $2 = Policy Label
add_list_item() {
    local POLICY="${1}"
    local POLICY_LABEL="${2}"
    
    if [[ -z "${POLICY}" ]]; then
      # No policy provided so quietly exit.
      echo "No policy to add to list."
      return
    fi
    if [[ -z "${POLICY_LABEL}" ]]; then
      # No policy label provided so we set a default value.
      POLICY_LABEL="Policy ${POLICY}"
    fi
    dialog_cmd "listitem: add, title: ${POLICY_LABEL}, status: pending, statustext: Pending..." 
}

# Function - Run Jamf policy with retries
# $1 = Jamf custom trigger
# $2 = Policy Label
# $3 = Number of retries to call a failed Jamf policy
run_jamf_policy() {
    local POLICY="${1}"
    local POLICY_LABEL="${2}"
    local RETRIES=${3}
    local RETRY_COUNT=0
    local EXIT_CODE=1
    
    if [[ -z "${POLICY}" ]]; then
      # A policy trigger wasn’t provided so quietly exit.
      echo "No policy to run."
      return
    fi
    if [[ -z "${POLICY_LABEL}" ]]; then
      # No policy label provided so we set a default value.
      POLICY_LABEL="Policy ${POLICY}"
    fi
    if [[ -z "${RETRIES}" ]]; then
      # No max retries provided so we set a default value.
      RETRIES=3
    fi

    # start the retry loop
    while [ ${RETRY_COUNT} -lt ${RETRIES} ]; do
        sleep 2
        echo "Running Jamf policy: ${POLICY} (Attempt $((RETRY_COUNT+1)))"
        dialog_cmd "listitem: title: ${POLICY_LABEL}, status: wait, statustext: Processing... "
        ${JAMF_CMD} policy -event "${POLICY}"
        EXIT_CODE=$?
        if [ ${EXIT_CODE} -eq 0 ]; then
            # Jamf policy completed so update status and leave the loop.
            dialog_cmd "listitem: title: ${POLICY_LABEL}, status: success, statustext: Complete. "
            break
        fi
        sleep 2
        echo "Jamf policy failed with exit code ${EXIT_CODE}. Retrying..."
        dialog_cmd "listitem: title: ${POLICY_LABEL}, status: error, statustext: Trying again. "
        ((RETRY_COUNT++))
    done
    
    if [ ${EXIT_CODE} -ne 0 ]; then
        echo "Failed to run Jamf policy: ${POLICY} after ${RETRIES} attempts."
        dialog_cmd "listitem: title: ${POLICY_LABEL}, status: fail, statustext: FAILED! "
        return 1
    fi
    return 0
}

#
# Main script
#

# script start
echo "Running List of Jamf policies."

# If a user is logged in then launch SwiftDialog
if pgrep -q -x "Finder" && pgrep -q -x "Dock" && [ $? -eq 0 ]; then
  echo "User is logged in so show the gui."

  # launch swiftdialog in the background 
  ${DIALOG} \
    --title "${DIALOG_TITLE}" \
    --message "${DIALOG_MESSAGE}" \
    --icon "${DIALOG_ICON}" \
    --commandfile "${DIALOG_LOG}" \
    --hidedefaultkeyboardaction \
    --button1text none \
    --width ${DIALOG_WIDTH} \
    --height ${DIALOG_HEIGHT} \
    $( [ "$DIALOG_BLURSCREEN" -eq 1 ] && echo "--blurscreen" ) \
    $( [ "$DIALOG_ONTOP" -eq 1 ] && echo "--ontop" ) \
    --moveable &
  sleep 1
fi

# show the list section in the window
dialog_cmd "list: show"

# add policies to dialog list
add_list_item "${4}" "${5}"
add_list_item "${6}" "${7}"
add_list_item "${8}" "${9}"
add_list_item "${10}" "${11}"

# run policy. if it fails then add 1 to the exit code.
run_jamf_policy "${4}" "${5}" ${JAMF_RETRIES} 
[ $? -ne 0 ] && ((SCRIPT_EXIT_FLAG+=1))

# run policy. if it fails then add 2 to the exit code.
run_jamf_policy "${6}" "${7}" ${JAMF_RETRIES} 
[ $? -ne 0 ] && ((SCRIPT_EXIT_FLAG+=2))

# run policy. if it fails then add 4 to the exit code.
run_jamf_policy "${8}" "${9}" ${JAMF_RETRIES} 
[ $? -ne 0 ] && ((SCRIPT_EXIT_FLAG+=4))

# run policy. if it fails then add 8 to the exit code.
run_jamf_policy "${10}" "${11}" ${JAMF_RETRIES} 
[ $? -ne 0 ] && ((SCRIPT_EXIT_FLAG+=8))

# script end
sleep 3
echo "Done running Jamf Policies."
dialog_cmd "quit:"
exit ${SCRIPT_EXIT_FLAG}

Script #2: Run up to 8 policies

The second script allows you to run up to 8 Jamf policies. To get up to 8 policies comes at a cost. You have to enter policy trigger followed by a comma and then the display label. The script comments show an example.

#!/bin/bash
# 
# Basic workflow with GUI status updates
#
# This script runs a list of up to 8 Jamf policies and displays their statuses
# to the user. The gui is only displayed if a user is logged in.
#
# DEPENDENCIES  
# SwiftDialog - https://github.com/swiftDialog/swiftDialog
#
# SCRIPT ARGUMENTS (each argument is formatted as: POLICY,POLICY_LABEL )
# Example: myPolicy,Installing my policy
# 4
# Jamf Policy #1,Display Label for policy (Optional)
# 5
# Jamf Policy #2,Display Label for policy (Optional)
# 6
# Jamf Policy #3,Display Label for policy (Optional)
# 7
# Jamf Policy #4,Display Label for policy (Optional)
# 8
# Jamf Policy #5,Display Label for policy (Optional)
# 9
# Jamf Policy #6,Display Label for policy (Optional)
# 10
# Jamf Policy #7,Display Label for policy (Optional)
# 11
# Jamf Policy #8,Display Label for policy (Optional)
#
# HISTORY
# 20240610 RC Initial Release
#

#
# Settings
#

# User Configurable Settings
DIALOG_TITLE="Task Progress"
DIALOG_MESSAGE="Please wait while we complete the tasks below."
DIALOG_ICON="info"    # icon type or path to icon  
DIALOG_WIDTH=800      # width of the window
DIALOG_HEIGHT=480     # height of the window
DIALOG_ONTOP=1        # set to 1 to keep the window on top.
DIALOG_BLURSCREEN=0   # set to 1 to blur the screen behind the window.
JAMF_RETRIES=3        # Max retries to run for a failed Jamf command.

# Script Settings
DIALOG_LOG="/var/tmp/myDialog.log"
DIALOG="/usr/local/bin/dialog"
JAMF_CMD="/usr/local/bin/jamf"
SCRIPT_EXIT_FLAG=0

#
# Functions
#

# Function - Send command to dialog
# $1 = Command to send.
dialog_cmd() {
  echo "${1}"
  [ -f "${DIALOG_LOG}" ] && echo "${1}" >> "${DIALOG_LOG}"
}

# Function - Add list item
# $1 = Policy Trigger
# $2 = Policy Label
add_list_item() {
    local POLICY="${1}"
    local POLICY_LABEL="${2}"
    
    if [[ -z "${POLICY}" ]]; then
      # No policy provided so quietly exit.
      echo "No policy to add to list."
      return
    fi
    if [[ -z "${POLICY_LABEL}" ]]; then
      # No policy label provided so we set a default value.
      POLICY_LABEL="Policy ${POLICY}"
    fi
    dialog_cmd "listitem: add, title: ${POLICY_LABEL}, status: pending, statustext: Pending..." 
}

# Function - Run Jamf policy with retries
# $1 = Jamf custom trigger
# $2 = Policy Label
# $3 = Number of retries to call a failed Jamf policy
run_jamf_policy() {
    local POLICY="${1}"
    local POLICY_LABEL="${2}"
    local RETRIES=${3}
    local RETRY_COUNT=0
    local EXIT_CODE=1
    
    if [[ -z "${POLICY}" ]]; then
      # A policy trigger wasn’t provided so quietly exit.
      echo "No policy to run."
      return
    fi
    if [[ -z "${POLICY_LABEL}" ]]; then
      # No policy label provided so we set a default value.
      POLICY_LABEL="Policy ${POLICY}"
    fi
    if [[ -z "${RETRIES}" ]]; then
      # No max retries provided so we set a default value.
      RETRIES=3
    fi

    # start the retry loop
    while [ ${RETRY_COUNT} -lt ${RETRIES} ]; do
        sleep 2
        echo "Running Jamf policy: ${POLICY} (Attempt $((RETRY_COUNT+1)))"
        dialog_cmd "listitem: title: ${POLICY_LABEL}, status: wait, statustext: Processing... "
        ${JAMF_CMD} policy -event "${POLICY}"
        EXIT_CODE=$?
        if [ ${EXIT_CODE} -eq 0 ]; then
            # Jamf policy completed so update status and leave the loop.
            dialog_cmd "listitem: title: ${POLICY_LABEL}, status: success, statustext: Complete. "
            break
        fi
        sleep 2
        echo "Jamf policy failed with exit code ${EXIT_CODE}. Retrying..."
        dialog_cmd "listitem: title: ${POLICY_LABEL}, status: error, statustext: Trying again. "
        ((RETRY_COUNT++))
    done
    
    if [ ${EXIT_CODE} -ne 0 ]; then
        echo "Failed to run Jamf policy: ${POLICY} after ${RETRIES} attempts."
        dialog_cmd "listitem: title: ${POLICY_LABEL}, status: fail, statustext: FAILED! "
        return 1
    fi
    return 0
}

#
# Main script
#

# script start
echo "Running List of Jamf policies."

# If a user is logged in then launch SwiftDialog
if pgrep -q -x "Finder" && pgrep -q -x "Dock" && [ $? -eq 0 ]; then
  echo "User is logged in so show the gui."

  # launch swiftdialog in the background 
  ${DIALOG} \
    --title "${DIALOG_TITLE}" \
    --message "${DIALOG_MESSAGE}" \
    --icon "${DIALOG_ICON}" \
    --commandfile "${DIALOG_LOG}" \
    --hidedefaultkeyboardaction \
    --button1text none \
    --width ${DIALOG_WIDTH} \
    --height ${DIALOG_HEIGHT} \
    $( [ "$DIALOG_BLURSCREEN" -eq 1 ] && echo "--blurscreen" ) \
    $( [ "$DIALOG_ONTOP" -eq 1 ] && echo "--ontop" ) \
    --moveable &
  sleep 1
fi

# show the list section in the window
dialog_cmd "list: show"

# Loop through parameters 4 through 11
for ((i = 4; i <= 11; i++)); do
  # Get the parameter at position $i
  PARAM="${!i}"

  # Split the parameter into POLICY and POLICY_LABEL using comma as the separator
  IFS=',' read -r MYPOLICY MYPOLICY_LABEL <<< "${PARAM}"

  # add policy to list
  add_list_item "${MYPOLICY}" "${MYPOLICY_LABEL}"
done

# Loop through parameters 4 through 11
for ((i = 4; i <= 11; i++)); do
  # Get the parameter at position $i
  PARAM="${!i}"
  
  # Split the parameter into POLICY and POLICY_LABEL using comma as the separat$
  IFS=',' read -r MYPOLICY MYPOLICY_LABEL <<< "${PARAM}"
  
  # run policy. if it fails then add 1 to the exit code.
  run_jamf_policy "${MYPOLICY}" "${MYPOLICY_LABEL}" ${JAMF_RETRIES} 
  [ $? -ne 0 ] && ((SCRIPT_EXIT_FLAG+=1))
done

# script end
sleep 3
echo "Done running Jamf Policies."
dialog_cmd "quit:"
exit ${SCRIPT_EXIT_FLAG}

Conclusion

I hope these scripts help out someone. Sometimes you need a quick and easy GUI.