Onboard
macOS
devices
to
Microsoft
Intune
using
a
bash
script
that
initiates
the
process.
Optionally,
the
script
converts
mobile
accounts,
resets
the
FileVault
key,
and
removes
...
Published by
Popovici Ioan
·
Jun 25, 2024
· 2 mins read
Quick Summary
We recently encountered a scenario where we needed un-enroll all macOS devices from JAMF in bulk because the customer wanted to change the macOS management provider. This script is a result of that scenario.
To solve this issue, we created a bash script that uses the JAMF API to un-enroll the all devices from JAMF Management.
Run the script in a test environment before using it in production.
Use multiple test devices to validate your configuration.
Notes
The script logs in the /Library/Logs/${COMPANY_NAME} folder.
Notes
Although the script helped un-enroll about 2.000 devices, it cannot account for all edge cases. Please test thoroughly before using it in production.
User Defined Variables
COMPANY_NAME
This variable specifies the company name.
DISPLAY_NAME
This variable specifies the script name displayed to the user.
SUPPORTED_OS_MAJOR_VERSION
This variable specifies the last supported macOS major version.
JAMF_API_URL
This variable specifies the JAMF API URL. Used for off-boarding from JAMF.
JAMF_API_USER
This variable specifies the JAMF API user. Used for off-boarding from JAMF.
JAMF_API_PASSWORD
This variable specifies the JAMF API password. Used for off-boarding from JAMF.
JAMF_GROUP_ID
This variable specifies the JAMF Group ID used for off-boarding from JAMF.
Notes
Do not modify any other variables unless you know what you are doing.
Preview
I have no demo so instead you get this...
Code
1#!/bin/bash
2#set -x 3 4#.SYNOPSIS 5# Unmanages JAMF devices in bulk. 6#.DESCRIPTION 7# Unmanages JAMF devices in bulk, by sending the 'UnmanageDevice' command trough the JAMF API. 8#.EXAMPLE 9# bulkUnmanageJAMFDevices.sh 10#.NOTES 11# Created by Ioan Popovici 12# A JAMF search group needs to be created as a pre-requisite, with the devices you wish to unmanage, adding the serial number to the search display result so it can be queried by the script. 13# You then need to add the search group ID to the script. The script will then loop through the computer search group and send the 'UnanamgeDevice' command to the devices. 14# Return Codes: 15# 0 - Success 16# 10 - OS version not supported 17# 11 - Company Portal application not installed 18# 120 - Failed to display notification 19# 130 - Failed to display dialog 20# 131 - User cancelled dialog 21# 140 - Failed to display alert 22# 141 - User cancelled alert 23# 150 - OS version not supported 24# 200 - Failed to get JAMF API token 25# 201 - Failed to invalidate JAMF API token 26# 202 - Invalid JAMF API token action 27# 210 - Failed to perform JAMF send command action 28# 211 - Invalid JAMF device id 29#.LINK 30# https://MEM.Zone 31#.LINK 32# https://MEMZ.one/macOS-JAMF-Bulk-Unmanage 33#.LINK 34# https://MEMZ.one/macOS-JAMF-Bulk-Unmanage-CHANGELOG 35#.LINK 36# https://MEMZ.one/macOS-JAMF-Bulk-Unmanage-GIT 37#.LINK 38# https://MEM.Zone/ISSUES 39 40##*============================================= 41##* VARIABLE DECLARATION 42##*============================================= 43#region VariableDeclaration 44 45## User Defined variables 46COMPANY_NAME='MEM.Zone IT' 47DISPLAY_NAME='JAMF Bulk Unmanaging Tool' 48# Specify last supported OS major version 49SUPPORTED_OS_MAJOR_VERSION=12 50# JAMF API MDM Removal. 51JAMF_API_URL='' 52JAMF_API_USER='' 53JAMF_API_PASSWORD='' 54# JAMF Search Group ID. Please se notes above. 55JAMF_SEARCH_GROUPID='' 56 57## Script variables 58# Version 59SCRIPT_VERSION=5.0.1
60OS_VERSION=$(sw_vers -productVersion) 61# Author 62AUTHOR='Ioan Popovici' 63# Script Name 64SCRIPT_NAME=$(/usr/bin/basename "$0") 65FULL_SCRIPT_NAME="$(realpath "$(dirname "${BASH_SOURCE[0]}")")/${SCRIPT_NAME}" 66SCRIPT_NAME_WITHOUT_EXTENSION=$(basename "$0"| sed 's/\(.*\)\..*/\1/') 67# JAMF API Variables 68BEARER_TOKEN='' 69TOKEN_EXPIRATION_EPOCH=0 70# Messages 71MESSAGE_TITLE=$COMPANY_NAME 72MESSAGE_SUBTITLE=$DISPLAY_NAME 73# Logging 74LOG_NAME=$SCRIPT_NAME_WITHOUT_EXTENSION 75LOG_DIR="/Library/Logs/${COMPANY_NAME}/${DISPLAY_NAME}" 76LOG_HEADER="Script Version: $SCRIPT_VERSION \n# Author: $AUTHOR \n# OS Version: $OS_VERSION \n" 77 78#endregion 79##*============================================= 80##* END VARIABLE DECLARATION 81##*============================================= 82 83##*============================================= 84##* FUNCTION LISTINGS 85##*============================================= 86#region FunctionListings 87 88#region Function runAsRoot 89#Assigned Error Codes: 100 - 109 90function runAsRoot(){ 91#.SYNOPSIS 92# Checks for root privileges. 93#.DESCRIPTION 94# Checks for root privileges and asks for elevation. 95#.EXAMPLE 96# runAsRoot 97#.NOTES 98# This is an internal script function and should typically not be called directly. 99#.LINK100# https://MEM.Zone101#.LINK102# https://MEM.Zone/ISSUES103104## Set human readable parameters105localscriptPath="$1"106107## Check if the script is run as root108if[[$EUID -ne 0]];then109 displayNotification 'This application must be run as root. Please authenticate!'110if[[ -t 1]];then111 sudo "$scriptPath"112else113 gksu "$scriptPath"114fi115exit0116fi117}118#endregion119120#region Function checkOSVersion121122#region Function startLogging123#Assigned Error Codes: 110 - 119124function startLogging(){125#.SYNOPSIS126# Starts logging.127#.DESCRIPTION128# Starts logging to to log file and STDOUT.129#.PARAMETER logName130# Specifies the name of the log file.131#.PARAMETER logDir132# Specifies the folder of the log file.133#.PARAMETER logHeader134# Specifies additional header information to be added to the log file.135#.EXAMPLE136# startLogging "logName" "logDir" "logHeader"137#.NOTES138# This is an internal script function and should typically not be called directly.139#.LINK140# https://MEM.Zone141#.LINK142# https://MEM.Zone/ISSUES143144## Set human readable parameters145locallogName="$1"146locallogDir="$2"147locallogHeader="$3"148149## Set log file path150logFullName="${logDir}/${logName}.log"151152## Creating log directory153if[[ ! -d "$logDir"]];then154echo"$(date) | Creating '$logDir' to store logs"155 sudo mkdir -p "$logDir"156fi157158## Start logging to log file159exec&> >(sudo tee -a "$logFullName")160161## Write log header162echo""163echo"##*====================================================================================="164echo"# $(date) | Logging run of '$logName' to log file"165echo"# Log Path: '$logFullName'"166printf"# ${logHeader}"167echo"##*====================================================================================="168echo""169}170#endregion171172#region Function displayNotification173#Assigned Error Codes: 120 - 129174function displayNotification(){175#.SYNOPSIS176# Displays a notification.177#.DESCRIPTION178# Displays a notification to the user.179#.PARAMETER messageText180# Specifies the message of the notification.181#.PARAMETER messageTitle182# Specifies the title of the notification. Defaults to $MESSAGE_TITLE.183#.PARAMETER messageSubtitle184# Specifies the subtitle of the notification. Defaults to $MESSAGE_SUBTITLE.185#.PARAMETER notificationDelay186# Specifies the minimum delay between the notifications in seconds. Defaults to 2.187#.PARAMETER suppressNotification188# Suppresses the notification. Defaults to false.189#.PARAMETER suppressTerminal190# Suppresses the notification in the terminal. Defaults to false.191#.EXAMPLE192# displayNotification 'message' 'title' 'subtitle' 'duration'193#.EXAMPLE194# displayNotification 'message' 'title' 'subtitle' '' '' 'suppressTerminal'195#.EXAMPLE196# displayNotification 'message' 'title' 'subtitle' '' 'suppressNotification' ''197#.EXAMPLE198# displayNotification 'message'199#.NOTES200# This is an internal script function and should typically not be called directly.201#.LINK202# https://MEM.Zone203#.LINK204# https://MEM.Zone/ISSUES205206## Set human readable parameters207local messageText
208local messageTitle
209local messageSubtitle
210local notificationDelay
211local suppressTerminal
212local suppressNotification
213localexecutionStatus=0214# Message215messageText="${1}"216# Title217if[[ -z "${2}"]];then218messageTitle="${MESSAGE_TITLE}"219elsemessageTitle="${2}"220fi221# Subtitle222if[[ -z "${3}"]];then223messageSubtitle="${MESSAGE_SUBTITLE}"224elsemessageSubtitle="${3}"225fi226# Duration227if[[ -z "${4}"]];then228notificationDelay=2229elsenotificationDelay="${4}"230fi231# Suppress notification232if[[ -z "${5}"]];then233suppressNotification='false'234elsesuppressNotification="${5}"235fi236# Suppress terminal237if[[ -z "${6}"]];then238suppressTerminal='false'239elsesuppressTerminal="${6}"240fi241242## Debug variables243#echo "messageText: $messageText; messageTitle: $messageTitle; messageSubtitle: $messageSubtitle; notificationDelay: $notificationDelay ; suppressNotification: $suppressNotification ; suppressTerminal: $suppressTerminal"244245## Display notification246if[["$suppressNotification"='false']];then247 osascript -e "display notification \"${messageText}\" with title \"${messageTitle}\" subtitle \"${messageSubtitle}\""248executionStatus=$?249 sleep "$notificationDelay"250fi251252## Display notification in terminal253if[["$suppressTerminal"='false']];thenecho"$(date) | $messageText";fi254255## Return execution status256if[["$executionStatus" -ne 0]];then257echo"$(date) | Failed to display notification. Error: '$executionStatus'"258return120259fi260}261#endregion262263#region Function displayDialog264#Assigned Error Codes: 130 - 139265function displayDialog(){266#.SYNOPSIS267# Displays a dialog box.268#.DESCRIPTION269# Displays a dialog box with customizable buttons and optional password prompt.270#.PARAMETER messageTitle271# Specifies the title of the dialog. Defaults to $MESSAGE_TITLE.272#.PARAMETER messageText273# Specifies the message of the dialog.274#.PARAMETER messageSubtitle275# Specifies the subtitle of the notification. Defaults to $MESSAGE_SUBTITLE.276#.PARAMETER buttonNames277# Specifies the names of the buttons. Defaults to '{Cancel, Ok}'.278#.PARAMETER defaultButton279# Specifies the default button. Defaults to '1'.280#.PARAMETER cancelButton281# Specifies the button to exit on. Defaults to ''.282#.PARAMETER messageIcon283# Specifies the dialog icon as:284# * 'stop', 'note', 'caution'285# * the name of one of the system icons286# * the resource name or ID of the icon287# * the icon POSIX file path288# Defaults to ''.289#.PARAMETER promptType290# Specifies the type of prompt.291# Available options:292# 'buttonPrompt' - Button prompt.293# 'textPrompt' - Text prompt.294# 'passwordPrompt' - Password prompt.295# Defaults to 'buttonPrompt'.296#.EXAMPLE297# displayDialog 'messageTitle' 'messageSubtitle' 'messageText' '{"Ok", "Agree"}' '1' '' '' 'buttonPrompt' 'stop'298#.EXAMPLE299# displayDialog 'messageTitle' 'messageSubtitle' 'messageText' '{"Ok", "Stop"}' '1' 'Stop' '/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/FinderIcon.icns' 'textPrompt'300#.EXAMPLE301# displayDialog 'messageTitle' 'messageSubtitle' 'messageText' "{\"Ok\", \"Don't Continue\"}" '1' "Don't Continue" '/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/FinderIcon.icns' 'passwordPrompt'302#.NOTES303# This is an internal script function and should typically not be called directly.304#.LINK305# https://MEM.Zone306#.LINK307# https://MEM.Zone/ISSUES308309## Set human readable parameters310local messageTitle
311local messageSubtitle
312local messageText
313local buttonNames
314local defaultButton
315local cancelButton
316local messageIcon
317local promptType
318local commandOutput
319localexecutionStatus=0320321## Set parameter values322# Title323if[[ -z "${1}"]];then324messageTitle="${MESSAGE_TITLE}"325elsemessageTitle="${1}"326fi327# Subtitle328if[[ -z "${2}"]];then329messageSubtitle="${MESSAGE_SUBTITLE}"330elsemessageSubtitle="${2}"331fi332# Message333messageText="${3}"334# Button names335if[[ -z "${4}"]];then336buttonNames='{"Cancel", "Ok"}'337elsebuttonNames="${4}"338fi339# Default button340if[[ -z "${5}"]];then341defaultButton='1'342elsedefaultButton="${5}"343fi344# Cancel button345if[[ -z "${6}"]];then346cancelButton=''347elsecancelButton="cancel button \"${6}\""348fi349# Icon350if[[ -z "${7}"]];then351messageIcon=''352elif[["${7}"= *'/'* ]];then353messageIcon="with icon POSIX file \"${7}\""354elsemessageIcon="with icon ${7}"355fi356# Prompt type357case"${8}" in
358'buttonPrompt')359promptType='buttonPrompt'360;;361'textPrompt')362promptType='textPrompt'363;;364'passwordPrompt')365promptType='passwordPrompt'366;;367 *)368promptType='buttonPrompt'369;;370esac371372## Debug variables373#echo "messageTitle: $messageTitle; messageSubtitle: $messageSubtitle; messageText: $messageText; buttonNames: $buttonNames; defaultButton: $defaultButton; cancelButton: $cancelButton; messageIcon: $messageIcon; promptType: $promptType"374375## Display dialog box376case"$promptType" in
377'buttonPrompt')378# Display dialog with no input. Returns button pressed.379commandOutput=$(osascript -e "
380 on run
381 display dialog \"${messageSubtitle}\n${messageText}\" with title \"${messageTitle}\" buttons ${buttonNames} default button ${defaultButton}${cancelButton}${messageIcon}382 set commandOutput to button returned of the result
383 return commandOutput
384 end run
385 ")386executionStatus=$?387;;388'textPrompt')389# Display dialog with text input. Returns text.390commandOutput=$(osascript -e "
391 on run
392 display dialog \"${messageSubtitle}\n${messageText}\" default answer \"\" with title \"${messageTitle}\" with text and answer buttons ${buttonNames} default button ${defaultButton}${cancelButton}${messageIcon}393 set commandOutput to text returned of the result
394 return commandOutput
395 end run
396 ")397executionStatus=$?398;;399'passwordPrompt')400# Display dialog with hidden password input. Returns text.401commandOutput=$(osascript -e "
402 on run
403 display dialog \"${messageSubtitle}\n${messageText}\" default answer \"\" with title \"${messageTitle}\" with text and hidden answer buttons ${buttonNames} default button ${defaultButton}${cancelButton}${messageIcon}404 set commandOutput to text returned of the result
405 return commandOutput
406 end run
407 ")408executionStatus=$?409;;410esac411412## Exit on error413if[[$commandOutput= *"Error"* ]];then414 displayNotification "Failed to display alert. Error: '$commandOutput'"'''''''suppressNotification'415return130416fi417418## Return cancel if pressed419if[[$executionStatus !=0]];then420 displayNotification "User cancelled dialog."'''''''suppressNotification'421return131422fi423424## Return commandOutput. Remember to assign the result to a variable, if you print it to the terminal, it will be logged.425echo"$commandOutput"426}427#endregion428429#region Function displayAlert430#Assigned Error Codes: 140 - 149431function displayAlert(){432#.SYNOPSIS433# Displays a alert box.434#.DESCRIPTION435# Displays a alert box with customizable buttons and icon.436#.PARAMETER alertText437# Specifies the alert text.438#.PARAMETER messageText439# Specifies the message text.440#.PARAMETER alertCriticality441# Specifies the alert criticality.442# Available options:443# 'informational' - Informational alert.444# 'critical' - Critical alert.445# 'warning' - Warning alert.446# Defaults to 'informational'.447#.PARAMETER buttonNames448# Specifies the names of the buttons. Defaults to '{Cancel, Ok}'.449#.PARAMETER defaultButton450# Specifies the default button. Defaults to '1'.451#.PARAMETER cancelButton452# Specifies the button to exit on. Defaults to ''.453#.PARAMETER givingUpAfter454# Specifies the number of seconds to wait before dismissing the alert. Defaults to ''.455#.EXAMPLE456# displayAlert 'alertText' 'messageText' 'critical' "{\"Don't Continue\", \"Dismiss Alert\"}" '1' "Don't Continue" '5'457#.NOTES458# This is an internal script function and should typically not be called directly.459#.LINK460# https://MEM.Zone461#.LINK462# https://MEM.Zone/ISSUES463464## Set human readable parameters465local alertText
466local messageText
467local alertCriticality
468local buttonNames
469local defaultButton
470local cancelButton
471localgivingUpAfter=''472local commandOutput
473localexecutionStatus=0474475# Alert text476alertText="${1}"477# Message text478messageText="${2}"479# Alert criticality480case"${3}" in
481'informational')482alertCriticality='as informational'483;;484'critical')485alertCriticality='as critical'486;;487'warning')488alertCriticality='as warning'489;;490 *)491alertCriticality='informational'492;;493esac494# Button names495if[[ -z "${4}"]];then496buttonNames="{'Cance', 'Ok'}"497elsebuttonNames="${4}"498fi499# Default button500if[[ -z "${5}"]];then501defaultButton='1'502elsedefaultButton="${5}"503fi504# Cancel button505if[[ -z "${6}"]];then506cancelButton=''507elsecancelButton="cancel button \"${6}\""508fi509# Giving up after510if[[ -z "${7}"]];then511givingUpAfter=''512elsegivingUpAfter="giving up after ${7}"513fi514515## Debug variables516#echo "alertText: $alertText; messageText: $messageText; alertCriticality: $alertCriticality; buttonNames: $buttonNames; defaultButton: $defaultButton; cancelButton: $cancelButton; givingUpAfter: $givingUpAfter"517518## Display the alert.519commandOutput=$(osascript -e "
520 on run
521 display alert \"${alertText}\" message \"${messageText}\" ${alertCriticality} buttons ${buttonNames} default button ${defaultButton}${cancelButton}${givingUpAfter}522 set commandOutput to alert reply of the result
523 return commandOutput
524 end run
525 ")526executionStatus=$?527528## Exit on error529if[[$commandOutput= *"Error"* ]];then530 displayNotification "Failed to display alert. Error: '$commandOutput'"'''''''suppressNotification'531return140532fi533534## Return cancel if pressed535if[[$executionStatus !=0]];then536 displayNotification "User cancelled alert."'''''''suppressNotification'537return141538fi539540## Return commandOutput. Remember to assign the result to a variable, if you print it to the terminal, it will be logged.541echo"$commandOutput"542}543#endregion544545#region Function checkSupportedOS546#Assigned Error Codes: 150 - 159547function checkSupportedOS(){548#.SYNOPSIS549# Checks if the OS is supported.550#.DESCRIPTION551# Checks if the OS is supported and exits if it is not.552#.PARAMETER supportedOSMajorVersion553# Specify the major version of the OS to check.554#.EXAMPLE555# checkSupportedOS '13'556#.NOTES557# This is an internal script function and should typically not be called directly.558#.LINK559# https://MEM.Zone560#.LINK561# https://MEM.Zone/ISSUES562563## Set human readable parameters564localsupportedOSMajorVersion="$1"565566## Variable declaration567local macOSVersion
568local macOSMajorVersion
569local macOSAllLatestVersions
570local macOSSupportedName
571local macOSName
572573## Set variables574macOSVersion=$(sw_vers -productVersion)575macOSMajorVersion=$(echo"$macOSVersion"| cut -d'.' -f1)576577## Set display notification and alert variables578# Get all supported OS versions579macOSAllLatestVersions=$((echo"<table>"; curl -sfLS "https://support.apple.com/en-us/HT201260"\
580| tidy --tidy-mark no --char-encoding utf8 --wrap 0 --show-errors 0 --show-warnings no --clean yes --force-output yes --output-xhtml yes --quiet yes \
581| sed -e '1,/<table/d; /<\/table>/,$d' -e 's#<br />##g';echo"</table>")\
582| xmllint --html --xpath "//table/tbody/tr/td/text()" - 2>/dev/null
583)584# Get supported OS display name585macOSSupportedName=$(echo"$macOSAllLatestVersions"| awk "/^${supportedOSMajorVersion}/{getline; print}")586# Get current installed OS display name587macOSName=$(echo"$macOSAllLatestVersions"| awk "/^${macOSMajorVersion}/{getline; print}")588589## Check if OS is supported590if[["$macOSMajorVersion" -lt "$supportedOSMajorVersion"]];then591592# Display notification and alert593 displayNotification "Unsupported OS '$macOSName ($macOSVersion)', please upgrade. Terminating execution!"594 displayAlert "OS needs to be at least '$macOSSupportedName ($supportedOSMajorVersion)'"'Please upgrade and try again!''critical''{"Upgrade macOS"}'595596# Forcefully install latest OS update597 sudo softwareupdate -i -a
598exit150599else600 displayNotification "Supported OS version '$macOSName ($macOSVersion)', continuing..."601return0602fi603}604#endregion605606#region Function invokeJamfApiTokenAction607#Assigned Error Codes: 200 - 209608function invokeJamfApiTokenAction(){609#.SYNOPSIS610# Performs a JAMF API token action.611#.DESCRIPTION612# Performs a JAMF API token action, such as getting, checking validity or invalidating a token.613#.PARAMETER apiUrl614# Specifies the JAMF API server url.615#.PARAMETER apiUser616# Specifies the JAMF API username.617#.PARAMETER apiPassword618# Specifies the JAMF API password.619#.PARAMETER tokenAction620# Specifies the action to perform.621# Possible values: get, check, invalidate622#.EXAMPLE623# invokeJamfApiTokenAction 'memzone@jamfcloud.com' 'jamf-api-user' 'strongpassword' 'get'624#.NOTES625# Returns the token and the token expiration epoch in the global variables BEARER_TOKEN and TOKEN_EXPIRATION_EPOCH.626# This is an internal script function and should typically not be called directly.627#.LINK628# https://MEM.Zone629#.LINK630# https://MEM.Zone/ISSUES631#.LINK632# https://developer.jamf.com/reference/jamf-pro/633634## Variable declarations635local apiUrl
636local apiUser
637local apiPassword
638local tokenAction
639local response
640local responseCode
641local nowEpochUTC
642643## Set variable values644if[[ -z "${1}"]]||[[ -z "${2}"]]||[[ -z "${3}"]];then645apiUrl="$JAMF_API_URL"646apiUser="$JAMF_API_USER"647apiPassword="$JAMF_API_PASSWORD"648else649apiUrl="${1}"650apiUser="${2}"651apiPassword="${3}"652fi653tokenAction="${4}"654655#region Inline Functions656 getBearerToken(){657response=$(curl -s -u "$apiUser":"$apiPassword""$apiUrl"/api/v1/auth/token -X POST)658BEARER_TOKEN=$(echo"$response"| plutil -extract token raw -)659tokenExpiration=$(echo"$response"| plutil -extract expires raw - | awk -F . '{print $1}')660TOKEN_EXPIRATION_EPOCH=$(date -j -f "%Y-%m-%dT%T""$tokenExpiration" +"%s")661if[[ -z "$BEARER_TOKEN"]];then662 displayNotification "Failed to get a valid API token!"'''''''suppressNotification'663return200664else665 displayNotification "API token successfully retrieved!"'''''''suppressNotification'666fi667}668669 checkTokenExpiration(){670nowEpochUTC=$(date -j -f "%Y-%m-%dT%T""$(date -u +"%Y-%m-%dT%T")" +"%s")671if[[ TOKEN_EXPIRATION_EPOCH -gt nowEpochUTC ]];then672 displayNotification "API token valid until the following epoch time: $TOKEN_EXPIRATION_EPOCH"'''''''suppressNotification'673else674 displayNotification "No valid API token available..."'''''''suppressNotification'675 getBearerToken
676fi677}678679 invalidateToken(){680responseCode=$(curl -w "%{http_code}" -H "Authorization: Bearer ${BEARER_TOKEN}""$apiUrl"/api/v1/auth/invalidate-token -X POST -s -o /dev/null)681if[[${responseCode}==204]];then682 displayNotification "Token successfully invalidated!"'''''''suppressNotification'683BEARER_TOKEN=''684TOKEN_EXPIRATION_EPOCH=0685elif[[${responseCode}==401]];then686 displayNotification "Token already invalid!"'''''''suppressNotification'687else688 displayNotification "An unknown error occurred invalidating the token!"'''''''suppressNotification'689return201690fi691}692#endregion693694## Perform token action695case"$tokenAction" in
696 get)697 displayNotification "Getting new token..."'''''''suppressNotification'698 getBearerToken
699;;700 check)701 displayNotification "Checking token validity..."'''''''suppressNotification'702 checkTokenExpiration
703;;704 invalidate)705 displayNotification "Invalidating token..."'''''''suppressNotification'706 invalidateToken
707;;708 *)709 displayNotification "Invalid token action '$tokenAction' specified! Terminating execution..."710exit202711;;712esac713}714#endregion715716#region Function invokeSendJamfCommand717#Assigned Error Codes: 210 - 219718function invokeSendJamfCommand(){719#.SYNOPSIS720# Performs a JAMF API send command.721#.DESCRIPTION722# Performs a JAMF API send command, with the specified command and device serial number.723#.PARAMETER apiUrl724# Specifies the JAMF API server url.725#.PARAMETER apiUser726# Specifies the JAMF API username.727#.PARAMETER apiPassword728# Specifies the JAMF API password.729#.PARAMETER serialNumber730# Specifies the device serial number.731#.PARAMETER command732# Specifies the command to perform, keep in mind that you need to specify the command and the parameters in one string.733#.EXAMPLE734# invokeSendJamfCommand 'memzone@jamfcloud.com' 'jamf-api-user' 'strongpassword' 'FVFHX12QQ6LY' 'UnmanageDevice'735#.EXAMPLE736# invokeSendJamfCommand 'memzone@jamfcloud.com' 'jamf-api-user' 'strongpassword' 'FVFHX12QQ6LY' 'EraseDevice/passcode/123456'737#.NOTES738# This is an internal script function and should typically not be called directly.739#.LINK740# https://MEM.Zone741#.LINK742# https://MEM.Zone/ISSUES743#.LINK744# https://developer.jamf.com/reference/jamf-pro/745746## Variable declarations747local apiUrl
748local apiUser
749local apiPassword
750localcommand751local serialNumber
752local deviceId
753local result
754755## Set variable values756if[[ -z "${1}"]]||[[ -z "${2}"]]||[[ -z "${3}"]];then757apiUrl="$JAMF_API_URL"758apiUser="$JAMF_API_USER"759apiPassword="$JAMF_API_PASSWORD"760else761apiUrl="${1}"762apiUser="${2}"763apiPassword="${3}"764fi765serialNumber="${4}"766command="${5}"767768## Get API token769 invokeJamfApiTokenAction "$apiUrl""$apiUser""$apiPassword"'get'770771## Get JAMF device ID772deviceId=$(curl --request GET \
773 --url "${apiUrl}"/JSSResource/computers/serialnumber/"${serialNumber}"/subset/general \
774 --header 'Accept: application/xml'\
775 --header "Authorization: Bearer ${BEARER_TOKEN}"\
776 --silent --show-error --fail | xmllint --xpath '//computer/general/id/text()' -
777)778779## Perform action780if[[$deviceId -gt 0]];then781result=$(curl -s -o /dev/null -I -w "%{http_code}"\
782 --request POST \
783 --url "${apiUrl}"/JSSResource/computercommands/command/"${command}"/id/"${deviceId}"\
784 --header 'Content-Type: application/xml'\
785 --header "Authorization: Bearer ${BEARER_TOKEN}"\
786)787## Check result (201 = Created/Success)788if[[$result -eq 201]];then789 displayNotification "Successfully performed command '${command}' on device '${serialNumber} [${deviceId}]'!"'''''''suppressNotification'790return0791else792 displayNotification "Failed to perform command '${command}' on device '${serialNumber} [${deviceId}]'!"'''''''suppressNotification'793return210794fi795else796 displayNotification "Invalid device id '${deviceId} [${serialNumber}]'. Skipping '${command}'..."'''''''suppressNotification'797return211798fi799}800#endregion801802#endregion803##*=============================================804##* END FUNCTION LISTINGS805##*=============================================806807##*=============================================808##* SCRIPT BODY809##*=============================================810#region ScriptBody811812## Check if script is running as root813runAsRoot "$FULL_SCRIPT_NAME"814815## Start logging816startLogging "$LOG_NAME""$LOG_DIR""$LOG_HEADER"817818## Show script version and suppress terminal output819displayNotification "Running $SCRIPT_NAME version $SCRIPT_VERSION"'''''''''suppressTerminal'820821## Check if OS is supported822checkSupportedOS "$SUPPORTED_OS_MAJOR_VERSION"823824## Get API token825invokeJamfApiTokenAction "$apiUrl""$apiUser""$apiPassword"'get'826827## Get all devices in a specific advanced search group828computerList=$(curl --request GET \
829 --url "https://visma.jamfcloud.com/JSSResource/advancedcomputersearches/id/${JAMF_SEARCH_GROUPID}"\
830 --header 'Accept: application/xml'\
831 --header "Authorization: Bearer ${BEARER_TOKEN}"\
832 --silent --show-error --fail | xmllint --xpath '//Serial_Number/text() ' -
833)834835for computer in $computerList;do836 invokeSendJamfCommand "$apiUrl""$apiUser""$apiPassword""$computer""UnmanageDevice"837done838839## Invalidate API token840invokeJamfApiTokenAction "$apiUrl""$apiUser""$apiPassword"'invalidate'841842#endregion843##*=============================================844##* END SCRIPT BODY845##*=============================================
👍 Liked this? Share it with your team and consider subscribing and following—it helps us keep the good stuff coming. 😎
SHARE
Message Sent!
Thank you for your interest, we’ll get back to you as soon as possible!
Onboard
macOS
devices
to
Microsoft
Intune
using
a
bash
script
that
initiates
the
process.
Optionally,
the
script
converts
mobile
accounts,
resets
the
FileVault
key,
and
removes
...