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.

Mac OS X – Install Tools to Program in Micropython on the ESP32

Introduction

Its easier then ever to get started with micropython developement on Mac OS X.  This howto assumes that the user is starting from zero.  Lets get started.

Install Python 3.7

Mac OS X comes with Python 2.7.  We will need Python 3 for our developement tools.  Viisit python.org and download Python 3.7 64 bit for mac os x.

Once the download is complete open the package to install Python.  Follow the instructions in the installer to complete the installation.

Install NodeMCU ESP32 Serial Driver

OS X doesn’t include a driver for the USB to serial chip used on the NodeMCU boards.  Click the link below to download the driver.

Download Serial Driver

Next, unzip the download.  Open the folder and double click on the “.dmg” file.  Run the installer and follow the instructions.

Install esptool

The first tool we’ll install is a command line tool for programming ESP8266 and ESP32 based boards.  You can use this tool to write the micropython firmware to the ESP32.

To install esptool you’ll need to open the Terminal.app.  Its located in the Applications -> Utilities folder.  In terminal type the following:

> pip3 install esptool

Install uPyCraft IDE

Our primary tool for micropython development will be uPyCraft.  Click on the link below to down uPyCraft.

Download uPyCraft

Open the zip file and copy uPyCraft to the Applications folder.

Download Micropython Firmware

Now visit the Micropython website and download the firmware for the ESP32.  The link below will take you to the download page.

Micropython Download Page

Write Micropython to the ESP32

Liet’s write the firmware to the ESP32.  We will do this using esptool.  Open terminal.app.  You will need to switch to the directory where you downloaded the bin file.  For me its the Downloads folder.  Be sure only one ESP32 is connected to your Mac.  In terminal type the following:

> cd ~/Downloads
> esptool erase_flash
> esptool

write_flash 0x1000 esp32-idf3-20191220-v1.12.bin

 

Conclusion

That’s all there is to it.  You now have everything you need to develop in micropython on the Mac.

 

Mac OS X – Install Tools to Program in Micropython on the ESP8266

Introduction

Its easier then ever to get started with micropython developement on Mac OS X.  This howto assumes that the user is starting from zero.  Lets get started.

Install Python 3.7

Mac OS X comes with Python 2.7.  We will need Python 3 for our developement tools.  Viisit python.org and download Python 3.7 64 bit for mac os x.

Once the download is complete open the package to install Python.  Follow the instructions in the installer to complete the installation.

Install NodeMCU Serial Driver

OS X doesn’t include a driver for the USB to serial chip used on the NodeMCU board.  Click the link below to download the driver.

Download Serial Driver

Next, unzip the download.  Open the folder and double click on the “.dmg” file.  Run the installer and follow the instructions.

Install esptool

The first tool we’ll install is a command line tool for programming ESP8266 and ESP32 based boards.  You can use this tool to write the micropython firmware to the NodeMCU.

To install esptool you’ll need to open the Terminal.app.  Its located in the Applications -> Utilities folder.  In terminal type the following:

> pip3 install esptool

Install uPyCraft IDE

Our primary tool for micropython development will be uPyCraft.  Click on the link below to down uPyCraft.

Download uPyCraft

Open the zip file and copy uPyCraft to the Applications folder.

Download Micropython Firmware

Now visit the Micropython website and download the firmware for the ESP8266.  The link below will take you to the download page.

Micropython Download Page

Write Micropython to the NodeMCU

Liet’s write the firmware to the ESP8266.  We will do this using esptool.  Open terminal.app.  You will need to switch to the directory where you downloaded the bin file.  For me its the Downloads folder.  Be sure only one NodeMCU is connected to your Mac.  In terminal type the following:

> cd ~/Downloads
> esptool erase_flash
> esptool write_flash esp8266-20191220-v1.12.bin

Conclusion

That’s all there is to it.  You now have everything you need to develop in micropython on the Mac.

 

Fix VMWare Fusion Upload to Server Problems

Introduction

Sometimes things don’t go to smoothly.  I couldn’t get a VM from VMWare Fusion 11 to upload to a VMWare ESXI 6 server.  Here’s my solution to the problem.

VM won’t upload hard disk file:

Well this was caused because I set a startup disk in the VM’s settings.  When you are done setting a startup disk be sure to click the ‘default settings’ button.

Conclusion

My research shows that many things cause this problem.  I’ll keep adding to the page as I run across the problem.