My OS X deployment workflow is a very hands-off approach. As Mike Boylan and I described in our post “How to Lose 100 Pounds in 10 Days,” my process involves crafting an OS X installer package using Greg Neagle’s CreateOSXInstallPkg to incorporate Munki, iCloud prompt / Lion registration suppression, a local admin account, and ARD privileges for the admin account. I use DeployStudio to deploy the OS X Install pkg along with a Munki bootstrap, so the machine installs Lion / Mountain Lion and then reboots, and automatically kicks Munki in.
95% of our new Mac purchases are MacBook Airs, which don’t have ethernet. So we had to buy a number of Thunderbolt -> Gigabit Ethernet adapters to be able to accommodate using DeployStudio effectively on these devices, since I find the DeployStudio-over-wifi performance to be risky and unpredictable, but often slow. I’d simply rather use a gigabit connection to deploy a 5 GB install if possible. I’ve got seven of these Thunderbolt adapters, but I’ve got 120 MacBook Airs to go through.
What I want is to deploy the machines from DeployStudio over the wired ethernet connection via the adapter, but then have them do the Munki bootstrap on the wireless network. That way, I can get more machines started and I can move those MacBook Airs off my deployment desk and into a larger storage area, where they can wirelessly download their software and configurations.
Thankfully, Rich Trouton had created a helpful script to join a wireless network that he uses as part of a DeployStudio workflow. This is effective when you’re using DeployStudio to actually restore an image onto a drive, because DeployStudio can run its ds_finalize script that also runs postponed packages or scripts after the restoration. That unfortunately doesn’t work when you use the CreateOSXInstallPkg workflow, which requires a live install, as there’s no image restoration and therefore no ds_finalize. With no ds_finalize, you can’t execute any postponed scripts or packages. All of those scripts have to function as part of the CreateOSXInstallPkg package itself, so I’d have to incorporate this script into the package creation process, along with the other packages I mentioned above.
The primary problem is that the script relies on this command:
networksetup -addpreferredwirelessnetworkatindex $wifiDevice $SSID $INDEX $SECURITY $PASSWORD
This command adds a preferred wireless network to the list, and saves the password in the Keychain. When incorporated into CreateOSXInstallpkg, this script tries to run during the Lion install process. In the very-stripped-down Lion Install environment, this doesn’t do what we want it to, at all. It just doesn’t function, because it’s not actually making changes to the system we’re installing. It only applies to the boot environment, and we don’t need to add a preferred wireless network to the environment that is about to go away once we restart.
So in other words, we need to find run this command after the OS is installed, and rebooted successfully.
Luckily, OS X has a mechanism for this. We can create a LaunchAgent that runs our script at startup, which should then join the network at the login window. So I created a package to do just that, by deploying a launchd plist item into /Library/LaunchAgents/, and the script itself goes into /usr/local/libexec/. This is what the launchd item looks like:
We specify the log files so we can diagnose any issues that come up. I incorporate this package into my InstallLion package created with CreateOSXInstallPkg, I deploy it, and then… it doesn’t work.
At the login window, the machine decides not to connect to the wifi, and just sits there instead mocking me. I log in to the admin account, and lo and behold, there’s now wifi access. From further testing, I determined that the script above, which adds a preferred wireless network into the Keychain, doesn’t take effect until the first time a user logs in. If I were to leave it at this, I would have to log in each of my deployed MacBook Airs first before I could trigger the Munki bootstrap. While that isn’t the end of the world, the goal here is to automate the process as much as possible so that I don’t have to touch the machines once I start the deployment. I definitely want to avoid manually logging in to the admin accounts on each of the 100+ MacBook Airs I have to deploy.
I dig into networksetup a bit more, and discover that there’s another way to add a wireless network. I can simply force the device to associate right away with a valid SSID by using this command:
/usr/sbin/networksetup -setairportnetwork $wifiDevice $SSID $PASSWORD
When executed at the login window, this has the intended effect of lighting up the AirPort menu and joining the network, thus allowing the Munki bootstrap to proceed, without having to log in a user first.
But Wait, There’s More. More Issues.
Unfortunately, I can’t just leave it at that. There are some consequences to using that command.
- Unlike addpreferredwirelessnetworkatindex, the latter command does not work if the SSID cannot be found and joined right away. It will simply fail, and the network will not be entered into the list of wireless networks to join later.
- addpreferredwirelessnetworkatindex is idempotent. Since I placed this script into a LaunchAgent that executes at startup, it will run this script every time we boot. If you run the first command from Rich’s script over and over and over, it won’t do anything except tell you that the SSID is already in the preferred list. setairportnetwork, unfortunately, will always immediately attempt to join the network I specified. This means that in the future, if a user later changes the order in which networks are joined, or if the user reboots in a location where the SSID I specified isn’t available, the computer won’t join the correct network right away. One way or the other, it tramples over user preferences, and that’s a Bad Thing.
- In order to resolve the problem in #2, I’d either have to remove the launchagent after it executes (i.e. self-destruct), or find a way to accommodate user preferences without stamping them out.
To resolve this, I came up with a little flowchart to describe the actions I want to take based on the environment.
- Does the SSID currently exist right now?
- If it does, is it already in the preferred list?
- If not, is there another network set right now that isn’t the one we want?
- If yes, let’s not screw the user, we’ll add the SSID into the preferred list. If no, it’s safe to assume that we want to join the network now.
Using this logic, I came up with this script:
Using this script along with the LaunchAgent gets us the intended effect. Upon rebooting out of the OS X install process, the machine immediately associates with the correct SSID and joins the network, thus allowing the Munki bootstrap to take place right away without requiring me to touch the machine at all.
Once the DeployStudio workflow has completed, I can disconnect the ethernet cable, place the computer somewhere else, and come back in an hour to have a fully configured machine.