Skip to main content

Hi all,

Looking for advice with my 'Off-boarding Script'

Description:

This script (below) is crafted for system administrators utilizing Jamf Pro to facilitate the remote off-boarding of Mac devices that end users have purchased. It performs several functions to ensure the device is no longer managed or configured for enterprise use. The script automates the removal of management settings, promotes the current user to admin, creates backup accounts, and eliminates no longer necessary accounts and management software. Furthermore, it updates the device's details in Jamf Pro's inventory, sends essential information to a Google Sheet, and concludes by removing the Jamf management framework, making the process as hands-off as possible.

 

#!/bin/bash # Functions removePolicyBanner() { if [ -e "/Library/Security/PolicyBanner.rtfd" ]; then echo "Found PolicyBanner... Removing PolicyBanner" rm -rf "/Library/Security/PolicyBanner.rtfd" else echo "No PolicyBanner Installed" fi } removeNoMADLogin() { if [ -e "/usr/local/bin/authchanger" ]; then echo "Found Authchanger... removing now" /usr/local/bin/authchanger -reset else echo "No Authchanger" fi } unbindFromAD() { local result=$(dsconfigad -f -r -u "$1" -p "$2") if [ $? -ne 0 ]; then echo "Failed to unbind from AD: $result" #exit 1 fi } promoteToAdmin() { local user="$1" if [[ $(/usr/bin/dscl . read /Groups/admin GroupMembership | /usr/bin/grep -c $user) == 1 ]]; then echo "$user is in the admin group." else echo "$user is not an admin, promoting..." /usr/sbin/dseditgroup -o edit -a "$user" -t user admin fi } removeFortiClient() { if [ -e "/Applications/FortiClient.app" ]; then echo "/Applications/FortiClient located; proceeding ..." echo "Removing FortiClient ..." /Applications/FortiClientUninstaller.app/Contents/Resources/uninstall_helper rm -rf "/Library/Application Support/Fortinet/" echo "Removed /Applications/FortiClient." else echo "/Applications/FortiClient NOT found; nothing to remove." fi } checkJamfBinary() { if which jamf > /dev/null 2>&1; then return 0 else return 1 fi } deleteFirmwarePassword() { local primaryPassword="$1" local backupPassword="$2" /usr/bin/expect <<EOF set primary "${primaryPassword}" set backup "${backupPassword}" spawn /usr/sbin/firmwarepasswd -check expect { "Password Enabled: Yes" { spawn /usr/sbin/firmwarepasswd -delete expect "Enter password:" send -- "\\$primary\\r" expect { "ERROR | main | Exiting with error: 5" { puts "Primary password failed, trying backup." # Re-spawn the delete process for the backup password attempt spawn /usr/sbin/firmwarepasswd -delete expect "Enter password:" send -- "\\$backup\\r" expect { "ERROR | main | Exiting with error: 5" { puts "Backup password also failed." } -re "#" { puts "Backup password removed the firmware password successfully." } } } -re "#" { puts "Password removed successfully with primary password." } } } "Password Enabled: No" { puts "No firmware password set." } default { puts "Unexpected response from firmwarepasswd" exit 1 } } EOF } unmanageFromJamf() { local uname="$1" local pwd="$2" local server="$3" local LOGGED_IN_USER=$(stat -f%Su /dev/console) # Get current Jamf server if server not provided [[ -z "$server" ]] && server=$(defaults read /Library/Preferences/com.jamfsoftware.jamf.plist jss_url) # Ensure the server URL ends with a / [[ "${server: -1}" != "/" ]] && server="${server}/" # Get unique identifier for machine local udid=$(system_profiler SPHardwareDataType | awk '/UUID/ { print $3; }') # Get computer ID from Jamf server local compId=$(curl -sku ${uname}:${pwd} ${server}JSSResource/computers/udid/${udid}/subset/general -H "Accept: application/xml" | xpath -e "//computer/general/id/text()") # Check for curl errors or empty compId if [[ $? -ne 0 ]] || [[ -z "$compId" ]]; then osascript -e 'display dialog "Error fetching Comp ID from Jamf server." buttons {"OK"} default button "OK"' exit 1 fi # Send unmanage command to machine curl -X POST -sku ${uname}:${pwd} ${server}JSSResource/computercommands/command/UnmanageDevice/id/${compId} } function sendToGoogleSheet { # Gather system details timestamp=$(date '+%Y-%m-%d %H:%M:%S') hostname=$(hostname) loggedinuser=$(stat -f%Su /dev/console) serialnumber=$(system_profiler SPHardwareDataType | awk '/Serial/ {print $4}') macosversion=$(sw_vers -productVersion) # Check for the presence of the jamf binary if which jamf > /dev/null 2>&1; then jamfBinaryStatus="Active" else jamfBinaryStatus="Removed" fi # This will base64-encode your data DATA_TO_SEND="$timestamp,$currentComputerName,$newComputerName,$loggedinuser,$serialnumber,$macosversion,Unmannaged,$jamfBinaryStatus" ENCODED_DATA=$(echo -n "$DATA_TO_SEND" | base64) # Ensure DATA_TO_SEND is not empty if [[ -z "$DATA_TO_SEND" ]]; then echo "Error: DATA_TO_SEND is empty." return 1 fi # Ensure ENCODED_DATA is not empty if [[ -z "$ENCODED_DATA" ]]; then echo "Error: ENCODED_DATA is empty." return 1 fi # URL of the deployed Google Apps Script web app WEB_APP_URL="${11}?encodedValue=$ENCODED_DATA" # Use curl with the -L flag to follow redirects and send a GET request to the Google Apps Script web app curl -L "$WEB_APP_URL" echo "Data sent to Google Sheet." } # Variables from Jamf ADuser="$4" ADpw="$5" firmwarePassword1="$6" firmwarePassword2="$7" LOGGED_IN_USER=$(stat -f%Su /dev/console) removePolicyBanner removeNoMADLogin unbindFromAD "$ADuser" "$ADpw" deleteFirmwarePassword "$firmwarePassword1" "$firmwarePassword2" # Promote to admin, if not an admin already promoteToAdmin "$LOGGED_IN_USER" # Create Backup Local Account backup_username="localbackup" backup_password="localbackup" # Modify as needed sysadminctl -adminUser "$ADuser" -adminPassword "$ADpw" -addUser $backup_username -fullName "Local Backup User" -password $backup_password -admin echo "Created backup admin user: '$backup_username'" # Remove ecadmin account accountName="ecadmin" if [ "$accountName" != "$LOGGED_IN_USER" ]; then /usr/sbin/sysadminctl -deleteUser "$accountName" echo "$accountName deleted" fi removeFortiClient # Send custom event to Jamf Pro /usr/local/jamf/bin/jamf policy -event customSuccessEvent # This should quit Self-Service. killall "Self Service" # Adjust volume osascript -e "set volume output volume 80" # Play sound and show dialog with a 15-second timeout USER_RESPONSE=$(osascript <<EOD do shell script "afplay /System/Library/Sounds/Ping.aiff" delay 1 display dialog "Removing college settings..\\nThis will only take a moment\\n\\nAll the best in the future!" buttons {"Play Beats", "See ya!"} default button "See ya!" with icon file (POSIX file "/Applications/Self Service.app/Contents/Resources/AppIcon.icns") as alias giving up after 15 return button returned of result EOD ) # Check the response if [[ $USER_RESPONSE == "Play Beats" ]]; then open -a "Google Chrome" "https://youtu.be/gHwXNHXldi0?si=BsfeFL3gOTORNTEf&t=1907" fi # Background the sleep and kill sequence { sleep 10 pkill -a -i 'osascript' } & # Determine the current computer name currentComputerName=$(hostname) # Append the abbreviation to the current name newComputerName="${currentComputerName}_UM23" echo "Computer name was: '${currentComputerName}' set to '${newComputerName}'" # Update the computer name in Jamf Pro Server /usr/local/jamf/bin/jamf setComputerName -name "$newComputerName" # Run a recon to report back the updated inventory data to Jamf Pro /usr/local/jamf/bin/jamf recon unmanageFromJamf "$8" "$9" "${10}" echo "Completed unmanageFromJamf" # Run a recon to report back the updated inventory data to Jamf Pro checkJamfBinary && /usr/local/jamf/bin/jamf recon # Verify Jamf binary existence before sending the data to Google checkJamfBinary if [ $? -eq 0 ]; then echo "Warning: Jamf binary still exists!" sendToGoogleSheet else echo "Sending info to Google..." sendToGoogleSheet fi sleep 15 # Lastly, remove the Jamf framework and perform any other necessary cleanup. # Given that this is at the end, it ensures all previous tasks reliant on the Jamf binary have completed. checkJamfBinary && { echo "Running the jamf removeFramework command..." /usr/local/jamf/bin/jamf removeFramework } # Exit the script cleanly exit 0

 

Key Steps in the Script:

  1. Remove Policy Banner: Erases any existing Policy Banner from the device.
  2. Remove NoMAD Login: If NoMAD Login components are found, the script resets the login window to its default configuration.
  3. Unbind from Active Directory: Uses provided credentials to sever the connection between the device and Active Directory.
  4. Delete Firmware Password: Strips the firmware password from the machine using one of the two supplied passwords.
  5. Promote to Admin: Checks if the logged-in user is an admin and promotes them if they are not.
  6. Create Backup Local Account: Generates a new local user with administrative rights to serve as a backup.
  7. Remove FortiClient: Finds and uninstalls FortiClient from the Mac, if present.
  8. Unmanage Device from Jamf Pro: Executes an API call to mark the device as unmanaged in the Jamf Pro server.
  9. Send Data to Google Sheet: Encodes and dispatches device details to a specified Google Sheet, which acts as a log.
  10. Post-Processing and Cleanup: Includes computer renaming, updating inventory via Jamf recon, and the crucial step of eliminating the Jamf framework from the device.

I'm sharing a script that's been developed to off-board Mac devices remotely via Jamf Pro and am curious to hear if anyone in the community has tackled a similar challenge. The script effectively completes all off-boarding tasks, but the devices remain "managed" in Jamf Pro until the “Allow Jamf Pro to perform management tasks” checkbox is unchecked manually within the inventory tab. My goal is to automate this final step to fully optimize the off-boarding workflow. Does anyone have experience with this, or can anyone provide guidance on how to automate the unchecking of this box?

I appreciate any advice or shared experiences in making this process as efficient as possible.

You got yourself a good-looking script, however Apples recommendation is to wipe a device when it changes hands.

What to do before you sell, give away, trade in, or recycle your Mac - Apple Support

 

I would suggest simply using the Wipe Computer MDM Command in JAMF. It takes about 30 seconds to hit the device, and the data is sanitized (crypto erased if FileVault is enabled). Apple Silicon Macs take about 5 minutes to set up after the command is received. Intel Macs need an OS reinstall, but it leaves you in recovery.

 


You got yourself a good-looking script, however Apples recommendation is to wipe a device when it changes hands.

What to do before you sell, give away, trade in, or recycle your Mac - Apple Support

 

I would suggest simply using the Wipe Computer MDM Command in JAMF. It takes about 30 seconds to hit the device, and the data is sanitized (crypto erased if FileVault is enabled). Apple Silicon Macs take about 5 minutes to set up after the command is received. Intel Macs need an OS reinstall, but it leaves you in recovery.

 


 

Thanks AJ,

This script applies to students that wish to retain there data in place and it is a more flexible solution to offboard 200+ devices without wiping them, this logs the results in a google doc and the renaming means we have the historical records going forward without a licence.

We do in fact use your suggested method in another script to wipe spare devices, primarily due to onboarded staff and students throughout the year.

I greatly appreciate you taking the time to respond, always good to have feedback,
Cheers