Leverage swiftDialog to display a user-friendly message about the health of BeyondTrust Endpoint Privilege Management for Mac, while capturing various under-the-hood settings behind-the-scenes

Vendor Overview
BeyondTrust Endpoint Privilege Management allows organizations to:
Enforce least privilege dynamically to prevent malware, ransomware, and identity-based attacks, achieve compliance across Windows, macOS, and Linux endpoints, and enable your zero trust strategy — without compromising on productivity.
Inspiration
During a recent case with BeyondTrust Support, we were asked to obtain the output of several elevated Terminal commands from the affected user’s Mac.
Having previously written a similar solution for CrowdStrike Falcon, creating a health inspector for BeyondTrust EPM seemed like the obvious solution.
Capture Config for macOS
As part of its Mac suite of Endpoint Privilege Management products, BeyondTrust offers a standalone log-capturing tool named CaptureConfig.app which is deployed only as needed to endpoints which are not working as expected. While full-featured, live IT Support interaction is required to actually execute the app and retrieve its output.
While certainly not a replacement for CaptureConfig.app, BeyondTrust EPM Inspector.zsh allows affected end-users to self-diagnose EPM-related issues and captures its output to a Jamf Pro Policy Log, which can be easily shared with BeyondTrust Support representatives, without the “let’s-schedule-a-remote-support-session” requirements of the CaptureConfig.app.
Configuration
Complete the following steps to display a user-friendly message about the health of BeyondTrust Endpoint Privilege Management for Mac, while capturing various under-the-hood settings behind-the-scenes with swiftDialog.
A. Customize the BeyondTrust EPM Inspector.zsh script for your environment
- Review and adjust the Global Variables as required for your environment
scriptLog(i.e., the location of your client-side logs)
############################################################################################ # # Global Variables # ############################################################################################ export PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin/ # Script Version scriptVersion="0.0.11" # Client-side Log scriptLog="/var/log/org.churchofjesuschrist.log"
- Set your preferred script defaults (which can be optionally trumped in a policy’s script parameters)
- Parameter 4:
operationModedebug: Used when testing the scriptnormal: Outputs basic information to the Jamf Pro Policy Logverbose: Outputs basic information to the Jamf Pro Policy Log and detailed information toscriptLog
- Parameter 5:
anticipationDuration(i.e., the number of seconds end-users will have to wait in anticipation for the results to be displayed) - Parameter 6:
expectedPolicyChecksum(i.e., the output of:openssl dgst -sha256 "${targetPolicy}" | awk -F'= ' '{print $2}' )
- Parameter 4:
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Jamf Pro Script Parameters
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Parameter 4: Operation Mode [ debug | normal | verbose ]
operationMode="${4:-"verbose"}"
# Parameter 5: "Anticipation" Duration (in seconds)
anticipationDuration="${5:-"3"}"
# Parameter 6: Expected Policy Checksum [ $( openssl dgst -sha256 "${targetPolicy}" | awk -F'= ' '{print $2}' ) ]
expectedPolicyChecksum="${6:-"f42199e2af570d41e6143f9095de74c19ebf1a05d0b279accb27ef2c20510b56"}"
- Set your preferred Organization Variables
- Note: Adjust
targetPolicyas required for your environment
- Note: Adjust
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Organization Variables # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Script Human-readabale Name humanReadableScriptName="BeyondTrust EPM Inspector" # Organization's Script Name organizationScriptName="BT-EPM-I" # Client-side BeyondTrust EMP Policy targetPolicy="/etc/defendpoint/ic3.xml"
BeyondTrust EPM Inspector.zsh
Latest version available on GitHub.
#!/bin/zsh --no-rcs
# shellcheck shell=bash
####################################################################################################
#
# BeyondTrust EPM Inspector
#
# Purpose: Displays an end-user message about BeyondTrust EPM via swiftDialog
#
####################################################################################################
#
# HISTORY
#
# Version 0.0.1, 16-Jul-2024, Dan K. Snelson (@dan-snelson)
# - Original, proof-of-concept version
# - Based on: https://snelson.us/2023/04/crowdstrike-falcon-inspector-0-0-2-with-swiftdialog/
#
# Version 0.0.2, 17-Jul-2024, Dan K. Snelson (@dan-snelson)
# - Added `progressSteps` variable
# - Included macOS version
#
# Version 0.0.3, 17-Jul-2024, Dan K. Snelson (@dan-snelson)
# - Output MDM Overrides to scriptLog
#
# Version 0.0.4, 17-Jul-2024, Dan K. Snelson (@dan-snelson)
# - Changed `debugMode` to more robust `operationMode`
#
# Version 0.0.5, 18-Jul-2024, Dan K. Snelson (@dan-snelson)
# - Added output for the BeyondTrust PMC Server URL
# - Output 'defendpoint.plist' to ${scriptLog} (in "verbose" Operation Mode)
#
# Version 0.0.6, 29-Jul-2024, Dan K. Snelson (@dan-snelson)
# - Updates inspired by "CrowdStrike Falcon Inspector"
#
# Version 0.0.7, 22-Aug-2024, Dan K. Snelson (@dan-snelson)
# - Added checksum validation of "${targetPolicy}"
# - Stopped incrementing the progress bar like an animal
#
# Version 0.0.8, 22-Aug-2024, Dan K. Snelson (@dan-snelson)
# - Added "pmfmdiag all" (thanks, @tziegmann!)
#
# Version 0.0.9, 23-Aug-2024, Dan K. Snelson (@dan-snelson)
# - Added UseSheets status check
#
# Version 0.0.10, 23-Aug-2024, Dan K. Snelson (@dan-snelson)
# - Added output for assigned flexibility (See: [BeyondTrust PMfM Workstyle.zsh](https://github.com/dan-snelson/BeyondTrust-EPM/blob/main/BeyondTrust%20PMfM%20Workstyle.zsh))
#
# Version 0.0.11, 09-Sep-2024, Dan K. Snelson (@dan-snelson)
# - Added output for BeyondTrust PMfM Accounts (See: [9. macOS Sequoia 15 and “missing” EPM accounts](https://snelson.us/2024/08/beyondtrust-epm-racing-stripes/#9))
#
####################################################################################################
####################################################################################################
#
# Global Variables
#
####################################################################################################
export PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin/
# Script Version
scriptVersion="0.0.11"
# Client-side Log
scriptLog="/var/log/org.churchofjesuschrist.log"
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Jamf Pro Script Parameters
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Parameter 4: Operation Mode [ debug | normal | verbose ]
operationMode="${4:-"verbose"}"
# Parameter 5: "Anticipation" Duration (in seconds)
anticipationDuration="${5:-"3"}"
# Parameter 6: Expected Policy Checksum [ $( openssl dgst -sha256 "${targetPolicy}" | awk -F'= ' '{print $2}' ) ]
expectedPolicyChecksum="${6:-"f42199e2af570d41e6143f9095de74c19ebf1a05d0b279accb27ef2c20510b56"}"
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Organization Variables
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Script Human-readabale Name
humanReadableScriptName="BeyondTrust EPM Inspector"
# Organization's Script Name
organizationScriptName="BT-EPM-I"
# Client-side BeyondTrust EMP Policy
targetPolicy="/etc/defendpoint/ic3.xml"
# Client-side Policy Checksum
clientPolicyChecksum=$( openssl dgst -sha256 "${targetPolicy}" | awk -F'= ' '{print $2}' )
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Operating System, Computer Model Name, etc.
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
osVersion=$( sw_vers -productVersion )
osVersionExtra=$( sw_vers -productVersionExtra )
osBuild=$( sw_vers -buildVersion )
osMajorVersion=$( echo "${osVersion}" | awk -F '.' '{print $1}' )
if [[ -n $osVersionExtra ]] && [[ "${osMajorVersion}" -ge 13 ]]; then osVersion="${osVersion} ${osVersionExtra}"; fi # Report RSR sub version if applicable
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Logged-in User Variables
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
loggedInUser=$( echo "show State:/Users/ConsoleUser" | scutil | awk '/Name :/ { print $3 }' )
loggedInUserFullname=$( id -F "${loggedInUser}" )
loggedInUserFirstname=$( echo "$loggedInUserFullname" | sed -E 's/^.*, // ; s/([^ ]*).*/\1/' | sed 's/\(.\{25\}\).*/\1…/' | awk '{print ( $0 == toupper($0) ? toupper(substr($0,1,1))substr(tolower($0),2) : toupper(substr($0,1,1))substr($0,2) )}' )
loggedInUserID=$( id -u "${loggedInUser}" )
loggedInUserGroupMembership=$( id -Gn "${loggedInUser}" )
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# UseSheets Status
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
useSheetStatus=$( defaults read "/Users/${loggedInUser}/Library/Preferences/com.apple.Preferences.plist" UseSheets 2>&1 )
if [[ "${useSheetStatus}" == "0" ]]; then
useSheetStatusHumanReadable="passed"
else
useSheetStatusHumanReadable="failed"
fi
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Dialog binary (and enable swiftDialog's `--verbose` mode with script's operationMode)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# swiftDialog Binary Path
dialogBinary="/usr/local/bin/dialog"
# Debug Mode Features
case ${operationMode} in
"debug" ) dialogBinary="${dialogBinary} --verbose --resizable --debug red" ;;
esac
# swiftDialog Command File
dialogWelcomeLog=$( mktemp /var/tmp/dialogWelcomeLog.XXXX )
# The total number of steps for the progress bar, plus one (i.e., updateWelcomeDialog "progress: increment")
progressSteps="18"
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Welcome Dialog Title, Message and Icon
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
title="${humanReadableScriptName} (${scriptVersion})"
message="**Happy $( date +'%A' ), ${loggedInUserFirstname}!**<br><br>This script analyzes the installation of BeyondTrust Endpoint Privilege Management then reports the findings in this window.<br><br>Please wait …"
icon="https://ics.services.jamfcloud.com/icon/hash_a6d0e6852d3319a200e58036039cc69bb09a0882d89e799263c951a632d3a5d2"
# overlayIcon=$( defaults read /Library/Preferences/com.jamfsoftware.jamf.plist self_service_app_path )
button1text="Wait"
infobuttontext="KB8675309"
infobuttonaction="https://servicenow.company.com/support?id=kb_article_view&sysparm_article=${infobuttontext}"
welcomeProgressText="Initializing …"
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Welcome Dialog Settings and Features
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
dialogWelcome="$dialogBinary \
--title \"$title\" \
--message \"$message\" \
--icon \"$icon\" \
--button1text \"$button1text\" \
--button1disabled \
--infobuttontext \"$infobuttontext\" \
--infobuttonaction \"$infobuttonaction\" \
--progress \"$progressSteps\" \
--progresstext \"$welcomeProgressText\" \
--moveable \
--titlefont size=22 \
--messagefont size=14 \
--iconsize 135 \
--width 650 \
--height 375 \
--commandfile \"$dialogWelcomeLog\" "
# --overlayicon \"$overlayIcon\" \
####################################################################################################
#
# Functions
#
####################################################################################################
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Client-side Logging
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
function updateScriptLog() {
echo "${organizationScriptName} ($scriptVersion): $( date +%Y-%m-%d\ %H:%M:%S ) - ${1}" | tee -a "${scriptLog}"
}
function preFlight() {
updateScriptLog "[PRE-FLIGHT] ${1}"
}
function logComment() {
updateScriptLog " ${1}"
}
function notice() {
updateScriptLog "[NOTICE] ${1}"
}
function info() {
updateScriptLog "[INFO] ${1}"
}
function debug() {
if [[ "$operationMode" == "debug" ]]; then
updateScriptLog "[DEBUG] ${1}"
fi
}
function errorOut(){
updateScriptLog "[ERROR] ${1}"
}
function error() {
updateScriptLog "[ERROR] ${1}"
let errorCount++
}
function warning() {
updateScriptLog "[WARNING] ${1}"
let errorCount++
}
function fatal() {
updateScriptLog "[FATAL ERROR] ${1}"
exit 1
}
function quitOut(){
updateScriptLog "[QUIT] ${1}"
}
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Validate / install swiftDialog (Thanks big bunches, @acodega!)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
function dialogInstall() {
# Get the URL of the latest PKG From the Dialog GitHub repo
dialogURL=$(curl -L --silent --fail "https://api.github.com/repos/swiftDialog/swiftDialog/releases/latest" | awk -F '"' "/browser_download_url/ && /pkg\"/ { print \$4; exit }")
# Expected Team ID of the downloaded PKG
expectedDialogTeamID="PWA5E9TQ59"
preFlight "Installing swiftDialog..."
# Create temporary working directory
workDirectory=$( /usr/bin/basename "$0" )
tempDirectory=$( /usr/bin/mktemp -d "/private/tmp/$workDirectory.XXXXXX" )
# Download the installer package
/usr/bin/curl --location --silent "$dialogURL" -o "$tempDirectory/Dialog.pkg"
# Verify the download
teamID=$(/usr/sbin/spctl -a -vv -t install "$tempDirectory/Dialog.pkg" 2>&1 | awk '/origin=/ {print $NF }' | tr -d '()')
# Install the package if Team ID validates
if [[ "$expectedDialogTeamID" == "$teamID" ]]; then
/usr/sbin/installer -pkg "$tempDirectory/Dialog.pkg" -target /
sleep 2
dialogVersion=$( /usr/local/bin/dialog --version )
preFlight "swiftDialog version ${dialogVersion} installed; proceeding..."
else
# Display a so-called "simple" dialog if Team ID fails to validate
osascript -e 'display dialog "Please advise your Support Representative of the following error:\r\r• Dialog Team ID verification failed\r\r" with title "Setup Your Mac: Error" buttons {"Close"} with icon caution'
completionActionOption="Quit"
exitCode="1"
quitScript
fi
# Remove the temporary working directory when done
/bin/rm -Rf "$tempDirectory"
}
function dialogCheck() {
# Output Line Number in `verbose` Debug Mode
if [[ "${operationMode}" == "debug" ]]; then preFlight "# # # VERBOSE DEBUG MODE: Line No. ${LINENO} # # #" ; fi
# Check for Dialog and install if not found
if [ ! -e "/Library/Application Support/Dialog/Dialog.app" ]; then
preFlight "swiftDialog not found. Installing..."
dialogInstall
else
dialogVersion=$(/usr/local/bin/dialog --version)
if [[ "${dialogVersion}" < "${swiftDialogMinimumRequiredVersion}" ]]; then
preFlight "swiftDialog version ${dialogVersion} found but swiftDialog ${swiftDialogMinimumRequiredVersion} or newer is required; updating..."
dialogInstall
else
preFlight "swiftDialog version ${dialogVersion} found; proceeding..."
fi
fi
}
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Quit Script (thanks, @bartreadon!)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
function quitScript() {
quitOut "Quitting …"
updateWelcomeDialog "quit: "
sleep 1
quitOut "Exiting …"
# Remove dialogWelcomeLog
if [[ -e ${dialogWelcomeLog} ]]; then
quitOut "Removing ${dialogWelcomeLog} …"
rm "${dialogWelcomeLog}"
fi
quitOut "Goodbye!"
exit "${1}"
}
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Update Welcome Dialog
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
function updateWelcomeDialog() {
sleep 0.3
echo "${1}" >> "${dialogWelcomeLog}"
}
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Check for running processes (supplied as Parameter 1)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
function procesStatus() {
processToCheck="${1}"
logComment "Process: ${processToCheck}"
processToCheckStatus=$( /usr/bin/pgrep -x "${processToCheck}" )
if [[ -n ${processToCheckStatus} ]]; then
processCheckResult+="'${processToCheck}' running; "
else
processCheckResult+="'${processToCheck}' NOT running; "
fi
}
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Check for assigned flexibilty
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
function flexibilityCheck() {
case "${loggedInUserGroupMembership}" in
*"high"* ) assignedFlexibility="High" ;;
*"medium"* ) assignedFlexibility="Medium" ;;
*"low"* ) assignedFlexibility="Low" ;;
* )
workstyle=$( grep '"Workstyle":' /var/log/defendpoint/audit.log | tail -n 1 | awk -F'": "' '{print $3,$NF}' | sed 's/",//g; s/^ *//g' )
assignedFlexibility="${workstyle}"
;;
esac
}
####################################################################################################
#
# Pre-flight Checks
#
####################################################################################################
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Client-side Logging
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
if [[ ! -f "${scriptLog}" ]]; then
touch "${scriptLog}"
if [[ -f "${scriptLog}" ]]; then
preFlight "Created specified scriptLog: ${scriptLog}"
else
fatal "Unable to create specified scriptLog '${scriptLog}'; exiting.\n\n(Is this script running as 'root' ?)"
fi
else
preFlight "Specified scriptLog '${scriptLog}' exists; writing log entries to it"
fi
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Logging Preamble
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
preFlight "\n\n###\n# $humanReadableScriptName (${scriptVersion})\n# Operation Mode: ${operationMode}\n###\n"
preFlight "Initiating …"
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Confirm script is running as root
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
if [[ $(id -u) -ne 0 ]]; then
fatal "This script must be run as root; exiting."
fi
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Validate swiftDialog is installed
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
preFlight "Validate swiftDialog is installed"
dialogCheck
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Validate BeyondTrust EPM installation (or exit with error)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
if [[ -e "/Applications/PrivilegeManagement.app" ]]; then
preFlight "BeyondTrust EPM Client installed; proceeding …"
else
fatal "BeyondTrust EPM Client NOT installed; exiting"
fi
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Complete
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
preFlight "Complete!"
####################################################################################################
#
# Program
#
####################################################################################################
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Create Welcome Dialog
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
notice "Create Welcome Dialog …"
eval "$dialogWelcome" & sleep 0.3
if [[ ${operationMode} == "debug" ]]; then
updateWelcomeDialog "title: DEBUG MODE | $title"
sleep "${anticipationDuration}"
updateWelcomeDialog "message: DEBUG MODE. Please wait for ${anticipationDuration} seconds …"
updateWelcomeDialog "progresstext: DEBUG MODE. Pausing for ${anticipationDuration} seconds"
sleep "${anticipationDuration}"
computerName="DEBUG"
btEpmClient="DEBUG"
btEpmAdapter="DEBUG"
btEpmPackageManager="DEBUG"
systemExtensionStatus="DEBUG"
policyName="DEBUG"
policyRevision="DEBUG"
updateWelcomeDialog "message: **Results for ${loggedInUser}**<br><br><br>- **macOS Version:** ${osVersion} (${osBuild})<br>- **Policy Name and Revision:** ${policyName} (r${policyRevision})<br>- **Computer Name:** ${computerName}<br>- **Installation Status:** DEBUG<br>- **Client Version:** ${btEpmClient} <br>- **Adapter Version:** ${btEpmAdapter}<br>- **Package Manager Version:** ${btEpmPackageManager}<br>- **System Extension:** ${systemExtensionStatus}"
else
updateWelcomeDialog "progress: increment"
updateWelcomeDialog "progresstext: Inspecting …"
sleep "${anticipationDuration}"
SECONDS="0"
# BeyondTrust EPM Inspection: Computer Name
info "Computer Name"
computerName=$( scutil --get LocalHostName )
updateWelcomeDialog "progress: increment"
updateWelcomeDialog "progresstext: Computer Name …"
# BeyondTrust EPM Inspection: Installation
info "Installation"
updateWelcomeDialog "progress: increment"
updateWelcomeDialog "progresstext: Installation …"
# BeyondTrust EPM Inspection: Client Version
info "Client Version"
btEpmClient=$( defaults read /Applications/PrivilegeManagement.app/Contents/Info.plist CFBundleVersion )
updateWelcomeDialog "progress: increment"
updateWelcomeDialog "progresstext: Client Version …"
# BeyondTrust EPM Inspection: Adapter Version
info "Adapter Version"
btEpmAdapter=$( defaults read /usr/local/libexec/Avecto/iC3Adapter/1.0/PMCAdapter.app/Contents/Info.plist CFBundleVersion )
updateWelcomeDialog "progress: increment"
updateWelcomeDialog "progresstext: Adapter Version …"
# BeyondTrust EPM Inspection: Package Manager Version
info "Package Manager Version"
btEpmPackageManager=$( defaults read /Applications/BeyondTrust/PMCPackageManager.app/Contents/Info.plist CFBundleVersion )
updateWelcomeDialog "progress: increment"
updateWelcomeDialog "progresstext: Package Manager Version …"
# BeyondTrust EPM Inspection: System Extension
info "System Extension"
systemExtensionStatus=$( systemextensionsctl list | awk -F"[][]" '/com.beyondtrust.endpointsecurity/ {print $2}' )
updateWelcomeDialog "progress: increment"
updateWelcomeDialog "progresstext: System Extension …"
# BeyondTrust EPM Inspection: Policy Name and Revision
info "Policy Name and Revision"
policyName=$( xmllint --xpath "string(//@PolicyName)" "${targetPolicy}" )
policyRevision=$( xmllint --xpath "string(//@RevisionNumber)" "${targetPolicy}" )
updateWelcomeDialog "progress: increment"
updateWelcomeDialog "progresstext: Policy Name and Revision …"
# BeyondTrust EPM Inspection: Policy Checksum Validation
info "Policy Checksum Validation"
if [[ "${clientPolicyChecksum}" == "${expectedPolicyChecksum}" ]]; then
checksumValidation="Checksum Passed"
logComment "${checksumValidation}"
else
checksumValidation="Checksum Failed"
warning "${checksumValidation}"
fi
updateWelcomeDialog "progress: increment"
updateWelcomeDialog "progresstext: Policy Checksum Validation …"
# BeyondTrust EPM Inspection: Assigned Flexibility
info "Assigned Flexibility"
flexibilityCheck
updateWelcomeDialog "progress: increment"
updateWelcomeDialog "progresstext: Assigned Flexibility …"
# BeyondTrust EPM Inspection: Processes
info "Processes"
updateWelcomeDialog "progress: increment"
updateWelcomeDialog "progresstext: BeyondTrust EPM Process 1 of 6 …"
procesStatus "defendpointd"
updateWelcomeDialog "progress: increment"
updateWelcomeDialog "progresstext: BeyondTrust EPM Process 2 of 6 …"
procesStatus "Custodian"
updateWelcomeDialog "progress: increment"
updateWelcomeDialog "progresstext: BeyondTrust EPM Process 3 of 6 …"
procesStatus "PMCAdapter"
updateWelcomeDialog "progress: increment"
updateWelcomeDialog "progresstext: BeyondTrust EPM Process 4 of 6 …"
procesStatus "PMCPackageManager"
updateWelcomeDialog "progress: increment"
updateWelcomeDialog "progresstext: BeyondTrust EPM Process 5 of 6 …"
procesStatus "PrivilegeManagement"
updateWelcomeDialog "progress: increment"
updateWelcomeDialog "progresstext: BeyondTrust EPM Process 6 of 6 …"
procesStatus "NewPrivilegeManagement"
processCheckResult=${processCheckResult/%; }
###
# BeyondTrust EPM Inspection: Output results to log
###
notice "Output results to log"
updateWelcomeDialog "progress: increment"
updateWelcomeDialog "progresstext: Analyzing …"
logComment "Results for ${loggedInUser}:"
info "$( id "${loggedInUser}" )"
logComment "Computer Name: ${computerName}"
logComment "macOS Version: ${osVersion} (${osBuild})"
logComment "EPM Accounts: $( dscl . list /Users | grep -E "(_avectodaemon|_defendpoint)" | tr '\n' ' ')"
logComment "Installation Status: Installed"
logComment "UseSheets Status: ${useSheetStatusHumanReadable}"
logComment "Server: $( defaults read /Library/Application\ Support/Avecto/iC3Adapter/config.plist Server )"
logComment "Client Version: ${btEpmClient}"
logComment "Adapter Version: ${btEpmAdapter}"
logComment "Package Manager Version: ${btEpmPackageManager}"
logComment "System Extension: ${systemExtensionStatus}"
logComment "Policy Name and Revision: ${policyName} (r${policyRevision})"
logComment "Assigned Flexibility: ${assignedFlexibility}"
if [[ "${checksumValidation}" == *"Failed" ]]; then
warning "Policy Checksum Validation: ${checksumValidation}"
else
logComment "Policy Checksum Validation: ${checksumValidation}"
fi
info "Processes: ${processCheckResult}"
info "PMfM Status: $( pmfm status )"
info "pmfmdiag: $( pmfmdiag all )"
info "sudo.conf Check: $( ls -lah /etc/sudo.conf )"
info "sudo.conf Contents: $( cat /etc/sudo.conf )"
info "sudoserver Check: $( ls -lah /var/run/defendpoint_sudoserver )"
if [[ "${operationMode}" == "verbose" ]]; then
info "Output 'defendpoint.plist' to ${scriptLog} …"
plutil -p /Library/Application\ Support/Avecto/Defendpoint/defendpoint.plist >> "${scriptLog}"
info "Output MDM Overrides to ${scriptLog} …"
plutil -p /Library/Application\ Support/com.apple.TCC/MDMOverrides.plist >> "${scriptLog}"
fi
# BeyondTrust EPM Inspection: Display results to user
notice "Display results to user"
timestamp="$( date '+%Y-%m-%d-%H%M%S' )"
updateWelcomeDialog "message: \
**Results for ${loggedInUser} on ${timestamp}**<br><br><br> \
- **macOS Version:** ${osVersion} (${osBuild})<br> \
- **Policy Name and Revision:** ${policyName} (r${policyRevision})<br> \
- **Assigned Flexibility:** ${assignedFlexibility}<br> \
- **Computer Name:** ${computerName}<br> \
- **Installation Status:** Installed<br> \
- **Client Version:** ${btEpmClient} <br> \
- **Adapter Version:** ${btEpmAdapter}<br> \
- **Package Manager Version:** ${btEpmPackageManager}<br> \
- **System Extension:** ${systemExtensionStatus}
"
updateWelcomeDialog "progress: complete"
updateWelcomeDialog "progresstext: Complete!"
sleep "${anticipationDuration}"
fi
updateWelcomeDialog "button1text: Done"
updateWelcomeDialog "button1: enable"
updateWelcomeDialog "progress: 100"
updateWelcomeDialog "progresstext: Elapsed Time: $(printf '%dh:%dm:%ds\n' $((SECONDS/3600)) $((SECONDS%3600/60)) $((SECONDS%60)))"
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Exit
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
wait
info "Elapsed Time: $(printf '%dh:%dm:%ds\n' $((SECONDS/3600)) $((SECONDS%3600/60)) $((SECONDS%60)))"
quitOut "End-of-line."
quitScript "0"
B. Add the BeyondTrust EPM Inspector.zsh script to your Jamf Pro server
- Add the
BeyondTrust EPM Inspector.zshscript to your Jamf Pro server - Specify the following for Options > Parameter Labels
- Parameter 4:
Operation Mode [ debug | normal | verbose ] - Parameter 5:
"Anticipation" Duration (in seconds) - Parameter 6:
Expected Policy Checksum [ $( openssl dgst -sha256 "${targetPolicy}" | awk -F'= ' '{print $2}' ) ]
- Parameter 4:
- Click Save

C. Create a Jamf Pro Policy to execute BeyondTrust EPM Inspector
- Create a new Jamf Pro Policy, using the following as a guide for Options > General:
- Set Display Name to
BeyondTrust EPM Inspector (0.0.11)
- Set Execution Frequency to
Ongoing
- Set Display Name to
- Select the Scripts payload and add the
BeyondTrust EPM Inspector (0.0.11)script, specifying the following Parameter Values- Operation Mode:
verbose - “Anticipation” Duration (in seconds):
3 - Expected Policy Checksum [ $( openssl dgst -sha256 “${targetPolicy}” | awk -F’= ‘ ‘{print $2}’ ) ]:
f55991238d3476e92d222d1d837ad89f9bbd3a81fdbbe5606b5a7af30937a63e
- Operation Mode:

- Adjust Scope to your liking; we use our custom BeyondTrust PMfM Client Smart Computer Group
- Use the following for Self Service
- Self Service Display Name:
BeyondTrust EPM Inspector (0.0.11) - Button Name Before Initiation:
Inspect - Button Name After Initiation:
Reinspect
- Icon:
(download) - Description:
- Self Service Display Name:
Performs the following tests on BeyondTrust Endpoint Privilege Management: - Validates client installation - Reports various component versions - Confirms System Extension status - Tests various related processes - Validates configuration
- Click Save
“Unofficial” Support
While unofficial, best-effort support is available on the Mac Admins Slack (free, registration required) #beyondtrust-priv-man Channel, it’s probably best to direct-message me on Slack if your mileage varies too greatly.