Post

Updating Our DEPNotify Process With a LaunchDaemon

In my previous post I discussed our process of using DEPNotify to assign and deploy laptops. After writing the post and sharing it, several people asked me “what happens if the policy runs before the GUI is ready?” I found that the deployment policy ran and would just hang due to no user input because the DEPNotify window wouldn’t show. My simple, yet crude solution was to have our tech’s manually create the admin account (which would then log in automatically) and just wait for the dock to launch. This also proved to be problematic as the Dock process would start too quickly and the DEPNotify window would again fail to open. I knew it was a matter of time until I switched over to a launch daemon…well….that time is now.

Creating the Scripts

At first I just wanted a simple launch daemon that would call our enrollment script (the one I used previously), this launch daemon would run every 10 seconds until conditions were met. With a little help from slack I created my launch daemon.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>GroupName</key>
	<string>wheel</string>
	<key>InitGroups</key>
	<false/>
	<key>Label</key>
	<string>com.uarts.launch</string>
	<key>Program</key>
	<string>/var/tmp/com.uarts.DEPprovisioning.facstaff</string>
	<key>RunAtLoad</key>
	<true/>
	<key>StartInterval</key>
	<integer>10</integer>
	<key>UserName</key>
	<string>root</string>
	<key>StandardErrorPath</key>
	<string>/var/tmp/depnotify.launch.err</string>
	<key>StandardOutPath</key>
	<string>/var/tmp/depnotify.launch.out</string>
</dict>
</plist>

The checks I added were to be sure that Finder and the Dock were both running, that the user was not _mbsetupuser (the built-in setup account) and that the “setupDone” BOM receipt I am creating afterward is not there, if those conditions are met, the launch daemon finally runs. If the process was successful, the BOM receipt drops in and will not allow it to run again, it then removes the launch daemon. Please note that I’m not unloading the launch daemon, I ran into some trouble with trying to unload it so I just went with removing it and using the BOM file to stop it from launching again if the machine isn’t rebooted after deployment (we always reboot, so it goes away anyway).

Now, at this point in my testing I was pretty satisfied but after some thought I decided that I wanted my launch daemon to do all of the heavy lifting; instead of having an enrollment policy install a script which calls another policy which calls more policies and scripts I decided to take out the middleman. Enrollment policy installs script which does everything else (runs policies and scripts). My final provisioning script ended up like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#!/bin/bash
#
#
# Created by John Mahlman, University of the Arts Philadelphia (jmahlman@uarts.edu)
# Name: com.uarts.DEPprovisioning.facstaff
#
# Purpose: Install and run DEPNotify at enrollment time and do some final touches
# for the users.  It also checks for software updates and installs them if found.
# This gets put in the composer package along with DEPNotofy, com.uarts.launch.plist,
# and any supporting files. Then add the post install script to the package.
#
# Get the logged in user
CURRENTUSER=$(/usr/bin/python -c 'from SystemConfiguration import SCDynamicStoreCopyConsoleUser; import sys; username = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None])[0]; username = [username,""][username in [u"loginwindow", None, u""]]; sys.stdout.write(username + "\n");')
# Setup Done File
setupDone="/var/db/receipts/com.uarts.provisioning.done.bom"

JAMFBIN=/usr/local/jamf/bin/jamf

if pgrep -x "Finder" \
&& pgrep -x "Dock" \
&& [ "$CURRENTUSER" != "_mbsetupuser" ] \
&& [ ! -f "${setupDone}" ]; then

  # Kill any installer process running
  killall Installer
  # Wait a few seconds
  sleep 5

  # Let's Roll!

  # DEPNotify Log file
  DNLOG=/var/tmp/depnotify.log

  # Configure DEPNotify
  sudo -u "$CURRENTUSER" defaults write menu.nomad.DEPNotify PathToPlistFile /var/tmp/
  sudo -u "$CURRENTUSER" defaults write menu.nomad.DEPNotify RegisterMainTitle "Assignment..."
  sudo -u "$CURRENTUSER" defaults write menu.nomad.DEPNotify RegisterButtonLabel Assign
  sudo -u "$CURRENTUSER" defaults write menu.nomad.DEPNotify UITextFieldUpperLabel "Assigned User"
  sudo -u "$CURRENTUSER" defaults write menu.nomad.DEPNotify UITextFieldUpperPlaceholder "dadams"
  sudo -u "$CURRENTUSER" defaults write menu.nomad.DEPNotify UITextFieldLowerLabel "Asset Tag"
  sudo -u "$CURRENTUSER" defaults write menu.nomad.DEPNotify UITextFieldLowerPlaceholder "UA42LAP1337"

  echo "Command: MainTitle: Click Assign to begin Deployment" >> $DNLOG
  echo "Command: MainText: This process will assign this device and install base software." >> $DNLOG
  echo "Command: Image: /var/tmp/uarts-logo.png" >> $DNLOG
  echo "Command: DeterminateManual: 5" >> $DNLOG

  # Open DepNotify
  sudo -u "$CURRENTUSER" /var/tmp/DEPNotify.app/Contents/MacOS/DEPNotify &

  # Let's caffinate the mac because this can take long
  /usr/bin/caffeinate -d -i -m -u &
  caffeinatepid=$!

  # get user input...
  echo "Command: ContinueButtonRegister: Assign" >> $DNLOG
  echo "Status: Just waiting for you..." >> $DNLOG
  DNPLIST=/var/tmp/DEPNotify.plist
  # hold here until the user enters something
  while : ; do
  	[[ -f $DNPLIST ]] && break
  	sleep 1
  done
  # grab the username from the plist that is created so we can use it to automaticlaly create the account
  USERNAME=$(/usr/libexec/plistbuddy $DNPLIST -c "print 'Assigned User'" | tr [A-Z] [a-z])

  echo "Command: MainTitle: Preparing the system for Deployment" >> $DNLOG
  echo "Command: MainText: Please do not shutdown, reboot, or close your device, it will automatically reboot when complete." >> $DNLOG

  echo "Command: DeterminateManualStep:" >> $DNLOG
  # Do the things! We're calling a single policy now.
  echo "Status: Installing base software..." >> $DNLOG
  $JAMFBIN policy -event enroll-firstRunFACSTAFF

  echo "Command: DeterminateManualStep:" >> $DNLOG
  echo "Status: Creating local user account with password as username..." >> $DNLOG
  /usr/local/jamf/bin/jamf createAccount -username $USERNAME -realname $USERNAME -password $USERNAME -admin

  echo "Command: DeterminateManualStep:" >> $DNLOG
  echo "Status: Assigning and renaming device..." >> $DNLOG
  $JAMFBIN policy -event enroll-assignDevice

  echo "Status: Updating Inventory..." >> $DNLOG
  $JAMFBIN recon

  echo "Command: MainTitle: Almost done!" >> $DNLOG
  echo "Command: DeterminateManualStep:" >> $DNLOG
  echo "Status: Checking for and installing any OS updates..." >> $DNLOG
  /usr/sbin/softwareupdate -ia

  kill "$caffeinatepid"

  echo "Command: RestartNow:" >>  $DNLOG

  # Remove DEPNotify and the logs
  /bin/rm -Rf /var/tmp/DEPNotify.app
  /bin/rm -Rf /var/tmp/uarts-logo.png
  /bin/rm -Rf $DNLOG
  /bin/rm -Rf $DNPLIST

  # Wait a few seconds
  sleep 5
  # Create a bom file that allow this script to stop launching DEPNotify after done
  /usr/bin/touch /var/db/receipts/com.uarts.provisioning.done.bom
  # Remove the Launch Daemon
  /bin/rm -Rf /Library/LaunchDaemons/com.uarts.launch.plist

fi
exit 0

If you choose to use the leaner script it will still work, just be sure whatever policy you call sets up DEPNotify.

Putting it All Together

The next task was getting the program and scripts on the system at enrollment, since my last post was basically doing this in a different method it was simple to accomplish. I used Jamf Composer to build a package to drop DEPNotify, my logo file, my launch daemon, and my script, then launch the launch daemon when done.

Composer with all scripts and files placed.
Composer with all scripts and files placed.

Make sure all of your file owners and groups are correct (everything is basically root:wheel) and be sure the permissions are correct (644 seems to work fine). And finally, the postinstall script for that package.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/bin/sh
## postinstall

#!/bin/sh

echo  "disable auto updates ASAP" >> /var/log/jamf.log
	defaults write /Library/Preferences/com.apple.SoftwareUpdate.plist AutomaticDownload -bool NO  
	defaults write /Library/Preferences/com.apple.SoftwareUpdate.plist ConfigDataInstall -bool NO  
	defaults write /Library/Preferences/com.apple.SoftwareUpdate.plist CriticalUpdateInstall -bool NO  
	defaults write /Library/Preferences/com.apple.commerce.plist AutoUpdateRestartRequired -bool NO  
	defaults write /Library/Preferences/com.apple.commerce.plist AutoUpdate -bool NO
	defaults write /Library/Preferences/com.apple.SoftwareUpdate.plist AutomaticCheckEnabled -bool NO

## Make the main script executable
echo  "setting main script permissions" >> /var/log/jamf.log
	chmod a+x /var/tmp/com.uarts.DEPprovisioning.facstaff.sh

## Set permissions and ownership for launch daemon
echo  "set LaunchDaemon permissions" >> /var/log/jamf.log
	chmod 644 /Library/LaunchDaemons/com.uarts.launch.plist
	chown root:wheel /Library/LaunchDaemons/com.uarts.launch.plist

## Load launch daemon into the Launchd system
echo  "load LaunchDaemon" >> /var/log/jamf.log
	launchctl load /Library/LaunchDaemons/com.uarts.launch.plist

exit 0		## Success
exit 1		## Failure

While lines 18-32 are the only ones needed I really like to have the rest. They temporarily change some settings so that if the person setting up the machine (in our case, the techs) decide to let the laptop sit for a while before deploying or if they close the lid/shutdown updates don’t try to run.

Build the package, set it to run with an “Enrollment” trigger on your appropriate Pre-stage enrollment machines and you should be off to the races.

While testing this method the DEPNotify GUI always comes up (even with waiting at weird times) and the deployment policy I have never runs until the user is fully logged in. Would I say this will work every single time? No, because there will always be an edge case, but so far it’s been working for us perfectly.

In order for us to move forward with a more “unattended” DEP deployment I’m going to work on a process that will create a user that will automatically log in (probably using pyCreateUserpkg), reboot then launch the launch daemon without user input. This will probably happen within the next few weeks and hopefully I’ll have another updated post!

You can find all of the scripts I use with DEPNotify on my Git. That folder also includes my original script from the last post as well as my “assign and rename” script.

Notes

  • Thanks to @techgrltweeter in the Mac Admins slack for some of her suggestions with the postinstall script and launch daemon
  • Thanks to everyone in the #depnotify channel also, you are all awesome!
This post is licensed under CC BY-SA 4.0 by the author.