Dark mode

Dark mode

There are 0 results matching

article card image dark article card image light

Published by · Jun 25, 2024 tools · 2 mins read

Introducing: macOS JAMF Offboarding Tool

Offboarding macOS Devices from JAMF in Bulk using the JAMF API with a bash script ...

See More
article card image dark article card image light

Published by · Jun 25, 2024 tools · 2 mins read

Introducing: Microsoft Cloud License Automation Tool - Part 1

Automating Microsoft Cloud License Assignment and Reporting with PowerShell and Slack for Enterprise Mobility and Security E3 ...

See More
article card image dark article card image light

Published by · Jun 18, 2024 tools · 2 mins read

Introducing: Configuration Manager Set Implicit Uninstall Flag Tool

Setting Configuration Manager ConfigMgr Implicit Uninstall Flag with PowerShell for Required Application Deployments ...

See More
article card image dark article card image light

Published by · Jun 11, 2024 configmgr · 2 mins read

Configuration Manager Next Maintenance Window SQL Function

Get Next Configuration Manager Maintenance Window from a Schedule Token with Offset Days using an SQL Function. ...

See More
article card image dark article card image light

Published by · Jun 3, 2024 tools · 2 mins read

Introducing: Windows User Rights Assignment Tool - Part 3

Add, Remove, or Replace Windows Rights Assignment with our PowerShell Tool. ...

See More
article card image dark article card image light

Published by · May 28, 2024 tools · 2 mins read

Introducing: Windows User Rights Assignment Tool - Part 2

Get and Report Windows Rights Assignment with our PowerShell Tool. ...

See More
article card image dark article card image light

Published by · May 22, 2024 tools · 1 mins read

Introducing: Windows User Rights Assignment Tool - Part 1

Get Windows Rights Assignment with our PowerShell Tool. ...

See More
article card image dark article card image light

Published by · Apr 11, 2024 tools · 2 mins read

Introducing: Intune Linux Onboarding Tool

Onboard Ubuntu Linux devices to Microsoft Intune using a bash script. Installs prerequisites and starts the user-driven enrollment. ...

See More
article card image dark article card image light

Published by · Apr 11, 2024 tools · 2 mins read

Introducing: Intune macOS Onboarding Tool

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 ...

See More
article card image dark article card image light

Published by · Jan 23, 2024 tools · 3 mins read

Introducing: Intune Device Renaming Tool

Rename Intune Devices by setting a Prefix or using a User Attribute as Prefix. Supports Windows, macOS, and Linux ...

See More
article card image dark article card image light

Published by · Dec 8, 2023 intune · 5 mins read

Intune Logs: A Deep Dive into Locations, Interpretation, and Configuration

A Comprehensive Guide to Locations, Interpretation, and Configuration of Intune Logs ...

See More
article card image dark article card image light

Published by · Aug 14, 2023 configmgr · 2 mins read

Configuration Manager Console Extension to show Device Collection Membership with Console Builder

Use the Configuration Manager Console Builder, to add Collection Membership View to the Device Node ...

See More
article card image dark article card image light

Published by · Aug 3, 2023 tools · 3 mins read

Introducing: Configuration Manager SSRS Dashboards

A Configuration Manager Dashboards solution with Reports for Software Updates, Bitlocker and more ...

See More
article card image dark article card image light

Published by · Aug 3, 2023 tools · 2 mins read

Introducing: PowerShell WMI Management Toolkit Module

Streamline your WMI Namespace, Class, and Instance Management with our PowerShell Module ...

See More
article card image dark article card image light

Published by · Jul 14, 2023 configmgr · 1 mins read

Configuration Manager detailed, filterable Port Documentation

Configuration Manager detailed, filterable port documentation as an excel document ...

See More
article card image dark article card image light

Published by · Jul 14, 2023 configmgr · 3 mins read

Configuration Manager PXE TFTP Window Size Bug

Configuration Manager TFTP Block Size and TFTP Window Size Correct Configuration ...

See More
article card image dark article card image light

Published by · Jun 18, 2023 tools · 4 mins read

Introducing: Configuration Manager Client Cache Cleanup Tool

Cleaning the Configuration Manager Client Cache the Right Way with PowerShell and Configuration Baselines ...

See More
article card image dark article card image light

Published by · Jun 18, 2023 tools · 2 mins read

Introducing: Windows Cache Cleanup Tool

Cleaning Windows and Configuration Manager Caches for Configuration Manager Build and Capture Task Sequence or Standalone Use ...

See More
article card image dark article card image light

Published by · Jun 17, 2023 tools · 1 mins read

Introducing: Windows Update Database Reinitialization Tool

Proactively repair corrupted Windows Update Database with Powershell and Configuration Manager ...

See More
article card image dark article card image light

Published by · Mar 31, 2023 tools · 3 mins read

Introducing: Configuration Manager SQL Products Reporting

A Complete SQL Products reporting solution using Configuration Manager ...

See More
article card image dark article card image light

Published by · Jan 28, 2023 configmgr · 1 mins read

Application Detection Method using the Configuration Manager Application Version

Replace hardcoded application version in scripts, with the Configuration Manager Application Version ...

See More
article card image dark article card image light

Published by · Jan 28, 2023 tools · 3 mins read

Introducing: Certificate Management Toolkit

Managing Certificates with Configuration Manager and PowerShell by using just the Public Key ...

See More
article card image dark article card image light

Published by · Jan 7, 2019 reports · 2 mins read

Configuration Manager Device Boundary and Network Information Report

List Device Boundaries and Network Information with Configuration Manager ...

See More
article card image dark article card image light

Published by · Sep 9, 1980 help · 5 mins read

MEM.Zone Blog Publishing Documentation

Publishing Documentation for MEM.Zone ...

See More

We couldn’t find anything related to

“SCCM”

BLOG / tools zone

Introducing: macOS JAMF Offboarding Tool

Published by Popovici Ioan · Jun 25, 2024 · 2 mins read
article card image dark article card image light

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.

Prerequisites

  • JAMF Instance
  • JAMF Group with the devices to un-enroll
  • JAMF API Access
  • Bash Script

Recommendations

  • 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

article card image demo-macos-jamf-offboarding-tool
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#.LINK
100#    https://MEM.Zone
101#.LINK
102#    https://MEM.Zone/ISSUES
103
104    ## Set human readable parameters
105    local scriptPath="$1"
106
107    ## Check if the script is run as root
108    if [[ $EUID -ne 0 ]]; then
109        displayNotification 'This application must be run as root. Please authenticate!'
110        if [[ -t 1 ]]; then
111            sudo "$scriptPath"
112        else
113            gksu "$scriptPath"
114        fi
115        exit 0
116    fi
117}
118#endregion
119
120#region Function checkOSVersion
121
122#region Function startLogging
123#Assigned Error Codes: 110 - 119
124function startLogging() {
125#.SYNOPSIS
126#    Starts logging.
127#.DESCRIPTION
128#    Starts logging to to log file and STDOUT.
129#.PARAMETER logName
130#    Specifies the name of the log file.
131#.PARAMETER logDir
132#    Specifies the folder of the log file.
133#.PARAMETER logHeader
134#    Specifies additional header information to be added to the log file.
135#.EXAMPLE
136#    startLogging "logName" "logDir" "logHeader"
137#.NOTES
138#    This is an internal script function and should typically not be called directly.
139#.LINK
140#    https://MEM.Zone
141#.LINK
142#    https://MEM.Zone/ISSUES
143
144    ## Set human readable parameters
145    local logName="$1"
146    local logDir="$2"
147    local logHeader="$3"
148
149    ## Set log file path
150    logFullName="${logDir}/${logName}.log"
151
152    ## Creating log directory
153    if [[ ! -d "$logDir" ]]; then
154        echo "$(date) | Creating '$logDir' to store logs"
155        sudo mkdir -p "$logDir"
156    fi
157
158    ## Start logging to log file
159    exec &> >(sudo tee -a "$logFullName")
160
161    ## Write log header
162    echo   ""
163    echo   "##*====================================================================================="
164    echo   "# $(date) | Logging run of '$logName' to log file"
165    echo   "# Log Path: '$logFullName'"
166    printf "# ${logHeader}"
167    echo   "##*====================================================================================="
168    echo   ""
169}
170#endregion
171
172#region Function displayNotification
173#Assigned Error Codes: 120 - 129
174function displayNotification() {
175#.SYNOPSIS
176#    Displays a notification.
177#.DESCRIPTION
178#    Displays a notification to the user.
179#.PARAMETER messageText
180#    Specifies the message of the notification.
181#.PARAMETER messageTitle
182#    Specifies the title of the notification. Defaults to $MESSAGE_TITLE.
183#.PARAMETER messageSubtitle
184#    Specifies the subtitle of the notification. Defaults to $MESSAGE_SUBTITLE.
185#.PARAMETER notificationDelay
186#    Specifies the minimum delay between the notifications in seconds. Defaults to 2.
187#.PARAMETER suppressNotification
188#    Suppresses the notification. Defaults to false.
189#.PARAMETER suppressTerminal
190#    Suppresses the notification in the terminal. Defaults to false.
191#.EXAMPLE
192#    displayNotification 'message' 'title' 'subtitle' 'duration'
193#.EXAMPLE
194#    displayNotification 'message' 'title' 'subtitle' '' '' 'suppressTerminal'
195#.EXAMPLE
196#    displayNotification 'message' 'title' 'subtitle' '' 'suppressNotification' ''
197#.EXAMPLE
198#    displayNotification 'message'
199#.NOTES
200#    This is an internal script function and should typically not be called directly.
201#.LINK
202#    https://MEM.Zone
203#.LINK
204#    https://MEM.Zone/ISSUES
205
206    ## Set human readable parameters
207    local messageText
208    local messageTitle
209    local messageSubtitle
210    local notificationDelay
211    local suppressTerminal
212    local suppressNotification
213    local executionStatus=0
214    #  Message
215    messageText="${1}"
216    #  Title
217    if [[ -z "${2}" ]]; then
218        messageTitle="${MESSAGE_TITLE}"
219    else messageTitle="${2}"
220    fi
221    #  Subtitle
222    if [[ -z "${3}" ]]; then
223        messageSubtitle="${MESSAGE_SUBTITLE}"
224    else messageSubtitle="${3}"
225    fi
226    #  Duration
227    if [[ -z "${4}" ]]; then
228        notificationDelay=2
229    else notificationDelay="${4}"
230    fi
231    #  Suppress notification
232    if [[ -z "${5}" ]]; then
233        suppressNotification='false'
234    else suppressNotification="${5}"
235    fi
236    #  Suppress terminal
237    if [[ -z "${6}" ]]; then
238        suppressTerminal='false'
239    else suppressTerminal="${6}"
240    fi
241
242    ## Debug variables
243    #echo "messageText: $messageText; messageTitle: $messageTitle; messageSubtitle: $messageSubtitle; notificationDelay: $notificationDelay ; suppressNotification: $suppressNotification ; suppressTerminal: $suppressTerminal"
244
245    ## Display notification
246    if [[ "$suppressNotification" = 'false' ]]; then
247        osascript -e "display notification \"${messageText}\" with title \"${messageTitle}\" subtitle \"${messageSubtitle}\""
248        executionStatus=$?
249        sleep "$notificationDelay"
250    fi
251
252    ## Display notification in terminal
253    if [[ "$suppressTerminal" = 'false' ]]; then echo "$(date) | $messageText" ; fi
254
255    ## Return execution status
256    if [[ "$executionStatus" -ne 0 ]]; then
257        echo "$(date) | Failed to display notification. Error: '$executionStatus'"
258        return 120
259    fi
260}
261#endregion
262
263#region Function displayDialog
264#Assigned Error Codes: 130 - 139
265function displayDialog() {
266#.SYNOPSIS
267#    Displays a dialog box.
268#.DESCRIPTION
269#    Displays a dialog box with customizable buttons and optional password prompt.
270#.PARAMETER messageTitle
271#    Specifies the title of the dialog. Defaults to $MESSAGE_TITLE.
272#.PARAMETER messageText
273#    Specifies the message of the dialog.
274#.PARAMETER messageSubtitle
275#    Specifies the subtitle of the notification. Defaults to $MESSAGE_SUBTITLE.
276#.PARAMETER buttonNames
277#    Specifies the names of the buttons. Defaults to '{Cancel, Ok}'.
278#.PARAMETER defaultButton
279#    Specifies the default button. Defaults to '1'.
280#.PARAMETER cancelButton
281#    Specifies the button to exit on. Defaults to ''.
282#.PARAMETER messageIcon
283#    Specifies the dialog icon as:
284#       * 'stop', 'note', 'caution'
285#       * the name of one of the system icons
286#       * the resource name or ID of the icon
287#       * the icon POSIX file path
288#   Defaults to ''.
289#.PARAMETER promptType
290#    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#.EXAMPLE
297#    displayDialog 'messageTitle' 'messageSubtitle' 'messageText' '{"Ok", "Agree"}' '1' '' '' 'buttonPrompt' 'stop'
298#.EXAMPLE
299#    displayDialog 'messageTitle' 'messageSubtitle' 'messageText' '{"Ok", "Stop"}' '1' 'Stop' '/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/FinderIcon.icns' 'textPrompt'
300#.EXAMPLE
301#    displayDialog 'messageTitle' 'messageSubtitle' 'messageText' "{\"Ok\", \"Don't Continue\"}" '1' "Don't Continue" '/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/FinderIcon.icns' 'passwordPrompt'
302#.NOTES
303#    This is an internal script function and should typically not be called directly.
304#.LINK
305#    https://MEM.Zone
306#.LINK
307#    https://MEM.Zone/ISSUES
308
309    ## Set human readable parameters
310    local messageTitle
311    local messageSubtitle
312    local messageText
313    local buttonNames
314    local defaultButton
315    local cancelButton
316    local messageIcon
317    local promptType
318    local commandOutput
319    local executionStatus=0
320
321    ## Set parameter values
322    #  Title
323    if [[ -z "${1}" ]] ; then
324        messageTitle="${MESSAGE_TITLE}"
325    else messageTitle="${1}"
326    fi
327    #  Subtitle
328    if [[ -z "${2}" ]] ; then
329        messageSubtitle="${MESSAGE_SUBTITLE}"
330    else messageSubtitle="${2}"
331    fi
332    #  Message
333    messageText="${3}"
334    #  Button names
335    if [[ -z "${4}" ]] ; then
336        buttonNames='{"Cancel", "Ok"}'
337    else buttonNames="${4}"
338    fi
339    #  Default button
340    if [[ -z "${5}" ]] ; then
341        defaultButton='1'
342    else defaultButton="${5}"
343    fi
344    #  Cancel button
345    if [[ -z "${6}" ]] ; then
346        cancelButton=''
347    else cancelButton="cancel button \"${6}\""
348    fi
349    #  Icon
350    if [[ -z "${7}" ]] ; then
351        messageIcon=''
352    elif [[ "${7}" = *'/'* ]] ; then
353        messageIcon="with icon POSIX file \"${7}\""
354    else messageIcon="with icon ${7}"
355    fi
356    #  Prompt type
357    case "${8}" in
358        'buttonPrompt')
359            promptType='buttonPrompt'
360        ;;
361        'textPrompt')
362            promptType='textPrompt'
363        ;;
364        'passwordPrompt')
365            promptType='passwordPrompt'
366        ;;
367        *)
368            promptType='buttonPrompt'
369        ;;
370    esac
371
372    ## Debug variables
373    #echo "messageTitle: $messageTitle; messageSubtitle: $messageSubtitle; messageText: $messageText; buttonNames: $buttonNames; defaultButton: $defaultButton; cancelButton: $cancelButton; messageIcon: $messageIcon; promptType: $promptType"
374
375    ## Display dialog box
376    case "$promptType" in
377        'buttonPrompt')
378            #  Display dialog with no input. Returns button pressed.
379            commandOutput=$(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            ")
386            executionStatus=$?
387        ;;
388        'textPrompt')
389            #  Display dialog with text input. Returns text.
390            commandOutput=$(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            ")
397            executionStatus=$?
398        ;;
399        'passwordPrompt')
400            #  Display dialog with hidden password input. Returns text.
401            commandOutput=$(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            ")
408            executionStatus=$?
409        ;;
410    esac
411
412    ## Exit on error
413    if [[ $commandOutput = *"Error"* ]] ; then
414        displayNotification "Failed to display alert. Error: '$commandOutput'" '' '' '' 'suppressNotification'
415        return 130
416    fi
417
418    ## Return cancel if pressed
419    if [[ $executionStatus != 0 ]] ; then
420        displayNotification "User cancelled dialog." '' '' '' 'suppressNotification'
421        return 131
422    fi
423
424    ## Return commandOutput. Remember to assign the result to a variable, if you print it to the terminal, it will be logged.
425    echo "$commandOutput"
426}
427#endregion
428
429#region Function displayAlert
430#Assigned Error Codes: 140 - 149
431function displayAlert() {
432#.SYNOPSIS
433#    Displays a alert box.
434#.DESCRIPTION
435#    Displays a alert box with customizable buttons and icon.
436#.PARAMETER alertText
437#    Specifies the alert text.
438#.PARAMETER messageText
439#    Specifies the message text.
440#.PARAMETER alertCriticality
441#    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 buttonNames
448#    Specifies the names of the buttons. Defaults to '{Cancel, Ok}'.
449#.PARAMETER defaultButton
450#    Specifies the default button. Defaults to '1'.
451#.PARAMETER cancelButton
452#    Specifies the button to exit on. Defaults to ''.
453#.PARAMETER givingUpAfter
454#    Specifies the number of seconds to wait before dismissing the alert. Defaults to ''.
455#.EXAMPLE
456#   displayAlert 'alertText' 'messageText' 'critical' "{\"Don't Continue\", \"Dismiss Alert\"}" '1' "Don't Continue" '5'
457#.NOTES
458#    This is an internal script function and should typically not be called directly.
459#.LINK
460#    https://MEM.Zone
461#.LINK
462#    https://MEM.Zone/ISSUES
463
464    ## Set human readable parameters
465    local alertText
466    local messageText
467    local alertCriticality
468    local buttonNames
469    local defaultButton
470    local cancelButton
471    local givingUpAfter=''
472    local commandOutput
473    local executionStatus=0
474
475    #  Alert text
476    alertText="${1}"
477    #  Message text
478    messageText="${2}"
479    #  Alert criticality
480    case "${3}" in
481        'informational')
482            alertCriticality='as informational'
483        ;;
484        'critical')
485            alertCriticality='as critical'
486        ;;
487        'warning')
488            alertCriticality='as warning'
489        ;;
490        *)
491            alertCriticality='informational'
492        ;;
493    esac
494    #  Button names
495    if [[ -z "${4}" ]] ; then
496        buttonNames="{'Cance', 'Ok'}"
497    else buttonNames="${4}"
498    fi
499    #  Default button
500    if [[ -z "${5}" ]] ; then
501        defaultButton='1'
502    else defaultButton="${5}"
503    fi
504    #  Cancel button
505    if [[ -z "${6}" ]] ; then
506        cancelButton=''
507    else cancelButton="cancel button \"${6}\""
508    fi
509    #  Giving up after
510    if [[ -z "${7}" ]] ; then
511        givingUpAfter=''
512    else givingUpAfter="giving up after ${7}"
513    fi
514
515    ## Debug variables
516    #echo "alertText: $alertText; messageText: $messageText; alertCriticality: $alertCriticality; buttonNames: $buttonNames; defaultButton: $defaultButton; cancelButton: $cancelButton; givingUpAfter: $givingUpAfter"
517
518    ## Display the alert.
519    commandOutput=$(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    ")
526    executionStatus=$?
527
528    ## Exit on error
529    if [[ $commandOutput = *"Error"* ]] ; then
530        displayNotification "Failed to display alert. Error: '$commandOutput'" '' '' '' 'suppressNotification'
531        return 140
532    fi
533
534    ## Return cancel if pressed
535    if [[ $executionStatus != 0 ]] ; then
536        displayNotification "User cancelled alert." '' '' '' 'suppressNotification'
537        return 141
538    fi
539
540    ## Return commandOutput. Remember to assign the result to a variable, if you print it to the terminal, it will be logged.
541    echo "$commandOutput"
542}
543#endregion
544
545#region Function checkSupportedOS
546#Assigned Error Codes: 150 - 159
547function checkSupportedOS() {
548#.SYNOPSIS
549#    Checks if the OS is supported.
550#.DESCRIPTION
551#    Checks if the OS is supported and exits if it is not.
552#.PARAMETER supportedOSMajorVersion
553#    Specify the major version of the OS to check.
554#.EXAMPLE
555#    checkSupportedOS '13'
556#.NOTES
557#    This is an internal script function and should typically not be called directly.
558#.LINK
559#    https://MEM.Zone
560#.LINK
561#    https://MEM.Zone/ISSUES
562
563    ## Set human readable parameters
564    local supportedOSMajorVersion="$1"
565
566    ## Variable declaration
567    local macOSVersion
568    local macOSMajorVersion
569    local macOSAllLatestVersions
570    local macOSSupportedName
571    local macOSName
572
573    ## Set variables
574    macOSVersion=$(sw_vers -productVersion)
575    macOSMajorVersion=$(echo "$macOSVersion" | cut -d'.' -f1)
576
577    ## Set display notification and alert variables
578    #  Get all supported OS versions
579    macOSAllLatestVersions=$( (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 name
585    macOSSupportedName=$(echo "$macOSAllLatestVersions" | awk "/^${supportedOSMajorVersion}/{getline; print}")
586    #  Get current installed OS display name
587    macOSName=$(echo "$macOSAllLatestVersions" | awk "/^${macOSMajorVersion}/{getline; print}")
588
589    ## Check if OS is supported
590    if [[ "$macOSMajorVersion" -lt "$supportedOSMajorVersion" ]] ; then
591
592        #  Display notification and alert
593        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"}'
595
596        #  Forcefully install latest OS update
597        sudo softwareupdate -i -a
598        exit 150
599    else
600        displayNotification "Supported OS version '$macOSName ($macOSVersion)', continuing..."
601        return 0
602    fi
603}
604#endregion
605
606#region Function invokeJamfApiTokenAction
607#Assigned Error Codes: 200 - 209
608function invokeJamfApiTokenAction() {
609#.SYNOPSIS
610#    Performs a JAMF API token action.
611#.DESCRIPTION
612#    Performs a JAMF API token action, such as getting, checking validity or invalidating a token.
613#.PARAMETER apiUrl
614#    Specifies the JAMF API server url.
615#.PARAMETER apiUser
616#    Specifies the JAMF API username.
617#.PARAMETER apiPassword
618#    Specifies the JAMF API password.
619#.PARAMETER tokenAction
620#    Specifies the action to perform.
621#    Possible values: get, check, invalidate
622#.EXAMPLE
623#    invokeJamfApiTokenAction '[email protected]' 'jamf-api-user' 'strongpassword' 'get'
624#.NOTES
625#    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#.LINK
628#    https://MEM.Zone
629#.LINK
630#    https://MEM.Zone/ISSUES
631#.LINK
632#    https://developer.jamf.com/reference/jamf-pro/
633
634    ## Variable declarations
635    local apiUrl
636    local apiUser
637    local apiPassword
638    local tokenAction
639    local response
640    local responseCode
641    local nowEpochUTC
642
643    ## Set variable values
644    if [[ -z "${1}" ]] || [[ -z "${2}" ]] || [[ -z "${3}" ]] ; then
645        apiUrl="$JAMF_API_URL"
646        apiUser="$JAMF_API_USER"
647        apiPassword="$JAMF_API_PASSWORD"
648    else
649        apiUrl="${1}"
650        apiUser="${2}"
651        apiPassword="${3}"
652    fi
653    tokenAction="${4}"
654
655    #region Inline Functions
656    getBearerToken() {
657        response=$(curl -s -u "$apiUser":"$apiPassword" "$apiUrl"/api/v1/auth/token -X POST)
658        BEARER_TOKEN=$(echo "$response" | plutil -extract token raw -)
659        tokenExpiration=$(echo "$response" | plutil -extract expires raw - | awk -F . '{print $1}')
660        TOKEN_EXPIRATION_EPOCH=$(date -j -f "%Y-%m-%dT%T" "$tokenExpiration" +"%s")
661        if [[ -z "$BEARER_TOKEN" ]] ; then
662            displayNotification "Failed to get a valid API token!" '' '' '' 'suppressNotification'
663            return 200
664        else
665            displayNotification "API token successfully retrieved!" '' '' '' 'suppressNotification'
666        fi
667    }
668
669    checkTokenExpiration() {
670        nowEpochUTC=$(date -j -f "%Y-%m-%dT%T" "$(date -u +"%Y-%m-%dT%T")" +"%s")
671        if [[ TOKEN_EXPIRATION_EPOCH -gt nowEpochUTC ]] ; then
672            displayNotification "API token valid until the following epoch time: $TOKEN_EXPIRATION_EPOCH" '' '' '' 'suppressNotification'
673        else
674            displayNotification "No valid API token available..." '' '' '' 'suppressNotification'
675            getBearerToken
676        fi
677    }
678
679    invalidateToken() {
680        responseCode=$(curl -w "%{http_code}" -H "Authorization: Bearer ${BEARER_TOKEN}" "$apiUrl"/api/v1/auth/invalidate-token -X POST -s -o /dev/null)
681        if [[ ${responseCode} == 204 ]] ; then
682            displayNotification "Token successfully invalidated!" '' '' '' 'suppressNotification'
683            BEARER_TOKEN=''
684            TOKEN_EXPIRATION_EPOCH=0
685        elif [[ ${responseCode} == 401 ]] ; then
686            displayNotification "Token already invalid!" '' '' '' 'suppressNotification'
687        else
688            displayNotification "An unknown error occurred invalidating the token!" '' '' '' 'suppressNotification'
689            return 201
690        fi
691    }
692    #endregion
693
694    ## Perform token action
695    case "$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..."
710            exit 202
711            ;;
712    esac
713}
714#endregion
715
716#region Function invokeSendJamfCommand
717#Assigned Error Codes: 210 - 219
718function invokeSendJamfCommand() {
719#.SYNOPSIS
720#    Performs a JAMF API send command.
721#.DESCRIPTION
722#    Performs a JAMF API send command, with the specified command and device serial number.
723#.PARAMETER apiUrl
724#    Specifies the JAMF API server url.
725#.PARAMETER apiUser
726#    Specifies the JAMF API username.
727#.PARAMETER apiPassword
728#    Specifies the JAMF API password.
729#.PARAMETER serialNumber
730#    Specifies the device serial number.
731#.PARAMETER command
732#    Specifies the command to perform, keep in mind that you need to specify the command and the parameters in one string.
733#.EXAMPLE
734#    invokeSendJamfCommand '[email protected]' 'jamf-api-user' 'strongpassword' 'FVFHX12QQ6LY' 'UnmanageDevice'
735#.EXAMPLE
736#    invokeSendJamfCommand '[email protected]' 'jamf-api-user' 'strongpassword' 'FVFHX12QQ6LY' 'EraseDevice/passcode/123456'
737#.NOTES
738#    This is an internal script function and should typically not be called directly.
739#.LINK
740#    https://MEM.Zone
741#.LINK
742#    https://MEM.Zone/ISSUES
743#.LINK
744#    https://developer.jamf.com/reference/jamf-pro/
745
746    ## Variable declarations
747    local apiUrl
748    local apiUser
749    local apiPassword
750    local command
751    local serialNumber
752    local deviceId
753    local result
754
755    ## Set variable values
756    if [[ -z "${1}" ]] || [[ -z "${2}" ]] || [[ -z "${3}" ]] ; then
757        apiUrl="$JAMF_API_URL"
758        apiUser="$JAMF_API_USER"
759        apiPassword="$JAMF_API_PASSWORD"
760    else
761        apiUrl="${1}"
762        apiUser="${2}"
763        apiPassword="${3}"
764    fi
765    serialNumber="${4}"
766    command="${5}"
767
768    ## Get API token
769    invokeJamfApiTokenAction "$apiUrl" "$apiUser" "$apiPassword" 'get'
770
771    ## Get JAMF device ID
772    deviceId=$(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    )
778
779    ## Perform action
780    if [[ $deviceId -gt 0 ]]; then
781       result=$(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)
788        if [[ $result -eq 201 ]]; then
789            displayNotification "Successfully performed command '${command}' on device '${serialNumber} [${deviceId}]'!" '' '' '' 'suppressNotification'
790            return 0
791        else
792            displayNotification "Failed to perform command '${command}' on device '${serialNumber} [${deviceId}]'!" '' '' '' 'suppressNotification'
793            return 210
794        fi
795    else
796        displayNotification "Invalid device id '${deviceId} [${serialNumber}]'. Skipping '${command}'..." '' '' '' 'suppressNotification'
797        return 211
798    fi
799}
800#endregion
801
802#endregion
803##*=============================================
804##* END FUNCTION LISTINGS
805##*=============================================
806
807##*=============================================
808##* SCRIPT BODY
809##*=============================================
810#region ScriptBody
811
812## Check if script is running as root
813runAsRoot "$FULL_SCRIPT_NAME"
814
815## Start logging
816startLogging "$LOG_NAME" "$LOG_DIR" "$LOG_HEADER"
817
818## Show script version and suppress terminal output
819displayNotification "Running $SCRIPT_NAME version $SCRIPT_VERSION" '' '' '' '' 'suppressTerminal'
820
821## Check if OS is supported
822checkSupportedOS "$SUPPORTED_OS_MAJOR_VERSION"
823
824## Get API token
825invokeJamfApiTokenAction "$apiUrl" "$apiUser" "$apiPassword" 'get'
826
827## Get all devices in a specific advanced search group
828computerList=$(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)
834
835for computer in $computerList ; do
836    invokeSendJamfCommand "$apiUrl" "$apiUser" "$apiPassword" "$computer" "UnmanageDevice"
837done
838
839## Invalidate API token
840invokeJamfApiTokenAction "$apiUrl" "$apiUser" "$apiPassword" 'invalidate'
841
842#endregion
843##*=============================================
844##* END SCRIPT BODY
845##*=============================================

SHARE

article card image dark article card image light

Published by · Apr 11, 2024 tools · 2 mins read

Introducing: Intune Linux Onboarding Tool

Onboard Ubuntu Linux devices to Microsoft Intune using a bash script. Installs prerequisites and starts the user-driven enrollment. ...

See More
article card image dark article card image light

Published by · Apr 11, 2024 tools · 2 mins read

Introducing: Intune macOS Onboarding Tool

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 ...

See More