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