ucantblamem

Auto-mounting network drives with AppleScript for specific locations

2nd Jan 2008

One of the things that I love about OS X is locations, which allow you to define different network settings for the different places/environments you’re in. So, for instance I use a DHCP (obtained IP) style network at home and I have a static IP at work.

Locations on OS X

My only issue is that it doesn’t allow me to do other things when I’m in a specific location. For instance, at work I need to mount a number of network drives to be productive and while Leopard makes this a relatively simple process (Shared Computers list), considering that I need to mount 5 or more drives it could be simpler.

Please note that the following process is only necessary if you want to auto-mount drives for a specific location. If all you need to do is auto-mount a network drive, please see this article.

Thankfully, being a Unix based system, OS X provides us with a number of scripting languages to automate tasks including Ruby, Python, PHP and even Cupertino’s own AppleScript. One of the major advantages to AppleScript is that it can communicate very effectively with the UI and the OS X systems in general (Whereas the other mentioned languages take a little more effort).

With this in mind, having had no (real) prior experience with AppleScript, it didn’t take me long to throw something together to Auto-Mount these drives. I also very quickly and simply implemented the necessary code to display nice little Growl notifications as each drive is mounted:

AppleScript Growl Notification

Okay, so to get started on this, open Script Editor, which can be found in /Applications/AppleScript/Script Editor.app and paste in the code from this file or simply download the *.scpt file itself (zipped).

In this script we first define some variables:

global current_location
global script_name
global error_issued
set script_name to "Auto Mount Work Drives Script"
set error_issued to 0

and then find out which location we are currently in:

tell application "System Events"
	tell network preferences
		get properties
		set current_location to the name of current location
	end tell
end tell

Because I only want to mount drives if I’m at work, we need a little control statement:

if current_location is "Work" then
	tell application "Finder"
		my mount_drive("smb://stewie/www", "www", "192.168.1.8")
		my mount_drive("smb://leela/ImageLibrary", "ImageLibrary", "192.168.1.7")
		my mount_drive("smb://leela/Musicadium", "Musicadium", "192.168.1.7")
		my mount_drive("smb://leela/Websites", "Websites", "192.168.1.7")
		my mount_drive("smb://leela/CompanyProject", "CompanyProject", "192.168.1.7")
	end tell
end if
set error_issued to 0

I’ll explain what that last line’s for in a moment.

From the above you can also see the calls to the mount_drive() sub-routine, which is essentially a function defined later in the script (for all those PHP devs out there). As an aside, you’ll note that to call your own sub-routines you prepend the calls with the my operator.

From here on in, it’s just defining a couple of sub-routines. mount_drives() is a bit full-on as it needs to check that it can access the servers/drives before attempting to mount them. It also quickly checks to make sure we don’t already have the drive mounted and handles any errors gracefully. The last thing I should mention is that the error_issued variable we set earlier is used to record whether we’ve previously sent an error message, so that we’re not sending notifications for each failed mount (Imagine if you had 10 volumes that failed to mount). Of course, we want to see errors the next time we run this script, so we reset this variable after our control statement. The sub-routine itself looks like so:

on mount_drive(the_volume, the_name, the_ip)
	set ping_result to "none"
	if the_name is not in (do shell script "ls /Volumes") then
		try
			set ping_result to do shell script "ping -c 1 " & the_ip
		on error error_message number error_number
			if error_number is 68 then
				if error_issued is 0 then
					growl_notification("Ping Failed", "The volume '" & the_volume & "' could not be found")
					set error_issued to 1
				end if
				return
			else
				my growl_notification("Error " & error_number, error_message)
				return
			end if
		end try

		try
			mount volume the_volume
			my growl_notification(the_name & " available", "The drive '" & the_name & "' was successfully mounted")
		on error error_message number error_number
			my growl_notification("Error " & error_number, error_message)
		end try
	end if
end mount_drive

It does look complicated, but when you break it down, it simply attempts to mount the volume and gracefully handles any errors encountered along the way. I decided to house the Growl notification handling in its own sub-routine called growl_notification() (ironically), but there’s really not a lot to the code itself:

on growl_notification(the_title, the_description)
	tell application "GrowlHelperApp"
		-- Make a list of all the notification types
		-- that this script will ever send:
		--set the allNotificationsList to {"Drive Mounted"}

		-- Make a list of the notifications
		-- that will be enabled by default.
		-- Those not enabled by default can be enabled later
		-- in the 'Applications' tab of the growl prefpane.
		--set the enabledNotificationsList to {"Drive Mounted"}

		-- Register our script with growl.
		-- You can optionally (as here) set a default icon
		-- for this script's notifications.
		--register as application script_name all notifications allNotificationsList default notifications enabledNotificationsList icon of application "Script Editor"

		--	Send a Notification...
		notify with name "Drive Mounted" title the_title description the_description application name script_name

	end tell
end growl_notification

It’s worth noting a few things about Growl notifications however; Firstly, you need to make sure you have it installed. If you use Adium or Skype like every other Mac user, then you should have it already. Secondly, an application needs to register itself (and the types of notifications it will make) with Growl, which is why there are a number of lines in the sub-routine which are commented out (”–” [read: Double-dash] is the comment operator in AppleScript). So, once you have Growl installed, uncomment the three lines and run the script once:

set the allNotificationsList to {"Drive Mounted"}
set the enabledNotificationsList to {"Drive Mounted"}
register as application script_name all notifications allNotificationsList default notifications enabledNotificationsList icon of application "Script Editor"

Then you can just (re-)comment out those lines. Of course, if you don’t have Growl and couldn’t care less about it, just delete all the growl_notification() code.

Now the fun part; getting OS X to run our script when we’re on the “Work” network. There are a number of options for us here and certainly the simplest would be to just have the script run on Login, but we can also detect when a new location has been selected and run the script then.

Launching the script when we login

We can’t (easily) run the script from login, but we can run an application and thankfully AppleScripts can be turned into applications with a few clicks:

From Script Editor save the script as an “Application Bundle” and make sure the “Startup Screen” checkbox is unchecked and the “Run Only” checkbox is checked. This will create a native Mac Application out of your Script which will run without prompting you to make sure you want to run the script (hence why we unchecked the “Startup Screen” option).

I personally don’t want this application to show in the Dock or display any kind of menu in the menubar (as Mac Applications usually would), so I have an extra step for the courageous (you may have to install the Apple Developer Tools to complete this step, otherwise skip to the bottom of this article):

Right-click on the *.app application bundle that you just created and click Show Package Contents from the context-menu (which may not be available if you haven’t go the Developer Tools installed).

Show Package Contents

Navigate into the subsequent Contents folder and open the Info.plist file in your favourite text-editor and add the following code to the <dict> array:

<key>NSUIElement</key>
<string>1</string>

Like so:

Add NSUIElement key to Info.plist file

Once you’ve done that, save the file and then drag the *.app file to your Login Items, which you’ll find by clicking the Apple (top left hand corner of the screen), then System Preferences, then Accounts, select your account and then click the Login Items button (drag the *.app file into the available list).

Launching the script when we change locations

This is by far the more technical option, but it’s the one I’ve employed as it fits my needs more closely. I usually come straight from home, having checked my mail etc, so I will already be logged in - First thing I will do is change the location to work, which is the perfect time to mount all the drives.

OS X provides us with some great tools for detecting events. The method I’m going to use is known as a LaunchAgent. Essentially, we can just create a simple XML file that defines the event we want to detect and the (terminal/shell) command to run when the event is fired. This system is all run by the launchd daemon and you can find information on the XML options here.

I only need this script to run for my user-account, so I will put the XML file in /Users/{USERNAME}/Library/LaunchAgents/onlocationchange.plist, but if you want to run a script for all users, you can put that in /Library/LaunchAgents/onlocationchange.plist

It’s worth noting that /Users/{USERNAME}/Library/LaunchAgents doesn’t exist by default, so you just create the folder and of course, there should be one XML file (*.plist) per event ideally.

When you change network locations, it in fact just changes a line in the /Library/Preferences/SystemConfiguration/preferences.plist file which launchd can watch and detect (the change).

So, my onlocationchange.plist file contains this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
	"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Label</key>
	<string>com.ucantblamem.onlocationchange</string>
	<key>ProgramArguments</key>
	<array>
		<string>osascript</string>
		<string>/Users/ucantblamem/Documents/scripts/auto_mount_work_drives.scpt</string>
	</array>
	<key>WatchPaths</key>
	<array>
		<string>/Library/Preferences/SystemConfiguration</string>
	</array>
</dict>
</plist>

The “Label” string can be whatever you like - it is just a namespace for the event, so that launchd doesn’t have two events with the same name (usually the convention would be com.{USERNAME}.event). You will also note that I’m just detecting changes to any of the configuration files and not just preferences.plist, which it will handle just fine (there’s no particular reason for this as such). Lastly, notice the two ProgramArguments; the first is a call to the osascript program (the AppleScript runtime) and the second is the location of our script.

Once the XML file is created and saved, we need to register this configuration with launchd, which you in fact do using the launchctl tool (confused yet???). Open up a terminal and type the following:

launchctl load ./Library/LaunchAgents/onlocationchange.plist

To test that everything is working I quickly added the following line to my script (around line 16) and changed locations (You should see a Growl notification):

growl_notification("HELLO WORLD", "Yup, we're up and running boss.")

If you see the notification, you can pull that line back out and you’re done. Otherwise, you may want to double check everything or even refer to this article to get a bit more background on LaunchAgents.

And that’s a wrap

That’s it! (Phew). Now, each time you login and/or change locations, this script will run and if you are in the “Work” location (or any other location you define in your script), it will mount the appropriate drives for you ready for a day of productivity.

Leave a Reply