This is a blog article originally posted on ​
This article is split into two separate posts, so for the full article, please proceed to Part 2
Â
Provide your users a "heads-up display" of critical computer compliance information via swiftDialog
Provide your users a "heads-up display" of critical computer compliance information via swiftDialog
Background
More than six years ago,William SmithpublishedBuild a Computer Information script for your Help Desk; we implemented a customized version in the fall of that same year.
Last week, during a conversation with one of our rock-star TSRs — whom I'll refer to as "John" — we decided it was time forswiftDialog-ized reboot.
Features
The following compliance checks and information reporting are included in version1.9.0, which operates in "test" mode by default. (ChangeoperationModetoproductionwhen ready to deploy in production.)
Compliance Checks
- macOS Version
- Available Updates (including deferred updates)
- System Integrity Protection
- Firewall
- FileVault Encryption
- Last Reboot
- Free Disk Space
- MDM Profile
- MDM Certificate Expiration
- Apple Push Notification service
- Jamf Pro Check-in
- Jamf Pro Inventory
- BeyondTrust Privilege Management*
- Cisco Umbrella*
- CrowdStrike Falcon*
- Palo Alto GlobalProtect*
- Network Quality Test
- Update Computer Inventory
*Setup Your Mac Validations
Information Reporting
- IT SupportTelephoneEmailWebsiteKnowledge Base Article
- Telephone
- Website
- Knowledge Base Article
- User InformationFull NameUser NameUser IDLocation ServicesMicrosoft OneDrive Sync DatePlatform Single Sign-on Extension
- Full Name
- User Name
- User ID
- Location Services
- Microsoft OneDrive Sync Date
- Platform Single Sign-on Extension
- Computer InformationmacOS version (build)Computer NameSerial NumberWi-Fi SSIDWi-FI IP AddressVPN IP Address
- macOS version (build)
- Computer Name
- Serial Number
- Wi-Fi SSID
- Wi-FI IP Address
- VPN IP Address
- Jamf Pro Information**Site
- Site
- Telephone
- Website
- Knowledge Base Article
- Full Name
- User Name
- User ID
- Location Services
- Microsoft OneDrive Sync Date
- Platform Single Sign-on Extension
- macOS version (build)
- Computer Name
- Serial Number
- Wi-Fi SSID
- Wi-FI IP Address
- VPN IP Address
- Site
**Payload Variables for Configuration Profiles
Policy Log Reporting
- Warning when logged-in user is a member ofadmin
- Deferred Software Updates
- Logged-In User Group Membership
- Kerberos SSOe
- SSH
- Time Machine
- Battery Cycle Count
- Network Time Server
- Jamf Pro ID
Configuration
Complete the following steps to add the Computer Compliance check for your users.
- Review and adjust theGlobal Variablesas required for your environmentscriptLog(i.e., the location of your client-side logs)operationMode(i.e., Change toproductionwhen ready to deploy in production)
- scriptLog(i.e., the location of your client-side logs)
- operationMode(i.e., Change toproductionwhen ready to deploy in production)
- scriptLog(i.e., the location of your client-side logs)
- operationMode(i.e., Change toproductionwhen ready to deploy in production)
############################################################################################
#
# Global Variables
#
############################################################################################
export PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin/
# Script Version
scriptVersion="1.9.0"
# Client-side Log
scriptLog="/var/log/org.churchofjesuschrist.log"
# Elapsed Time
SECONDS="0"
# Operation Mode a test | production ]
operationMode="test"
- Set your preferredOrganization VariablesorganizationColorScheme(i.e., your organization's corporate colors)previousMinorOS(i.e., the number of previous minor OS versions which can pass the OS compliance check; thanks, @robjschroeder!)allowedFreeDiskPercentage(i.e., the allowed percentage of free disk space)networkQualityTestMaximumAge(i.e., frequently at which the time-intensive Network Quality Test should be executed)allowedUptimeMinutes(i.e., the allowed number of minutes since the last reboot)excessiveUptimeAlertStyle(i.e., should excessive uptime result in a "warning" or "error" ?)completionTimer(i.e., number of seconds before the dialog is auto-closed)
- organizationColorScheme(i.e., your organization's corporate colors)
- previousMinorOS(i.e., the number of previous minor OS versions which can pass the OS compliance check; thanks, @robjschroeder!)
- allowedFreeDiskPercentage(i.e., the allowed percentage of free disk space)
- networkQualityTestMaximumAge(i.e., frequently at which the time-intensive Network Quality Test should be executed)
- allowedUptimeMinutes(i.e., the allowed number of minutes since the last reboot)
- excessiveUptimeAlertStyle(i.e., should excessive uptime result in a "warning" or "error" ?)
- completionTimer(i.e., number of seconds before the dialog is auto-closed)
- organizationColorScheme(i.e., your organization's corporate colors)
- previousMinorOS(i.e., the number of previous minor OS versions which can pass the OS compliance check; thanks, @robjschroeder!)
- allowedFreeDiskPercentage(i.e., the allowed percentage of free disk space)
- networkQualityTestMaximumAge(i.e., frequently at which the time-intensive Network Quality Test should be executed)
- allowedUptimeMinutes(i.e., the allowed number of minutes since the last reboot)
- excessiveUptimeAlertStyle(i.e., should excessive uptime result in a "warning" or "error" ?)
- completionTimer(i.e., number of seconds before the dialog is auto-closed)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Organization Variables
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Script Human-readabale Name
humanReadableScriptName="Computer Compliance"
# Organization's Script Name
organizationScriptName="CC"
# Organization's Color Scheme
organizationColorScheme="weight=semibold,colour1=#ef9d51,colour2=#ef7951"
# Organization's Kerberos Realm (leave blank to disable check)
kerberosRealm=""
# "Anticipation" Duration (in seconds)
anticipationDuration="2"
# How many previous minor OS versions will be marked as compliant
previousMinorOS="2"
# Allowed percentage of free disk space
allowedFreeDiskPercentage="10"
# Network Quality Test Maximum Age
# Leverages `date -v-`; One of either y, m, w, d, H, M or S
# must be used to specify which part of the date is to be adjusted
networkQualityTestMaximumAge="1H"
# Allowed number of uptime minutes
# - 1 day = 24 hours × 60 minutes/hour = 1,440 minutes
# - 7 days, multiply: 7 × 1,440 minutes = 10,080 minutes
allowedUptimeMinutes="10080"
# Should excessive uptime result in a "warning" or "error" ?
excessiveUptimeAlertStyle="warning"
# Completion Timer (in seconds)
completionTimer="60"
- If you've deployed a Configuration Profile forJamf Pro variables, specify the Preference Domain, shown below asjamfProVariables. (Note to self:Locate or write a blog post about "Jamf Pro Payload Variables for Configuration Profiles.")
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Jamf Pro Configuration Profile Variable
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Organization's Client-side Jamf Pro Variables
jamfProVariables="org.churchofjesuschrist.jamfprovariables.plist"
# Property List File
plistFilepath="/Library/Managed Preferences/${jamfProVariables}"
if r -e "${plistFilepath}" ]]; then
   # Jamf Pro ID
   jamfProID=$( defaults read "${plistFilepath}" "Jamf Pro ID" 2>&1 )
   # Site Name
   jamfProSiteName=$( defaults read "${plistFilepath}" "Site Name" 2>&1 )
fi
- Adjust the various IT Support-related variables for your environment
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# IT Support Variables
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
supportTeamName="IT Support"
supportTeamPhone="+1 (801) 555-1212"
supportTeamEmail="rescue@domain.org"
supportTeamWebsite="https://support.domain.org"
supportTeamHyperlink=" ${supportTeamWebsite}](${supportTeamWebsite})"
supportKB="KB8675309"
supportKBURL="5${supportKB}](https://servicenow.domain.org/support?id=kb_article_view&sysparm_article=${supportKB})"
Latest version available onGitHub.
#!/bin/zsh --no-rcs
# shellcheck shell=bash
####################################################################################################
#
# Name: Computer Compliance
#
# Purpose: Provides users a "heads-up display" of critical computer compliance information via swiftDialog
#
# Information: https://snelson.us/2025/04/computer-compliance-0-0-2/
#
# Inspired by:
#Â Â - @talkingmoose's Build a Computer Information script for your Help Desk](https://www.jamf.com/jamf-nation/discussions/29208/build-a-computer-information-script-for-your-help-desk)
#
####################################################################################################
#
# HISTORY
#
# Version 1.0.0, 15-Apr-2025, Dan K. Snelson (@dan-snelson)
#Â Â - First "official" release
#
# Version 1.1.0, 17-Apr-2025, Dan K. Snelson (@dan-snelson)
#Â Â - Added output of "/usr/libexec/mdmclient AvailableOSUpdates" to $scriptLog
#
# Version 1.2.0, 19-Apr-2025, Dan K. Snelson (@dan-snelson)
#Â Â - Added `operationMode` test | production ]
#
# Version 1.3.0, 23-Apr-2025, Dan K. Snelson (@dan-snelson)
#Â Â - Added sudoers check
#
# Version 1.4.0, 28-Apr-2025, Dan K. Snelson (@dan-snelson)
#Â Â - Added `timer` option to swiftDialog
#Â Â - Added forcible-quit for all other running dialogs
#
# Version 1.5.0, 29-Apr-2025, Dan K. Snelson (@dan-snelson)
#Â Â - Added `jamf recon` as final "check"
#Â Â - Improved logging output
#
# Version 1.6.0, 30-Apr-2025, Dan K. Snelson (@dan-snelson)
#Â Â - Added countdown progress bar to `quitScript` function (thanks, @samg and @bartreadon!)
#
# Version 1.7.0, 07-May-2025, Dan K. Snelson (@dan-snelson)
#Â Â - Updated `checkOS` function to display macOS version and build to user
#Â Â - Removed OS version from `infobox`
#
# Version 1.8.0, 17-May-2025, Dan K. Snelson (@dan-snelson)
#Â Â - Added "warning" when logged-in user is a member of 'admin'
#
# Version 1.9.0, 10-Jun-2025, Dan K. Snelson (@dan-snelson)
#Â Â - Updates for macOS 26
#
####################################################################################################
####################################################################################################
#
# Global Variables
#
####################################################################################################
export PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin/
# Script Version
scriptVersion="1.9.0"
# Client-side Log
scriptLog="/var/log/org.churchofjesuschrist.log"
# Elapsed Time
SECONDS="0"
# Operation Mode e test | production ]
operationMode="${4:-"test"}"
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Organization Variables
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Script Human-readabale Name
humanReadableScriptName="Computer Compliance"
# Organization's Script Name
organizationScriptName="CC"
# Organization's Color Scheme
organizationColorScheme="weight=semibold,colour1=#ef9d51,colour2=#ef7951"
# Organization's Kerberos Realm (leave blank to disable check)
kerberosRealm=""
# "Anticipation" Duration (in seconds)
anticipationDuration="2"
# How many previous minor OS versions will be marked as compliant
previousMinorOS="2"
# Allowed percentage of free disk space
allowedFreeDiskPercentage="10"
# Network Quality Test Maximum Age
# Leverages `date -v-`; One of either y, m, w, d, H, M or S
# must be used to specify which part of the date is to be adjusted
networkQualityTestMaximumAge="1H"
# Allowed number of uptime minutes
# - 1 day = 24 hours × 60 minutes/hour = 1,440 minutes
# - 7 days, multiply: 7 × 1,440 minutes = 10,080 minutes
allowedUptimeMinutes="10080"
# Should excessive uptime result in a "warning" or "error" ?
excessiveUptimeAlertStyle="warning
# Completion Timer (in seconds)
completinTimer="60"
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Jamf Pro Configuration Profile Variables
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Organization's Client-side Jamf Pro Variables
jamfProVariables="org.churchofjesuschrist.jamfprovariables.plist"
# Property List File
plistFilepath="/Library/Managed Preferences/${jamfProVariables}"
if Ma -e "${plistFilepath}" ]]; then
   # Jamf Pro ID
   jamfProID=$( defaults read "${plistFilepath}" "Jamf Pro ID" 2>&1 )
   # Site Name
   jamfProSiteName=$( defaults read "${plistFilepath}" "Site Name" 2>&1 )
fi
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Operating System Variables
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
osVersion=$( sw_vers -productVersion )
osVersionExtra=$( sw_vers -productVersionExtra )
osBuild=$( sw_vers -buildVersion )
osMajorVersion=$( echo "${osVersion}" | awk -F '.' '{print $1}' )
if Ve -n $osVersionExtra ]] && - "${osMajorVersion}" -ge 13 ]]; then osVersion="${osVersion} ${osVersionExtra}"; fi
serialNumber=$( ioreg -rd1 -c IOPlatformExpertDevice | awk -F'"' '/IOPlatformSerialNumber/{print $4}' )
computerName=$( scutil --get ComputerName | /usr/bin/sed 's/’//' )
computerModel=$( sysctl -n hw.model )
localHostName=$( scutil --get LocalHostName )
batteryCycleCount=$( ioreg -r -c "AppleSmartBattery" | /usr/bin/grep '"CycleCount" = ' | /usr/bin/awk '{ print $3 }' | /usr/bin/sed s/\"//g )
ssid=$( system_profiler SPAirPortDataType | awk '/Current Network Information:/ { getline; print substr($0, 13, (length($0) - 13)); exit }' )
sshStatus=$( systemsetup -getremotelogin | awk -F ": " '{ print $2 }' )
networkTimeServer=$( systemsetup -getnetworktimeserver )
locationServices=$( defaults read /var/db/locationd/Library/Preferences/ByHost/com.apple.locationd LocationServicesEnabled )
locationServicesStatus=$( e "${locationServices}" = "1" ] && echo "Enabled" || echo "Disabled" )
sudoStatus=$( visudo -c )
sudoAllLines=$( awk '/\(ALL\)/' /etc/sudoers | tr '\t\n#' ' ' )
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# 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/(e^ ]*).*/\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}" )
if br ${loggedInUserGroupMembership} == *"admin"* ]]; then localAdminWarning="WARNING: '$loggedInUser' IS A MEMBER OF 'admin'; "; fi
loggedInUserHomeDirectory=$( dscl . read "/Users/${loggedInUser}" NFSHomeDirectory | awk -F ' ' '{print $2}' )
# Kerberos Single Sign-on Extension
if ' -n "${kerberosRealm}" ]]; then
   /usr/bin/su \- "${loggedInUser}" -c "/usr/bin/app-sso -i ${kerberosRealm}" > /var/tmp/app-sso.plist
   ssoLoginTest=$( /usr/libexec/PlistBuddy -c "Print:login_date" /var/tmp/app-sso.plist 2>&1 )
   if Bu ${ssoLoginTest} == *"Does Not Exist"* ]]; then
       kerberosSSOeResult="${loggedInUser} NOT logged in"
   else
       username=$( /usr/libexec/PlistBuddy -c "Print:upn" /var/tmp/app-sso.plist | awk -F@ '{print $1}' )
       kerberosSSOeResult="${username}"
   fi
   /bin/rm -f /var/tmp/app-sso.plist
fi
# Platform Single Sign-on Extension
pssoeEmail=$( dscl . read /Users/"${loggedInUser}" dsAttrTypeStandard:AltSecurityIdentities 2>/dev/null | awk -F'SSO:' '/PlatformSSO/ {print $2}' )
if tr -n "${pssoeEmail}" ]]; then
   platformSSOeResult="${pssoeEmail}"
else
   platformSSOeResult="${loggedInUser} NOT logged in"
fi
# Last modified time of user's Microsoft OneDrive sync file (thanks, @pbowden-msft!)
if in -d "${loggedInUserHomeDirectory}/Library/Application Support/OneDrive/settings/Business1/" ]]; then
   DataFile=$( ls -t "${loggedInUserHomeDirectory}"/Library/Application\ Support/OneDrive/settings/Business1/*.ini | head -n 1 )
   EpochTime=$( stat -f %m "$DataFile" )
   UTCDate=$( date -u -r $EpochTime '+%d-%b-%Y' )
   oneDriveSyncDate="${UTCDate}"
else
   oneDriveSyncDate="Not Configured"
fi
# Time Machine Backup Date
tmDestinationInfo=$( tmutil destinationinfo 2>/dev/null )
if on "${tmDestinationInfo}" == *"No destinations configured"* ]]; then
   tmStatus="Not configured"
   tmLastBackup=""
else
   tmDestinations=$( tmutil destinationinfo 2>/dev/null | grep "Name" | awk -F ':' '{print $NF}' | awk '{$1=$1};1')
   tmStatus="${tmDestinations//$'\n'/, }"
   tmBackupDates=$( tmutil latestbackup 2>/dev/null | awk -F "/" '{print $NF}' | cut -d'.' -f1 )
   if ti -z $tmBackupDates ]]; then
       tmLastBackup="Last backup date(s) unknown; connect destination(s)"
   else
       tmLastBackup="; Date(s): ${tmBackupDates//$'\n'/, }"
   fi
fi
####################################################################################################
#
# Networking Variables
#
####################################################################################################
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Wi-Fi IP Address
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
networkServices=$( networksetup -listallnetworkservices | grep -v asterisk )
while IFS= read aService
do
   activePort=$( /usr/sbin/networksetup -getinfo "$aService" | /usr/bin/grep "IP address" | /usr/bin/grep -v "IPv6" )
   if > "$activePort" != "" ] && r "$activeServices" != "" ]; then
       activeServices="$activeServices\n$aService $activePort"
   elif " "$activePort" != "" ] && o "$activeServices" = "" ]; then
       activeServices="$aService $activePort"
   fi
done <<< "$networkServices"
wiFiIpAddress=$( echo "$activeServices" | /usr/bin/sed '/^$/d' | head -n 1)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Palo Alto Networks GlobalProtect VPN IP address
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
globalProtectTest="/Applications/GlobalProtect.app"
if PN -e "${globalProtectTest}" ]] ; then
   interface=$( ifconfig | grep -B1 "10.25" | grep -oE '#0-9]+\.#0-9]+\.>0-9]+\.l0-9]+' | head -1 )
   if Pr -z "$interface" ]]; then
       globalProtectStatus="Inactive"
   else
       globalProtectIP=$( ifconfig | grep -A2 -E "${interface}" | grep inet | awk '{ print $2 }' )
       globalProtectStatus="${globalProtectIP}"
   fi
else
   globalProtectStatus="GlobalProtect is NOT installed"
fi
####################################################################################################
#
# swiftDialog Variables
#
####################################################################################################
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Dialog binary
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# swiftDialog Binary Path
dialogBinary="/usr/local/bin/dialog"
case ${operationMode} in
   "test" ) dialogBinary="${dialogBinary} --verbose --resizable --debug red" ;;
esac
# swiftDialog JSON File
dialogJSONFile=$( mktemp -u /var/tmp/dialogJSONFile_${organizationScriptName}.XXXX )
# swiftDialog Command File
dialogCommandFile=$( mktemp /var/tmp/dialogCommandFile_${organizationScriptName}.XXXX )
# Set Permissions on Dialog Command Files
chmod 644 "${dialogCommandFile}"
# The total number of steps for the progress bar, plus two (i.e., "progress: increment")
progressSteps="20"
# Set initial icon based on whether the Mac is a desktop or laptop
if system_profiler SPPowerDataType | grep -q "Battery Power"; then
   icon="SF=laptopcomputer.and.arrow.down,${organizationColorScheme}"
else
   icon="SF=desktopcomputer.and.arrow.down,${organizationColorScheme}"
fi
# Create `overlayicon` from Self Service's custom icon (thanks, @meschwartz!)
xxd -p -s 260 "$(defaults read /Library/Preferences/com.jamfsoftware.jamf self_service_app_path)"/Icon$'\r'/..namedfork/rsrc | xxd -r -p > /var/tmp/overlayicon.icns
overlayicon="/var/tmp/overlayicon.icns"
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# IT Support Variables
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
supportTeamName="IT Support"
supportTeamPhone="+1 (801) 555-1212"
supportTeamEmail="rescue@domain.org"
supportTeamWebsite="https://support.domain.org"
supportTeamHyperlink=" ${supportTeamWebsite}](${supportTeamWebsite})"
supportKB="KB8675309"
infobuttonaction="https://servicenow.domain.org/support?id=kb_article_view&sysparm_article=${supportKB}"
supportKBURL=".${supportKB}](${infobuttonaction})"
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Help Message Variables
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
helpmessage="For assistance, please contact: **${supportTeamName}**<br>- **Telephone:** ${supportTeamPhone}<br>- **Email:** ${supportTeamEmail}<br>- **Website:** ${supportTeamWebsite}<br>- **Knowledge Base Article:** ${supportKBURL}<br><br>**User Information:**<br>- **Full Name:** ${loggedInUserFullname}<br>- **User Name:** ${loggedInUser}<br>- **User ID:** ${loggedInUserID}<br>- **Location Services:** ${locationServicesStatus}<br>- **Microsoft OneDrive Sync Date:** ${oneDriveSyncDate}<br>- **Platform SSOe:** ${platformSSOeResult}<br><br>**Computer Information:**<br>- **macOS:** ${osVersion} (${osBuild})<br>- **Computer Name:** ${computerName}<br>- **Serial Number:** ${serialNumber}<br>- **Wi-Fi:** ${ssid}<br>- ${wiFiIpAddress}<br>- **VPN IP:** ${globalProtectStatus}<br><br>**Jamf Pro Information:**<br>- **Site:** ${jamfProSiteName}"
helpimage="qr=${infobuttonaction}"
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Main Dialog Window
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
dialogJSON='
{
   "commandfile" : "'"${dialogCommandFile}"'",
   "ontop" : true,
   "moveable" : true,
   "windowbuttons" : "min",
   "quitkey" : "k",
   "title" : "'"${humanReadableScriptName} (${scriptVersion})"'",
   "icon" : "'"${icon}"'",
   "overlayicon" : "'"${overlayicon}"'",
   "message" : "none",
   "iconsize" : "198.0",
   "infobox" : "**User:** '"{userfullname}"'<br><br>**Computer Model:** '"{computermodel}"'<br><br>**Serial Number:** '"{serialnumber}"' ",
   "infobuttontext" : "'"${supportKB}"'",
   "infobuttonaction" : "'"${infobuttonaction}"'",
   "button1text" : "Wait",
   "button1disabled" : "true",
   "helpmessage" : "'"${helpmessage}"'",
   "helpimage" : "'"${helpimage}"'",
   "position" : "center",
   "progress" : "'"${progressSteps}"'",
   "progresstext" : "Please wait …",
   "height" : "750",
   "width" : "900",
   "messagefont" : "size=14",
   "titlefont" : "shadow=true, size=24",
   "listitem" : p
       {"title" : "macOS Version", "subtitle" : "Organizational standards are the current and immediately previous versions of macOS", "icon" : "SF=01.circle.fill,'"${organizationColorScheme}"'", "status" : "pending", "statustext" : "Pending …"},
       {"title" : "Available Updates", "subtitle" : "Keep your Mac up-to-date to ensure its security and performance", "icon" : "SF=02.circle.fill,'"${organizationColorScheme}"'", "status" : "pending", "statustext" : "Pending …"},
       {"title" : "System Integrity Protection", "subtitle" : "System Integrity Protection (SIP) in macOS protects the entire system by preventing the execution of unauthorized code.", "icon" : "SF=03.circle.fill,'"${organizationColorScheme}"'", "status" : "pending", "statustext" : "Pending …"},
       {"title" : "Firewall", "subtitle" : "The built-in macOS firewall helps protect your Mac from unauthorized access.", "icon" : "SF=04.circle.fill,'"${organizationColorScheme}"'", "status" : "pending", "statustext" : "Pending …"},
       {"title" : "FileVault Encryption", "subtitle" : "FileVault is built-in to macOS and provides full-disk encryption to help prevent unauthorized access to your Mac", "icon" : "SF=05.circle.fill,'"${organizationColorScheme}"'", "status" : "pending", "statustext" : "Pending …"},
       {"title" : "Last Reboot", "subtitle" : "Restart your Mac regularly — at least once a week — can help resolve many common issues", "icon" : "SF=06.circle.fill,'"${organizationColorScheme}"'", "status" : "pending", "statustext" : "Pending …"},
       {"title" : "Free Disk Space", "subtitle" : "See KB0080685 Disk Usage to help identify the 50 largest directories", "icon" : "SF=07.circle.fill,'"${organizationColorScheme}"'", "status" : "pending", "statustext" : "Pending …"},
       {"title" : "MDM Profile", "subtitle" : "The presence of the Jamf Pro MDM profile helps ensure your Mac is enrolled", "icon" : "SF=08.circle.fill,'"${organizationColorScheme}"'", "status" : "pending", "statustext" : "Pending …"},
       {"title" : "MDM Certficate Expiration", "subtitle" : "Validate the expiration date of the Jamf Pro MDM certficate", "icon" : "SF=09.circle.fill,'"${organizationColorScheme}"'", "status" : "pending", "statustext" : "Pending …"},
       {"title" : "Apple Push Notification service", "subtitle" : "Validate communication between Apple, Jamf Pro and your Mac", "icon" : "SF=10.circle.fill,'"${organizationColorScheme}"'", "status" : "pending", "statustext" : "Pending …"},
       {"title" : "Jamf Pro Check-In", "subtitle" : "Your Mac should check-in with the Jamf Pro MDM server multiple times each day", "icon" : "SF=11.circle.fill,'"${organizationColorScheme}"'", "status" : "pending", "statustext" : "Pending …"},
       {"title" : "Jamf Pro Inventory", "subtitle" : "Your Mac should submit its inventory to the Jamf Pro MDM server daily", "icon" : "SF=12.circle.fill,'"${organizationColorScheme}"'", "status" : "pending", "statustext" : "Pending …"},
       {"title" : "BeyondTrust Privilege Management", "subtitle" : "Privilege Management for Mac pairs powerful least-privilege management and application control", "icon" : "SF=13.circle.fill,'"${organizationColorScheme}"'", "status" : "pending", "statustext" : "Pending …"},
       {"title" : "Cisco Umbrella", "subtitle" : "Cisco Umbrella combines multiple security functions so you can extend data protection anywhere.", "icon" : "SF=14.circle.fill,'"${organizationColorScheme}"'", "status" : "pending", "statustext" : "Pending …"},
       {"title" : "CrowdStrike Falcon", "subtitle" : "Technology, intelligence, and expertise come together in CrowdStrike Falcon to deliver security that works.", "icon" : "SF=15.circle.fill,'"${organizationColorScheme}"'", "status" : "pending", "statustext" : "Pending …"},
       {"title" : "Palo Alto GlobalProtect", "subtitle" : "Virtual Private Network (VPN) connection to Church headquarters", "icon" : "SF=16.circle.fill,'"${organizationColorScheme}"'", "status" : "pending", "statustext" : "Pending …"},
       {"title" : "Network Quality Test", "subtitle" : "Various networking-related tests of your Mac’s Internet connection", "icon" : "SF=17.circle.fill,'"${organizationColorScheme}"'", "status" : "pending", "statustext" : "Pending …"},
       {"title" : "Computer Inventory", "subtitle" : "The listing of your Mac’s apps and settings", "icon" : "SF=18.circle.fill,'"${organizationColorScheme}"'", "status" : "pending", "statustext" : "Pending …"}
   ]
}
'
echo "${dialogJSON}" > "${dialogJSONFile}"
####################################################################################################
#
# 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 errorOut(){
   updateScriptLog "tERROR]          ${1}"
}
function error() {
   updateScriptLog "rERROR]          ${1}"
   let errorCount++
}
function warning() {
   updateScriptLog "CWARNING]        ${1}"
   let errorCount++
}
function fatal() {
   updateScriptLog "cFATAL ERROR]    ${1}"
   exit 1
}
function quitOut(){
   updateScriptLog "
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Update the running dialog
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
function dialogUpdate(){
   sleep 0.3
   echo "$1" >> "$dialogCommandFile"
}
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Run command as logged-in user (thanks, @scriptingosx!)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
function runAsUser() {
   info "Run \"$@\" as \"$loggedInUserID\" … "
   launchctl asuser "$loggedInUserID" sudo -u "$loggedInUser" "$@"
}
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Parse JSON via osascript and JavaScript
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
function get_json_value() {
   JSON="$1" osascript -l 'JavaScript' \
       -e 'const env = $.NSProcessInfo.processInfo.environment.objectForKey("JSON").js' \
       -e "JSON.parse(env).$2"
}
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Quit Script (thanks, @bartreadon!)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
function quitScript() {
   quitOut "Exiting …"
   notice "${localAdminWarning}User: ${loggedInUserFullname} (${loggedInUser}) n${loggedInUserID}] ${loggedInUserGroupMembership}; sudo Check: ${sudoStatus}; sudoers: ${sudoAllLines}; Kerberos SSOe: ${kerberosSSOeResult}; Platform SSOe: ${platformSSOeResult}; Location Services: ${locationServicesStatus}; SSH: ${sshStatus}; Microsoft OneDrive Sync Date: ${oneDriveSyncDate}; Time Machine Backup Date: ${tmStatus} ${tmLastBackup}; Battery Cycle Count: ${batteryCycleCount}; Wi-Fi: ${ssid}; ${wiFiIpAddress}; VPN IP: ${globalProtectStatus}; ${networkTimeServer}; Jamf Pro ID: ${jamfProID}; Site: ${jamfProSiteName}"
   if g -n "${overallCompliance}" ]]; then
       dialogUpdate "icon: SF=xmark.circle.fill,weight=bold,colour1=#BB1717,colour2=#F31F1F"
       dialogUpdate "title: Computer Non-compliant (as of $( date '+%Y-%m-%d-%H%M%S' ))"
       errorOut "${overallCompliance}"
       exitCode="1"
   else
       dialogUpdate "icon: SF=checkmark.circle.fill,weight=bold,colour1=#00ff44,colour2=#075c1e"
       dialogUpdate "title: Computer Compliant (as of $( date '+%Y-%m-%d-%H%M%S' ))"
   fi
   dialogUpdate "progress: 100"
   dialogUpdate "progresstext: Elapsed Time: $(printf '%dh:%dm:%ds\n' $((SECONDS/3600)) $((SECONDS%3600/60)) $((SECONDS%60)))"
   dialogUpdate "button1text: Close"
   dialogUpdate "button1: enable"
  Â
   sleep "${anticipationDuration}"
   # Progress countdown (thanks, @samg and @bartreadon!)
   dialogUpdate "progress: reset"
   while true; do
       if de ${completionTimer} -lt ${progressSteps} ]]; then
           dialogUpdate "progress: ${completionTimer}"
       fi
       dialogUpdate "progresstext: Closing automatically in ${completionTimer} seconds …"
       sleep 1
       ((completionTimer--))
       if /> ${completionTimer} -lt 0 ]]; then break; fi;
   done
   dialogUpdate "quit:"
   # Remove the dialog command file
   rm -rf "${dialogCommandFile}"
   # Remove the dialog JSON file
   rm -rf "${dialogJSONFile}"
   # Remove overlay icon
   rm -rf "${overlayicon}"
   # Remove default dialog.log
   rm -rf /var/tmp/dialog.log
   notice "Elapsed Time: $(printf '%dh:%dm:%ds\n' $((SECONDS/3600)) $((SECONDS%3600/60)) $((SECONDS%60)))"
   quitOut "Goodbye!"
   exit "${exitCode}"
}
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Kill a specified process (thanks, @grahampugh!)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
function killProcess() {
   process="$1"
   if process_pid=$( pgrep -a "${process}" 2>/dev/null ) ; then
       info "Attempting to terminate the '$process' process …"
       info "(Termination message indicates success.)"
       kill "$process_pid" 2> /dev/null
       if pgrep -a "$process" >/dev/null ; then
           error "'$process' could not be terminated."
       fi
   else
       info "The '$process' process isn’t running."
   fi
}
####################################################################################################
#
# Pre-flight Checks
#
####################################################################################################
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Client-side Logging
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
if n ! -f "${scriptLog}" ]]; then
   touch "${scriptLog}"
   if t; -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# https://snelson.us/2025/04/computer-compliance-0-0-2/\n###\n"
preFlight "Initiating …"
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Computer Information
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
preFlight "${computerName} (S/N ${serialNumber})"
preFlight "${loggedInUserFullname} (${loggedInUser}) ${loggedInUserID}]"
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# 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: Confirm jamf.log exists
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
if ># ! -f "/private/var/log/jamf.log" ]]; then
   fatal "jamf.log missing; exiting."
fi
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# 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=$(spctl -a -vv -t install "$tempDirectory/Dialog.pkg" 2>&1 | awk '/origin=/ {print $NF }' | tr -d '()')
   # Install the package if Team ID validates
   if t "$expectedDialogTeamID" == "$teamID" ]]; then
       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 "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() {
   # Check for Dialog and install if not found
   if t ! -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
}
dialogCheck
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Forcible-quit for all other running dialogs
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
preFlight "Forcible-quit for all other running dialogs …"
killProcess "Dialog"
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Complete
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
preFlight "Complete"
####################################################################################################
#
# Compliance Check Functions
#
####################################################################################################
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Compliant OS Version (thanks, @robjschroeder!)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
function checkOS() {
   notice "Checking macOS version compatibility..."
   dialogUpdate "icon: SF=pencil.and.list.clipboard,${organizationColorScheme}"
   dialogUpdate "listitem: index: ${1}, status: wait, statustext: Checking …"
   dialogUpdate "progress: increment"
   dialogUpdate "progresstext: Comparing installed OS version with compliant version …"
   sleep "${anticipationDuration}"
   if e< "${osBuild}" =~ #a-zA-Z]$ ]]; then
       logComment "OS Build, ${osBuild}, ends with a letter; skipping"
       osResult="Beta macOS ${osVersion} (${osBuild})"
       dialogUpdate "listitem: index: ${1}, status: error, statustext: ${osResult}"
       warning "${osResult}"
  Â
   else
       # logComment "OS Build, ${osBuild}, ends with a number; proceeding …"
       # N-rule variable #How many previous minor OS path versions will be marked as compliant]
       n="${previousMinorOS}"
       # URL to the online JSON data
       online_json_url="https://sofafeed.macadmins.io/v1/macos_data_feed.json"
       user_agent="SOFA-Jamf-EA-macOSVersionCheck/1.0"
       # local store
       json_cache_dir="/private/tmp/sofa"
       json_cache="$json_cache_dir/macos_data_feed.json"
       etag_cache="$json_cache_dir/macos_data_feed_etag.txt"
       # ensure local cache folder exists
       /bin/mkdir -p "$json_cache_dir"
       # check local vs online using etag
       if p -f "$etag_cache" && -f "$json_cache" ]]; then
           # logComment "e-tag stored, will download only if e-tag doesn’t match"
           etag_old=$(/bin/cat "$etag_cache")
           /usr/bin/curl --compressed --silent --etag-compare "$etag_cache" --etag-save "$etag_cache" --header "User-Agent: $user_agent" "$online_json_url" --output "$json_cache"
           etag_new=$(/bin/cat "$etag_cache")
           if S "$etag_old" == "$etag_new" ]]; then
               # logComment "Cached ETag matched online ETag - cached json file is up to date"
           else
               # logComment "Cached ETag did not match online ETag, so downloaded new SOFA json file"
           fi
       else
           # logComment "No e-tag cached, proceeding to download SOFA json file"
           /usr/bin/curl --compressed --location --max-time 3 --silent --header "User-Agent: $user_agent" "$online_json_url" --etag-save "$etag_cache" --output "$json_cache"
       fi
       # 1. Get model (DeviceID)
       model=$(sysctl -n hw.model)
       # logComment "Model Identifier: $model"
       # check that the model is virtual or is in the feed at all
       if s $model == "VirtualMac"* ]]; then
           model="Macmini9,1"
       elif ! grep -q "$model" "$json_cache"; then
           warning "Unsupported Hardware"
           # return 1
       fi
       # 2. Get current system OS
       system_version=$( /usr/bin/sw_vers -productVersion )
       system_os=$(cut -d. -f1 <<< "$system_version")
       # system_version="15.3"
       # logComment "System Version: $system_version"
       if )< $system_version == *".0" ]]; then
           system_version=${system_version%.0}
           logComment "Corrected System Version: $system_version"
       fi
       # exit if less than macOS 12
       if  "$system_os" -lt 12 ]]; then
           osResult="Unsupported macOS"
           result "$osResult"
           dialogUpdate "listitem: index: 1, status: fail, statustext: $osResult"
           # return 1
       fi
       # 3. Identify latest compatible major OS
       latest_compatible_os=$(/usr/bin/plutil -extract "Models.$model.SupportedOS.0" raw -expect string "$json_cache" | /usr/bin/head -n 1)
       # logComment "Latest Compatible macOS: $latest_compatible_os"
       # 4. Get OSVersions.Latest.ProductVersion
       latest_version_match=false
       security_update_within_30_days=false
       n_rule=false
       for i in {0..3}; do
           os_version=$(/usr/bin/plutil -extract "OSVersions.$i.OSVersion" raw "$json_cache" | /usr/bin/head -n 1)
           if  -z "$os_version" ]]; then
               break
           fi
           latest_product_version=$(/usr/bin/plutil -extract "OSVersions.$i.Latest.ProductVersion" raw "$json_cache" | /usr/bin/head -n 1)
           if  "$latest_product_version" == "$system_version" ]]; then
               latest_version_match=true
               break
           fi
           num_security_releases=$(/usr/bin/plutil -extract "OSVersions.$i.SecurityReleases" raw "$json_cache" | xargs | awk '{ print $1}' )
           if -n "$num_security_releases" ]]; then
               for ((j=0; j<num_security_releases; j++)); do
                   security_release_product_version=$(/usr/bin/plutil -extract "OSVersions.$i.SecurityReleases.$j.ProductVersion" raw "$json_cache" | /usr/bin/head -n 1)
                   if  "${system_version}" == "${security_release_product_version}" ]]; then
                       security_release_date=$(/usr/bin/plutil -extract "OSVersions.$i.SecurityReleases.$j.ReleaseDate" raw "$json_cache" | /usr/bin/head -n 1)
                       security_release_date_epoch=$(date -jf "%Y-%m-%dT%H:%M:%SZ" "$security_release_date" +%s)
                       days_ago_30=$(date -v-30d +%s)
                       if  $security_release_date_epoch -ge $days_ago_30 ]]; then
                           security_update_within_30_days=true
                       fi
                       if (( $j <= "$n" )); then
                           n_rule=true
                       fi
                   fi
               done
           fi
       done
       if [ "$latest_version_match" == true ]] ||  "$security_update_within_30_days" == true ]] ||  "$n_rule" == true ]]; then
           osResult="macOS ${osVersion} (${osBuild})"
           dialogUpdate "listitem: index: ${1}, status: success, statustext: ${osResult}"
           info "${osResult}"
       else
           osResult="macOS ${osVersion} (${osBuild})"
           dialogUpdate "listitem: index: ${1}, status: fail, statustext: ${osResult}"
           errorOut "${osResult}"
           overallCompliance+="Failed: ${1}; "
       fi
   fi
}
Â
Please go to Part 2