Menu Close

“Negative Trust” Jamf Pro Inventory Health Check

Leverage a client-side LaunchDaemon, script and .plist trio to determine computer health, based on the Mac’s ability to execute an inventory update policy

Background

In the spring of 2022, I renewed my Utah’s driver license and noted it wouldn’t expire for six years. When I obtained my Ohio’s driver license last Halloween, I was tickled with the option for an eight-year expiration: “Yes, please!”

When I enrolled a Mac in our Dev lane yesterday, I was also pleased that its Jamf Pro-related certificates won’t expire for more than three years. (Although, by the time you’re reading this, that box has probably already been nuked-and-paved. Thrice.)

If we base a Mac’s compliance solely on the presence of valid MDM certificates, we’re probably allowing too many computers access to sensitive data

However, if at next week’s traffic stop the police officer simply confirmed I had a valid driver’s license and sent me on my way with a warning to “slow down” — never double-checking what I’ve actually been up to using the computer in the police cruiser — I could continue not worrying about all those unpaid parking tickets.

Similarly, just because a Mac has valid MDM certificates doesn’t guarantee its enrollment is healthy.

Overview

The Jamf Pro Health Check script executes on the following approach:

  1. Creates a client-side LaunchDaemon and script pair which marks the Mac as unhealthy each morning shortly after midnight (local time) and immediately after each restart (i.e., negative trust).
  2. Adding this script to your recurring Jamf Pro inventory update policy will then mark the Mac as healthy when the policy executes successfully; end-users can also self-remediate by logging into Self Service and manually running your modified “update computer inventory” policy.
  3. You can then leverage a vendor’s ability to read client-side .plist values to determine if the Mac is healthy or unhealthy (based on the Mac’s ability to successfully execute the assigned Jamf Pro inventory update policies).

Assumptions

While the project does include a Jamf Pro Computer Extension Attribute which can be used with Smart Groups, this approach presumes you’ll be leveraging something similar to Palo Alto Networks GlobalProtect HIP-Based Policy Enforcement to read the value of ${key} from ${plistFilepath}.

Implementation

A. Customize and add the Jamf Pro Health Check script to your Jamf Pro server
  1. Review and adjust the Organization Variables as required
    • Reverse Domain Name Notation (i.e., com.company.division): reverseDomainNameNotation
    • Script Human-readable Name: humanReadableScriptName
    • Organization’s Directory (i.e., where your client-side scripts reside; must previously exist): organizationDirectory
    • Organization’s Script Name: organizationScriptName
    • Property List “Key” for which the value will be set: key
    • Property List “Healthy Value”: healthyValue
    • Property List “Unhealthy Value”: unhealthyValue
    • Vendor’s Directory (validated to ensure vendor’s software is installed; must previously exist): vendorDirectory
  2. Modify the variables in the Jamf-Pro-Health-Check-EA.zsh to match your organizational customizations made in Step No. 1.
  3. Add your modified Jamf Pro Health Check script and its Extension Attribute to your Jamf Pro server
  4. Specify the following for Settings > Computer Management > Scripts > Options > Parameter Labels
    • Parameter 4: Script Log Location (i.e., Your organization's default location for client-side logs)
    • Parameter 5: Configuration Files to Reset (i.e., None (blank) | All | LaunchDaemon | Script | Uninstall )
  5. Click Save
Jamf Pro Script Parameter Labels
Jamf Pro Script Parameter Labels
Jamf-Pro-Health-Check.zsh

Latest version available on GitHub.

#!/bin/zsh --no-rcs
# shellcheck shell=bash
# shellcheck disable=SC2001

####################################################################################################
#
# Jamf Pro Health Check
# https://snelson.us/jphc
#
# Overview:
#
#   1.  This script creates a client-side LaunchDaemon which marks the Mac as "unhealthy"
#       each morning shortly after midnight.
#
#   2.  Adding this script to your Jamf Pro daily inventory update policy will mark the Mac
#       as "healthy" each time the policy is executed successfully.
#
#   3.  Leverage a vendor's ability to read client-side `.plist` values to determine if the Mac is
#       "healthy" or "unhealthy", based on the Mac's ability to update its inventory with the
#       Jamf Pro server.
#
####################################################################################################
#
# HISTORY
#
#   Version 0.0.1, 25-Jan-2024, Dan K. Snelson (@dan-snelson)
#   - Original version, with code and inspiration from:
#       - [robjschroeder](https://github.com/robjschroeder)
#       - [bigmacadmin](https://github.com/bigmacadmin)
#       - [drtaru](https://github.com/drtaru)
#
#   Version 0.0.2, 26-Jan-2024, Dan K. Snelson (@dan-snelson)
#   - LaunchDaemon modifications
#
#   Version 0.0.3, 26-Jan-2024, Dan K. Snelson (@dan-snelson)
#   - LaunchDaemon modifications
#   - Logging modifications
#
#   Version 0.0.4, 26-Jan-2024, Dan K. Snelson (@dan-snelson)
#   - Conditional LaunchDaemon unloading (to avoid "Boot-out failed: 5: Input/output error")
#
#   Version 0.0.5, 27-Jan-2024, Dan K. Snelson (@dan-snelson)
#   - Added `resetConfiguration` options: None (blank) | All | LaunchDaemon | Script | Uninstall
#   - Added logging to "unhealthy" script
#
#   Version 0.0.6, 27-Jan-2024, Dan K. Snelson (@dan-snelson)
#   - Corrected LaunchDaemon loading
#
#   Version 0.0.7, 19-Mar-2024, Dan K. Snelson (@dan-snelson)
#       Added `--no-rcs` to the `#!/bin/zsh` shebang
#
####################################################################################################



####################################################################################################
#
# Global Variables
#
####################################################################################################

export PATH=/usr/bin:/bin:/usr/sbin:/sbin

# Script Version
scriptVersion="0.0.7"



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Jamf Pro Script Parameters
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

# Parameter 4: Script Log (i.e., Your organization's default location for client-side logs)
scriptLog="${4:-"/var/log/org.churchofjesuschrist.log"}"

# Parameter 5: Configuration Files to Reset (i.e., None (blank) | All | LaunchDaemon | Script | Uninstall)
resetConfiguration="${5:-""}"



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Organization Variables
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

# Organization's Reverse Domain Name Notation (i.e., com.company.division)
reverseDomainNameNotation="org.churchofjesuschrist"

# Script Human-readable Name
humanReadableScriptName="Jamf Pro Health Check"

# Organization's Directory (i.e., where your client-side scripts reside; must previously exist)
organizationDirectory="/path/to/your/client-side/scripts/"

# Organization's Script Name
organizationScriptName="jphc"

# LaunchDaemon Name & Path
launchDaemonName="${reverseDomainNameNotation}.${organizationScriptName}.plist"
launchDaemonPath="/Library/LaunchDaemons/${launchDaemonName}"

# Property List File
plistFilepath="/Library/Preferences/${reverseDomainNameNotation}.${organizationScriptName}.plist"

# Property List "Key" for which the value will be set
key="${humanReadableScriptName}"

# Property List "Healthy Value"
healthyValue="true"

# Property List "Unhealthy Value"
unhealthyValue="false"

# Vendor's Directory (validated to ensure vendor's software is installed; must previously exist)
vendorDirectory="/Library/Application Support/PaloAltoNetworks/GlobalProtect/"



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Computer Variables
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

computerName=$( scutil --get ComputerName )
serialNumber=$( ioreg -rd1 -c IOPlatformExpertDevice | awk -F'"' '/IOPlatformSerialNumber/{print $4}' )
modelName=$( /usr/libexec/PlistBuddy -c 'Print :0:_items:0:machine_name' /dev/stdin <<< "$(system_profiler -xml SPHardwareDataType)" )



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Operating System Variables
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

osVersion=$( sw_vers -productVersion )
osVersionExtra=$( sw_vers -productVersionExtra ) 
osBuild=$( sw_vers -buildVersion )
osMajorVersion=$( echo "${osVersion}" | awk -F '.' '{print $1}' )

# Report RSR sub-version if applicable
if [[ -n $osVersionExtra ]] && [[ "${osMajorVersion}" -ge 13 ]]; then osVersion="${osVersion} ${osVersionExtra}"; fi



####################################################################################################
#
# 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 debugVerbose() {
    if [[ "$debugMode" == "verbose" ]]; then
        updateScriptLog "[DEBUG VERBOSE]   ${1}"
    fi
}

function debug() {
    if [[ "$debugMode" == "true" ]]; 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}"
}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Reset Configuration
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function resetConfiguration() {

    notice "Reset Configuration: ${1}"

    case ${1} in

        "All" )

            info "Reset All Configuration Files … "

            # Reset LaunchDaemon
            info "Reset LaunchDaemon … "
            launchDaemonStatus
            if [[ -n "${launchDaemonStatus}" ]]; then
                logComment "Unload '${launchDaemonPath}' … "
                launchctl bootout system "${launchDaemonPath}"
                launchDaemonStatus
            fi
            logComment "Removing '${launchDaemonPath}' … "
            rm -f "${launchDaemonPath}" 2>&1
            logComment "Removed '${launchDaemonPath}'"

            # Reset Script
            info "Reset Script … "
            logComment "Removing '${organizationDirectory}/${organizationScriptName}-unhealthy.zsh' … "
            rm -f "${organizationDirectory}/${organizationScriptName}-unhealthy.zsh"
            logComment "Removed '${organizationDirectory}/${organizationScriptName}-unhealthy.zsh' "
            ;;

        "LaunchDaemon" )

            info "Reset LaunchDaemon … "
            launchDaemonStatus
            if [[ -n "${launchDaemonStatus}" ]]; then
                logComment "Unload '${launchDaemonPath}' … "
                launchctl bootout system "${launchDaemonPath}"
                launchDaemonStatus
            fi
            logComment "Removing '${launchDaemonPath}' … "
            rm -f "${launchDaemonPath}" 2>&1
            logComment "Removed '${launchDaemonPath}'"
            ;;

        "Script" )

            info "Reset Script … "
            logComment "Removing '${organizationDirectory}/${organizationScriptName}-unhealthy.zsh' … "
            rm -f "${organizationDirectory}/${organizationScriptName}-unhealthy.zsh"
            logComment "Removed '${organizationDirectory}/${organizationScriptName}-unhealthy.zsh' "
            ;;

        "Uninstall" )

            warning "*** UNINSTALLING ${humanReadableScriptName} ***"

            # Uninstall LaunchDaemon
            info "Uninstall LaunchDaemon … "
            launchDaemonStatus
            if [[ -n "${launchDaemonStatus}" ]]; then
                logComment "Unload '${launchDaemonPath}' … "
                launchctl bootout system "${launchDaemonPath}"
                launchDaemonStatus
            fi
            logComment "Removing '${launchDaemonPath}' … "
            rm -f "${launchDaemonPath}" 2>&1
            logComment "Removed '${launchDaemonPath}'"

            # Uninstall Script
            info "Uninstall Script … "
            logComment "Removing '${organizationDirectory}/${organizationScriptName}-unhealthy.zsh' … "
            rm -f "${organizationDirectory}/${organizationScriptName}-unhealthy.zsh"
            logComment "Removed '${organizationDirectory}/${organizationScriptName}-unhealthy.zsh' "

            # Uninstall .plist
            info "Uninstall .plist … "
            logComment "Removing '${plistFilepath}' … "
            rm -f "${plistFilepath}"
            logComment "Removed '${plistFilepath}'"

            # Exit
            logComment "Uninstalled all ${humanReadableScriptName} configuration files"
            notice "Thanks for using ${humanReadableScriptName}!"
            exit 0
            ;;
            
        * )

            warning "None of the expected reset options was entered; don't reset anything"
            ;;

    esac

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Write "Healthy" Plist Value
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function writeHealthyPlistValue() {

    info "Write Healthy Plist Value: \"${key}\" \"${healthyValue}\" "
    /usr/bin/defaults write "${plistFilepath}" "${key}" -string "${healthyValue}"

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Write "Unhealthy" Plist Value
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function writeUnhealthyPlistValue() {

    info "Write Healthy Plist Value: '${key}' '${unhealthyValue}'"
    defaults write "${plistFilepath}" "${key}" -string "${unhealthyValue}"

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Read Plist Value
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function readPlistValue() {

    info "Read Plist Value: '${key}'"
    writtenValue=$( defaults read "${plistFilepath}" "${key}" 2>&1 )
    logComment "'${key}' is set to '${writtenValue}'"

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Create "Unhealthy" Script
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function createUnhealthyScript() {

    info "Create 'Unhealthy' Script: ${organizationDirectory}/${organizationScriptName}-unhealthy.zsh"

    # The following creates a script that writes the "unhealthy" value to the client-side .plist. 
    # (Note: Leave a full return at the end of the content before the last "ENDOFUNHEALTHYSCRIPT" line.)

(
cat <<ENDOFUNHEALTHYSCRIPT
#!/bin/zsh 

####################################################################################################
#
# Jamf Pro Health Check: Write Unhealthy Value
# https://snelson.us/jphc
#
####################################################################################################



####################################################################################################
#
# Global Variables
#
####################################################################################################

export PATH=/usr/bin:/bin:/usr/sbin:/sbin




####################################################################################################
#
# Functions
#
####################################################################################################

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Client-side Logging
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function updateScriptLog() {
    echo "${organizationScriptName}-unhealthy ($scriptVersion): \$( date +%Y-%m-%d\ %H:%M:%S ) - \${1}"
}


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

updateScriptLog "\n\n***\n* Jamf Pro Health Check: Write Unhealthy Value\n***\n"

updateScriptLog "Current Status"
defaults read "${plistFilepath}" "${key}"

updateScriptLog "Change Status"
defaults write "${plistFilepath}" "${key}" -string "${unhealthyValue}"

updateScriptLog "Updated Status"
defaults read "${plistFilepath}" "${key}"

exit 0

ENDOFUNHEALTHYSCRIPT
) > "${organizationDirectory}/${organizationScriptName}-unhealthy.zsh"

    logComment "Unhealthy script created"
    logComment "Setting permissions …"

    chmod 755 "${organizationDirectory}/${organizationScriptName}-unhealthy.zsh"
    chown root:wheel "${organizationDirectory}/${organizationScriptName}-unhealthy.zsh"

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Create LaunchDaemon
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function createLaunchDaemon() {

    info "Create LaunchDaemon"

    # The following creates the LaunchDaemon file which executes the "unhealthy" script
    # (Note: Leave a full return at the end of the content before the last "ENDOFLAUNCHDAEMON" line.)

logComment "Creating '${launchDaemonPath}' …"

(
cat <<ENDOFLAUNCHDAEMON
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>${launchDaemonName}</string>
    <key>UserName</key>
    <string>root</string>
    <key>ProgramArguments</key>
    <array>
        <string>/bin/zsh</string>
        <string>${organizationDirectory}/${organizationScriptName}-unhealthy.zsh</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>StartCalendarInterval</key>
    <dict>
        <key>Hour</key>
        <integer>0</integer>
        <key>Minute</key>
        <integer>1</integer>
    </dict>
    <key>StandardErrorPath</key>
    <string>${scriptLog}</string>
    <key>StandardOutPath</key>
    <string>${scriptLog}</string>
</dict>
</plist>

ENDOFLAUNCHDAEMON
)  > "${launchDaemonPath}"

    logComment "Setting permissions for '${launchDaemonPath}' …"
    chmod 644 "${launchDaemonPath}"
    chown root:wheel "${launchDaemonPath}"

    logComment "Loading '${launchDaemonName}' …"
    launchctl bootstrap system "${launchDaemonPath}"
    launchctl start "${launchDaemonPath}"

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# LaunchDaemon Status
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function launchDaemonStatus() {

    info "LaunchDaemon Status"
    
    launchDaemonStatus=$( launchctl list | grep "${launchDaemonName}" )

    if [[ -n "${launchDaemonStatus}" ]]; then
        logComment "${launchDaemonStatus}"
    else
        logComment "${launchDaemonName} is NOT loaded"
    fi

}



####################################################################################################
#
# 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# https://snelson.us/jphc\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 Organization Directory
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

if [[ -d "${organizationDirectory}" ]]; then
    preFlight "Specified Organization Directory of '${organizationDirectory}' exists; proceeding …"
else
    fatal "The specified Organization Directory of '${organizationDirectory}' is NOT found; exiting."
fi



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Validate Vendor Directory
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

if [[ -d "${vendorDirectory}" ]]; then
    preFlight "Specified Vendor Directory of '${vendorDirectory}' exists; proceeding …"
else
    fatal "The specified Vendor Directory of '${vendorDirectory}' is NOT found; exiting."
fi



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Complete
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

preFlight "Complete!"



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

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Reset Configuration
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

resetConfiguration "${resetConfiguration}"



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Script Validation / Creation
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

notice "*** VALIDATING SCRIPT ***"

if [[ -f "${organizationDirectory}/${organizationScriptName}-unhealthy.zsh" ]]; then

    logComment "Unhealthy script '"${organizationDirectory}/${organizationScriptName}-unhealthy.zsh"' exists"

else

    createUnhealthyScript

fi



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# LaunchDaemon Validation / Creation
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

notice "*** VALIDATING LAUNCHDAEMON ***"

logComment "Checking for LaunchDaemon '${launchDaemonPath}' …"

if [[ -f "${launchDaemonPath}" ]]; then

    logComment "LaunchDaemon '${launchDaemonPath}' exists"

    launchDaemonStatus

    if [[ -n "${launchDaemonStatus}" ]]; then

        logComment "${launchDaemonName} IS loaded"

    else

        logComment "Loading '${launchDaemonName}' …"
        launchctl bootstrap system "${launchDaemonPath}"
        launchctl start "${launchDaemonPath}"
        launchDaemonStatus

    fi

else

    createLaunchDaemon

fi



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Status Checks
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

notice "*** STATUS CHECKS ***"

logComment "I/O pause …"
sleep 1.3

launchDaemonStatus

writeHealthyPlistValue

readPlistValue



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Exit
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

notice "*** Thank you! ***"

exit 0
B. Modify your Inventory Updating policies

Modify your inventory updating policies to include the Jamf Pro Health Check script.

Modify your inventory updating policies to include the Jamf Pro Health Check script
Modify your inventory updating policies to include the Jamf Pro Health Check script
C. Testing Jamf Pro Health Check

During our initial testing, we only executed the Jamf Pro Health Check script once on computers used by individuals who opted-in to our internal Beta Test program.

Update Inventory for Opt-in Beta Testers

Following this approach, after a day or two, all opt-in Beta Test Computer Records should be marked as unhealthy since there isn’t (yet) a Jamf Pro inventory update policy to mark the computers as healthy.

If you can’t wait that long, simply restart your test Mac and review its .plist and client-side logs.

Client-side Logs

.plist

defaults read ${plistFilepath} "${key}"

Initial Run

Running script Jamf Pro Health Check (0.0.6)...
Script exit code: 0
Script result: jphc (0.0.6): 2024-01-27 05:35:11 - [PRE-FLIGHT]      Specified scriptLog '/var/log/org.churchofjesuschrist.log' exists; writing log entries to it
jphc (0.0.6): 2024-01-27 05:35:11 - [PRE-FLIGHT]      

###
# Jamf Pro Health Check (0.0.6)
# https:/snelson.us/jphc
###

jphc (0.0.6): 2024-01-27 05:35:11 - [PRE-FLIGHT]      Initiating …
jphc (0.0.6): 2024-01-27 05:35:11 - [PRE-FLIGHT]      Specified Organization Directory of '/path/to/your/client-side/scripts/' exists; proceeding …
jphc (0.0.6): 2024-01-27 05:35:11 - [PRE-FLIGHT]      Specified Vendor Directory of '/Library/Application Support/PaloAltoNetworks/GlobalProtect/' exists; proceeding …
jphc (0.0.6): 2024-01-27 05:35:11 - [PRE-FLIGHT]      Complete!
jphc (0.0.6): 2024-01-27 05:35:11 - [NOTICE]          Reset Configuration: 
jphc (0.0.6): 2024-01-27 05:35:11 - [WARNING]         None of the expected reset options was entered; don't reset anything
jphc (0.0.6): 2024-01-27 05:35:11 - [NOTICE]          *** VALIDATING SCRIPT ***
jphc (0.0.6): 2024-01-27 05:35:11 - [INFO]            Create 'Unhealthy' Script: /path/to/your/client-side/scripts/jphc-unhealthy.zsh
jphc (0.0.6): 2024-01-27 05:35:11 -                   Unhealthy script created
jphc (0.0.6): 2024-01-27 05:35:11 -                   Setting permissions …
jphc (0.0.6): 2024-01-27 05:35:11 - [NOTICE]          *** VALIDATING LAUNCHDAEMON ***
jphc (0.0.6): 2024-01-27 05:35:11 -                   Checking for LaunchDaemon '/Library/LaunchDaemons/org.churchofjesuschrist.jphc.plist' …
jphc (0.0.6): 2024-01-27 05:35:11 - [INFO]            Create LaunchDaemon
jphc (0.0.6): 2024-01-27 05:35:11 -                   Creating '/Library/LaunchDaemons/org.churchofjesuschrist.jphc.plist' …
jphc (0.0.6): 2024-01-27 05:35:11 -                   Setting permissions for '/Library/LaunchDaemons/org.churchofjesuschrist.jphc.plist' …
jphc (0.0.6): 2024-01-27 05:35:11 -                   Loading 'org.churchofjesuschrist.jphc.plist' …
jphc (0.0.6): 2024-01-27 05:35:11 - [NOTICE]          *** STATUS CHECKS ***
jphc (0.0.6): 2024-01-27 05:35:11 -                   I/O pause …
jphc (0.0.6): 2024-01-27 05:35:12 - [INFO]            LaunchDaemon Status
jphc (0.0.6): 2024-01-27 05:35:12 -                   -	0	org.churchofjesuschrist.jphc.plist
jphc (0.0.6): 2024-01-27 05:35:12 - [INFO]            Write Healthy Plist Value: "Jamf Pro Health Check" "true" 
jphc (0.0.6): 2024-01-27 05:35:12 - [INFO]            Read Plist Value: 'Jamf Pro Health Check'
jphc (0.0.6): 2024-01-27 05:35:12 -                   'Jamf Pro Health Check' is set to 'true'
jphc (0.0.6): 2024-01-27 05:35:12 - [NOTICE]          *** Thank you! ***

Reset Configuration: Script

Running script Jamf Pro Health Check (0.0.6)...
Script exit code: 0
Script result: jphc (0.0.6): 2024-01-27 05:38:50 - [PRE-FLIGHT]      Specified scriptLog '/var/log/org.churchofjesuschrist.log' exists; writing log entries to it
jphc (0.0.6): 2024-01-27 05:38:50 - [PRE-FLIGHT]      

###
# Jamf Pro Health Check (0.0.6)
# https:/snelson.us/jphc
###

jphc (0.0.6): 2024-01-27 05:38:50 - [PRE-FLIGHT]      Initiating …
jphc (0.0.6): 2024-01-27 05:38:50 - [PRE-FLIGHT]      Specified Organization Directory of '/path/to/your/client-side/scripts/' exists; proceeding …
jphc (0.0.6): 2024-01-27 05:38:50 - [PRE-FLIGHT]      Specified Vendor Directory of '/Library/Application Support/PaloAltoNetworks/GlobalProtect/' exists; proceeding …
jphc (0.0.6): 2024-01-27 05:38:50 - [PRE-FLIGHT]      Complete!
jphc (0.0.6): 2024-01-27 05:38:50 - [NOTICE]          Reset Configuration: Script
jphc (0.0.6): 2024-01-27 05:38:50 - [INFO]            Reset Script … 
jphc (0.0.6): 2024-01-27 05:38:50 -                   Removing '/path/to/your/client-side/scripts/jphc-unhealthy.zsh' … 
jphc (0.0.6): 2024-01-27 05:38:50 -                   Removed '/path/to/your/client-side/scripts/jphc-unhealthy.zsh' 
jphc (0.0.6): 2024-01-27 05:38:50 - [NOTICE]          *** VALIDATING SCRIPT ***
jphc (0.0.6): 2024-01-27 05:38:50 - [INFO]            Create 'Unhealthy' Script: /path/to/your/client-side/scripts/jphc-unhealthy.zsh
jphc (0.0.6): 2024-01-27 05:38:50 -                   Unhealthy script created
jphc (0.0.6): 2024-01-27 05:38:50 -                   Setting permissions …
jphc (0.0.6): 2024-01-27 05:38:50 - [NOTICE]          *** VALIDATING LAUNCHDAEMON ***
jphc (0.0.6): 2024-01-27 05:38:50 -                   Checking for LaunchDaemon '/Library/LaunchDaemons/org.churchofjesuschrist.jphc.plist' …
jphc (0.0.6): 2024-01-27 05:38:50 -                   LaunchDaemon '/Library/LaunchDaemons/org.churchofjesuschrist.jphc.plist' exists
jphc (0.0.6): 2024-01-27 05:38:50 - [INFO]            LaunchDaemon Status
jphc (0.0.6): 2024-01-27 05:38:50 -                   -	0	org.churchofjesuschrist.jphc.plist
jphc (0.0.6): 2024-01-27 05:38:50 -                   org.churchofjesuschrist.jphc.plist IS loaded
jphc (0.0.6): 2024-01-27 05:38:50 - [NOTICE]          *** STATUS CHECKS ***
jphc (0.0.6): 2024-01-27 05:38:50 -                   I/O pause …
jphc (0.0.6): 2024-01-27 05:38:52 - [INFO]            LaunchDaemon Status
jphc (0.0.6): 2024-01-27 05:38:52 -                   -	0	org.churchofjesuschrist.jphc.plist
jphc (0.0.6): 2024-01-27 05:38:52 - [INFO]            Write Healthy Plist Value: "Jamf Pro Health Check" "true" 
jphc (0.0.6): 2024-01-27 05:38:52 - [INFO]            Read Plist Value: 'Jamf Pro Health Check'
jphc (0.0.6): 2024-01-27 05:38:52 -                   'Jamf Pro Health Check' is set to 'true'
jphc (0.0.6): 2024-01-27 05:38:52 - [NOTICE]          *** Thank you! ***

Reset Configuration: Uninstall

Running script Jamf Pro Health Check (0.0.6)...
Script exit code: 0
Script result: jphc (0.0.6): 2024-01-27 05:31:15 - [PRE-FLIGHT]      Specified scriptLog '/var/log/org.churchofjesuschrist.log' exists; writing log entries to it
jphc (0.0.6): 2024-01-27 05:31:15 - [PRE-FLIGHT]      

###
# Jamf Pro Health Check (0.0.6)
# https:/snelson.us/jphc
###

jphc (0.0.6): 2024-01-27 05:31:15 - [PRE-FLIGHT]      Initiating …
jphc (0.0.6): 2024-01-27 05:31:15 - [PRE-FLIGHT]      Specified Organization Directory of '/path/to/your/client-side/scripts/' exists; proceeding …
jphc (0.0.6): 2024-01-27 05:31:15 - [PRE-FLIGHT]      Specified Vendor Directory of '/Library/Application Support/PaloAltoNetworks/GlobalProtect/' exists; proceeding …
jphc (0.0.6): 2024-01-27 05:31:15 - [PRE-FLIGHT]      Complete!
jphc (0.0.6): 2024-01-27 05:31:15 - [NOTICE]          Reset Configuration: Uninstall
jphc (0.0.6): 2024-01-27 05:31:15 - [WARNING]         *** UNINSTALLING Jamf Pro Health Check ***
jphc (0.0.6): 2024-01-27 05:31:15 - [INFO]            Uninstall LaunchDaemon … 
jphc (0.0.6): 2024-01-27 05:31:15 - [INFO]            LaunchDaemon Status
jphc (0.0.6): 2024-01-27 05:31:15 -                   -	0	org.churchofjesuschrist.jphc.plist
jphc (0.0.6): 2024-01-27 05:31:15 -                   Unload '/Library/LaunchDaemons/org.churchofjesuschrist.jphc.plist' … 
jphc (0.0.6): 2024-01-27 05:31:15 - [INFO]            LaunchDaemon Status
jphc (0.0.6): 2024-01-27 05:31:15 -                   org.churchofjesuschrist.jphc.plist is NOT loaded
jphc (0.0.6): 2024-01-27 05:31:15 -                   Removing '/Library/LaunchDaemons/org.churchofjesuschrist.jphc.plist' … 
jphc (0.0.6): 2024-01-27 05:31:15 -                   Removed '/Library/LaunchDaemons/org.churchofjesuschrist.jphc.plist'
jphc (0.0.6): 2024-01-27 05:31:15 - [INFO]            Uninstall Script … 
jphc (0.0.6): 2024-01-27 05:31:15 -                   Removing '/path/to/your/client-side/scripts/jphc-unhealthy.zsh' … 
jphc (0.0.6): 2024-01-27 05:31:15 -                   Removed '/path/to/your/client-side/scripts/jphc-unhealthy.zsh' 
jphc (0.0.6): 2024-01-27 05:31:15 - [INFO]            Uninstall .plist … 
jphc (0.0.6): 2024-01-27 05:31:15 -                   Removing '/Library/Preferences/org.churchofjesuschrist.jphc.plist' … 
jphc (0.0.6): 2024-01-27 05:31:15 -                   Removed '/Library/Preferences/org.churchofjesuschrist.jphc.plist'
jphc (0.0.6): 2024-01-27 05:31:15 -                   Uninstalled all Jamf Pro Health Check configuration files
jphc (0.0.6): 2024-01-27 05:31:15 - [NOTICE]          Thanks for using Jamf Pro Health Check!

When you’ve validated all is working as expected, update your inventory updating policies as detailed in B. Modify your Inventory Updating policies.

Posted in Jamf Pro, Scripts, SecOps, Tips & Tricks

Related Posts