Recently, I was challenged to develop an imaging process that is more automated than the typical creation of numerous imaging configurations that need to be sifted through and selected during the imaging process. I was working with a customer that had a large, distributed environment where there could potentially be hundreds of configurations. They wanted a way for the imaging to automatically deploy the correct applications and settings based on where the machine is or its assigned user.
This seemed like a great opportunity to leverage policies scoped to smart groups and network segments and set them to run on the enrollment trigger, allowing the JSS to handle all of the decision making for what gets installed on each computer based on pre-defined criteria. This worked great, but we quickly ran into two problems:
1) Since re-enrollment can be used as a troubleshooting step, we didn’t want the policies running again if a machine was re-enrolled without being re-imaged.
2) How do we know when a machine is done running these policies when it just sits on the login window while all the magic happens in the background?
The first step is to call BigHonkingText, which will display a full screen message letting the user know that there is something happening.
/usr/local/postImagingConfig/resources/BigHonkingText -w 100% -h 100% -m -p 0 "Post imaging configuration in progress, please wait." >>/dev/null 2>&1 &
The -w and -h options set to 100% run BHT at full screen. The -m option ignores mouse clicks. The -p option set to 0 means BHT will run indefinitely. Finally, the message displayed on screen is in quotes.
Since this is mainly a policy-based workflow, it relies on the machine being enrolled to function. I’ve noticed that enrollment after imaging can sometimes be delayed by a few minutes in version 9 of the JSS. I suspect that this is due to the enrollment script first kicking off before networking is active. If the enrollment hasn’t happened yet, the script needs to pause until enrollment is complete.
Here is the logic I used to check for enrollment:
until [ ! -f "/Library/Application Support/JAMF/FirstRun/Enroll/enroll.sh" ] do /Library/Application Support/JAMF/FirstRun/Enroll/enroll.sh if [ ! -f "/Library/Application Support/JAMF/FirstRun/Enroll/enroll.sh" ] then break else sleep 30 fi done
Okay, let’s break this down step by step.
I’m using an until loop to check for the existence of the JAMF enrollment script. Since this script deletes itself when finished, this is a pretty surefire way to tell if a machine is enrolled. If the script is found, it will attempt to kick off the script. This isn’t technically necessary, as JAMF’s Launch Daemon will call the script every 5 minutes until enrollment is successful; however, I wanted a way to speed things up so I call it at the beginning of the loop.
Once the script runs, an if statement checks for the existence of the script once again. If the script is found, it will sleep for 30 seconds, then the until loop will restart. If the script isn’t found, it will break the loop and the script will continue.
The next step is to enforce the management framework. This should be happening automatically as part of enrollment, but I’ve had mixed success with it happening before the script finishes, so I like to force it here.
Since a lot of my post-imaging configurations are based on Smart Groups, I like to update the inventory here, just to make sure it’s 100% accurate.
Now it’s time for the main event. My post-imaging configuration policies are all on a manual trigger called “config” which is what I’m calling here:
/usr/sbin/jamf policy -event config
Since my policies install packages and run software updates, I make sure to update the inventory one last time so the JSS is completely up-to-date.
Finally, we run cleanup, which looks like this.
/bin/rm -rf /usr/local/postImagingConfig/ /bin/rm -f /Library/LaunchDaemons/com.pretendco.firstrun.plist /usr/sbin/jamf reboot -immediately
This removes the entire postImagingConfig directory, which is where the script and BigHonkingText live. It then removes the Launch Daemon that’s calling the script, and finishes by calling a reboot using the jamf binary.
Once the machine reboots, the user is presented with the login window, and the machine is ready to go.
The Launch Daemon
You’ll need a Launch Daemon to kick this off. Mine looks like this:
<?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>KeepAlive</key> <true/> <key>Label</key> <string>com.pretendco.firstrun.plist</string> <key>ProgramArguments</key> <array> <string>/usr/local/postImagingConfig/scripts/postImageConfig.sh</string> </array> <key>RunAtLoad</key> <true/> </dict> </plist>
This calls the script at startup and keeps it alive until a reboot. I recommend using Lingon to make your Launch Daemon.
Packaging is fairly straightforward. I used Composer to package up the Launch Daemon, BigHonkingText, and the postImageConfig script. The package I built looks like this.
Deployment is also fairly simple. I set the package to be installed after reboot, and it’s the only thing that is laid down at imaging time aside from the OS. Once the machine has completed the Casper Imaging process and reboots, the Launch Daemon will start up the post-imaging configuration script and the Smart Groups and policies take over the rest of the process.
All of the desired software and settings are set up in the JSS as policies, and set to the manual trigger of “config” which is called by the postImagingConfig.sh script.
A site tech boots the machine to Casper Imaging and selects the one and only configuration available. The machine is imaged with a base OS and the postImagingConfig package. The machine reboots, and postImagingConfig begins. The tech sees a full screen message that says “Post imaging configuration in progress. Please wait.” In the background, enrollment completes and the “config” trigger policies are then run.
Once the policies have completed, inventory is updated in the JSS, the postImagingConfig script and LaunchDaemon are deleted, and the machine reboots. It is then ready for the end user.
I’ve made the script, launch daemon and package available on Github.