├── .gitignore
├── LICENSE
├── LaunchDaemons
└── com.corp.swiftdeploy.plist
├── README.md
├── main script
└── swiftdeploy.sh
└── pkg install scripts
├── postinstall.sh
└── preinstall.sh
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | .DS_Store
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Richard Purves
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/LaunchDaemons/com.corp.swiftdeploy.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Label
5 | com.corp.swiftdeploy
6 | ProgramArguments
7 |
8 | /usr/local/corp/scripts/swiftdeploy.sh
9 |
10 | KeepAlive
11 |
12 | RunAtLoad
13 |
14 | EnvironmentVariables
15 |
16 | PATH
17 | /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
18 |
19 | StandardOutPath
20 | /dev/null
21 | StandardErrorPath
22 | /dev/null
23 |
24 |
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SwiftDeploy
2 | Zero touch deployment method for Jamf Pro and SwiftDialog
3 |
4 | This project is directly inspired by [Setup Your Mac](https://snelson.us/sym) by Dan Snelson, is meant for exclusive use with [Jamf Pro](https://www.jamf.com/products/jamf-pro/) management system and finally makes extensive use of [SwiftDialog](https://github.com/swiftDialog/swiftDialog) by Bart Reardon.
5 |
6 | To those projects and authors, I give my thanks.
7 |
8 | ## What does this do?
9 |
10 | I like [Setup Your Mac](https://snelson.us/sym) as a concept, but unfortunately I have requirements for a more complex and flexible system. As a result, SwiftDeploy now exists.
11 |
12 | The principle difference between the two projects is that I worked out a way to autogenerate the policy lists in SwiftDialog directly from the output of the Jamf binary instead of hard coding everything. So at the expense of code complexity, this system will auto generate and display the correct list every time.
13 |
14 | ## Can I see this in action?
15 |
16 | Sure!
17 |
18 | https://github.com/franton/SwiftDeploy/assets/5807892/f8382ff2-e7f8-43b4-9d73-0e767a8cfe5f
19 |
20 | This shows a very sped up deployment process, where the script initiated the following process:
21 |
22 | - Work out the name of the user that signed in and upload that to Jamf Pro.
23 | - Auto set the name of the computer based on the asset management system data.
24 | - Execute a ```jamf policy -event deploy -verbose``` command.
25 | - Then execute a ```jamf policy -verbose``` command to run check in.
26 | - Re-enables Jamf automatic check in.
27 | - Cleans up and exits.
28 |
29 | For those who remember my JNUC 2022 talk, I am still claiming the prize for fastest Touch ID setup ever :D
30 |
31 | ## What sorcery is this?
32 |
33 | Blog post coming soon that will go into details.
34 |
35 | TL;DR:
36 |
37 | A pipe is set up between the Jamf binary and this script, and we force this to operate in an asychronous mode. The risk otherwise is the pipe could stall and the binary could be prematurely terminated. We invoke the binary using the verbose switch to get extra output.
38 |
39 | A script loop, coded to be as fast as possible processes the output received from the pipe and updates SwiftDialog accordingly
40 |
41 | From all the verbose output we get all the policy names that the binary is to act on. Those names are processed into image files names so we can use appropriately named files. We also get start info, is a policy running a pkg or a script, did it work or did it fail and update accordingly. We also know when we're finished because otherwise async pipes don't terminate.
42 |
43 | The blog post will have more detail. [Eventually.](https://developer.valvesoftware.com/wiki/Valve_Time).
44 |
45 | ## How do I use this?
46 |
47 | - Create a package in your packaging tool of choice.
48 | - Customise the pre and post install scripts to your needs.
49 | - Customise the launch daemon file with your corp details and file locations.
50 | - Customise the main script (see section below) to your needs.
51 | - Add your corporate banner file to the package. (635 × 133 pixel png file is best)
52 | - Add ALL the icon files you're going to require. (We autogenerate the names for those from the policy names.)
53 | - Package, codesign and add to your Jamf Pro prestage.
54 | - Create a policy in Jamf with the custom trigger "isjssup", Ongoing that simply runs ```echo "up"``` .
55 | - TEST!
56 |
57 | Here's a screenshot from my packaging project so you can see where all the files are located.
58 |
59 |
60 |
61 | ## Icon file example.
62 |
63 | A policy with a name such as
64 | ```Deploy BeyondTrust Support Client```
65 | is translated to
66 | ```beyondtrustsupportclient.png```
67 |
68 | The code chops off the first word, amalgamates the other text, forces lower case and assumes .png format.
69 |
70 | All files I generated using [SAP's macOS icon generator](https://github.com/SAP/macOS-icon-generator) at 512x512 png.
71 |
72 | By default ALL icon files live in /usr/local/corp/deployimgs but that can be customised. See section below.
73 |
74 | ## Areas of note in the code:
75 |
76 | - L15 and 16: Jamf API Role client id and secret. Used for various API accesses.
77 |
78 | | Permissions |
79 | | ------ |
80 | | Read Buildings |
81 | | Read Computers |
82 | | Read Departments |
83 | | Read Computer Check-In |
84 | | Send Computer Remote Command to Install Package |
85 |
86 | - L20: Log file location.
87 | - L21: Path to working folder. Change this to suit your own needs.
88 | - L22: Name and path of icons folder. Used for banner and auto populating icons.
89 | - L23: Change this to the name and path of your banner image
90 | - L28: Hardcoded URL for Jamf Pro because device wont have enrolled at this stage
91 | - L30: URL of your Jamf Pro server. This runs before any auto detection is possible.
92 | - L31: Your corporate domain. Used for working out email address from current user.
93 | - L319: Detection code to see if enrollment failed and initiate re-enrollment if so. Uses same client id/secret.
94 | - L446: Custom code to run named triggers for adding user details to device record and device naming. (Not provided in this project.)
95 | - L468: Regex for automated device naming checks. Currently set to a dummy of 8 numbers only.
96 |
97 | ## Acknowledgements
98 |
99 | [Mac Admins Slack](https://www.macadmins.org/)
100 | - @dan-snelson For his Setup Your Mac project showing it was all possible
101 | - @bartreardon For his SwiftDialog project that really made the display possible
102 | - @pico For advise on some of the nastier bits of shell pipe handling. (Although neither of us had gone quite this far!)
103 | - The current users of the #zsh , #bash and #swiftdialog channels. (Hi to #jamfnation too!)
104 | - The original authors of cocoaDialog for putting the idea of named async pipes in my head from their original documentation
105 | - @tlark @macmule @rabbitt @bradtchapman @rquigley @marcusransom and other for support and chats during the development of this.
106 |
107 | If i've missed you out, get in touch and i'll fix that mistake.
108 |
--------------------------------------------------------------------------------
/main script/swiftdeploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/zsh
2 |
3 | # Deploy script for SwiftDialog ZeroTouch
4 | # richard@richard-purves.com
5 |
6 | # Logging output to a file for testing
7 | #time=$( /bin/date "+%d%m%y-%H%M" )
8 | #set -x
9 | #logfile=/private/tmp/swiftverbose.log
10 | #exec > $logfile 2>&1
11 |
12 | # Set up global variables here. User variables to be set up after login.
13 | scriptversion="1.13 - 25th January 2024"
14 |
15 | clientid=""
16 | clientsecret=""
17 |
18 | ld="/Library/LaunchDaemons/com.jamfsoftware.task.1.plist"
19 | sdld="/Library/LaunchDaemons/com.corp.swiftdeploy.plist"
20 | sdldlabel=$( defaults read $sdld Label )
21 | scriptloc=${0:A}
22 |
23 | logfile="/private/tmp/swiftdeploy.log"
24 | workfolder="/usr/local/corp"
25 | icons="$workfolder/deployimgs"
26 | bannerimage="$icons/banner.png"
27 |
28 | sdcontrolfile="/private/tmp/sdcontrol.log"
29 |
30 | sd="/usr/local/bin/dialog"
31 | jb="/usr/local/bin/jamf"
32 |
33 | jssurl="https://corp.jamfcloud.com/"
34 | domain="corp.com"
35 |
36 | macosver=$( /usr/bin/sw_vers -productVersion )
37 | majver=$( echo $macosver | cut -d"." -f1 )
38 | macosbuild=$( /usr/bin/sw_vers -buildVersion )
39 | serial=$( /usr/sbin/ioreg -c IOPlatformExpertDevice -d 2 | /usr/bin/awk -F\" '/IOPlatformSerialNumber/{print $(NF-1)}' )
40 | udid=$( /usr/sbin/ioreg -d2 -c IOPlatformExpertDevice | /usr/bin/awk -F\" '/IOPlatformUUID/{print $(NF-1)}' )
41 |
42 | # Set up various functions here
43 | function logme()
44 | {
45 | # Check to see if function has been called correctly
46 | if [ -z "$1" ];
47 | then
48 | echo $( /bin/date )" - ERROR: No text passed to function! Please recheck code!" | /usr/bin/tee -a "${logfile}"
49 | exit 1
50 | fi
51 |
52 | # Log the passed details
53 | echo -e $( /bin/date )" - $1" | /usr/bin/tee -a "${logfile}"
54 | }
55 |
56 | function updatestatus()
57 | {
58 | logme "SD Command issued: $1"
59 | echo "$1" >> "$sdcontrolfile"
60 | }
61 |
62 | function getjamftoken()
63 | {
64 | # Check we have the correct number of parameters passed
65 | if [ $# -lt 3 ];
66 | then
67 | echo "Usage: $funcstack[1] "
68 | return 1
69 | fi
70 |
71 | # Sort these out into appropriate variables
72 | id="$1"
73 | secret="$2"
74 | jssurl="$3"
75 |
76 | # Use the new oauth system to get a bearer token
77 | jsonresponse=$( /usr/bin/curl --silent --location \
78 | --request POST "${jssurl}api/oauth/token" \
79 | --header 'Content-Type: application/x-www-form-urlencoded' \
80 | --data-urlencode "client_id=${id}" \
81 | --data-urlencode 'grant_type=client_credentials' \
82 | --data-urlencode "client_secret=${secret}" )
83 |
84 | # Return any replies to the original caller
85 | echo "$jsonresponse"
86 | }
87 |
88 | function invalidatejamftoken()
89 | {
90 | if [ $# -lt 2 ];
91 | then
92 | logme "Usage: $funcstack[1] "
93 | fi
94 |
95 | creds="$1"
96 | jssurl="$2"
97 |
98 | # Send API command to invalidate the token
99 | /usr/bin/curl -s -k "${jssurl}api/v1/auth/invalidate-token" -H "authorization: Bearer ${token}" -X POST
100 | }
101 |
102 | function getcomputerid()
103 | {
104 | if [ $# -lt 3 ];
105 | then
106 | logme "Usage: $funcstack[1] "
107 | fi
108 |
109 | apitoken="$1"
110 | jssurl="$2"
111 | udid="$3"
112 |
113 | compjson=$( /usr/bin/curl -s "${jssurl}api/v1/computers-inventory?section=GENERAL&filter=udid%3D%3D%22${udid}%22" -H "authorization: Bearer ${apitoken}" )
114 |
115 | compid=$( /usr/bin/plutil -extract results.0.id raw -o - - <<< "$compjson" )
116 |
117 | echo "$compid"
118 | }
119 |
120 | function jamfprocess()
121 | {
122 | logme "Calling jamf policy $1"
123 |
124 | # Create a named pipe to feed output from jamf binary into.
125 | # Attach it to file descriptor "3" and delete the created file.
126 | /usr/bin/mkfifo /private/tmp/jamfoutput
127 | exec 3<>"/private/tmp/jamfoutput"
128 | /bin/rm /private/tmp/jamfoutput
129 |
130 | # Run the named policy in the background or check-in if not named,
131 | # and direct the output to the file descriptor.
132 | if [ $# -eq 1 ];
133 | then
134 | $jb policy -event "$1" -verbose >&3 &
135 | jbpid=$!
136 | else
137 | $jb policy -verbose >&3 &
138 | jbpid=$!
139 | fi
140 |
141 | counter=0
142 | progressbar="1"
143 |
144 | while read output;
145 | do
146 | # Uncomment this for actual jamf verbose to a file
147 | #echo "$output" >> /private/tmp/jamfverbose.log
148 |
149 | # Parse for policy list output. If we get one, add to the list.
150 | if [ $( echo $output | grep -c "verbose: Parsing Policy" ) -gt 0 ];
151 | then
152 | array+=(${(f)"$( echo $output | /usr/bin/grep "verbose: Parsing Policy" | /usr/bin/awk '{ print substr($0, index($0,$5)) }' | /usr/bin/awk '{NF--; print}' | /usr/bin/sed 's/Enable //g' | /usr/bin/sed 's/Install //g' | /usr/bin/sed 's/Deploy //g' | /usr/bin/sed 's/Configure //g' )"} )
153 | image="${icons}/$( echo ${array[-1]} | /usr/bin/tr -d " " | /usr/bin/tr '[:upper:]' '[:lower:]' ).png"
154 | updatestatus "listitem: add, title: ${array[-1]}, icon: $image, statustext: Waiting, status: pending"
155 | progressbarstep=$(( 100 / $#array ))
156 | continue
157 | fi
158 |
159 | # Work out what policy we're doing and it's status.
160 | execpolicyname=$( echo $output | grep "Executing Policy" | awk '{ print substr($0, index($1,$5)) }' 2>/dev/null )
161 |
162 | # Save the name for the next loop round. Also work out the proper name for the dialog updates.
163 | # Also increment the policy counter at the detection of a new policy start.
164 | if [ ! -z "$execpolicyname" ];
165 | then
166 | policyname="$execpolicyname"
167 | updatename=$( echo $output | awk '{ print substr($0, index($0,$4)) }' )
168 | updatename=$( echo $updatename | /usr/bin/sed 's/Enable //g' | /usr/bin/sed 's/Install //g' | /usr/bin/sed 's/Deploy //g' | /usr/bin/sed 's/Configure //g' )
169 | fi
170 |
171 | # If we get a blank line, skip checking for anything.
172 | # Otherwise parse the verbose output for which state we're in.
173 | if [ ! -z "$policyname" ];
174 | then
175 | downloading=$( echo $output | grep -c "Downloading" )
176 | installing=$( echo $output | grep -c "Installing" ) # pkgs
177 | executing=$( echo $output | grep -c "Running script" ) # scripts
178 | runcommand=$( echo $output | grep -c "Running command" ) # commands
179 | finished=$( echo $output | grep -c -E 'Successfully|Script exit code: 0|Result of command' )
180 | failedpkg=$( echo $output | grep -c "Installation failed" )
181 | failedinstall=$( echo $output | grep "installer:" | grep -c "failed" )
182 | failedscript=$( echo $output | grep "Script exit code:" | awk '{ print $NF }' )
183 | fi
184 |
185 | # Depending on state, update swiftDialog list entry
186 | if [ "$downloading" -eq 1 ];
187 | then
188 | updatestatus "listitem: title: $updatename, statustext: Downloading, status: wait"
189 | updatestatus "progresstext: Downloading $updatename"
190 | fi
191 |
192 | if [ "$installing" -eq 1 ];
193 | then
194 | updatestatus "listitem: title: $updatename, statustext: Installing, status: wait"
195 | updatestatus "progresstext: Installing $updatename"
196 | fi
197 |
198 | if [ "$executing" -eq 1 ];
199 | then
200 | updatestatus "listitem: title: $updatename, statustext: Running Script, status: wait"
201 | updatestatus "progresstext: Running Script for $updatename"
202 | fi
203 |
204 | if [ "$runcommand" -eq 1 ];
205 | then
206 | updatestatus "listitem: title: $updatename, statustext: Running Command, status: wait"
207 | updatestatus "progresstext: Running Command for $updatename"
208 | fi
209 |
210 | if [ "$failedpkg" -eq 1 ] || [ "$failedinstall" -eq 1 ] || [ "$failedscript" -ne 0 ];
211 | then
212 | progressbar=$(( $progressbar + $progressbarstep ))
213 | counter=$(( $counter + 1 ))
214 | updatestatus "progress: $progressbar"
215 | updatestatus "listitem: title: $updatename, statustext: Failed, status: fail"
216 | updatestatus "progresstext: Installatiion of $updatename FAILED"
217 | unset execpolicyname policyname
218 | fi
219 |
220 | if [ "$finished" = "1" ];
221 | then
222 | progressbar=$(( $progressbar + $progressbarstep ))
223 | counter=$(( $counter + 1 ))
224 | updatestatus "progress: $progressbar"
225 | updatestatus "listitem: title: $updatename, statustext: Completed, status: success"
226 | updatestatus "progresstext: $updatename Completed"
227 |
228 | # Clean out previous completed task. Only ones left should be the failed ones.
229 | # Then store the current name to be cleaned on the next task
230 | [ ! -z "$previouspolicy" ] && updatestatus "listitem: delete, title: $previouspolicy"
231 | previouspolicy="$updatename"
232 |
233 | unset execpolicyname policyname
234 | fi
235 |
236 | # Clear variables for future loops
237 | unset downloading installing executing finished failedpkg failedscript
238 |
239 | # Loop will never exit on it's own. Break out when the counter
240 | # reaches the same or greater number than the indexes in the deployarray array.
241 | [ "$counter" -ge "$#array" ] && [ "$#array" -gt 0 ] && break
242 |
243 | done <&3
244 |
245 | # Clear the file descriptor we set up earlier
246 | exec 3>&-
247 |
248 | # Ensure the Jamf Binary has terminated before proceeding
249 | wait $jbpid
250 |
251 | # Clear final icon from the list
252 | updatestatus "listitem: delete, title: $previouspolicy"
253 |
254 | # Clear remaining variables used
255 | unset previouspolicy array counter jbpid
256 | }
257 |
258 | function exitscript()
259 | {
260 | if [ $# -lt 2 ]
261 | then
262 | logme "Usage: $funcstack[1] "
263 | exit 1
264 | fi
265 |
266 | logme "Exit code: $1"
267 | logme "Exit message: $2"
268 |
269 | /bin/rm -rf "$icons"
270 | /bin/rm -f "$sdcontrolfile"
271 | /bin/rm -f "$sdld"
272 | /bin/rm "$scriptloc"
273 | /bin/launchctl bootout system/$sdldlabel
274 | sleep 1
275 |
276 | # parse $1 to get exit code.
277 | exit $1
278 | }
279 |
280 | #
281 | ## Start preparation for deployment
282 | #
283 |
284 | # Set error trapping here
285 | trap 'logme "Error at line $LINENO"; exitscript 1 "Script error. Check /private/tmp/swiftdeploy.log"'
286 |
287 | # Caffeinate the mac so it doesn't go to sleep on us. Give it the PID of this script
288 | # so that it auto quits when we're done.
289 | logme "Starting Deployment preparation."
290 | logme "Loading caffeinate so the computer doesn't sleep."
291 | /usr/bin/caffeinate -dimu -w $$ &
292 |
293 | # Loop and wait for enrollment to complete
294 | while [ -f /Library/LaunchDaemons/com.jamf.management.enroll.plist ]; do : ; done
295 |
296 | # Ensure checkin is disabled
297 | while [ ! -f "$ld" ]; do : ; done
298 | /bin/launchctl bootout system "$ld"
299 |
300 | # Enable localadmin SSH access
301 | logme "Enabling SSH access for admin account."
302 | /usr/sbin/systemsetup -f -setremotelogin off 2>&1 >/dev/null
303 | /usr/sbin/dseditgroup -o delete -t group com.apple.access_ssh 2>&1 >/dev/null
304 | /usr/sbin/dseditgroup -o create -q com.apple.access_ssh 2>&1 >/dev/null
305 | /usr/sbin/dseditgroup -o edit -a admin -t user com.apple.access_ssh 2>&1 >/dev/null
306 | /usr/sbin/systemsetup -f -setremotelogin on 2>&1 >/dev/null
307 |
308 | # Wait for the user environment to start before we proceed. Also make sure Jamf check in is disabled.
309 | logme "Waiting for user login before proceeding."
310 | while ! /usr/bin/pgrep -xq Finder;
311 | do
312 | if [ $( /bin/launchctl list | /usr/bin/grep -c "com.jamfsoftware.task.Every 15 Minutes" ) != "0" ];
313 | then
314 | logme "Disabling Jamf Check-In."
315 | /bin/launchctl bootout "system/com.jamfsoftware.task.Every 15 Minutes"
316 | jamfpid=$( /bin/ps -ax | /usr/bin/grep "jamf policy -randomDelaySeconds" | /usr/bin/grep -v "grep" | /usr/bin/awk '{ print $1 }' )
317 | [ "$jamfpid" != "" ] && kill -9 "$jamfpid"
318 | unset jamfpid
319 | fi
320 | done
321 | logme "Dock process running. Starting deployment process."
322 |
323 | # Attempt to check if device has properly enrolled into Jamf Pro
324 | logme "Checking if Jamf Enrollment has completed properly"
325 | jsstest=$( /usr/local/bin/jamf policy -event isjssup 2>/dev/null | /usr/bin/grep -c "Script result: up" )
326 |
327 | # Initiate re-enrollment of computer if the earlier test failed
328 | if [ "$jsstest" = "0" ];
329 | then
330 | # Write marker file for Jamf auditing
331 | /usr/bin/touch ${workfolder}/enrollretry
332 |
333 | # Jamf API enrollment credentials and URL
334 | jsonresponse=$( getjamftoken "$clientid" "$clientsecret" "$jssurl" )
335 |
336 | jamftoken=$( /usr/bin/plutil -extract access_token raw -o - - <<< "$jsonresponse" )
337 | type=$( /usr/bin/plutil -extract token_type raw -o - - <<< "$jsonresponse" )
338 | expiry=$( /usr/bin/plutil -extract expires_in raw -o - - <<< "$jsonresponse" )
339 |
340 | # Find and grab computer jamf ID number
341 | jamfid=$( getcomputerid "$jamftoken" "$jssurl" "$udid" )
342 |
343 | # Send re-enrollment command
344 | /usr/bin/curl -s -X POST "${jssurl}api/v1/jamf-management-framework/redeploy/${jamfid}" -H "accept: application/json" -H "Authorization: ${type} ${jamftoken}"
345 |
346 | # Invalidate the requested API token
347 | invalidatejamftoken "$token" "$jssurl"
348 |
349 | # No computer record, thus we can't re-enroll so we must exit hard here.
350 | if [[ "$computerid" =~ stdin ]];
351 | then
352 | opts+=(${(f)}"--title \"Computer Deployment Failed\"")
353 | opts+=(${(f)}"--icon ${icons}/jamfuserdetails.png")
354 | opts+=(${(f)}"--message \"Your computer has failed to install correct software. \n\nClick [here to contact IT Support](https://help.corp.com) and request assistance. \n\nThen please click \"Finish\" to exit this message.\n\nThis message will automatically disappear in 30 seconds time.\"")
355 | opts+=(${(f)}"--quitkey o")
356 | opts+=(${(f)}"--position center")
357 | opts+=(${(f)}"--timer 30")
358 | opts+=(${(f)}"--button1text \"Finish\"")
359 |
360 | eval "$sd" --args "${opts[*]}" &
361 | sleep 0.1
362 |
363 | exitscript 1 'Re-enrollment of device failed'
364 | fi
365 |
366 | # Wait for enrollment to complete
367 | while [ "$jsstest" != "0" ]; do jsstest=$( /usr/local/bin/jamf policy -event isjssup 2>/dev/null | /usr/bin/grep -c "Script result: up" ); sleep 0.1; done
368 | fi
369 |
370 | # Kill any check-in in progress
371 | jamfpid=$( /bin/ps -ax | /usr/bin/grep "jamf policy -randomDelaySeconds" | /usr/bin/grep -v "grep" | /usr/bin/awk '{ print $1 }' )
372 | if [ "$jamfpid" != "" ];
373 | then
374 | kill -9 "$jamfpid"
375 | fi
376 |
377 | #
378 | ## Start the deployment process now a user has logged in
379 | #
380 |
381 | # Write out deployment start timestamp
382 | timestamp=$( /bin/date '+%Y-%m-%d %H:%M:%S' | /usr/bin/tee -a $workfolder/.deploystart )
383 |
384 | # Who's the current user and their details?
385 | currentuser=$( /usr/sbin/scutil <<< "show State:/Users/ConsoleUser" | /usr/bin/awk -F': ' '/[[:space:]]+Name[[:space:]]:/ { if ( $2 != "loginwindow" ) { print $2 }}' )
386 | userid=$( /usr/bin/id -u $currentuser )
387 | userhome=$( /usr/bin/dscl . read /Users/${currentuser} NFSHomeDirectory | /usr/bin/awk '{print $NF}' )
388 | userpref="${userhome}/Library/Preferences/com.jamf.connect.state.plist"
389 | useremail="${currentuser}@${domain}"
390 | userkeychain="${userhome}/Library/Keychains/login.keychain-db"
391 | jssurl=$( /usr/bin/defaults read /Library/Preferences/com.jamfsoftware.jamf.plist jss_url )
392 |
393 | # Work out user's first name and capitalise first letter
394 | firstname=$( echo $currentuser | /usr/bin/cut -d"." -f1 )
395 | firstname=$( /usr/bin/tr '[:lower:]' '[:upper:]' <<< ${firstname:0:1} )${firstname:1}
396 |
397 | #
398 | ## Load the initial swiftDialog screen
399 | #
400 | logme "Start the swiftDialog process with the initial screen."
401 |
402 | # Set initial icon based on whether the Mac is a desktop or laptop
403 | # Use SF symbols for this https://developer.apple.com/sf-symbols/
404 | if /usr/sbin/ioreg -rd1 -c IOPlatformExpertDevice | /usr/bin/awk -F'["|"]' '/model/{print $4}' | /usr/bin/grep -q "Book";
405 | then
406 | icon="SF=laptopcomputer.and.arrow.down,weight=semibold,colour1=black"
407 | else
408 | icon="SF=desktopcomputer.and.arrow.down,weight=semibold,colour1=black"
409 | fi
410 |
411 | dialogver=$( /usr/local/bin/dialog --version )
412 |
413 | title="Hi $firstname, Welcome to your new computer!"
414 | message="Please wait while we configure your computer ..."
415 | helpmessage="If you need assistance, please contact the IT Service Desk: \n- e-mail your line manager. \n\n**Computer Information:** \n- **Operating System:** ${macosver} (${macosbuild}) \n- **Serial Number:** ${serial} \n- **Dialog:** ${dialogver} \n- **Started:** ${timestamp}"
416 | infobox="TRR Device Info\n\n**Username:**\n- Pending\n\n**Computer Name:** \n- Pending\n\n**Department**\n- Pending\n\n**Location**\n- Pending"
417 |
418 | # Set an array of options here. (If you're wondering what all this ${(f)}) stuff is,
419 | # it's a better way of not splitting on spaces without messing with IFS.
420 | opts+=(${(f)}"--bannerimage \"$bannerimage\"")
421 | opts+=(${(f)}"--title \"$title\"")
422 | opts+=(${(f)}"--message \"$message\"")
423 | opts+=(${(f)}"--helpmessage \"$helpmessage\"")
424 | opts+=(${(f)}"--icon \"$icon\"")
425 | opts+=(${(f)}"--infobox \"${infobox}\"")
426 | opts+=(${(f)}"--progress")
427 | opts+=(${(f)}"--progresstext \"Initializing configuration ...\"")
428 | opts+=(${(f)}"--button1text \"Wait\"")
429 | opts+=(${(f)}"--button1disabled")
430 | opts+=(${(f)}"--infotext \"$scriptversion\"")
431 | opts+=(${(f)}"--messagefont 'size=14'")
432 | opts+=(${(f)}"--height '780'")
433 | opts+=(${(f)}"--position centre")
434 | opts+=(${(f)}"--blurscreen")
435 | opts+=(${(f)}"--ontop")
436 | opts+=(${(f)}"--quitkey o")
437 | opts+=(${(f)}"--commandfile \"$sdcontrolfile\"")
438 |
439 | # Now run swiftDialog and get this party started
440 | eval "$sd" "${opts[*]}" &
441 |
442 | # Wait and clear the options array
443 | sleep 1
444 | unset opts
445 |
446 | #
447 | ## Get current user details and put them in Jamf but also ...
448 | #
449 |
450 | # Set up the first two list items by hand
451 | updatestatus "list: show"
452 | sleep 1
453 | updatestatus "listitem: add, title: Update Jamf User Details, icon: ${icons}/jamfuserdetails.png, statustext: Waiting, status pending"
454 | updatestatus "listitem: add, title: Set Computer Name, icon: ${icons}/setcomputername.png, statustext: Waiting, status: pending"
455 |
456 | ## HORRIBLE workaround for Jamf Connect bug
457 | logme "Writing username: $useremail to Jamf Connect plist"
458 | updatestatus "progresstext: Update Jamf User Details"
459 | updatestatus "listitem: title: Update Jamf User Details, statustext: Configuring, status: wait"
460 | /bin/launchctl bootout gui/$userid /Library/LaunchAgents/com.jamf.connect.plist
461 | /usr/bin/defaults write "$userpref" DisplayName -string "$useremail"
462 | /usr/sbin/chown ${currentuser}:staff "$userpref"
463 | /usr/local/bin/jamf policy -event getusername
464 | updatestatus 'infobox: Device Info\\n\\n**Username:** \\n- '"$currentuser"'\\n\\n**Computer Name:** \\n- Pending\\n\\n**Department**\\n- Pending\\n\\n**Location**\\n- Pending'
465 | updatestatus "listitem: title: Update Jamf User Details, statustext: Completed, status: success"
466 |
467 | #
468 | ## Set computer name via auto script in Jamf.
469 | #
470 | logme "Setting computer name via Jamf policy"
471 | updatestatus "listitem: title: Set Computer Name, statustext: Configuring, status: wait"
472 | updatestatus "progresstext: Setting Computer Name"
473 | /usr/local/bin/jamf policy -event autoname
474 | computername=$( /usr/sbin/scutil --get ComputerName )
475 |
476 | # Now the computer hostname. Do a regex match for 12345678 type format.
477 | logme "Checking if computer name was set correctly"
478 | if [[ $computername =~ ^[0-9]{8}$ ]];
479 | then
480 | logme "Computer name is in the correct format"
481 | updatestatus "listitem: title: Set Computer Name, statustext: Completed, status: success"
482 | else
483 | logme "Computer name not set correctly"
484 | computername="ERROR"
485 | updatestatus "listitem: title: Set Computer Name, statustext: Failed, status: fail"
486 | fi
487 | updatestatus 'infobox: Device Info\\n\\n**Username:** \\n- '"$currentuser"'\\n\\n**Computer Name:** \\n- '"$computername"'\\n\\n**Department**\\n- Pending\\n\\n**Location**\\n- Pending'
488 |
489 | #
490 | ## Get building and department info
491 | #
492 |
493 | # Obtain an access token for the Jamf API
494 | jsonresponse=$( getjamftoken "$clientid" "$clientsecret" "$jssurl" )
495 | jamftoken=$( /usr/bin/plutil -extract access_token raw -o - - <<< "$jsonresponse" )
496 | type=$( /usr/bin/plutil -extract token_type raw -o - - <<< "$jsonresponse" )
497 |
498 | # Now get the device record from the hardware udid
499 | deviceuserrecord=$( /usr/bin/curl -s -X GET "${jssurl}api/v1/computers-inventory/?section=USER_AND_LOCATION&page=0&page-size=1&sort=id%3Aasc&filter=udid%3D%3D${udid}" -H "accept: application/json" -H "authorization: ${type} ${jamftoken}" )
500 |
501 | # From that we can extract out the building and department info from the device record
502 | buildingid=$( /usr/bin/plutil -extract results.0.userAndLocation.buildingId raw -o - - <<< "$deviceuserrecord" )
503 | departmentid=$( /usr/bin/plutil -extract results.0.userAndLocation.departmentId raw -o - - <<< "$deviceuserrecord" )
504 |
505 | # Problem: They're listed in the record as their Jamf ID numbers. So do more calls to capture the records for those IDs
506 | buildingjson=$( /usr/bin/curl -s -X GET "${jssurl}api/v1/buildings/${buildingid}" -H "accept: application/json" -H "authorization: Bearer ${jamftoken}" )
507 | departmentjson=$( /usr/bin/curl -s -X GET "${jssurl}api/v1/departments/${departmentid}" -H "accept: application/json" -H "authorization: Bearer ${jamftoken}" )
508 |
509 | building=$( /usr/bin/plutil -extract name raw -o - - <<< "$buildingjson" 2>/dev/null )
510 | department=$( /usr/bin/plutil -extract name raw -o - - <<< "$departmentjson" 2>/dev/null )
511 |
512 | # Finally we have names. Or do we? Fail out if not. Report if we do.
513 | if [ ! -z "$building" ] && [ ! -z "$department" ];
514 | then
515 | updatestatus 'infobox: Device Info\\n\\n**Username:** \\n- '"$currentuser"'\\n\\n**Computer Name:** \\n- '"$computername"'\\n\\n**Department**\\n- '"$department"'\\n\\n**Location**\\n- '"$building"''
516 | else
517 | updatestatus 'infobox: Device Info\\n\\n**Username:** \\n- '"$currentuser"'\\n\\n**Computer Name:** \\n- '"$computername"'\\n\\n**Department**\\n- Retrieval FAILED\\n\\n**Location**\\n- Contact I.T.'
518 | fi
519 |
520 | # Invalidate the requested API token
521 | invalidatejamftoken "$jamftoken" "$jssurl"
522 |
523 | # Clean up icons
524 | updatestatus "listitem: delete, title: Update Jamf User Details"
525 | updatestatus "listitem: delete, title: Set Computer Name"
526 |
527 | #
528 | ## Software Deployment Prep Process
529 | #
530 | logme "Getting policy list from Jamf"
531 | updatestatus "progresstext: Starting Deployment Process"
532 | jamfprocess deploy
533 |
534 | #
535 | ## Device Inventory Process
536 | #
537 | updatestatus "listitem: add, title: Update Inventory, icon: ${icons}/updateinventory.png, statustext: In Progress, status: pending"
538 | updatestatus "progresstext: Updating Jamf Inventory Record"
539 | $jb recon
540 | updatestatus "listitem: title: Update Inventory, statustext: Completed, status: success"
541 | sleep 1
542 |
543 | #
544 | ## Check-in Policy Process
545 | #
546 | updatestatus "progresstext: Starting check-in policies"
547 | updatestatus "listitem: delete, title: Update Inventory"
548 | jamfprocess
549 |
550 | #
551 | ## Set up following tasks to be displayed
552 | #
553 | updatestatus "listitem: add, title: Enable Jamf Check-In, icon: ${icons}/jamfcheckin.png, statustext: Waiting, status: pending"
554 | updatestatus "listitem: add, title: Finalising Deployment, icon: ${icons}/finished.png, statustext: Waiting, status: pending"
555 | sleep 1
556 |
557 | #
558 | ## Re-enable Jamf check-in
559 | #
560 | updatestatus "progresstext: Enabling Jamf Check-In"
561 | updatestatus "listitem: title: Enable Jamf Check-In, statustext: Waiting, status: wait"
562 | /bin/launchctl bootstrap system "$ld"
563 | sleep 1
564 | updatestatus "listitem: title: Enable Jamf Check-In, statustext: Completed, status: success"
565 |
566 | #
567 | ## Create deploy finished touch file and final device inventory process
568 | #
569 | updatestatus "listitem: title: Finalising Deployment, statustext: Waiting, status: pending"
570 | updatestatus "progresstext: Finalising the device deployment"
571 | /bin/date '+%Y-%m-%d %H:%M:%S' | /usr/bin/tee -a $workfolder/.deploycomplete
572 | $jb recon
573 |
574 | #
575 | ## Force Jamf Connect menu agent to run
576 | #
577 | /bin/launchctl bootstrap gui/$userid /Library/LaunchAgents/com.jamf.connect.plist
578 | sleep 1
579 | updatestatus "listitem: delete, title: Enable Jamf Check-In"
580 | updatestatus "listitem: title: Finalising Deployment, statustext: Completed, status: success"
581 |
582 | #
583 | ## Quit main swiftDialog window
584 | #
585 | updatestatus "progresstext: Deployment Completed"
586 | sleep 10
587 | updatestatus "quit:"
588 | sleep 0.1
589 |
590 | #
591 | ## Clean up files and script and exit
592 | #
593 | logme "Cleaning up working files and exiting."
594 | exitscript 0 'Success!'
595 |
--------------------------------------------------------------------------------
/pkg install scripts/postinstall.sh:
--------------------------------------------------------------------------------
1 | #!/bin/zsh
2 |
3 | # Preinstallation script for installing swiftDialog plus deploy
4 | # Author - richard@richard-purves.com
5 |
6 | # Version 1.0 - 04-14-2023
7 |
8 | # Check for current logged in user
9 | loggedinuser=$( /usr/sbin/scutil <<< "show State:/Users/ConsoleUser" | /usr/bin/awk -F': ' '/[[:space:]]+Name[[:space:]]:/ { if ( $2 != "loginwindow" ) { print $2 }}' )
10 |
11 | # For some reason best known to Apple, our files occasionally have quarantine attributes set. Fix this.
12 | /usr/bin/xattr -r -d com.apple.quarantine /usr/local/corp
13 | /usr/bin/xattr -r -d com.apple.quarantine /Library/LaunchDaemons/com.corp.swiftdeploy.plist
14 | /usr/bin/xattr -r -d com.apple.quarantine /Library/Application\ Support/Dialog
15 | /usr/bin/xattr -r -d com.apple.quarantine /usr/local/bin/dialog
16 |
17 | # If loginwindow, setup assistant or no user, then we're in a DEP environment. Load the LaunchDaemon.
18 | if [[ "$loggedinuser" = "loginwindow" ]] || [[ "$loggedinuser" = "_mbsetupuser" ]] || [[ -z "$loggedinuser" ]];
19 | then
20 | /bin/launchctl bootstrap system /Library/LaunchDaemons/com.corp.swiftdeploy.plist
21 | fi
22 |
23 | # User initiated enrollment devices excluded. We'll pick them up after a restart via Jamf.
24 | exit
--------------------------------------------------------------------------------
/pkg install scripts/preinstall.sh:
--------------------------------------------------------------------------------
1 | #!/bin/zsh
2 |
3 | # Preinstallation script for installing swiftDialog plus deploy
4 | # Very much based on the work of Dan Snelson and Bart Reardon
5 | # Author - richard@richard-purves.com
6 |
7 | # Version 1.1 - 01-08-2024
8 |
9 | # Variables here
10 | dialogteamid="PWA5E9TQ59"
11 |
12 | # We're deploying some branding graphics so let's make sure the folders exist for them to go into!
13 | [ ! -d "/usr/local/corp" ] && mkdir /usr/local/corp
14 |
15 | # Now let's make sure the ownership and permissions are correct
16 | chown -R root:wheel /usr/local/corp
17 | chmod -R 755 /usr/local/corp
18 |
19 | # Download swiftDialog for our use if it's not already present
20 | if [ ! -e "/Library/Application Support/Dialog/Dialog.app" ];
21 | then
22 | for i in {1..5};
23 | do
24 | echo "Finding swiftDialog latest URL. Attempt $i of 5."
25 | sdurl=$( /usr/bin/curl -s "https://api.github.com/repos/swiftDialog/swiftDialog/releases/latest" | /usr/bin/awk -F '"' "/browser_download_url/ && /pkg\"/ { print \$4; exit }" )
26 | [[ "$sdurl" =~ ^https?:\/\/(.*) ]] && break
27 | sleep 0.2
28 | done
29 |
30 | # In case things move or the autodetect fails, you can just hardcode a path to the installer here
31 | #sdurl="https://github.com/swiftDialog/swiftDialog/releases/download/v2.3.3-4734/dialog-2.3.3-4734.pkg"
32 |
33 | [ "$i" -ge 5 ] && { echo "URL detection failed."; exit 1; }
34 |
35 | for i in {1..5};
36 | do
37 | echo "Download attempt no. $i of 5."
38 | httpstatus=$( /usr/bin/curl -L -s -o "/private/tmp/dialog.pkg" "$sdurl" -w "%{http_code}" )
39 | [ "$httpstatus" = "200" ] && break
40 | sleep 0.2
41 | done
42 |
43 | [ "$i" -ge 5 ] && { echo "Download failed."; exit 1; }
44 |
45 | echo "Checking swiftDialog download."
46 | testteamid=$( /usr/sbin/spctl -a -vv -t install "/private/tmp/Dialog.pkg" 2>&1 | /usr/bin/awk '/origin=/ {print $NF }' | tr -d '()' )
47 |
48 | [ "$testteamid" != "$dialogteamid" ] && { echo "Download check failed."; exit 1; }
49 |
50 | echo "Installing SwiftDialog."
51 | /usr/sbin/installer -pkg "/private/tmp/dialog.pkg" -target /
52 | sleep 2
53 |
54 | echo "Checking installed swiftDialog."
55 | if [ -f "/usr/local/bin/dialog" ];
56 | then
57 | echo "swiftDialog version $( /usr/local/bin/dialog --version ) installed."
58 | else
59 | echo "swiftDialog install failed."
60 | exit 1
61 | fi
62 |
63 | else
64 | echo "SwiftDialog already installed. Skipping download."
65 | fi
66 |
67 | /bin/rm -f /private/tmp/dialog.pkg
68 |
69 | exit 0
70 |
--------------------------------------------------------------------------------