Remove Management

mscottblake
Valued Contributor

I have a group of computers that are being images with Casper, but they do not want the machines to be managed beyond that.

I currently have a site setup for them, and the only policy is one scoped to all machines that runs jamf removeFramework. The problem is that the machines no longer check in and do not report whether they have run the policy successfully.

Has anyone had a similar requirement and been able to handle it gracefully? I would like the machines to exist in the JSS, but report as being unmanaged.

1 ACCEPTED SOLUTION

mscottblake
Valued Contributor

Thanks to @mm2270, @john_wetter, and @bpavlov for their invaluable help, here's what I came up with:

#!/bin/bash
# This script runs one last recon, updates the JSS API to show the machine is unmanaged, and then creates a launchd to remove the jamf framework.

# Define variables
apiUsername=""
apiPassword=""
jssServer=""
xmlString="<?xml version="1.0" encoding="UTF-8"?><computer><general><remote_management><managed>false</managed><management_username/></remote_management></general></computer>"

# Identify the location of the jamf binary for the jamf_binary variable.
CheckBinary (){
    # Identify location of jamf binary.
    jamf_binary=`/usr/bin/which jamf`

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

# Update the computer inventory
RunRecon (){
    $jamf_binary recon
}

# Use the API to update the management status of the computer
# Takes a parameter for the computer name
UpdateAPI (){
    curl -sS -k -i -u ${apiUsername}:${apiPassword} -X PUT -H "Content-Type: text/xml" -d "${xmlString}" "${jssServer}/JSSResource/computers/name/$1"
}

# Create a temp launchd job in /private/tmp/ that uses a RunAtLoad key of false and StartInterval of 60 seconds
# This is done so that the script can return a result to the JSS before the framework is removed
CreateLaunchd (){
    echo "<?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>Disabled</key>
        <false/>
        <key>Label</key>
        <string>tmp_removeframework</string>
        <key>ProgramArguments</key>
        <array>
            <string>$jamf_binary</string>
            <string>removeFramework</string>
        </array>
        <key>RunAtLoad</key>
        <false/>
        <key>StartInterval</key>
        <integer>60</integer>
    </dict>
    </plist>" > /private/tmp/removetools.plist

    chown root:wheel /private/tmp/removetools.plist
    chmod 644 /private/tmp/removetools.plist 

    /bin/launchctl load /private/tmp/removetools.plist
}

CheckBinary
RunRecon
UpdateAPI $2
CreateLaunchd

View solution in original post

8 REPLIES 8

mm2270
Legendary Contributor III

Yes, I've run across a similar requirement in the past. As some background, we had a Mac that a former employee walked away with that we discovered was still using the system despite no longer being an employee. After a lengthy back and forth process with HR and security, etc. it was decided they just wanted to chalk it up as "lost", but remove the jamf framework (and a few other company related items) and report that the policy successfully ran on it. The problem as you noted is that it can't upload a policy log if it removes the framework first, so it stays in a "Pending" state forever in the JSS, even if it ran successfully.

I came up with something that helped, but the Mac isn't going to show up as "unmanaged" since I'm not sure how to make that happen outside of changing it through the API perhaps.

Here's a modified version of the script I used:

#!/bin/sh

echo "<?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>Disabled</key>
    <false/>
    <key>Label</key>
    <string>tmp_removeframework</string>
    <key>ProgramArguments</key>
    <array>
        <string>jamf</string>
        <string>removeFramework</string>
    </array>
    <key>RunAtLoad</key>
    <false/>
    <key>StartInterval</key>
    <integer>120</integer>
</dict>
</plist>" > /private/tmp/removetools.plist

chown root:wheel /private/tmp/removetools.plist
chmod 644 /private/tmp/removetools.plist

jamf recon

/bin/launchctl load /private/tmp/removetools.plist

exit 0

Essentially, it creates a tmp launchd job in /private/tmp/ that uses a RunAtLoad key of false and StartInterval of 2 minutes. Then does a recon for a final inventory collection, and finally loads the launchd, which removes the framework after the 2 minute delay. Putting the launchd job in /tmp/ ensures it will clear out from the system later when it reboots or when the Mac dumps the tmp directory contents. After this runs, the Mac is effectively unmanaged, no longer checks in to the JSS, but still shows as "managed" since again, we'd need to change that flag in the JSS in some other way I believe.

You might think that if its not located in /Library/LaunchDaemons/ it wouldn't be able to do this, but since the launchctl load command is getting run as root, it loads the launchd job as root, so its able to remove the framework successfully.

Edit: Just an additional note. This was all done before we upgraded to JSS 9.82 and the jamf binary moving into /usr/local/bin/jamf/ You would likely need to update the call to the jamf binary to use the full path since its been discussed on other threads that Launchd jobs don't use the same environment path settings as the shell, and may fail to run the removeFramework command.

mscottblake
Valued Contributor

Thanks @mm2270. A couple of us had a quick discussion in the #jamfnation Slack channel. The current thinking is that the API can probably be used to update the management status. Combining that with your script above may be the key.

I'll update this thread once I have a chance to write up a script to make the change to the API.

mscottblake
Valued Contributor

Thanks to @mm2270, @john_wetter, and @bpavlov for their invaluable help, here's what I came up with:

#!/bin/bash
# This script runs one last recon, updates the JSS API to show the machine is unmanaged, and then creates a launchd to remove the jamf framework.

# Define variables
apiUsername=""
apiPassword=""
jssServer=""
xmlString="<?xml version="1.0" encoding="UTF-8"?><computer><general><remote_management><managed>false</managed><management_username/></remote_management></general></computer>"

# Identify the location of the jamf binary for the jamf_binary variable.
CheckBinary (){
    # Identify location of jamf binary.
    jamf_binary=`/usr/bin/which jamf`

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

# Update the computer inventory
RunRecon (){
    $jamf_binary recon
}

# Use the API to update the management status of the computer
# Takes a parameter for the computer name
UpdateAPI (){
    curl -sS -k -i -u ${apiUsername}:${apiPassword} -X PUT -H "Content-Type: text/xml" -d "${xmlString}" "${jssServer}/JSSResource/computers/name/$1"
}

# Create a temp launchd job in /private/tmp/ that uses a RunAtLoad key of false and StartInterval of 60 seconds
# This is done so that the script can return a result to the JSS before the framework is removed
CreateLaunchd (){
    echo "<?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>Disabled</key>
        <false/>
        <key>Label</key>
        <string>tmp_removeframework</string>
        <key>ProgramArguments</key>
        <array>
            <string>$jamf_binary</string>
            <string>removeFramework</string>
        </array>
        <key>RunAtLoad</key>
        <false/>
        <key>StartInterval</key>
        <integer>60</integer>
    </dict>
    </plist>" > /private/tmp/removetools.plist

    chown root:wheel /private/tmp/removetools.plist
    chmod 644 /private/tmp/removetools.plist 

    /bin/launchctl load /private/tmp/removetools.plist
}

CheckBinary
RunRecon
UpdateAPI $2
CreateLaunchd

cstout
Contributor III
Contributor III

@mscottblake Thanks so much for sharing your script here. I had to make some minor additions for it to work in my environment where computer names have spaces. Without these modifications the curl command would fail on every Mac with a space in the name.

#!/bin/bash
# This script runs one last recon, updates the JSS API to show the machine is unmanaged, and then creates a launchd to remove the jamf framework.

# Define variables
apiUsername="apiusername"
apiPassword="password"
jssServer="https://jamf.pro.server.com"
name=`scutil --get ComputerName`
curlName=`echo "$name"|sed 's/ /%20/g'`
xmlString="<?xml version="1.0" encoding="UTF-8"?><computer><general><remote_management><managed>false</managed><management_username/></remote_management></general></computer>"

# Identify the location of the jamf binary for the jamf_binary variable.
CheckBinary (){
    # Identify location of jamf binary.
    jamf_binary=`/usr/bin/which jamf`

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

# Update the computer inventory
RunRecon (){
    $jamf_binary recon
}

# Use the API to update the management status of the computer
# Takes a parameter for the computer name
UpdateAPI (){
    curl -sS -k -i -u ${apiUsername}:${apiPassword} -X PUT -H "Content-Type: text/xml" -d "${xmlString}" ${jssServer}/JSSResource/computers/name/$curlName
}

# Create a temp launchd job in /private/tmp/ that uses a RunAtLoad key of false and StartInterval of 60 seconds
# This is done so that the script can return a result to the JSS before the framework is removed
CreateLaunchd (){
    echo "<?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>Disabled</key>
        <false/>
        <key>Label</key>
        <string>tmp_removeframework</string>
        <key>ProgramArguments</key>
        <array>
            <string>$jamf_binary</string>
            <string>removeFramework</string>
        </array>
        <key>RunAtLoad</key>
        <false/>
        <key>StartInterval</key>
        <integer>60</integer>
    </dict>
    </plist>" > /private/tmp/removetools.plist

    chown root:wheel /private/tmp/removetools.plist
    chmod 644 /private/tmp/removetools.plist 

    /bin/launchctl load /private/tmp/removetools.plist
}

CheckBinary
RunRecon
UpdateAPI
CreateLaunchd

mm2270
Legendary Contributor III

@cstout Here's a little Perl code you can use in the future to HTML'ize any variables, instead of relying on sed. This takes care of more than just converting spaces to the required %20 in the string. It also handles other items like apostrophes and such.

perl -MURI::Escape -e 'print uri_escape($ARGV[0]);' "$variable"

For example, if the $variable is "Macintosh HD" it prints out
Macintosh%20HD

If you threw something at it like Mike's MacBook Pro (not uncommon when an OOB Mac gets named using the built-in Setup Assistant process), it will convert it to:
Mike%27s%20MacBook%20Pro
where the %27 is the replacement for the apostrophe.

Those should all be usable in an API call to Jamf Pro since that seems to be what it expects.

Edit: As an aside, it would also be good to mention that you should be able to use other items that can't contain spaces or odd characters in them, such as the UUID string or Serial Number for this, both of which are pretty easy to get from the Mac the script is running on. It's not necessary to use the Computer Name string for most API calls.

cstout
Contributor III
Contributor III

Thanks, @mm2270! That's extremely useful. I did briefly try switching out the name for the serial number but I'm really rusty on using Jamf's API and I'm positive I was doing it incorrectly which is why I kept plugging away at getting the name variable to work. Your perl command saved me in advance from what I'm sure would have been a nightmare later troubleshooting an apostrophe.

cstout
Contributor III
Contributor III

Alright Mike, that perl bit was excellent. Here's my modified "UpdateAPI" command:

UpdateAPI (){
    curl -sS -k -i -u ${apiUsername}:${apiPassword} -X PUT -H "Content-Type: text/xml" -d "${xmlString}" ${jssServer}/JSSResource/computers/name/`perl -MURI::Escape -e 'print uri_escape($ARGV[0]);' "$name"`
}

I just tested it with the worst name I've ever submitted to my computer (C! Stout's iMac Name! !) and it worked without any issue. Thanks again.

Aaron
Contributor II

Something that I do in my environment, and you may be able to use as well - I fill in the "Asset" field in Casper, which is usually left blank. And then have 2 smart groups, "Macs with policies applied" which is applied to all Macs with a non-blank Asset field, and "Macs without polices applied" which is applied to all devices with a blank asset field. This way I can have non-managed Macs, but still keep track of them.

The reasoning being that if a device has an asset number, then it's a company device and needs to be managed.

This does mean an extra field to fill out when enrolling a device, but I script my enrollment so I include an API call to fill out the Asset field for me.