Enroll via BigFix / Command Line

New Contributor III

Similar questions have been asked, but I can't seem to find an answer.

We run a few different management systems, some of which can perform duplicate functions, but usually don't. In this case, we want one of those systems (BigFix) to verify a system is enrolled in Jamf, and vice-versa. We have the Jamf-verifying-BigFix-registration done, but still need to get BigFix to enroll a system in Jamf if it isn't already.

BigFix can install packages and run scripts, but it runs in the background with no user interaction. Since this is going to be running frequently on systems that are remote, going to each one (or connecting to each one) isn't feasible.

What I'm looking for is a script that will check for the existence of the Jamf files (this part's pretty easy), download the QuickAdd.pkg from our Jamf server, and install it (this part's also pretty easy). It can't ask for a username or password though, or ask for any unique information (like an enrollment ID), unless it retrieves one dynamically.


Valued Contributor II

@omatsei maybe some of the work @rtrouton did for Caspercheck might help you?

New Contributor III

Ah, that was the part I was missing... the QuickAdd created from Recon can enroll unlimited machines. I can just push that out in BigFix then. Thanks!

Valued Contributor

Any chance you can share the other side of this, @omatsei? I'd love a better way of making sure our Macs have BigFix installed - been a pain keeping up with it here.

New Contributor III

Absolutely. I'm testing it now... I'll post the whole workflow as soon as I'm reasonably sure it works.

New Contributor III

Here's what I've got...

Step 1. Create a QuickAdd.pkg using Recon. I entered my username and password, and accepted the defaults.

Step 2. Compress the QuickAdd.pkg to become QuickAdd.pkg.zip.

Step 3. Put the QuickAdd.pkg.zip onto a file server. The Jamf Suite is installed on a Windows server with IIS. Our server runs exclusively on HTTPS (I can't remember if Jamf even works on HTTP), so HTTP was open for use. The only thing existing on it was an index.html file that redirected incoming traffic to https://casperserver.url:8443. So I just put it at the base of the HTTP webroot, so the URL to the file is http://casperserver.url/QuickAdd.pkg.zip.

Step 4. Create a new fixlet in BigFix.

Step 5. Add the following lines at the beginning, so it creates the script as a file on the client system:

// Delete file, in case it exists delete createfile createfile until __EOF

Step 6. Paste the newest version of the Casper Check script and edit it, changing the Casper URL and the QuickAdd URL. Also since the single open bracket is a protected character in BigFix, change ALL the { to {{ (single open bracket to double open bracket). (I also had to change the quotation marks around the server name, since they were a different font.)

Step 7. Add the following lines at the end of the fixlet:

EOF wait chmod 555 "{(client folder of current site as string) & "/__createfile"}" delete "/tmp/caspercheck.sh" move "{(client folder of current site as string) & "/__createfile"}" "/tmp/caspercheck.sh" run /bin/sh -c "trap '' 15; /tmp/caspercheck.sh 2>&1 > /var/log/caspercheck.log"

Step 8. "Take action" in BigFix, pointed to the system(s) that you'd like to enroll.

Please note that I've tested this on one system, for less than an hour... so it's definitely not what I'd call production-ready, but it worked on my test system.

Valued Contributor

Thanks - if you get a chance, what I'm looking for is the opposite - using Casper to make sure BigFix is installed! ;). We've got a few machines that just seem to drop back out of BigFix regularly, would love to clean that up and be able to do some basic reporting on it from Casper.

New Contributor III

Just in case you want the full fixlet contents, here it is:

// Delete file, in case it exists
delete __createfile
createfile until __EOF__


# User-editable variables

# For the fileURL variable, put the complete address 
# of the zipped Casper QuickAdd installer package


# For the jss_server_address variable, put the complete 
# fully qualified domain name address of your Casper server


# For the jss_server_address variable, put the port number 
# of your Casper server. This is usually 8443; change as
# appropriate.


# For the log_location variable, put the preferred 
# location of the log file for this script. If you 
# don't have a preference, using the default setting
# should be fine.


# The variables below this line should not need to be edited.
# Use caution if doing so. 


# Begin function section
# =======================

# Function to provide custom curl options
myCurl () {{ /usr/bin/curl -k -L --retry 3 --silent --show-error "$@"; }

# Function to provide logging of the script's actions to
# the log file defined by the log_location variable


    DATE=`date +%Y-%m-%d %H:%M:%S`

    echo "$DATE" " $1" >> $LOG


# Determine if the network is up by looking for any non-loopback network interfaces.

    local test

    if [[ -z "${{NETWORKUP:=}" ]]; then
        test=$(ifconfig -a inet 2>/dev/null | sed -n -e '/' -e '/' -e '/inet/p' | wc -l)
        if [[ "${{test}" -gt 0 ]]; then

CheckSiteNetwork (){

  #  CheckSiteNetwork function adapted from Facebook's check_corp function script.
  #  check_corp script available on Facebook's IT-CPE Github repo:
  # check_corp:
  #   This script verifies a system is on the corporate network.
  #   Input: CORP_URL= set this to a hostname on your corp network
  #   Optional ($1) contains a parameter that is used for testing.
  #   Output: Returns a check_corp variable that will return "True" if on 
  #   corp network, "False" otherwise.
  #   If a parameter is passed ($1), the check_corp variable will return it
  #   This is useful for testing scripts where you want to force check_corp
  #   to be either "True" or "False"
  # USAGE: 
  #   check_corp        # No parameter passed
  #   check_corp "True"  # Parameter of "True" is passed and returned

  ping=`host -W .5 $jss_server_address`

  # If the ping fails - site_network="False"
  [[ $? -eq 0 ]] && site_network="True"

  # Check if we are using a test
  [[ -n "$1" ]] && site_network="$1"

# The update_quickadd function checks the timestamp of the fileURL variable and compares it against a locally
# cached timestamp. If the hosted file's timestamp is newer, then the Casper 
# QuickAdd installer gets downloaded and extracted into the target directory.
# This function uses the myCurl function defined at the top of the script.

update_quickadd () {{

    # Create the destination directory if needed

    if [[ ! -d "$quickadd_dir" ]]; then
        mkdir "$quickadd_dir"

    # If needed, remove existing files from the destination directory

    if [[ -d "$quickadd_dir" ]]; then
        /bin/rm -rf "$quickadd_dir"/*

    # Get modification date of fileURL

    modDate=$(myCurl --head $fileURL 2>/dev/null | awk -F': ' '/Last-Modified/{{print $2}')

    # Downloading Casper agent installer

    ScriptLogging "Downloading Casper agent installer from server."

    myCurl --output "$quickadd_zip" $fileURL

    # Check to make sure download occurred

    if [[ ! -f "$quickadd_zip" ]]; then
        ScriptLogging "$quickadd_zip not found. Exiting CasperCheck."
        ScriptLogging "======== CasperCheck Finished ========"
        exit 0

    # Verify that the downloaded zip file is a valid zip archive.

    zipfile_chk=`/usr/bin/unzip -tq $quickadd_zip > /dev/null; echo $?`

    if [ "$zipfile_chk" -eq 0 ]; then
       ScriptLogging "Downloaded zip file appears to be a valid zip archive. Proceeding."
       ScriptLogging "Downloaded zip file appears to be corrupted. Exiting CasperCheck."
       ScriptLogging "======== CasperCheck Finished ========"
       rm "$quickadd_zip"
       exit 0

    # Unzip the Casper agent install into the destination directory
    # and remove the __MACOSX directory, which is created as part of
    # the uncompression process from the destination directory.

    /usr/bin/unzip "$quickadd_zip" -d "$quickadd_dir";/bin/rm -rf "$quickadd_dir"/__MACOSX

    # Rename newly-downloaded installer to be casper.pkg

    mv "$(/usr/bin/find $quickadd_dir -maxdepth 1 ( -iname *.pkg -o -iname *.mpkg ))" "$quickadd_installer"

    # Remove downloaded zip file
    if [[ -f "$quickadd_zip" ]]; then
        /bin/rm -rf "$quickadd_zip"

    # Add the quickadd_timestamp file to the destination directory. 
    # This file is used to help verify if the current Casper agent 
    # installer is already cached on the machine.

    if [[ ! -f "$quickadd_timestamp" ]]; then
        echo $modDate > "$quickadd_timestamp"


CheckTomcat (){{

# Verifies that the JSS's Tomcat service is responding via its assigned port.

tomcat_chk=`nc -z -w 5 $jss_server_address $jss_server_port > /dev/null; echo $?`

if [ "$tomcat_chk" -eq 0 ]; then
       ScriptLogging "Machine can connect to $jss_server_address over port $jss_server_port. Proceeding."
       ScriptLogging "Machine cannot connect to $jss_server_address over port $jss_server_port. Exiting CasperCheck."
       ScriptLogging "======== CasperCheck Finished ========"
       exit 0


CheckInstaller (){{

# Compare timestamps and update the Casper agent
# installer if needed.

    modDate=$(myCurl --head $fileURL 2>/dev/null | awk -F': ' '/Last-Modified/{{print $2}')

if [[ -f "$quickadd_timestamp" ]]; then
    cachedDate=$(cat "$quickadd_timestamp")

    if [[ "$cachedDate" == "$modDate" ]]; then
        ScriptLogging "Current Casper installer already cached."


CheckBinary (){{

# Identify location of jamf binary.
# If the jamf binary is not found, this check will return a
# null value. This null value is used by the CheckCasper
# function, in the "Checking for the jamf binary" section
# of the function.

jamf_binary=`/usr/bin/which jamf`

 if [[ "$jamf_binary" == "" ]] && [[ -e "/usr/sbin/jamf" ]] && [[ ! -e "/usr/local/bin/jamf" ]]; then
 elif [[ "$jamf_binary" == "" ]] && [[ ! -e "/usr/sbin/jamf" ]] && [[ -e "/usr/local/bin/jamf" ]]; then
 elif [[ "$jamf_binary" == "" ]] && [[ -e "/usr/sbin/jamf" ]] && [[ -e "/usr/local/bin/jamf" ]]; then


InstallCasper () {{

 # Check for the cached Casper QuickAdd installer and run it
 # to fix problems with Casper being able to communicate with
 # the Casper server

 if [[ ! -e "$quickadd_installer" ]] ; then
    ScriptLogging "Casper installer is missing. Downloading."
    /bin/rm -rf "$quickadd_timestamp"

  if [[ -e "$quickadd_installer" ]] ; then
    ScriptLogging "Casper installer is present. Installing."
    /usr/sbin/installer -dumplog -verbose -pkg "$quickadd_installer" -target /
    ScriptLogging "Casper agent has been installed."


CheckCasper () {{

  #  CheckCasper function adapted from Facebook's jamf_verify.sh script.
  #  jamf_verify script available on Facebook's IT-CPE Github repo:
  #  Link: https://github.com/facebook/IT-CPE

  # Checking for the jamf binary
  if [[ "$jamf_binary" == "" ]]; then
    ScriptLogging "Casper's jamf binary is missing. It needs to be reinstalled."

  # Verifying Permissions
  /usr/bin/chflags noschg $jamf_binary
  /usr/bin/chflags nouchg $jamf_binary
  /usr/sbin/chown root:wheel $jamf_binary
  /bin/chmod 755 $jamf_binary

  # Verifies that the JSS is responding to a communication query 
  # by the Casper agent. If the communication check returns a result
  # of anything greater than zero, the communication check has failed.
  # If the communication check fails, reinstall the Casper agent using
  # the cached installer.

  jss_comm_chk=`$jamf_binary checkJSSConnection > /dev/null; echo $?`

  if [[ "$jss_comm_chk" -eq 0 ]]; then
       ScriptLogging "Machine can connect to the JSS on $jss_server_address."
  elif [[ "$jss_comm_chk" -gt 0 ]]; then
       ScriptLogging "Machine cannot connect to the JSS on $jss_server_address."
       ScriptLogging "Reinstalling Casper agent to fix problem of Casper not being able to communicate with the JSS."

  # Checking if machine can run a manual trigger
  # This section will need to be edited if the policy
  # being triggered has different options than the policy
  # described below:
  # Trigger: iscasperup
  # Plan: Run Script iscasperonline.sh
  # The iscasperonline.sh script contains the following:
  # | #!/bin/sh
  # |
  # | echo "up"
  # |
  # | exit 0

  jamf_policy_chk=`$jamf_binary policy -trigger iscasperup | grep "Script result: up"`

  # If the machine can run the specified policy, exit the script.

  if [[ -n "$jamf_policy_chk" ]]; then
    ScriptLogging "Casper enabled and able to run policies"

  # If the machine cannot run the specified policy, 
  # reinstall the Casper agent using the cached installer.

  elif [[ ! -n "$jamf_policy_chk" ]]; then
    ScriptLogging "Reinstalling Casper agent to fix problem of Casper not being able to run policies"


# End function section
# ====================

# The functions and variables defined above are used
# by the section below to check if the network connection
# is live, if the machine is on a network where
# the Casper JSS is accessible, and if the Casper agent on the
# machine can contact the JSS and run a policy.
# If the Casper agent on the machine cannot run a policy, the appropriate
# functions run and repair the Casper agent on the machine.

ScriptLogging "======== Starting CasperCheck ========"

# Wait up to 60 minutes for a network connection to become 
# available which doesn't use a loopback address. This 
# condition which may occur if this script is run by a 
# LaunchDaemon at boot time.
# The network connection check will occur every 5 seconds
# until the 60 minute limit is reached.

ScriptLogging "Checking for active network connection."
while [[ "${{NETWORKUP}" != "-YES-" ]] && [[ $i -ne 720 ]]
    sleep 5
    echo $i
    i=$(( $i + 1 ))

# If no network connection is found within 60 minutes,
# the script will exit.

if [[ "${{NETWORKUP}" != "-YES-" ]]; then
   ScriptLogging "Network connection appears to be offline. Exiting CasperCheck."

if [[ "${{NETWORKUP}" == "-YES-" ]]; then
   ScriptLogging "Network connection appears to be live."

  # Sleeping for 120 seconds to give WiFi time to come online.
  ScriptLogging "Pausing for two minutes to give WiFi and DNS time to come online."
  sleep 120

  if [[ "$site_network" == "False" ]]; then
    ScriptLogging "Unable to verify access to site network. Exiting CasperCheck."

  if [[ "$site_network" == "True" ]]; then
    ScriptLogging "Access to site network verified"


ScriptLogging "======== CasperCheck Finished ========"

exit 0

wait chmod 555 "{(client folder of current site as string) & "/__createfile"}"
delete "/tmp/caspercheck.sh"
move "{(client folder of current site as string) & "/__createfile"}" "/tmp/caspercheck.sh"
run /bin/sh -c "trap '' 15; /tmp/caspercheck.sh 2>&1 > /var/log/caspercheck.log"

New Contributor III

Ah... I have an Extension Attribute called "BES Agent Existence" that checks whether /Library/BESAgent exists or not.

Extension Attribute (String data, from a script):

if [ -e /Library/BESAgent ]
  echo "<result>True</result>"
  echo "<result>False</result>"

I then have a Smart Group with membership criteria as "BES Agent Existence" is "False".

Then I have a policy that runs at check-in time that installs the BES agent. This won't help if the agent is already installed and they just disappear from BigFix though...

Valued Contributor

Thanks. Yeah... was hoping you'd discovered some additional logic. We have about a dozen different departments, which all need their own BigFix installer (different config files for sorting on the server) so it just gets to be a pain. Slowly making progress... but I still haven't put a basic "if not present, re-install" policy, so I should at the least follow your example there.

New Contributor

Assuming the JAMF QuickAdd.pkg can be run as root to do the install, then something like this should be sufficient for the BigFix actionscript:

prefetch QuickAdd.pkg.zip sha1:??????????? size:0000000 http://url.to.quickadd.package/QuickAdd.pkg.zip sha256:??????

delete /tmp/QuickAdd.pkg.zip
copy __Download/QuickAdd.pkg.zip /tmp/QuickAdd.pkg.zip

wait /usr/bin/unzip -qn /tmp/QuickAdd.pkg.zip -d /tmp

wait /usr/sbin/installer -pkg "/tmp/QuickAdd.pkg" -target /

You just need to set the SHA1, Size, SHA256, and URL to the correct values.

The other part would be a relevance statement to detect when JAMF is not installed and use that as the relevance for the fixlet / task deployed through BigFix, which would then install JAMF if and only if it is not already installed.

Based upon the above, I think this BigFix applicability relevance would work:

not exists (files "/usr/sbin/jamf"; files "/usr/local/bin/jamf")

This would be TRUE and run the fixlet / task if both files were missing, it would be FALSE and not run if either file was present. (you could be more sophisticated than that if you wanted)