Note: Old article topic, just for reference
posted Dec 19, 2008 3:55 PM by Philip Rinehart [ updated May 17, 2009 10:44 PM by Greg Neagle ]
Written by Greg Neagle |
Wednesday, 24 November 2004 |
A common need in a managed OS X environment is to run certain scripts every time someone logs in, or to open certain items (applications, background utilities, folders, documents). Apple has provided a method for each user to specify items to be opened at login, but it is not entirely obvious how to specify certain items to be opened or executed for all users of a given computer. Fortunately, there is a simple way to do this. As it turns out, the loginwindow.plist file, located at ~/Library/Preferences/loginwindow.plist , works the way you'd wish all preference files worked. This file contains the list of items to open at login. If you copy this file to /Library/Preferences/ and make sure it is readable by everyone (chmod o+r /Library/Preferences/loginwindow.plist ), the items you've specified to open at login will now be opened for every user of that machine. What's even better is that if a user specifies his or her own items to be opened at login (using the "My Account" (Jaguar) or "Accounts" (Panther) preference pane), the items defined in/Library/Preferences/loginwindow.plist AND the items defined for the specific user at~/Library/Preferences/loginwindow.plist will be opened. So you can define items to be opened for every user of a machine without interfering with the ability for a user to define their own items.This technique can be further refined. I have defined a single item to be opened by every user of the machines I manage. It's an application I call "LoginLauncher". This application looks in a folder I've defined ( /Library/FA/LoginItems/) and opens everything in it. It knows how to run AppleScripts; execute shell scripts, Perl scripts, and other UNIX executables; and open anything else the same way the Finder would. The advantage of this method is that you do not have to keep editing/Library/Preferences/loginwindow.plist - instead, simply add or remove items from /Library/FA/LoginItems/ to control what is open or executed at start up.The solution detailed here demonstrates several useful techniques that can be used by Mac OS X administrators in a variety of situations. ImplementationThis solution has three parts:
Login items directoryThis is simply a folder in which you put items to be run/opened at login for every user. Mine is at/Library/FA/LoginItems . You can put yours anywhere you want.LoginLauncher.appThe LoginLauncher application is actually an executable shell script wrapped into an application bundle. This allows us to specify it as an item to be opened at login, control its visibility in the dock, give it a custom icon, and make it look like a "regular" OS X application.There are several techniques in use here that you might be able to apply to other situations. We'll start with the shell script. I've highlighted a few interesting parts in red:
Parsing the scriptI'll point out some interesting parts of the shell script:Output that is sent via " echo " goes to the console log. You can view it using Console.app. This is a handy way to debug - just put inecho statements and check the Console to see that it is doing what you expect.The script uses the dirname command and the special variable$0 to find the Contents directory of its own bundle. This works because$0 contains the path to the executable, which is atLoginLauncher.app/Contents/MacOS/LoginLauncher The first call to dirname returns/path/to/LoginLauncher.app/Contents/MacOS and the second returns /path/to/LoginLauncher.app/Contents Once we have the path to the Contents directory, we can find our Defaults.plist file, and use the defaults command to extract the path to the login items directory:loginItemsDir=`/usr/bin/defaults read "$contentsdir/Resources/Defaults" LoginItemsDir` Once we have the path to the login items directory, we loop through every item in it. If a item is executable, we run it. If it's not, we ask the Finder (via osascript , which is a command-line interface to AppleScript) what kind of file it is. If it's an AppleScript, we ask the Finder to run it. (We could run the AppleScript directly, but if it asks for user interaction or displays a dialog, it gets messier. It's more reliable and safer to ask the Finder to run the AppleScript, since that replicates the environment you probably used to build and test the AppleScript.)If the file is not an AppleScript, we pass it to the open command, which opens files and applications much the same way as if you had double-clicked them. Net result: each item in the login items directory is run, opened, or launched. Wrapping the script into a bundleMany Mac OS X applications are really bundles, which is a special kind of directory. The simplest bundle looks like this:
That is, a directory with a name ending in .app, containing a directory named Contents, containing a directory named MacOS, containing the actual executable file. You can convert any executable shell script into a double-clickable application by wrapping it up into a bundle in this way.Note that in this simplest case, the bundle and the executable must have the exact same name - the executable minus the ".app" extension. Stupid bundle tricksYou can make your bundle more Mac-like and control more aspects of its behavior by adding additional files and directories to your bundle. For LoginLauncher.app, I did not want it to appear in the Dock while it was running. It should do its work silently in the background. To achieve this behavior, you must add a "Info.plist" file to the bundle's Contents directory with the following contents:
The Info.plist file can actually contain a good deal more. Indeed, later we will specify a custom icon in this file. But right now, it contains a single key/value pair:LSBackgroundOnly = 1 . This tells Launch Services this is a background-only application that displays no windows, has no menubar, and needs no icon in the Dock.Default preferencesI also wanted the path to the login items directory to be stored in a preferences-style file so others could edit it without needing to edit the script itself. This also demonstrates how to store simple data outside of your executable. In this example, I'm storing only a single key/value pair, but you could store many.Traditionally, internal data an application needs is stored in the bundle's Contents/Resources directory. I created a "Resources" directory inside the Contents directory, and then created file called "Defaults.plist" inside the Resources directory:
This looks far more complicated than it is. In fact, I just copied and pasted the beginning and end of another .plist file I found in my Preferences directory, and then added these two lines:<key>LoginItemsDir</key> This allows us to use the defaults command to read this file. All sorts of preferences can be stored and read this way. This technique does not allow the end-user to modify defaults, but does provide a way to allow an admin to modify preferences for a script without editing the script itself.In the script itself, this line reads the value we stored in Defaults.plist: loginItemsDir=`/usr/bin/defaults read "$contentsdir/Resources/Defaults" LoginItemsDir` It uses the defaults command to read"$contentsdir/Resources/Defaults" (note no ".plist" at the end of the name) and return a value for the key"LoginItemsDir" . Earlier in the script,"$contentsdir" was assigned the path to the application bundle's Contents directory.Adding an iconFinally, for that professional appearance, all self-respecting Mac applications need their own icon, though it's certainly not necessary for an application like this. I created a custom icon using Icon Composer, part of Apple's Xcode developer tools, free with Mac OS X. I then copied that .icns file to the bundle's Contents/Resources directory. Finally, we have to tell the bundle to use the .icns file by adding two lines to the Contents/Info.plist file:
Note that the Contents/Resources directory is assumed, and the icon file name in the plist does NOT include the .icns extension. Pulling it all together with So - now we have an application that will open or run every item in a directory of our choosing. But we need to ensure that this application will itself be run at every login. We do this by editing the |
Last Updated ( Thursday, 07 April 2005 ) |