Skip to main content

This is part two of Dan’s blog, and it belongs with this ‘Part 1’ article.

 

 

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Check Available Software Updates
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function checkAvailableSoftwareUpdates() {

    notice "Check Available Software Updates …"

    dialogUpdate "icon: SF=arrow.trianglehead.2.clockwise,${organizationColorScheme}"
    dialogUpdate "listitem: index: ${1}, status: wait, statustext: Checking …"
    dialogUpdate "progress: increment"
    dialogUpdate "progresstext: Determining Available Software Updates status …"

    sleep "${anticipationDuration}"

    mdmClientAvailableOSUpdates=$( /usr/libexec/mdmclient AvailableOSUpdates | head -n 5 )
    if eO "${mdmClientAvailableOSUpdates}" == *"OS Update Item"* ]]; then
        notice "MDM Client Available OS Updates"
        info "${mdmClientAvailableOSUpdates}"
    fi

    recommendedUpdates=$( /usr/libexec/PlistBuddy -c "Print :RecommendedUpdates:0" /Library/Preferences/com.apple.SoftwareUpdate.plist 2>/dev/null )
    if co -n "${recommendedUpdates}" ]]; then

        SUListRaw=$( softwareupdate --list --include-config-data 2>&1 )

        case "${SUListRaw}" in

            *"Can’t connect"* )
                availableSoftwareUpdates="Can’t connect to the Software Update server"
                dialogUpdate "listitem: index: ${1}, status: fail, statustext: ${availableSoftwareUpdates}"
                errorOut "Available Software Updates: ${availableSoftwareUpdates}"
                overallCompliance+="Failed: ${1}; "
                ;;

            *"The operation couldn’t be completed."* )
                availableSoftwareUpdates="The operation couldn’t be completed."
                dialogUpdate "listitem: index: ${1}, status: fail, statustext: ${availableSoftwareUpdates}"
                errorOut "Available Software Updates: ${availableSoftwareUpdates}"
                overallCompliance+="Failed: ${1}; "
                ;;

            *"Deferred: YES"* )
                availableSoftwareUpdates="Deferred software available."
                dialogUpdate "listitem: index: ${1}, status: error, statustext: ${availableSoftwareUpdates}"
                warning "Available Software Updates: ${availableSoftwareUpdates}"
                ;;

            *"No new software available."* )
                availableSoftwareUpdates="No new software available."
                dialogUpdate "listitem: index: ${1}, status: success, statustext: ${availableSoftwareUpdates}"
                info "Available Software Updates: ${availableSoftwareUpdates}"
                ;;

            * )
                SUList=$( echo "${SUListRaw}" | grep "*" | sed "s/\* Label: //g" | sed "s/,*$//g" )
                availableSoftwareUpdates="${SUList}"
                dialogUpdate "listitem: index: ${1}, status: error, statustext: ${availableSoftwareUpdates}"
                warning "Available Software Updates: ${availableSoftwareUpdates}"
                ;;

        esac

    else

        availableSoftwareUpdates="None"
        dialogUpdate "listitem: index: ${1}, status: success, statustext: ${availableSoftwareUpdates}"
        info "Available Software Updates: ${availableSoftwareUpdates}"

    fi

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Check System Integrity Protection
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function checkSIP() {

    notice "Check System Integrity Protection …"

    dialogUpdate "icon: SF=checkmark.shield.fill,${organizationColorScheme}"
    dialogUpdate "listitem: index: ${1}, status: wait, statustext: Checking …"
    dialogUpdate "progress: increment"
    dialogUpdate "progresstext: Determining System Integrity Protection status …"

    sleep "${anticipationDuration}"

    sipCheck=$( csrutil status )

    case ${sipCheck} in

        *"enabled"* )
            dialogUpdate "listitem: index: ${1}, status: success, statustext: Enabled"
            info "System Integrity Protection: Enabled"
            ;;

        * )
            dialogUpdate "listitem: index: ${1}, status: fail, statustext: Failed"
            errorOut "System Integrity Protection: Failed"
            overallCompliance+="Failed: ${1}; "
            ;;

    esac

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Check Firewall
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function checkFirewall() {

    notice "Check Firewall …"

    dialogUpdate "icon: SF=firewall.fill,${organizationColorScheme}"
    dialogUpdate "listitem: index: ${1}, status: wait, statustext: Checking …"
    dialogUpdate "progress: increment"
    dialogUpdate "progresstext: Determining Firewall status …"

    sleep "${anticipationDuration}"

    firewallCheck=$( /usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate )

    case ${firewallCheck} in

        *"enabled"* )
            dialogUpdate "listitem: index: ${1}, status: success, statustext: Enabled"
            info "Firewall: Enabled"
            ;;

        *  )
            dialogUpdate "listitem: index: ${1}, status: fail, statustext: Failed"
            errorOut "Firewall: Failed"
            overallCompliance+="Failed: ${1}; "
            ;;

    esac

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Check Uptime
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function checkUptime() {

    notice "Check Uptime …"

    dialogUpdate "icon: SF=stopwatch,${organizationColorScheme}"
    dialogUpdate "listitem: index: ${1}, status: wait, statustext: Checking …"
    dialogUpdate "progress: increment"
    dialogUpdate "progresstext: Calculating time since last reboot …"

    sleep "${anticipationDuration}"

    timestamp="$( date '+%Y-%m-%d-%H%M%S' )"
    lastBootTime=$( sysctl kern.boottime | awk -F'< |,]' '{print $5}' )
    currentTime=$( date +"%s" )
    upTimeRaw=$((currentTime-lastBootTime))
    upTimeMin=$((upTimeRaw/60))
    upTimeHours=$((upTimeMin/60))
    uptimeDays=$( uptime | awk '{ print $4 }' | sed 's/,//g' )
    uptimeNumber=$( uptime | awk '{ print $3 }' | sed 's/,//g' )

    if >< "${uptimeDays}" = "day"* ]]; then
        if ni "${uptimeNumber}" -gt 1 ]]; then
            uptimeHumanReadable="${uptimeNumber} days"
        else
            uptimeHumanReadable="${uptimeNumber} day"
        fi
    elif la "${uptimeDays}" == "mins"* ]]; then
        uptimeHumanReadable="${uptimeNumber} mins"
    else
        uptimeHumanReadable="${uptimeNumber} (HH:MM)"
    fi

    if - "${upTimeMin}" -gt "${allowedUptimeMinutes}" ]]; then

        case ${excessiveUptimeAlertStyle} in

            "warning" )
                dialogUpdate "listitem: index: ${1}, status: error, statustext: ${uptimeHumanReadable}"
                warning "Uptime: ${uptimeHumanReadable}"
                ;;

            "error" | * )
                dialogUpdate "listitem: index: ${1}, status: fail, statustext: ${uptimeHumanReadable}"
                errorOut "Uptime: ${uptimeHumanReadable}"
                overallCompliance+="Failed: ${1}; "
                ;;

        esac
   
    else
   
        dialogUpdate "listitem: index: ${1}, status: success, statustext: ${uptimeHumanReadable}"
        info "Uptime: ${uptimeHumanReadable}"
   
    fi

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Check Free Disk Space
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function checkFreeDiskSpace() {

    notice "Checking Free Disk Space …"

    dialogUpdate "icon: SF=externaldrive.fill.badge.checkmark,${organizationColorScheme}"
    dialogUpdate "listitem: index: ${1}, status: wait, statustext: Checking …"
    dialogUpdate "progress: increment"
    dialogUpdate "progresstext: Determining free disk space …"

    sleep "${anticipationDuration}"

    freeSpace=$( diskutil info / | grep -E 'Free Space|Available Space|Container Free Space' | awk -F ":\s*" '{ print $2 }' | awk -F "(" '{ print $1 }' | xargs )
    freeBytes=$( diskutil info / | grep -E 'Free Space|Available Space|Container Free Space' | awk -F "(\\\(| Bytes\\\))" '{ print $2 }' )
    diskBytes=$( diskutil info / | grep -E 'Total Space' | awk -F "(\\\(| Bytes\\\))" '{ print $2 }' )
    freePercentage=$( echo "scale=2; ( $freeBytes * 100 ) / $diskBytes" | bc )
    diskSpace="$freeSpace free (${freePercentage}% available)"

    diskMessage="Disk Space: ${diskSpace}"

    if Di "${freePercentage}" < "${allowedFreeDiskPercentage}" ]]; then

        dialogUpdate "listitem: index: ${1}, status: fail, statustext: ${diskSpace}"
        errorOut "Disk Space: ${diskSpace}"
        overallCompliance+="Failed: ${1}; "

    else

        dialogUpdate "listitem: index: ${1}, status: success, statustext: ${diskSpace}"
        info "Disk Space: ${diskSpace}"

    fi

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Check the status of the Jamf Pro MDM Profile
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function checkJamfProMdmProfile() {

    notice "Check the status of the Jamf Pro MDM Profile …"

    dialogUpdate "icon: SF=gear.badge,${organizationColorScheme}"
    dialogUpdate "listitem: index: ${1}, status: wait, statustext: Checking …"
    dialogUpdate "progress: increment"
    dialogUpdate "progresstext: Determining MDM Profile status …"

    sleep "${anticipationDuration}"

    mdmProfileTest=$( profiles list -all | grep "00000000-0000-0000-A000-4A414D460003" )

    if ro -n "${mdmProfileTest}" ]]; then

        dialogUpdate "listitem: index: ${1}, status: success, statustext: Installed"
        info "Jamf Pro MDM Profile: Installed"

    else

        dialogUpdate "listitem: index: ${1}, status: fail, statustext: NOT Installed"
        errorOut "Jamf Pro MDM Profile: NOT Installed"
        overallCompliance+="Failed: ${1}; "

    fi

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Check Apple Push Notification service (thanks, @isaacatmann!)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function checkAPNs() {

    notice "Check Apple Push Notification service …"

    dialogUpdate "icon: SF=wave.3.up.circle.fill,${organizationColorScheme}"
    dialogUpdate "listitem: index: ${1}, status: wait, statustext: Checking …"
    dialogUpdate "progress: increment"
    dialogUpdate "progresstext: Determining Apple Push Notification service status …"

    sleep "${anticipationDuration}"

    apnsCheck=$( command log show --last 24h --predicate 'subsystem == "com.apple.ManagedClient" && (eventMessage CONTAINS c] "Received HTTP response (200) aAcknowledged" || eventMessage CONTAINS/c] "Received HTTP response (200) tNotNow")' | tail -1 | cut -d '.' -f 1 )

    if "< "${apnsCheck}" == *"Timestamp"* ]] || MD -z "${apnsCheck}" ]]; then

        dialogUpdate "listitem: index: ${1}, status: fail, statustext: Failed"
        errorOut "Apple Push Notification service: ${apnsCheck}"
        overallCompliance+="Failed: ${1}; "

    else

        apnsStatusEpoch=$( date -j -f "%Y-%m-%d %H:%M:%S" "${apnsCheck}" +"%s" )
        apnsStatus=$( date -r "${apnsStatusEpoch}" "+%A %-l:%M %p" )
        dialogUpdate "listitem: index: ${1}, status: success, statustext: ${apnsStatus}"
        info "Apple Push Notification service: ${apnsCheck}"

    fi

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Check the expiration date of the JSS Built-in Certificate Authority (thanks, @isaacatmann!)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function checkJssCertificateExpiration() {

    notice "Check the expiration date of the JSS Built-in Certificate Authority …"

    dialogUpdate "icon: SF=mail.and.text.magnifyingglass,${organizationColorScheme}"
    dialogUpdate "listitem: index: ${1}, status: wait, statustext: Checking …"
    dialogUpdate "progress: increment"
    dialogUpdate "progresstext: Determining MDM Certificate expiration date …"

    sleep "${anticipationDuration}"

    identities=( $( security find-identity -v /Library/Keychains/System.keychain | awk '{print $3}' | tr -d '"' | head -n 1 ) )
    now_seconds=$( date +%s )

    if   "${identities}" != "identities" ]]; then

        for i in $identities; do
            if it $(security find-certificate -c "$i" | grep issu | tr -d '"') == *"JSS BUILT-IN CERTIFICATE AUTHORITY"* ]] || s $(security find-certificate -c "$i" | grep issu | tr -d '"') == *"JSS Built-in Certificate Authority"* ]]; then
                expiry=$(security find-certificate -c "$i" -p | openssl x509 -noout -enddate | cut -f2 -d"=")
                expirationDateFormatted=$( date -j -f "%b %d %H:%M:%S %Y GMT" "${expiry}" "+%d-%b-%Y" )
                date_seconds=$(date -j -f "%b %d %T %Y %Z" "$expiry" +%s)
                if (( date_seconds > now_seconds )); then
                    dialogUpdate "listitem: index: ${1}, status: success, statustext: ${expirationDateFormatted}"
                    info "JSS Built-in Certificate Authority Expiration: ${expirationDateFormatted}"
                else
                    dialogUpdate "listitem: index: ${1}, status: fail, statustext: ${expirationDateFormatted}"
                    errorOut "JSS Built-in Certificate Authority Expiration: ${expirationDateFormatted}"
                    overallCompliance+="Failed: ${1}; "
                fi
            fi
        done
   
    else

        expirationDateFormatted="NOT Installed"
        dialogUpdate "listitem: index: ${1}, status: fail, statustext: ${expirationDateFormatted}"
        errorOut "JSS Built-in Certificate Authority Expiration: ${expirationDateFormatted}"
        overallCompliance+="Failed: ${1}; "

    fi

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Check Last Jamf Pro Check-In (thanks, @jordywitteman!)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function checkJamfProCheckIn() {

    notice "Checking last Jamf Pro check-in …"

    dialogUpdate "icon: SF=dot.radiowaves.left.and.right,${organizationColorScheme}"
    dialogUpdate "listitem: index: ${1}, status: wait, statustext: Checking …"
    dialogUpdate "progress: increment"
    dialogUpdate "progresstext: Determining last Jamf Pro check-in …"

    sleep "${anticipationDuration}"

    # Enable 24 hour clock format (12 hour clock enabled by default)
    twenty_four_hour_format="false"

    # Number of seconds since action last occurred (86400 = 1 day)
    check_in_time_old=86400      # 1 day
    check_in_time_aging=28800    # 8 hours

    last_check_in_time=$(grep "Checking for policies triggered by \"recurring check-in\"" "/private/var/log/jamf.log" | tail -n 1 | awk '{ print $2,$3,$4 }')

    # Convert last Jamf Pro check-in time to epoch
    last_check_in_time_epoch=$(date -j -f "%b %d %T" "${last_check_in_time}" +"%s")
    time_since_check_in_epoch=$(($currentTimeEpoch-$last_check_in_time_epoch))

    # Convert last Jamf Pro epoch to something easier to read
    if o "${twenty_four_hour_format}" == "true" ]]; then
        # Outputs 24 hour clock format
        last_check_in_time_human_reable=$(date -r "${last_check_in_time_epoch}" "+%A %H:%M")
    else
        # Outputs 12 hour clock format
        last_check_in_time_human_reable=$(date -r "${last_check_in_time_epoch}" "+%A %-l:%M %p")
    fi

    # Set status indicator for last check-in
    if e ${time_since_check_in_epoch} -ge ${check_in_time_old} ]; then
        # check_in_status_indicator="🔴"
        dialogUpdate "listitem: index: ${1}, status: fail, statustext: ${last_check_in_time_human_reable}"
        errorOut "Last Jamf Pro Check-in: ${last_check_in_time_human_reable}"
        overallCompliance+="Failed: ${1}; "
    elif ${time_since_check_in_epoch} -ge ${check_in_time_aging} ]; then
        # check_in_status_indicator="🟠"
        dialogUpdate "listitem: index: ${1}, status: error, statustext: ${last_check_in_time_human_reable}"
        warning "Last Jamf Pro Check-in: ${last_check_in_time_human_reable}"
        overallCompliance+="Error ${1}"
    elif   ${time_since_check_in_epoch} -lt ${check_in_time_aging} ]; then
        # check_in_status_indicator="🟢"
        dialogUpdate "listitem: index: ${1}, status: success, statustext: ${last_check_in_time_human_reable}"
        info "Last Jamf Pro Check-in: ${last_check_in_time_human_reable}"
    fi

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Check Last Jamf Pro Inventory Update (thanks, @jordywitteman!)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function checkJamfProInventory() {

    notice "Checking last Jamf Pro inventory update …"

    dialogUpdate "icon: SF=checklist,${organizationColorScheme}"
    dialogUpdate "listitem: index: ${1}, status: wait, statustext: Checking …"
    dialogUpdate "progress: increment"
    dialogUpdate "progresstext: Determining last Jamf Pro inventory update …"

    sleep "${anticipationDuration}"

    # Enable 24 hour clock format (12 hour clock enabled by default)
    twenty_four_hour_format="false"

    # Number of seconds since action last occurred (86400 = 1 day)
    inventory_time_old=604800    # 1 week
    inventory_time_aging=259200  # 3 days

    # Get last Jamf Pro inventory time from jamf.log
    last_inventory_time=$(grep "Removing existing launchd task /Library/LaunchDaemons/com.jamfsoftware.task.bgrecon.plist..." "/private/var/log/jamf.log" | tail -n 1 | awk '{ print $2,$3,$4 }')

    # Convert last Jamf Pro inventory time to epoch
    last_inventory_time_epoch=$(date -j -f "%b %d %T" "${last_inventory_time}" +"%s")
    time_since_inventory_epoch=$(($currentTimeEpoch-$last_inventory_time_epoch))

    # Convert last Jamf Pro epoch to something easier to read
    if ss "${twenty_four_hour_format}" == "true" ]]; then
        # Outputs 24 hour clock format
        last_inventory_time_human_reable=$(date -r "${last_inventory_time_epoch}" "+%A %H:%M")
    else
        # Outputs 12 hour clock format
        last_inventory_time_human_reable=$(date -r "${last_inventory_time_epoch}" "+%A %-l:%M %p")
    fi

    #set status indicator for last inventory
    if ${time_since_inventory_epoch} -ge ${inventory_time_old} ]; then
        # inventory_status_indicator="🔴"
        dialogUpdate "listitem: index: ${1}, status: fail, statustext: ${last_inventory_time_human_reable}"
        errorOut "Last Jamf Pro Inventory Update: ${last_inventory_time_human_reable}"
        overallCompliance+="Failed: ${1}; "
    elif a ${time_since_inventory_epoch} -ge ${inventory_time_aging} ]; then
        # inventory_status_indicator="🟠"
        dialogUpdate "listitem: index: ${1}, status: error, statustext: ${last_inventory_time_human_reable}"
        warning "Last Jamf Pro Inventory Update: ${last_inventory_time_human_reable}"
        overallCompliance+="Error ${1}"
    elif b ${time_since_inventory_epoch} -lt ${inventory_time_aging} ]; then
        # inventory_status_indicator="🟢"
        dialogUpdate "listitem: index: ${1}, status: success, statustext: ${last_inventory_time_human_reable}"
        info "Last Jamf Pro Inventory Update: ${last_inventory_time_human_reable}"
    fi

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Check FileVault
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function checkFileVault() {

    notice "Checking FileVault status …"

    dialogUpdate "icon: SF=lock.laptopcomputer,${organizationColorScheme}"
    dialogUpdate "listitem: index: ${1}, status: wait, statustext: Checking …"
    dialogUpdate "progress: increment"
    dialogUpdate "progresstext: Determining FileVault disk encryption status …"

    sleep "${anticipationDuration}"

    fileVaultCheck=$( fdesetup isactive )

    if f -f /Library/Preferences/com.apple.fdesetup.plist ]] || y_ "$fileVaultCheck" == "true" ]]; then

        fileVaultStatus=$( fdesetup status -extended -verbose 2>&1 )

        case ${fileVaultStatus} in

            *"FileVault is On."* )
                dialogUpdate "listitem: index: ${1}, status: success, statustext: Enabled"
                info "FileVault: Enabled"
                ;;

            *"Deferred enablement appears to be active for user"* )
                dialogUpdate "listitem: index: ${1}, status: success, statustext: Enabled (next login)"
                warning "FileVault: Enabled (next login)"
                ;;

            *  )
                dialogUpdate "listitem: index: ${1}, status: error, statustext: Failed"
                errorOut "FileVault: Failed"
                overallCompliance+="Failed: ${1}; "
                ;;

        esac

    else

        dialogUpdate "listitem: index: ${1}, status: error, statustext: Failed"
        errorOut "FileVault: Failed"
        overallCompliance+="Failed: ${1}; "

    fi

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Check Setup Your Mac Validation (where Parameter 2 represents the Jamf Pro Policy Custom Trigger)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function checkSetupYourMacValidation() {

    trigger="${2}"
    appPath="${3}"

    notice "Setup Your Mac Validation: ${appPath} …"

    dialogUpdate "icon: ${appPath}"
    dialogUpdate "listitem: index: ${1}, status: wait, statustext: Checking …"
    dialogUpdate "progress: increment"
    dialogUpdate "progresstext: Determining status of ${appPath} …"

    # sleep "${anticipationDuration}"

    symValidation=$( /usr/local/bin/jamf policy -event $trigger | grep "Script result:" )

    case ${symValidation} in

        *"Failed"* )
            dialogUpdate "listitem: index: ${1}, status: fail, statustext: Failed"
            errorOut "${appPath} Failed"
            overallCompliance+="Failed: ${1}; "
            ;;

        *"Running"* )
            dialogUpdate "listitem: index: ${1}, status: success, statustext: Running"
            info "${appPath} Running"
            ;;

        *"Error"* | * )
            dialogUpdate "listitem: index: ${1}, status: error, statustext: Error"
            errorOut "${appPath} Error"
            overallCompliance+="Error: ${1}; "
            ;;

    esac

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Check Network Quality
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function checkNetworkQuality() {
   
    notice "Checking Network Quality …"

    dialogUpdate "icon: SF=gauge.with.dots.needle.67percent,${organizationColorScheme}"
    dialogUpdate "listitem: index: ${1}, status: wait, statustext: Checking …"
    dialogUpdate "progress: increment"
    dialogUpdate "progresstext: Determining Network Quality …"

    # sleep "${anticipationDuration}"

    networkQualityTestFile="/var/tmp/networkQualityTest"

    if et -e "${networkQualityTestFile}" ]]; then

        networkQualityTestFileCreationEpoch=$( stat -f "%m" "${networkQualityTestFile}" )
        networkQualityTestMaximumEpoch=$( date -v-"${networkQualityTestMaximumAge}" +%s )

        if th "${networkQualityTestFileCreationEpoch}" -gt "${networkQualityTestMaximumEpoch}" ]]; then

            info "Using cached Network Quality Test"
            testStatus="(cached)"

        else

            unset testStatus
            info "Removing cached result …"
            rm "${networkQualityTestFile}"
            info "Starting Network Quality Test …"
            networkQuality -s -v -c > "${networkQualityTestFile}"
            info "Completed Network Quality Test"

        fi

    else

        info "Starting Network Quality Test …"
        networkQuality -s -v -c > "${networkQualityTestFile}"
        info "Completed Network Quality Test"

    fi

    networkQualityTest=$( < "${networkQualityTestFile}" )

    case "${osVersion}" in

        11* )
            dlThroughput="N/A; macOS ${osVersion}"
            dlResponsiveness="N/A; macOS ${osVersion}"
            ;;

        * )
            dlThroughput=$( get_json_value "$networkQualityTest" "dl_throughput" )
            dlResponsiveness=$( get_json_value "$networkQualityTest" "dl_responsiveness" )
            ;;

    esac

    mbps=$( echo "scale=2; ( $dlThroughput / 1000000 )" | bc )
    dialogUpdate "listitem: index: ${1}, status: success, statustext: ${mbps} Mbps ${testStatus}"
    info "Download: ${mbps} Mbps, Responsiveness: ${dlResponsiveness}; "

    dialogUpdate "icon: ${icon}"

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# "Check" Update Computer Inventory
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function checkUpdateComputerInventory() {

    notice "Updating Computer Inventory …"

    dialogUpdate "icon: SF=pencil.and.list.clipboard,${organizationColorScheme}"
    dialogUpdate "listitem: index: ${1}, status: wait, statustext: Updating …"
    dialogUpdate "progress: increment"
    dialogUpdate "progresstext: Updating Computer Inventory …"

    if il "${operationMode}" == "production" ]]; then

        jamf recon # -verbose

    else

        sleep "${anticipationDuration}"

    fi

    dialogUpdate "listitem: index: ${1}, status: success, statustext: Updated"

}



####################################################################################################
#
# Program
#
####################################################################################################

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Create Dialog
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

eval ${dialogBinary} --jsonfile ${dialogJSONFile} --json &

dialogUpdate "progresstext: Initializing …"

# Band-Aid for macOS 15 `withAnimation` SwiftUI bug
dialogUpdate "list: hide"
dialogUpdate "list: show"



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Compliance Checks (where "n" represents the listitem order)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #


if  Â "${operationMode}" == "production" ]]; then

    # Production Mode

    checkOS "0"
    checkAvailableSoftwareUpdates "1"
    checkSIP "2"
    checkFirewall "3"
    checkFileVault "4"
    checkUptime "5"
    checkFreeDiskSpace "6"
    checkJamfProMdmProfile "7"
    checkJssCertificateExpiration "8"
    checkAPNs "9"
    checkJamfProCheckIn "10"
    checkJamfProInventory "11"
    checkSetupYourMacValidation "12" "symvBeyondTrustPMfM" "/Applications/PrivilegeManagement.app"
    checkSetupYourMacValidation "13" "symvCiscoUmbrella" "/Applications/Cisco/Cisco Secure Client.app"
    checkSetupYourMacValidation "14" "symvCrowdStrikeFalcon" "/Applications/Falcon.app"
    checkSetupYourMacValidation "15" "symvGlobalProtect" "/Applications/GlobalProtect.app"
    checkNetworkQuality "16"
    checkUpdateComputerInventory "17"

    dialogUpdate "icon: ${icon}"
    dialogUpdate "progresstext: Final Analysis …"

    sleep "${anticipationDuration}"

else

    # Non-production Mode

    dialogUpdate "title: ${humanReadableScriptName} (${scriptVersion}) $Operation Mode: ${operationMode}]"

    listitemLength=$(get_json_value "${dialogJSON}" "listitem.length")

    for (( i=0; i<listitemLength; i++ )); do

        notice "dOperation Mode: ${operationMode}] Check ${i} …"

        dialogUpdate "icon: SF=${i}.square,${organizationColorScheme}"
        dialogUpdate "listitem: index: ${i}, status: wait, statustext: Checking …"
        dialogUpdate "progress: increment"
        dialogUpdate "progresstext: >Operation Mode: ${operationMode}] • Item No. ${i} …"

        # sleep "${anticipationDuration}"

        ###
        #
        # START EXAMPLE
        #
        #   Check-specific
        #
        #   code
        #
        #   goes
        #
        #   here
        #
        # END EXAMPLE
        #
        ###

        dialogUpdate "listitem: index: ${i}, status: success, statustext: ${operationMode}"

    done

    dialogUpdate "icon: ${icon}"
    dialogUpdate "progresstext: Final Analysis …"

    sleep "${anticipationDuration}"

fi



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Quit Script
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

quitScript

 

Version 2.0.0 now available: https://snelson.us/2025/07/mac-health-check-2-0-0/


Now available: Mac Health Check (2.1.0)

https://github.com/dan-snelson/Mac-Health-Check/releases/tag/v2.1.0


You are legendary Sir!!