Home > Casper, JSS, Mac administration > Automatically fixing Casper Mac MDM enrollment

Automatically fixing Casper Mac MDM enrollment

While I was working on a new laptop this afternoon, I noticed that the Profiles icon was missing from System Preferences.

Profiles in System Preferences

This system was managed by our Casper server and we’re using both certificate-based communication and an APN certificate, so it should have been there. Moreover, when I ran profiles -P, I saw that no profiles were installed.

Running jamf mdm -verbose fixed the issue by installing the MDM certificate, but I wanted to ensure that any other machines with the same issue were found and then automatically fixed by Casper. After a little research, I have a process that does this. See below the jump for details.

Update – 9-8-2013: It looks like Casper 9.x handles MDM certificate re-enrollment differently than Casper 8.x does. To avoid any confusion, I’m removing the Casper 9.x information from this post. Once I’ve got the right method, I plan a follow-up post covering how to do this with Casper 9.x.

Update – 7-31-2014: Click here to see how to do this with Casper 9.x.

UPDATE – 9-22-2014: JAMF brought back the jamf mdm command in Casper 9.4, so it’s possible to use the workflow described below in Casper 8.7.x and in Casper 9.4 and later.

JAMF provides three extension attributes with your Casper JSS server to help you identify machines with either problematic SSL certificates or missing MDM certificates.

JSS Certificate Validation

Verify Certificate Based Communication

Verify MDM Enrollment

Screen Shot 2013-08-30 at 3.07.10 PM copy

All can be installed from the JAMF Software category of your JSS server’s Extension Attribute Templates.

Screen Shot 2013-08-30 at 3.07.10 PM

From there, you can set up a Smart Group to look for machines that fit the following criteria:

JSS Certificate ValidationSuccess

Verify Certificate Based CommunicationEnabled

Verify MDM EnrollmentNot Enrolled

It should also currently be scoped to look for Macs running 10.7.x or higher, as earlier OSs won’t be enrolled in MDM.

Here’s how the smart group I set up looks in Casper 8.x:

Casper 8.x:

Screen Shot 2013-08-30 at 3.06.30 PM

From there, set up a policy that is scoped to run on members of that smart group. The policy I set up will run the jamf mdm -verbose command to install the MDM certificate on the Mac, then run a new inventory. The inventory update process should then allow the JSS to detect that the MDM certificate has been installed and take the machine out of the smart group.

Here’s how the policy I set up looks in Casper 8.x:

Casper 8.x:

Screen Shot 2013-08-30 at 3.08.56 PM

Screen Shot 2013-08-30 at 3.08.39 PM

Screen Shot 2013-08-30 at 3.08.18 PM

Categories: Casper, JSS, Mac administration
  1. September 5, 2013 at 8:16 pm

    Hi Rich
    Could it be that the “jamf mdm” command is not available anymore in Casper 9.x? I get

    There is a problem with your syntax.

    Error: The specified verb (mdm) is not recognized.

    Type “jamf help” for more information

    on all of my machines.

    I guess

    jamf manage

    is the new version with similar results.

    • September 5, 2013 at 8:32 pm

      On re-testing it, it looks like “jamf mdm” isn’t available in Casper 9. My mistake there, I must have been looking at the wrong test box.

      I’ll check with JAMF Support and see what the new version of the command is.

    • September 5, 2013 at 11:43 pm

      According to JAMF Support, the replacement commands are these:

      jamf removeMdmProfile


      jamf manage

      I can run this outside of a Casper policy like this:

      jamf removeMdmProfile -verbose && jamf manage -verbose

      I’m not able to run this as part of a policy so far, so I’ve followed up with JAMF.

  2. September 9, 2013 at 3:48 pm

    Thanks for the info. I will give this new commands a try. BTW: Looking forward to meet you in Sweden next week!

  3. Ssjonker
    October 11, 2013 at 7:55 pm


    Have you been able to get it to work reliably with JSS 9.xx? Just curious, thanks! I don’t seem to have anybody showing up in my smart group (we are using 9.12).

  4. April 15, 2014 at 1:44 pm

    Thanks for this article. I had been using it with Casper 8.x and like you, couldn’t get it to work in 9.x.

    I’ve since built a quick package with the luggage that installs a LaunchDaemon and postinstall script starts it. The LaunchDaemon periodically calls the jamf removeMDMProfile && jamf manage verbs, and when successful, does a recon and then shuts down the LaunchDaemon and removes it.

    Of course, at this point I don’t have anything to test it on, but I’m confident that it should work around the problems of trying to run the commands within a policy.

    • May 21, 2014 at 2:08 am


      Would you mind posting your solution, either on Github or elsewhere?

      • akismet-81edbd0379b605bdf363d603e975ae1d
        May 21, 2014 at 2:58 pm

        Hey Rich, I posted my package at: https://github.com/sheagcraig/fix_mdm_cert

        Of course, it’s basically just a package that does what you described in the post, except as a LaunchDaemon executing a script (and then cleaning up after itself) rather than as part of a jamf policy.

        Unfortunately I don’t have any broken computers at the moment that I can test with, but I can see in my policy logs that it took care of about 12 computers in the last couple of months. I can test some more once we reimage our fleet this summer (educational institution!)

        From: Der Flounder <comment-reply@wordpress.com> Reply-To: Der Flounder <comment+_htgtt-3_3o-2alw3aqrqbuqgsbv16zuq30om1ln@comment.wordpress.com> Date: Tuesday, May 20, 2014 at 10:08 PM To: Shea Craig <shea.craig@da.org> Subject: [New comment] Automatically fixing Casper Mac MDM enrollment

        rtrouton commented: “Shea, Would you mind posting your solution, either on Github or elsewhere?”

  5. June 12, 2014 at 1:17 am
    Thanks, Shea. I think I’ve figured out a way to do this without having to killall the jamf binary. I’ve tested this method with 10.7.x, 10.8.x and 10.9.x and so far it looks like it works OK. I’m planning to write this up at some point, but I’ve got a gist posted on Github: .gist table { margin-bottom: 0; } This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters Show hidden characters #!/bin/bash # Determine OS version OSVERS=$(sw_vers -productVersion | awk -F. '{print $2}') # Macs running 10.6.x or earlier are not able to use profiles. # If the script detects that it is running on an OS earlier than # 10.7.0, the script will exit at this point to avoid problems. if [[ ${OSVERS} -lt 7 ]]; then /usr/bin/logger "MDM profiles are not supported on this version of Mac OS X." exit 0 fi if [[ ${OSVERS} -ge 7 ]]; then # If any previous instances of the fixcaspermdm LaunchDaemon and script exist, # unload the LaunchDaemon and remove the LaunchDaemon and script files if [[ -f "/Library/LaunchDaemons/com.company.fixcaspermdm.plist" ]]; then /bin/launchctl unload "/Library/LaunchDaemons/com.company.fixcaspermdm.plist" /bin/rm "/Library/LaunchDaemons/com.company.fixcaspermdm.plist" fi if [[ -f "/var/root/fixcaspermdm.sh" ]]; then /bin/rm "/var/root/fixcaspermdm.sh" fi # Create the fixcaspermdm LaunchDaemon by using cat input redirection # to write the XML contained below to a new file. # # The LaunchDaemon will run at load and every ten minutes thereafter. /bin/cat > "/tmp/com.company.fixcaspermdm.plist" << 'CASPER_MDM_FIX_LAUNCHDAEMON' <?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"&gt; <plist version="1.0"> <dict> <key>Label</key> <string>com.company.fixcaspermdm</string> <key>ProgramArguments</key> <array> <string>sh</string> <string>/var/root/fixcaspermdm.sh</string> </array> <key>RunAtLoad</key> <true/> <key>StartInterval</key> <integer>600</integer> </dict> </plist> CASPER_MDM_FIX_LAUNCHDAEMON # Create the fixcaspermdm script by using cat input redirection # to write the shell script contained below to a new file. # # You will need to change the "jss_server_address" variable in the # script below. Please put the complete fully qualified domain name # address of your Casper server. # # You may need to change the "jss_server_port" variable in the # script below. Please put the port number of your Casper server # if it is different than 8443. /bin/cat > "/tmp/fixcaspermdm.sh" << 'CASPER_MDM_FIX_SCRIPT' #!/bin/bash # # User-editable variables # # For the jss_server_address variable, put the complete # fully qualified domain name address of your Casper server jss_server_address="casper.server.here" # For the jss_server_address variable, put the port number # of your Casper server. This is usually 8443; change as # appropriate. jss_server_port="8443" 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 site_network="False" 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" } 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 /usr/bin/logger "Machine can connect to $jss_server_address over port $jss_server_port. Proceeding." else /usr/bin/logger "Machine cannot connect to $jss_server_address over port $jss_server_port. Exiting." exit 0 fi } CheckLogAge (){ # Verifies that the /var/log/jamf.log hasn't been written to for at least five minutes. # This should help ensure that jamf manage can run and not have to wait for a policy to # finish running. jamf_log="/var/log/jamf.log" current_time=`date +%s` last_modified=`stat -f %m "$jamf_log"` if [[ $(($current_time-$last_modified)) -gt 300 ]]; then /usr/bin/logger "Log has not been modified in the past five minutes. Proceeding." else /usr/bin/logger "Log has been modified in the past five minutes. Exiting." exit 0 fi } FixMDM (){ # Verifies that the Mac can communicate with the Casper server. # Once communication is verified, it takes the following actions: # # 1. Removes the existing MDM certificate if one exists # 2. Runs jamf manage to fix the certificate # 3. Runs a recon to send an updated inventory to the JSS to report # that the MDM certificate is fixed. # jss_comm_chk=`/usr/sbin/jamf checkJSSConnection > /dev/null; echo $?` if [[ "$jss_comm_chk" -gt 0 ]]; then /usr/bin/logger "Machine cannot connect to the JSS. Exiting." exit 0 elif [[ "$jss_comm_chk" -eq 0 ]]; then /usr/bin/logger "Machine can connect to the JSS. Fixing MDM" /usr/sbin/jamf removeMdmProfile -verbose /usr/sbin/jamf manage -verbose /usr/sbin/jamf recon fi } SelfDestruct (){ # Removes script and associated LaunchDaemon if [[ -f "/Library/LaunchDaemons/com.company.fixcaspermdm.plist" ]]; then /bin/rm "/Library/LaunchDaemons/com.company.fixcaspermdm.plist" fi srm $0 } CheckSiteNetwork if [[ "$site_network" == "False" ]]; then /usr/bin/logger "Unable to verify access to site network. Exiting." fi if [[ "$site_network" == "True" ]]; then /usr/bin/logger "Access to site network verified" CheckTomcat CheckLogAge FixMDM SelfDestruct fi exit 0 CASPER_MDM_FIX_SCRIPT # Once the LaunchDaemon file has been created, fix the permissions # so that the file is owned by root:wheel and set to not be executable # After the permissions have been updated, move the LaunchDaemon into # place in /Library/LaunchDaemons. /usr/sbin/chown root:wheel "/tmp/com.company.fixcaspermdm.plist" /bin/chmod 755 "/tmp/com.company.fixcaspermdm.plist" /bin/chmod a-x "/tmp/com.company.fixcaspermdm.plist" /bin/mv "/tmp/com.company.fixcaspermdm.plist" "/Library/LaunchDaemons/com.company.fixcaspermdm.plist" # Once the script file has been created, fix the permissions # so that the file is owned by root:wheel and set to be executable # After the permissions have been updated, move the script into the # place that it will be executed from. /usr/sbin/chown root:wheel "/tmp/fixcaspermdm.sh" /bin/chmod 755 "/tmp/fixcaspermdm.sh" /bin/chmod a+x "/tmp/fixcaspermdm.sh" /bin/mv "/tmp/fixcaspermdm.sh" "/var/root/fixcaspermdm.sh" # After the LaunchDaemon and script are in place with proper permissions, # load the LaunchDaemon to begin the script's execution. /bin/launchctl load -w "/Library/LaunchDaemons/com.company.fixcaspermdm.plist" fi exit 0 view raw gistfile1.txt hosted with ❤ by GitHub
  6. tobiaslinder
    June 12, 2014 at 6:37 am

    Thanks Rich, I will give it a try.

  7. Michael
    May 19, 2016 at 8:37 am

    A finding in regard to DEP: PreStage Enrolment
    You have to Allow MDM Profile Removal (Allow the user to remove the MDM profile).
    Otherwise jamf mdm -verbose will not work.

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: