├── .gitignore ├── PreStage ├── payload │ ├── var │ │ └── tmp │ │ │ ├── eacs_menu_full.png │ │ │ ├── eacs_menu_half.png │ │ │ ├── eacs_menu_bar_full.png │ │ │ ├── eacs_menu_bar_half.png │ │ │ ├── 001_post_enrolment.sh │ │ │ └── com.github.smithjw.mac.swiftEnrolment.sh │ └── Library │ │ ├── Management │ │ └── Images │ │ │ ├── new_mac.jpg │ │ │ └── company_logo.png │ │ └── LaunchDaemons │ │ └── com.github.smithjw.mac.swiftEnrolment.plist ├── scripts │ ├── postinstall │ └── preinstall └── build-pkg ├── .vscode └── settings.json ├── README.md ├── 000_enrolment.sh └── _user_walkthrough.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pkg 2 | .DS_Store 3 | .vscode 4 | -------------------------------------------------------------------------------- /PreStage/payload/var/tmp/eacs_menu_full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smithjw/swiftEnrolment/HEAD/PreStage/payload/var/tmp/eacs_menu_full.png -------------------------------------------------------------------------------- /PreStage/payload/var/tmp/eacs_menu_half.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smithjw/swiftEnrolment/HEAD/PreStage/payload/var/tmp/eacs_menu_half.png -------------------------------------------------------------------------------- /PreStage/payload/var/tmp/eacs_menu_bar_full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smithjw/swiftEnrolment/HEAD/PreStage/payload/var/tmp/eacs_menu_bar_full.png -------------------------------------------------------------------------------- /PreStage/payload/var/tmp/eacs_menu_bar_half.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smithjw/swiftEnrolment/HEAD/PreStage/payload/var/tmp/eacs_menu_bar_half.png -------------------------------------------------------------------------------- /PreStage/payload/Library/Management/Images/new_mac.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smithjw/swiftEnrolment/HEAD/PreStage/payload/Library/Management/Images/new_mac.jpg -------------------------------------------------------------------------------- /PreStage/payload/Library/Management/Images/company_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smithjw/swiftEnrolment/HEAD/PreStage/payload/Library/Management/Images/company_logo.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorTheme": "Nord", 3 | "workbench.preferredDarkColorTheme": "Nord", 4 | "workbench.preferredHighContrastColorTheme": "Nord" 5 | } 6 | -------------------------------------------------------------------------------- /PreStage/payload/Library/LaunchDaemons/com.github.smithjw.mac.swiftEnrolment.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Label 6 | com.github.smithjw.mac.swiftEnrolment 7 | RunAtLoad 8 | 9 | UserName 10 | root 11 | ProgramArguments 12 | 13 | /bin/bash 14 | /var/tmp/com.github.smithjw.mac.swiftEnrolment.sh 15 | 16 | StandardErrorPath 17 | /var/log/com.github.smithjw.mac.swiftEnrolment.log 18 | StandardOutPath 19 | /var/log/com.github.smithjw.mac.swiftEnrolment.log 20 | 21 | 22 | -------------------------------------------------------------------------------- /PreStage/scripts/postinstall: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ## postinstall 3 | # Author: James Smith - james@anz.com / james@smithjw.me 4 | # Version: 2.0 5 | 6 | # Variables to kick things off 7 | working_dir="/var/tmp" 8 | log_folder="/private/var/log" 9 | log_name="enrolment.log" 10 | dialog_pkg="${working_dir}/dialog.pkg" 11 | installer_base_string="com.github.smithjw.mac.swiftEnrolment.sh" 12 | installer_script="${working_dir}/${installer_base_string}.sh" 13 | launchdaemon="/Library/LaunchDaemons/${installer_base_string}.plist" 14 | post_enrolment_script="/usr/local/outset/login-privileged-once/001_post_enrolment.sh" 15 | 16 | echo_logger() { 17 | log_folder="${log_folder:=/private/var/log}" 18 | log_name="${log_name:=log.log}" 19 | 20 | mkdir -p $log_folder 21 | 22 | echo -e "$(date) - $1" | tee -a $log_folder/$log_name 23 | } 24 | 25 | echo_logger "Installing swiftDialog" 26 | # ${array[-1]} grabs the last index in the array 27 | # ${array[*]} grabs all indexs in the array 28 | 29 | installer -pkg "${dialog_pkg}" -target / 30 | # rm "${dialog_pkg[*]}" 31 | 32 | echo_logger "Setting permissions for installer scripts" 33 | chmod 755 "${installer_script}" "${post_enrolment_script}" 34 | chown root:wheel "${installer_script}" "${post_enrolment_script}" 35 | 36 | echo_logger "Setting permissions for ${launchdaemon}." 37 | chmod 644 "${launchdaemon}" 38 | chown root:wheel "${launchdaemon}" 39 | 40 | echo_logger "Loading ${launchdaemon}." 41 | launchctl load "${launchdaemon}" 42 | 43 | exit 0 ## Success 44 | exit 1 ## Failure 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swiftEnrolment 2 | 3 | ## Jamf Enrolment Script 4 | 5 | You will need a policy setup in Jamf that uses a custom trigger called `configure-Mac`. If you wish to change this, you can edit the `enrolment_starter_trigger` variable in the `com.github.smithjw.mac.swiftEnrolment.sh` file. 6 | 7 | ## PreStage Package 8 | 9 | - Drop any assets/logos into the `PreStage/payload/Library/Management/Images` folder 10 | 11 | ### build-pkg 12 | 13 | Run this script to pull down the latest version of `swiftDialog` and create a PreStage Enrolment package 14 | 15 | Either update the variables within the script or run this pkg with the following options: 16 | 17 | - Signing Certificate Name: `-c Developer ID Installer: Pretend Co (ABCD1234)` 18 | - Apple Developer Account Email: `-E DEV_ACCOUNT@EMAIL.COM` 19 | - Apple Developer Account Password Item: `-K DEV_ACCOUNT_PASSWORD` 20 | - Apple Developer ASC Provider: `-A DEVELOPER_ASC_PROVIDER` 21 | - Package Identifier: `-i com.github.smithjw.mac.swiftEnrolment` 22 | - Package Name: `-n swiftEnrolment` 23 | - Package Version: `-v 1.0` 24 | - Enable Debug Mode `-d` 25 | 26 | You will also need to store the password for your developer account in the keychain using the following method: 27 | 28 | `security add-generic-password -s 'distbuild-DEV_ACCOUNT@EMAIL.COM' -a 'YOUR_USERNAME' -w 'DEV_ACCOUNT_PASSWORD'` 29 | 30 | 31 | ## This project was influenced by the following: 32 | # - https://github.com/jamfprofessionalservices/DEP-Notify 33 | # - https://gist.github.com/arekdreyer/a7af6eab0646a77b9684b2e620b59e1b 34 | -------------------------------------------------------------------------------- /PreStage/payload/var/tmp/001_post_enrolment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Post-Enrolment script used at ANZ 3 | # Author: James Smith - james@smithjw.me / james@anz.com 4 | # Version 3.0 5 | 6 | jamf_binary="/usr/local/jamf/bin/jamf" 7 | jamf_process=$(pgrep -x "jamf") 8 | finder_process=$(pgrep -l "Finder") 9 | log_folder="/private/var/log" 10 | log_name="swiftEnrolment.log" 11 | 12 | echo_logger() { 13 | log_folder="${log_folder:=/private/var/log}" 14 | log_name="${log_name:=log.log}" 15 | 16 | mkdir -p $log_folder 17 | 18 | echo -e "$(date) - $1" | tee -a $log_folder/$log_name 19 | } 20 | 21 | jamf_check() { 22 | trigger="$1" 23 | echo_logger "SCRIPT: Waiting until jamf is no longer running" 24 | until [ ! "$jamf_process" ]; do 25 | sleep 1 26 | jamf_process=$(pgrep -x "jamf") 27 | done 28 | 29 | echo_logger "SCRIPT: Running \"jamf $trigger\"" 30 | $jamf_binary "$trigger" 31 | sleep 2 32 | } 33 | 34 | echo_logger "SCRIPT: Waiting until Finder is running" 35 | until [ "$finder_process" != "" ]; do 36 | sleep 1 37 | finder_process=$(pgrep -l "Finder") 38 | done 39 | 40 | defaults write /Library/Management/management_info.plist enrol_login_policy_start "$(date +%s)" 41 | 42 | jamf_check "manage" 43 | jamf_check "recon" 44 | jamf_check "policy" 45 | 46 | defaults write /Library/Management/management_info.plist enrol_login_policy_end "$(date +%s)" 47 | 48 | echo_logger "SCRIPT: Cleaning up post-enrolment script" 49 | rm /usr/local/outset/login-privileged-once/001_post_enrolment.sh 50 | 51 | exit 0 52 | -------------------------------------------------------------------------------- /PreStage/scripts/preinstall: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ## preinstall 3 | # Author: James Smith - james@anz.com / james@smithjw.me 4 | # Version: 2.0 5 | 6 | log_folder="/private/var/log" 7 | log_name="enrolment.log" 8 | installer_base_string="com.github.smithjw.mac.swiftEnrolment" 9 | launchdaemon="/Library/LaunchDaemons/${installer_base_string}.plist" 10 | 11 | echo_logger() { 12 | log_folder="${log_folder:=/private/var/log}" 13 | log_name="${log_name:=log.log}" 14 | 15 | mkdir -p $log_folder 16 | 17 | echo -e "$(date) - $1" | tee -a $log_folder/$log_name 18 | } 19 | 20 | echo_logger "PREINSTALL: Create directory for mangement plist" 21 | mkdir -p /Library/Management 22 | 23 | echo_logger "PREINSTALL: Write enrolment start time to plist" 24 | defaults write /Library/Management/management_info.plist enrol_prestage_start "$(date +%s)" 25 | 26 | echo_logger "PREINSTALL: Checking for existing LaunchDaemon" 27 | if [ -f "$launchdaemon" ]; then 28 | launchctl unload "$launchdaemon" 29 | fi 30 | 31 | if [[ $( /usr/bin/arch ) = arm64* ]]; then 32 | echo_logger "PREINSTALL: This is an Apple Silicon Mac, checking for Rosetta" 33 | test=$( pgrep oahd 2>&1 >/dev/null ; echo $? ) 34 | if [[ "$test" = "0" ]]; then 35 | echo_logger "PREINSTALL: Rosetta is already installed" 36 | else 37 | /usr/sbin/softwareupdate --install-rosetta --agree-to-license 38 | 39 | test=$( pgrep oahd 2>&1 >/dev/null ; echo $? ) 40 | 41 | if [[ "$test" = "0" ]]; then 42 | echo_logger "PREINSTALL: Rosetta 2 was installed" 43 | else 44 | echo_logger "PREINSTALL: Rosetta 2 wasn't installed - trying again" 45 | /usr/sbin/softwareupdate --install-rosetta --agree-to-license 46 | 47 | test=$( pgrep oahd 2>&1 >/dev/null ; echo $? ) 48 | 49 | if [[ "$test" = "0" ]]; then 50 | echo_logger "PREINSTALL: Rosetta 2 is now installed" 51 | else 52 | echo_logger "PREINSTALL: Rosetta 2 wasn't installed" 53 | fi 54 | fi 55 | fi 56 | else 57 | echo_logger "PREINSTALL: This is an Intel Mac, moving on" 58 | fi 59 | 60 | exit 0 61 | 62 | -------------------------------------------------------------------------------- /PreStage/build-pkg: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Author: James Smith - james@smithjw.me / james@anz.com 3 | 4 | dialog_latest=$( curl -sL https://api.github.com/repos/bartreardon/swiftDialog/releases/latest ) 5 | logged_in_user=$( scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ && ! /loginwindow/ { print $3 }' ) 6 | mp="/usr/local/bin/munkipkg" 7 | 8 | pkg_result="0" 9 | pkg_version="0.0" 10 | pkg_dir=$PWD 11 | pkg_name="swiftEnrolment" 12 | pkg_output_dir="/Users/${logged_in_user}" 13 | pkg_identifier="com.github.smithjw.mac.swiftEnrolment" 14 | 15 | while getopts ":c:E:K:A:invd" o; do 16 | case "${o}" in 17 | c) 18 | signing_certificate="${OPTARG}" 19 | ;; 20 | E) 21 | developer_email="${OPTARG}" 22 | ;; 23 | K) 24 | developer_keychain_item="${OPTARG}" 25 | ;; 26 | A) 27 | developer_asc_provider="${OPTARG}" 28 | ;; 29 | i) 30 | pkg_identifier="${OPTARG}" 31 | ;; 32 | n) 33 | pkg_name="${OPTARG}" 34 | ;; 35 | v) 36 | pkg_version="${OPTARG}" 37 | ;; 38 | d) 39 | debug="true" 40 | ;; 41 | *) 42 | ;; 43 | esac 44 | done 45 | 46 | get_json_value() { 47 | JSON="$1" osascript -l 'JavaScript' \ 48 | -e 'const env = $.NSProcessInfo.processInfo.environment.objectForKey("JSON").js' \ 49 | -e "JSON.parse(env).$2" 50 | } 51 | 52 | # Download the latest version of swiftDialog 53 | # https://dev.to/saintdle/download-releases-from-github-using-curl-and-wget-54fi 54 | # https://daniel.haxx.se/blog/2020/09/10/store-the-curl-output-over-there/ 55 | # -L to follow redirects 56 | # -OJ to either use the filename in the URL or let the server set the name 57 | dialog_url=$(get_json_value "$dialog_latest" 'assets[0].browser_download_url') 58 | curl -L --output "dialog.pkg" --create-dirs --output-dir "payload/var/tmp" "$dialog_url" 59 | 60 | # Create the json file for signed munkipkg pkg 61 | /bin/cat << EOF > "$pkg_dir/build-info.json" 62 | { 63 | "ownership": "recommended", 64 | "suppress_bundle_relocation": true, 65 | "identifier": "$pkg_identifier", 66 | "postinstall_action": "none", 67 | "distribution_style": true, 68 | "version": "${pkg_version}", 69 | "name": "$pkg_name-$pkg_version.pkg", 70 | "install_location": "/", 71 | "signing_info": { 72 | "identity": "$signing_certificate", 73 | "timestamp": true 74 | }, 75 | "notarization_info": { 76 | "username": "$developer_email", 77 | "password": "@keychain:$developer_keychain_item", 78 | "asc_provider": "$developer_asc_provider", 79 | "stapler_timeout": 600 80 | } 81 | } 82 | EOF 83 | 84 | # Create the package if -d flag not set 85 | if [[ "$debug" != "true" ]]; then 86 | $mp "$pkg_dir" 87 | pkg_result="$?" 88 | fi 89 | 90 | if [ "${pkg_result}" != "0" ]; then 91 | echo "Could not sign package: ${pkg_result}" 1>&2 92 | else 93 | /bin/mv "$pkg_dir/build/$pkg_name-$pkg_version.pkg" "$pkg_output_dir" 94 | /bin/rm -r "$pkg_dir/build" 95 | /bin/rm "$pkg_dir/build-info.json" 96 | /bin/rm "payload/var/tmp/dialog.pkg" 97 | fi 98 | 99 | exit 0 100 | -------------------------------------------------------------------------------- /PreStage/payload/var/tmp/com.github.smithjw.mac.swiftEnrolment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # exec 1> >(logger -s -t $(basename $0)) 2>&1 3 | # set -x 4 | 5 | ######################################################################################### 6 | # Policy Variables to Modify 7 | ######################################################################################### 8 | counter_start=0 9 | enrolment_starter_trigger="configure-Mac" 10 | log_folder="/private/var/log" 11 | log_name="swiftEnrolment.log" 12 | jamf_binary="/usr/local/bin/jamf" 13 | 14 | dialog_app="/usr/local/bin/dialog" 15 | dialog_command_file="/var/tmp/dialog.log" 16 | dialog_icon="/Library/Management/Images/company_logo.png" 17 | dialog_initial_title="Welcome to your new Mac" 18 | dialog_initial_image="/Library/Management/Images/new_mac.jpg" 19 | dialog_error_url_prefix="https://github.com/smithjw/swiftEnrolment/raw/main" 20 | dialog_error_img_1="PreStage/payload/var/tmp/eacs_menu_bar_half.png?raw=true" 21 | dialog_error_img_2="PreStage/payload/var/tmp/eacs_menu_half.png?raw=true" 22 | dialog_error_title="Something went wrong" 23 | 24 | dialog_error_text=( 25 | 'The enrolment did not complete successfully, please perform the following: \n\n' 26 | '1. Launch System Preferences \n' 27 | '1. Click on the System Preferences menu item in the top-left of your screen \n\n' 28 | ' !'"[Menu Bar]($dialog_error_url_prefix/$dialog_error_img_1) \n" 29 | '1. Click Erase all Contents and Settings... \n\n' 30 | ' !'"[Menu Bar]($dialog_error_url_prefix/$dialog_error_img_2) \n" 31 | '1. Enter your password and try setting up your Mac again. \n\n' 32 | ) 33 | # Single quotes are used for the ! so that the shell does not try and interpret it. Leaving it followed by the 34 | # double-quoted string for the images on the same line for readability 35 | 36 | dialog_cmd=( 37 | "-p --title \"$dialog_initial_title\"" 38 | "--icon \"$dialog_icon\"" 39 | "--message \" \"" 40 | "--centericon" 41 | "--width 70%" 42 | "--height 70%" 43 | "--position centre" 44 | "--button1disabled" 45 | "--progress 60" 46 | ) 47 | 48 | # Because dialog_initial_title and dialog_initial_image are passed into the dialog command as command-line arguments 49 | # we need to ensure that they are quoted once constructed into the full command. We use double quotes for each argument 50 | # as parameter expansion does not work within single quotes. 51 | 52 | ######################################################################################### 53 | # Main Functions 54 | ######################################################################################### 55 | 56 | echo_logger() { 57 | log_folder="${log_folder:=/private/var/log}" 58 | log_name="${log_name:=log.log}" 59 | 60 | mkdir -p $log_folder 61 | 62 | echo -e "$(date) - $1" | tee -a $log_folder/$log_name 63 | } 64 | 65 | dialog_update() { 66 | echo_logger "DIALOG: $1" 67 | # shellcheck disable=2001 68 | echo "$1" >> "$dialog_command_file" 69 | } 70 | 71 | dialog_reset_progress() { 72 | dialog_update "progress: complete" 73 | dialog_update "progress: reset" 74 | counter_start=0 75 | 76 | # Adding "progress: complete" before "progress: reset" makes things look nicer within swiftDialog 77 | # Resetting $counter_start for another period before triggering a failed enrolment 78 | } 79 | 80 | dialog_finalise() { 81 | dialog_update "progresstext: Initial Enrolment Complete" 82 | sleep 1 83 | dialog_update "quit:" 84 | exit 0 85 | } 86 | 87 | get_json_value() { 88 | JSON="$1" osascript -l 'JavaScript' \ 89 | -e 'const env = $.NSProcessInfo.processInfo.environment.objectForKey("JSON").js' \ 90 | -e "JSON.parse(env).$2" 91 | } 92 | 93 | jamf_fixer () { 94 | 95 | dialog_eacs 96 | 97 | # TODO: after passing quit, relaunch smaller window top-right 98 | # TODO: Quit button to launch System Preferences 99 | # TODO: Change label of button 100 | 101 | 102 | } 103 | 104 | dialog_eacs () { 105 | dialog_update "title: $dialog_error_title" 106 | dialog_update "icon: left" 107 | dialog_update "icon: $dialog_icon" 108 | dialog_update "message: ${dialog_error_text[*]}" 109 | dialog_update "button1: enable" 110 | dialog_update "progresstext: " 111 | dialog_update "progress: complete" 112 | sleep "$jamf_fixer_sleep_time" 113 | dialog_update "quit:" 114 | exit 0 115 | } 116 | 117 | # Run script with -t to enable testing_mode 118 | while getopts "t" o; do 119 | case "${o}" in 120 | t) 121 | echo_logger "TESTING: Testing mode enabled" 122 | echo_logger "TESTING: Jamf Policies will not be run" 123 | echo_logger "TESTING: No management files will be written" 124 | testing_mode=true 125 | ;; 126 | *) 127 | ;; 128 | esac 129 | done 130 | 131 | ######################################################################################### 132 | # Testing Mode Configurations 133 | ######################################################################################### 134 | # $counter_limit controls how long this script will wait for the initial Jamf enrolment 135 | # and then once reset, how long it will watch the jamf log for "enrollmentComplete" before 136 | # running the jamf_fixer function 137 | 138 | if [[ -z "$testing_mode" ]]; then 139 | # Running in production 140 | dialog_cmd+=("--blurscreen") 141 | counter_limit=60 142 | jamf_fixer_sleep_time=30 143 | watch_log="/var/log/jamf.log" 144 | else 145 | # Running in testing_mode 146 | dialog_cmd+=("--blurscreen") 147 | counter_limit=5 148 | jamf_fixer_sleep_time=5 149 | watch_log="/var/log/jamf.log" 150 | # watch_log="/var/log/jamf_test.log" 151 | fi 152 | 153 | ######################################################################################### 154 | # Main Script Logic 155 | ######################################################################################### 156 | 157 | if [ ! -f "$dialog_app" ]; then 158 | echo_logger "swiftDialog not installed" 159 | dialog_latest=$( curl -sL https://api.github.com/repos/bartreardon/swiftDialog/releases/latest ) 160 | dialog_url=$(get_json_value "$dialog_latest" 'assets[0].browser_download_url') 161 | curl -L --output "dialog.pkg" --create-dirs --output-dir "/var/tmp" "$dialog_url" 162 | installer -pkg "/var/tmp/dialog.pkg" -target / 163 | fi 164 | 165 | # Waiting for Setup Assistant to complete 166 | setup_assistant_process=$(pgrep -l "Setup Assistant") 167 | until [ "$setup_assistant_process" = "" ]; do 168 | echo_logger "Setup Assistant Still Running. PID $setup_assistant_process." 169 | sleep 1 170 | setup_assistant_process=$(pgrep -l "Setup Assistant") 171 | done 172 | 173 | # Waiting for the Finder process to initialise 174 | finder_process=$(pgrep -l "Finder") 175 | until [ "$finder_process" != "" ]; do 176 | echo_logger "Finder process not found. Assuming device is at login screen." 177 | sleep 1 178 | finder_process=$(pgrep -l "Finder") 179 | done 180 | 181 | # Grabbing user information and launching swiftDialog 182 | logged_in_user=$( scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ && ! /loginwindow/ { print $3 }' ) 183 | echo_logger "Current user set to $logged_in_user." 184 | 185 | # Run swiftDialog 186 | eval "$dialog_app" "${dialog_cmd[*]}" & sleep 1 187 | 188 | dialog_update "icon: none" 189 | dialog_update "image: $dialog_initial_image" 190 | 191 | until [ -f "$watch_log" ] 192 | do 193 | dialog_update "progress: increment" 194 | dialog_update "progresstext: Waiting for Jamf installation" 195 | sleep 1 196 | ((counter_start++)) 197 | if [[ $counter_start -gt $counter_limit ]]; then 198 | jamf_fixer 199 | fi 200 | done 201 | 202 | dialog_reset_progress 203 | 204 | until ( /usr/bin/grep -q enrollmentComplete "$watch_log" ) 205 | do 206 | dialog_update "progresstext: $(tail -1 $watch_log)" 207 | dialog_update "progress: increment" 208 | sleep 1 209 | ((counter_start++)) 210 | if [[ $counter_start -gt $counter_limit ]]; then 211 | jamf_fixer 212 | fi 213 | done 214 | 215 | dialog_update "progresstext: Launching first-run scripts now" 216 | dialog_update "progress: indeterminate" 217 | 218 | # If we're not in testing_mode, write to the Management plist and call the Jamf Custom Trigger 219 | if [[ -z "$testing_mode" ]]; then 220 | defaults write /Library/Management/management_info.plist enrol_prestage_end "$(date +%s)" 221 | $jamf_binary policy -event ${enrolment_starter_trigger} 222 | fi 223 | 224 | exit 0 225 | exit 1 226 | -------------------------------------------------------------------------------- /000_enrolment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Enrolment script used at various companies 3 | # Author: James Smith - james@smithjw.me 4 | # Version 3.6.0 5 | 6 | /usr/bin/defaults write /Library/Management/management_info.plist enrol_initial_policy_start "$(date +%s)" 7 | 8 | working_dir="/private/var/tmp" 9 | identifier_prefix="com.github.smithjw" 10 | enrolment_base_string="${identifier_prefix}.mac.swiftEnrolment" 11 | enrolment_script="${working_dir}/${enrolment_base_string}.sh" 12 | enrolment_launchdaemon="/Library/LaunchDaemons/${enrolment_base_string}.plist" 13 | post_enrolment_launchdaemon="/Library/LaunchDaemons/${enrolment_base_string}_post.plist" 14 | jamf_binary="/usr/local/jamf/bin/jamf" 15 | fde_setup_binary="/usr/bin/fdesetup" 16 | self_service_branding_icon_name="brandingimage.png" 17 | self_service_branding_icon_location="/Library/Management/images" 18 | self_service_branding_icon_url="https://COMPANY.jamfcloud.com/api/v1/branding-images/download/9" 19 | dialog_app="/usr/local/bin/dialog" 20 | dialog_command_file="${working_dir}/dialog.log" 21 | dialog_icon="/Library/Management/images/company_logo.png" 22 | dialog_title="COMPANY Device Enrolment" 23 | dialog_title_complete="You're all done!" 24 | dialog_message="We're just installing up a few apps and configuring a few System Settings before you get started. \n\n This process should take about 10 minutes to complete. \n\n " 25 | dialog_message_testing="This is usually where swiftDialog would quit, and the user logged out. \n\nHowever, testing_mode is enabled and FileVault deferred status is on." 26 | dialog_message_final="We've finished installing up all the default apps required to get you started. \n\nIn a few moments you'll see a new dialog that will walk you through the remaining setup steps." 27 | dialog_status_initial="Initial Configuration Starting..." 28 | dialog_status_complete="Configuration Complete!" 29 | 30 | log_folder="/private/var/log" 31 | log_name="management.log" 32 | 33 | dialog_cmd=( 34 | -p 35 | --title "$dialog_title" 36 | --iconsize 200 37 | --width 70% 38 | --height 70% 39 | --position centre 40 | --button1disabled 41 | --progress 30 42 | --progresstext "$dialog_status_initial" 43 | --blurscreen 44 | ) 45 | 46 | ######################################################################################### 47 | # Policy Array to determine what's installed 48 | ######################################################################################### 49 | 50 | policy_array=(' 51 | { 52 | "steps": [ 53 | { 54 | "listitem": "Installing management tools...", 55 | "icon": "SF=terminal,colour=auto,weight=medium", 56 | "trigger_list": [ 57 | { 58 | "trigger": "install-Python", 59 | "path": "/usr/local/bin/managed_python3" 60 | }, 61 | { 62 | "trigger": "install-Nudge", 63 | "path": "/Applications/Utilities/Nudge.app/Contents/Info.plist" 64 | }, 65 | { 66 | "trigger": "install-Outset", 67 | "path": "/usr/local/outset/outset" 68 | } 69 | ] 70 | }, 71 | { 72 | "listitem": "Configuring Single Sign-On...", 73 | "icon": "SF=person.crop.square.filled.and.at.rectangle,colour=auto,weight=medium", 74 | "trigger_list": [ 75 | { 76 | "trigger": "install-nomad", 77 | "path": "/Applications/NoMAD.app/Contents/Info.plist" 78 | }, 79 | { 80 | "trigger": "install-Microsoft_Company_Portal", 81 | "path": "/Applications/Company Portal.app/Contents/Info.plist" 82 | } 83 | ] 84 | }, 85 | { 86 | "listitem": "Configuring network settings...", 87 | "icon": "SF=network.badge.shield.half.filled,colour=auto,weight=medium", 88 | "trigger_list": [ 89 | { 90 | "trigger": "configure-networkprefsaccess", 91 | "path": "" 92 | }, 93 | { 94 | "trigger": "configure-proxy", 95 | "path": "" 96 | } 97 | ] 98 | }, 99 | { 100 | "listitem": "Installing collaboration tools...", 101 | "icon": "SF=bubble.left.and.bubble.right,colour=auto,weight=medium", 102 | "trigger_list": [ 103 | { 104 | "trigger": "install-Slack", 105 | "path": "/Applications/Slack.app/Contents/Info.plist" 106 | }, 107 | { 108 | "trigger": "install-Microsoft_Teams", 109 | "path": "/Applications/Microsoft Teams.app/Contents/Info.plist" 110 | } 111 | ] 112 | }, 113 | { 114 | "listitem": "Installing browsers...", 115 | "icon": "SF=safari,colour=auto,weight=medium", 116 | "trigger_list": [ 117 | { 118 | "trigger": "install-Microsoft_Edge", 119 | "path": "/Applications/Microsoft Edge.app/Contents/Info.plist" 120 | } 121 | ] 122 | }, 123 | { 124 | "listitem": "Configuring account access...", 125 | "icon": "SF=lock.square,colour=auto,weight=medium", 126 | "trigger_list": [ 127 | { 128 | "trigger": "install-Privileges", 129 | "path": "/Applications/Privileges.app/Contents/Info.plist" 130 | }, 131 | { 132 | "trigger": "install-macOSLAPS", 133 | "path": "/usr/local/laps/macOSLAPS" 134 | }, 135 | { 136 | "trigger": "remove-admin", 137 | "path": "" 138 | } 139 | ] 140 | }, 141 | { 142 | "listitem": "Making things pretty...", 143 | "icon": "SF=sparkles.tv,colour=auto,weight=medium", 144 | "trigger_list": [ 145 | { 146 | "trigger": "install-desktoppr", 147 | "path": "/usr/local/bin/desktoppr" 148 | }, 149 | { 150 | "trigger": "configure-default-appearance", 151 | "path": "" 152 | } 153 | ] 154 | }, 155 | { 156 | "listitem": "Submitting Inital Computer Inventory...", 157 | "icon": "SF=tray.and.arrow.up.fill,colour=auto,weight=medium", 158 | "trigger_list": [ 159 | { 160 | "trigger": "recon", 161 | "path": "" 162 | } 163 | ] 164 | } 165 | ] 166 | } 167 | ') 168 | 169 | ######################################################################################### 170 | # Bash functions used later on 171 | ######################################################################################### 172 | 173 | echo_logger() { 174 | # echo_logger version 1.1 175 | log_folder="${log_folder:=/private/var/log}" 176 | /bin/mkdir -p "$log_folder" 177 | echo -e "$(date +'%Y-%m-%d %T%z') - ${log_prefix:+$log_prefix }${1}" | /usr/bin/tee -a "$log_folder/${log_name:=management.log}" 178 | } 179 | 180 | echo_logger_heading() { 181 | echo_logger "#########################################################################" 182 | echo_logger "$1" 183 | echo_logger "#########################################################################" 184 | } 185 | 186 | dialog_update() { 187 | echo_logger "DIALOG: $1" 188 | # shellcheck disable=2001 189 | echo "$1" >> "$dialog_command_file" 190 | } 191 | 192 | get_json_value() { 193 | JSON="$1" /usr/bin/osascript -l 'JavaScript' \ 194 | -e 'const env = $.NSProcessInfo.processInfo.environment.objectForKey("JSON").js' \ 195 | -e "JSON.parse(env).$2" 196 | } 197 | 198 | run_jamf_trigger() { 199 | trigger="$1" 200 | if [ "$testing_mode" = true ]; then 201 | echo_logger "TESTING: $trigger" 202 | /bin/sleep 1 203 | elif [ "$trigger" == "recon" ]; then 204 | echo_logger "RUNNING: $jamf_binary $trigger" 205 | "$jamf_binary" "$trigger" 206 | else 207 | echo_logger "RUNNING: $jamf_binary policy -event $trigger" 208 | "$jamf_binary" policy -event "$trigger" 209 | fi 210 | } 211 | 212 | # Run script with -t to enable testing_mode 213 | while getopts "t" o; do 214 | case "${o}" in 215 | t) 216 | echo_logger "TESTING: Testing mode enabled" 217 | echo_logger "TESTING: Jamf Policies will not be run" 218 | echo_logger "TESTING: No management files will be written" 219 | testing_mode=true 220 | ;; 221 | *) 222 | ;; 223 | esac 224 | done 225 | 226 | ######################################################################################### 227 | # Policy kickoff 228 | ######################################################################################### 229 | 230 | echo_logger_heading "Start of _enrolment.sh" 231 | 232 | # Check if Dialog is running 233 | if ! /usr/bin/pgrep -qx "Dialog"; then 234 | echo_logger "INFO: Dialog isn't running, launching now" 235 | "$dialog_app" "${dialog_cmd[@]}" & /bin/sleep 1 236 | else 237 | echo_logger "INFO: Dialog is running" 238 | echo_logger "INFO: Dialog Process: $(/usr/bin/pgrep -lx Dialog)" 239 | dialog_update "title: $dialog_title" 240 | dialog_update "progresstext: $dialog_status_initial" 241 | fi 242 | 243 | dialog_update "progress: complete" 244 | 245 | logged_in_user=$( scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ && ! /loginwindow/ { print $3 }' ) 246 | logged_in_user_home=$(dscl . read /Users/"$logged_in_user" NFSHomeDirectory | awk '{print $2}') 247 | logged_in_user_uid=$(id -u "$logged_in_user") 248 | echo_logger "INFO: User details:" 249 | echo_logger "INFO: logged_in_user: $logged_in_user" 250 | echo_logger "INFO: logged_in_user_home: $logged_in_user_home" 251 | echo_logger "INFO: logged_in_user_uid: $logged_in_user_uid" 252 | 253 | self_service_custom_icon="$logged_in_user_home/Library/Application Support/com.jamfsoftware.selfservice.mac/Documents/Images/brandingimage.png" 254 | self_service_path=$( /usr/bin/defaults read /Library/Preferences/com.jamfsoftware.jamf.plist self_service_app_path ) 255 | 256 | if /usr/bin/curl -JLo "$self_service_branding_icon_name" --create-dirs --output-dir "$self_service_branding_icon_location" "$self_service_branding_icon_url"; then 257 | self_service_branding_icon="$self_service_branding_icon_location/$self_service_branding_icon_name" 258 | if [[ -f "$self_service_branding_icon" ]]; then 259 | dialog_overlayicon="$self_service_branding_icon" 260 | fi 261 | elif [[ -f "$self_service_custom_icon" ]]; then 262 | dialog_overlayicon="$self_service_custom_icon" 263 | else 264 | dialog_overlayicon="$self_service_path" 265 | fi 266 | 267 | echo_logger "INFO: self_service_branding_icon: $self_service_branding_icon" 268 | 269 | ######################################################################################### 270 | # Main Script Logic 271 | ######################################################################################### 272 | 273 | # Iterate through policy_array json to construct the list for swiftDialog 274 | dialog_step_length=$(get_json_value "${policy_array[*]}" "steps.length") 275 | for (( i=0; i= 13: 604 | cmd = "open 'x-apple.systempreferences:com.apple.Software-Update-Settings.extension'" 605 | else: 606 | cmd = ( 607 | "open 'x-apple.systempreferences:com.apple.preferences.softwareupdate'" 608 | ) 609 | 610 | await asyncio.sleep(1.5) 611 | 612 | await dialog_prompt( 613 | title=f"{dialog_title_prefix}: Update Required", 614 | message=f"""\ 615 | Let's get the latest version of macOS installed now \n 616 | Current: {local_version} 617 | Latest: {latest_version}\ 618 | """, 619 | # In order to ensure the best experience and to maintain compliance, please click Update. 620 | button1text="Update Now", 621 | # height="325", 622 | # width="650", 623 | mini=True, 624 | blocking=True, 625 | ontop=False, 626 | position="topright", 627 | ) 628 | 629 | if demo_mode: 630 | logger.info("demo_mode enabled, bypassing Software Update launch") 631 | else: 632 | # Launch Software Update - Can't use a button action for the deep linking to Software Update 633 | result = await asyncio.create_subprocess_shell(cmd) 634 | logger.debug(f"result: {result}") 635 | else: 636 | await asyncio.sleep(1.5) 637 | 638 | await dialog_prompt( 639 | title=f"{dialog_title_prefix}: macOS Update", 640 | message="""\ 641 | Your Mac is already on the latest version of macOS. \n 642 | Let's move onto Device Compliance Registration. 643 | """, 644 | button1text="Next", 645 | # height="225", 646 | # width="650", 647 | mini=True, 648 | blocking=True, 649 | position="topright", 650 | timer="10", 651 | ) 652 | 653 | return 654 | 655 | 656 | async def walkthrough_device_registration(demo_mode): 657 | logger.debug("########################################################") 658 | logger.debug("walkthrough_device_registration") 659 | logger.debug("########################################################") 660 | await asyncio.sleep(0) 661 | 662 | await asyncio.sleep(1.5) 663 | 664 | # if demo_mode: 665 | # logger.info("demo_mode enabled, disabling button action") 666 | # button1action = None 667 | # else: 668 | # button1action = mem_registration_policy_url 669 | 670 | await dialog_prompt( 671 | title=f"{dialog_title_prefix}: Device Compliance Registration", 672 | message="""\ 673 | While your selected apps install, we need to register this device with Microsoft. \n 674 | We'll launch Self Service and continue there.\ 675 | """, 676 | button1text="Register", 677 | # height="250", 678 | # width="650", 679 | mini=True, 680 | blocking=True, 681 | # button1action=button1action, 682 | position="topright", 683 | ontop=False, 684 | ) 685 | 686 | if demo_mode: 687 | logger.info("demo_mode enabled, bypassing Self Service launch") 688 | else: 689 | cmd = f"open '{mem_registration_policy_url}'" 690 | result = await asyncio.create_subprocess_shell(cmd) 691 | logger.debug(f"result: {result}") 692 | 693 | 694 | async def main(): 695 | """Manage arguments and run workflow""" 696 | 697 | # Ensure there aren't any old dialog log files lying around... 698 | if os.path.exists(dialog_commandfile): 699 | logger.info(f"Removing prior dialog_commandfile: {dialog_commandfile}") 700 | result = os.remove(dialog_commandfile) 701 | logger.info(f"Result: {result}") 702 | if os.path.exists(dialog_custom_commandfile): 703 | logger.info(f"Removing prior dialog_custom_commandfile: {dialog_commandfile}") 704 | result = os.remove(dialog_custom_commandfile) 705 | logger.info(f"Result: {result}") 706 | 707 | args = parse_args() 708 | log_level = args.log 709 | demo_mode = args.demo 710 | 711 | if log_level: 712 | logger.setLevel(getattr(logging, log_level.upper())) 713 | else: 714 | logger.setLevel(logging.INFO) 715 | 716 | logger.info(f"argparse: {args}") 717 | 718 | loop = asyncio.get_running_loop() 719 | 720 | asyncio.create_task(walkthrough_welcome(demo_mode)) 721 | jamf_app_list = asyncio.create_task(walkthrough_role_app_selection()) 722 | asyncio.create_task(walkthrough_device_update(demo_mode)) 723 | asyncio.create_task(walkthrough_device_registration(demo_mode)) 724 | 725 | # https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor 726 | with concurrent.futures.ProcessPoolExecutor() as pool: 727 | await loop.run_in_executor( 728 | pool, 729 | process_jamf_app_list, 730 | await jamf_app_list, 731 | args.demo, 732 | ) 733 | 734 | 735 | if __name__ == "__main__": 736 | start_time = time.time() 737 | asyncio.run(main()) 738 | end_time = time.time() 739 | 740 | print(f"Total time elapsed: {end_time - start_time} seconds") 741 | --------------------------------------------------------------------------------