aedifica: Silhouette of a girl sitting at a computer (Girl at computer)
aedifica ([personal profile] aedifica) wrote in [site community profile] dw_dev_training2012-09-19 10:51 pm
Entry tags:

On reusing/tweaking code in a language you don't know

Hi all! This isn't directly about Dreamwidth development, but I was encouraged to post it here because it's an example of how one might approach a problem in a strange language.

Background on me: I do desktop tech support (your computer breaks, I fix it; you need software installed, I install it). I have a little bit of programming experience in Scheme and Java, which has also given me some generalized knowledge like "you write code in an editor," "programming languages often have a special character to show something is a comment for human eyes, not part of the program," and stuff like that.

Background on the problem I wanted to solve: We have a project coming up that will involve changing the address of pretty much every fileshare (a.k.a. network drive) our users connect to. Most of the users I support are on Macs, and there isn't any way in our environment that we can just automatically make everybody's Mac connect up to the proper new fileshare address after the move. That means that after the change, everybody will have to individually connect to the fileshare using its new address, and then manually add it to their login items--or more likely, ask me to do it. (On a Mac, if you want to automatically reconnect to a network drive every time you log in, the easiest way to do it is to go into System Preferences -> Accounts (or Users and Groups, depending on your OS version) and add it to the "Login Items" tab on your account.)

If I could figure out some way to automate connecting to the new address and adding a login item for it, maybe a script I could send out to all my Mac users, that would save them a whole lot of frustration the morning after the change (and save me a lot of running around to add it for everyone).

First step: figuring out what tools I can use to solve the problem. I tried using OS X's built-in program called Automator, which lets you string actions together and make simple programs from them. I found the actions to string together to connect to a fileshare, but I couldn't see any way to create a login item.

Eventually I tried Googling "automate creation of login item Mac" or something like that, and one of the first results was some sample AppleScript code for creating a login item. Great! Only I had no experience with AppleScript and hardly knew anything about it--at that point I didn't even know what editor to use for it.

Google comes to the rescue again. It turns out OS X has another nifty built-in program I totally overlooked, AppleScript Editor. It's an editor! For AppleScript! So I opened it up and pasted in the code I found on the website:

mount volume "smb://server.example.com/home"
tell application "Finder"
        --check to see if the volume mounted
	if disk "home" exists then
		display dialog "Success"
              --this "tell" statement makes the actual login item
		tell application "System Events"
			make login item at end with properties {path:"/Volumes/home", kind:volume}
		end tell
	else
              --if it doesn't work let the user know
		display dialog "Oops! This problem happened:" & return & errmsg
	end if
end tell

OK. Definitely not a language I'm familiar with, but I can start to see the shape of it. It looks like a double dash means a comment, and yay that the person who wrote this included comments! Let me try putting in one of our existing fileshare addresses, and see if it works.

*substitutes in fileshare addresses*

Hmm. It works if I use the address of a fileshare I'm already connected to, but not if I use one I'm not. Evidently "mount volume "[path to fileshare]"" doesn't actually connect me to a network drive. Google some more. Discover the syntax I need for what I want to do is "tell application "Finder" to open location "[path to fileshare]"". Cool.

So far, so good. The next thing I noticed was that
	if disk "home" exists then
		display dialog "Success"
              --this "tell" statement makes the actual login item
		tell application "System Events"
			make login item at end with properties {path:"/Volumes/home", kind:volume}
		end tell

actually had it giving the Success dialog before it even tried to create the login item. How about maybe I move that statement so it doesn't display until it's actually successfully done it? That gave me
	if disk "home" exists then
              --this "tell" statement makes the actual login item
		tell application "System Events"
			make login item at end with properties {path:"/Volumes/home", kind:volume}
		display dialog "Success"
		end tell

At which point I discovered another gap in my knowledge: I could run the script from within AppleScript Editor, but I had no freakin' clue how to run it outside of that. I tried saving it to my desktop, but when I double-clicked on it it opened the file in AppleScript Editor for editing, instead of running the script. Enter Google once again. Google told me I could accomplish that by changing the file type from "script" to "application" when I save it--there's a handy dropdown box for that in the Save As dialog--and Google was correct. Onwards!

At that point my code looked something like this:
tell application "Finder" to open location "smb://server.example.com/home"
tell application "Finder"
        --check to see if the volume mounted
	if disk "home" exists then
              --this "tell" statement makes the actual login item
		tell application "System Events"
			make login item at end with properties {path:"/Volumes/home", kind:volume}
		display dialog "Success"
		end tell
	else
              --if it doesn't work let the user know
		display dialog "Oops! This problem happened:" & return & errmsg
	end if
end tell

Here I tried running my script again, and I discovered that when I tried it with a network drive I wasn't already connected to, it would give me the error message defined in that display dialog there in the "else" clause, because it wasn't waiting for step 1 (connect to server) to finish before starting step 2 (check that server is connected, then make a login item for it). But there's got to be some way to tell it to wait, right? Enter Google yet again; I think the search terms I used this time were something like "applescript wait," but I'm not sure. Google told me about the "delay" command, which is followed by the number of seconds you want AppleScript to wait before executing the next command. 30 seconds seemed like it should be long enough for most computers, and if it's too short for a few of them I can do those by hand.

I had been updating comments as I went, and I also changed the error statement in the "else" clause to something more user-friendly. My final code looked like this:

--Written for fileshare moves Fall 2012, adapted & expanded by [aedifica's work name] from code found at http://macstuff.beachdogs.org/blog/?p=30 .
--Places to change the path or drive name are commented with "CHANGE"

--this "tell" statement connects to the server
tell application "Finder" to open location "smb://foo.ad.okoboji.edu/bar"
--CHANGE path to suit

--this "delay" statement stops the second part from running before the first part completes
delay 30

tell application "Finder"
	--check to see if the volume mounted
	if disk "bar" exists then
		--CHANGE disk name to suit
		
		--this "tell" statement makes the actual login item
		tell application "System Events"
			make login item at end with properties {path:"/Volumes/bar", kind:volume}
			--CHANGE path to suit
			display dialog "Success"
		end tell
	else
		--if it doesn't work let the user know
		display dialog "Error creating login item. Please check that the bar drive is mounted and try again."
		--CHANGE drivename to suit
	end if
end tell

I'd been testing it on my own computer as I went, and once it seemed finished I contacted one of my users and asked if I could test it on her computer. It worked on her computer too (and she's on a different version of OS X than I am, so it was an especially good sign that it worked for her too). Woohoo!

When we're doing the fileshare moves, I'm going to save the script as an app, zip the app file so I can email it, and send it out to all my affected users, with instructions to download the file, double-click it to unzip, double-click it to run... and that should be all they need to do. I'll be around in case they need me, but most of them will be up and running a lot faster because of my script!

TL;DR: If you know a very little bit about programming languages, you know English, and you have Google (or your search engine of choice), you may be able to tweak some existing code to do what you want. Hooray! Also, COMMENT YOUR CODE! The life you save may be your own! And so forth. :-D
denise: Image: Me, facing away from camera, on top of the Castel Sant'Angelo in Rome (Default)

[staff profile] denise 2012-09-20 04:15 am (UTC)(link)
This is an excellent writeup, and thank you so very much for doing it! It demonstrates the "start from basic principles, work your way through to working code" thought process so amazingly well. And those skills really are universal. :)

I love love love love love when people make posts like this. I think it's really important to show a variety of approaches and styles! Thank you :)
dreamatdrew: The iconic all-in-one Apple computer icon, frowning and crying. (Sad Mac)

[personal profile] dreamatdrew 2012-09-20 05:20 am (UTC)(link)
Very nicely handled. AppleSript is not universally useful, but very handy for a lot of things you wouldn't necessarily think of.
Also, if I may offer one small tweak: you could move the 'open location' command inside the second 'tell' block, which would A) be a bit cleaner code and B) _might_ eliminate the need for the delay. Having the delay inside a tell block is also non-harmful.
swaldman: View up into circular dome (architecture)

[personal profile] swaldman 2012-09-20 08:55 pm (UTC)(link)
that's totally how I've been figuring out perl :-)

[personal profile] swaldman 2012-09-24 10:37 am (UTC)(link)
With most languages I have a default assumption that anything is *possible*... but not necessarily worth the hassle. So I guess it's similar for me, but with "realising that it's reasonably easy" ;-)