Apple,Deployment,Tips March 6, 2013 at 7:00 am

Automatically Enable WiFi At Login Window

The Context

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.


The Issues

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.


Phase One

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:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "">
<plist version="1.0">

view raw
hosted with ❤ by GitHub

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.

  1. 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.
  2. 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.
  3. 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.

  1. Does the SSID currently exist right now?
  2. If it does, is it already in the preferred list?
  3. If not, is there another network set right now that isn’t the one we want?
  4. 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:

# Set paths to our utilities
# Determines which OS the script is running on
osvers=$(sw_vers -productVersion | awk -F. '{print $2}')
# On 10.7 and higher, the Wi-Fi interface needs to be identified.
# On 10.5 and 10.6, the Wi-Fi interface should be named as "AirPort"
if [[ ${osvers} -ge 7 ]]; then
wifiDevice=`/usr/sbin/networksetup -listallhardwareports | awk '/^Hardware Port: Wi-Fi/,/^Ethernet Address/' | head -2 | tail -1 | cut -c 9-`
/usr/sbin/networksetup -setnetworkserviceenabled Wi-Fi on
wifiDevice=`/usr/sbin/networksetup -listallhardwareports | awk '/^Hardware Port: AirPort/,/^Ethernet Address/' | head -2 | tail -1 | cut -c 9-`
/usr/sbin/networksetup -setnetworkserviceenabled AirPort on
# Set the SSID variable to your wireless network name
# to set the network name you want to connect to.
# Note: Wireless network name cannot contain spaces.
# Set the INDEX variable to the index number you'd like
# it to be assigned to (leave it as "0" if you do not know
# what index number to use.)
# Set the SECURITY variable to the security type of the
# wireless network (NONE, WEP, WPA, WPA2, WPAE or
# WPA2E) Setting it to NONE means that it's an open
# network with no encryption.
# Set the password here. For example, if you are using WPA
# encryption with a password of "thedrisin", set the PASSWORD
# variable to "thedrisin" (no quotes.)
# Once the running OS is determined, the settings for the specified
# wireless network are created and set as the first preferred network listed
if [[ $wifiDevice == "" ]]; then
echo "No Wi-Fi device found!"
exit 0;
# Run our tests first
airportScan=`/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -s`
preferredList=`/usr/sbin/networksetup -listpreferredwirelessnetworks $wifiDevice`
# First: turn on wifi
/usr/sbin/networksetup -setairportpower $wifiDevice on
# Does the wifi even exist?
if [[ -n "$airportScan | grep $SSID" ]]; then
# Yes, the wifi exists. Is it already on the preferred list?
if [[ -z `echo $preferredList | grep $SSID` ]]; then
# No, it isn't in the preferred list, so we set the connection right now.
/usr/sbin/networksetup -setairportnetwork $wifiDevice $SSID $PASSWORD
echo "Setting network now."
exit 0;
# If it gets here, it is on the preferred list and the wifi exists, so it should join automatically (or have another SSID to join).
# If it gets here, the wifi doesn't exist, so we shouldn't try to join it right now. Add it to the preferred list.
echo "Setting network later."
/usr/sbin/networksetup -addpreferredwirelessnetworkatindex $wifiDevice $SSID $INDEX $SECURITY

view raw
hosted with ❤ by GitHub

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.

Nick McSpadden

I'm Client Systems Manager for Schools of the Sacred Heart, San Francisco. I'm in charge of all OS X and iOS deployments to our faculty, staff, and students.

More Posts


  • Any thoughts if you are on an enterprise network with 802.1x certification. Drop the certificate on the computer first and trust it as part of the imaging process?

  • I have followed the steps and I guess I am missing somewhere. The LaunchAgent is still only launching when I login to my local account. Console is reporting nothing, except Permission Denied when trying trying to create log files.

  • Nevermind, I placed it in LaunchAgents and not LaunchDaemons, it made a big difference

  • @ubermedina:

    You can change the location of the log files to wherever you want. I just used /private/var/log/ because it’s a reasonably secure location. You can drop them into any directory you feel like, just edit the launchagent plist. I haven’t had any permissions issues though.

Leave a reply

You must be logged in to post a comment.