Posted on 11-01-2023 07:27 PM
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:
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.
Posted on 11-02-2023 05:59 AM
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.
Posted on 11-09-2023 03:41 PM
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