|
Running items at login
Note: Old article topic, just for reference
posted Dec 19, 2008 3:55 PMby Philip Rinehart[updated May 17, 2009 10:44 PMby Greg Neagle]Written by Greg Neagle | Wednesday, 24 November 2004 | Acommon 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.plistANDthe items defined for the specific user at~/Library/Preferences/loginwindow.plistwillbe 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 asingle 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 thatyou 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.
Thesolution detailed here demonstrates several useful techniques that can be used by Mac OS X administrators in a variety of situations.
Implementation
This solution has three parts:
- Login items directory: all items to be auto-launched at login go here.
- LoginLauncher.app:this application does the actual work of running/opening the items in the Login items directory. A zip archive, containing this file and it's icon can be downloadedhere
- /Library/Preferences/loginwindow.plistfile: this tells the OS to auto-launch LoginLauncher.app.
Login items directory
This 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.app
TheLoginLauncher 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 acustom 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:
#!/bin/sh
echo"Running login items..."
# find the bundle contents dir
macosdir=`/usr/bin/dirname $0`
contentsdir=`/usr/bin/dirname $macosdir`
# use the defaults command to read in the LoginItemsDir from
# $contentsdir/Resources/Defaults.plist
loginItemsDir=`/usr/bin/defaults read "$contentsdir/Resources/Defaults" LoginItemsDir`
for file in "$loginItemsDir"/*
do
if [ -x "$file" -a ! -d "$file" ]; then
# execute it
echo "Executing: $file"
"$file" &
else
macName=`osascript -e "get POSIX file "$file" as text"`
if [ $? -eq 0 ]; then
kind=`/usr/bin/osascript -e "tell application "Finder" to get kind of item "$macName""`
if [ "$kind" == "Alias" ]; then
kind=`/usr/bin/osascript -e "tell application "Finder" to get kind of original item of item "$macName""`
fi
if [ "$kind" == "Script" -o "$kind" == "script text" -o "$kind" == "compiled script" ]; then
# run the Applescript
echo "Running Applescript: $file"
/usr/bin/osascript -e "tell application "Finder" to run script file "$macName""
else
# just pass it to the open command, which will launch apps and open docs
echo "Opening: $file"
/usr/bin/open "$file"
fi
fi
fi
done
echo "Completed running login items."
Parsing the script
I'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 inechostatements and check the Console to see that it is doing what you expect.
The script uses thedirnamecommand and the special variable$0to find the Contents directory of its own bundle. This works because$0contains the path to the executable, which is at
LoginLauncher.app/Contents/MacOS/LoginLauncher
The first call todirnamereturns
/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 thedefaultscommand to extract the path to the login items directory:
loginItemsDir=`/usr/bin/defaults read "$contentsdir/Resources/Defaults" LoginItemsDir`
Oncewe 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 (viaosascript, which is a command-line interface toAppleScript) 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 theopencommand, 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 bundle
Many Mac OS X applications are really bundles, which is a special kind of directory. The simplest bundle looks like this:
MyBundle.app/
MyBundle.app/Contents/
MyBundle.app/Contents/MacOS/
MyBundle.app/Contents/MacOS/MyBundle
Thatis, 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 intoa 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 tricks
Youcan 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 Contentsdirectory with the following contents:
<plist>
<dict>
<key>LSBackgroundOnly</key>
<string>1</string>
</dict>
</plist>
TheInfo.plist file can actually contain a good deal more. Indeed, later wewill 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 preferences
Ialso 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:
<?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>LoginItemsDir</key>
<string>/Library/FA/LoginItems</string>
</dict>
</plist>
Thislooks 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>
<string>/Library/FA/LoginItems</string>
This allows us to use thedefaultscommandto read this file. All sorts of preferences can be stored and read thisway. This technique does not allow the end-user to modify defaults, butdoes provide a way to allow an admin to modify preferences for a scriptwithout 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 thedefaultscommand 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 icon
Finally,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, partof 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:
<plist>
<dict>
<key>LSBackgroundOnly</key>
<string>1</string>
<key>CFBundleIconFile</key>
<string>LoginLauncher</string>
</dict>
</plist>
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/Library/Preferences/loginwindow.plist
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/Library/Preferences/loginwindow.plistfile, creating it if needed.
Here's mine, with the key parts in red:
<?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>AutoLaunchedApplicationDictionary</key>
<array>
<dict>
<key>Hide</key>
<true/>
<key>Path</key>
<string>/Library/FA/LoginLauncher.app</string>
</dict>
</array>
<key>BuildVersionStampAsNumber</key>
<integer>12977088</integer>
<key>BuildVersionStampAsString</key>
<string>6G30</string>
<key>SystemVersionStampAsNumber</key>
<integer>167904000</integer>
<key>SystemVersionStampAsString</key>
<string>10.2.3</string>
</dict>
</plist>
Specifically,it tells the system to open "LoginLauncher.app", located at "/Library/FA/". You would need to modify the path to match your deployment.
Make sure the file is readable by everyone, but not changeable by anyone other than the owner (chmod 755 /Library/Preferences/loginwindow.plist).
Now, at every login, LoginLauncher.app will run and open or run every file in the directory you specified inLoginLauncher.app/Contents/Resources/Defaults.plist.
To add or remove items that open or run at login for every user, you simply add or remove items from the login items directory.
So now what?
The mechanism is in place - what sorts of things can we do with it?
Here are some examples I use:
Quota checks:
Weuse NFS-mounted home directories. At login, a script is launched that monitors the disk space being used and warns the user as they approach their quota.
Configuration scripts:
I have scripts thatadd and remove items from a user's Dock based on what is actually installed on a system, a script that offers to help a user configure their account on first login, one that sets up ColorSync profiles for all users of a given machine, and others.
Third-party apps:
There are several third-party apps that open background applications at login.
Someinstallers set this up for the user that was logged in when the app wasinstalled, but other users do not get these apps auto-launched.
Or the installer does correctly modify the/Library/Preferences/loginwindow.plistfile to auto-launch the helper apps for all users.
Ifdifferent apps make different modifications to this file, it becomes very difficult to manage with tools like radmind. Therefore, I identify these background "helper" apps and add symlinks to the login items directory that point to these helper apps.
This way, there is a single mechanism that manages these, and I can examine one place (the login items directory) to see what is auto-launched. Some examples:
- Kensington MouseWorks: MouseWorks Background.app
- Norton AntiVirus: DiskMountNotify.app
- Norton AntiVirus: ScanNotification.app
- Wacom: TabletDriver.app
There are many more.
Where to go for more information
Apple Developer bundle documentation:
http://developer.apple.com/documentation/MacOSX/Conceptual/SystemOverview/Bundles/chapter_4_section_1.html
Property lists (.plist files):
http://developer.apple.com/documentation/Cocoa/Conceptual/PropertyLists/Concepts/XMLPListsConcept.html
plist keys for the bundle's Info.plist:
http://developer.apple.com/documentation/MacOSX/Conceptual/BPRuntimeConfig/Concepts/PListKeys.html
dirname, defaults, osascript, opencommands:
Type "man [command name]" at a Terminal prompt.
Conclusion
Thisarticle details a solution to a specific problem facing Mac OS X administrators: running/opening specific items at logon for all users ofa machine.
As part of this solution, several useful techniques for Mac OS X system administrators have been presented:
- Running/opening items at login for every user of a machine
- Simple shell scripting
- Calling AppleScript from shell scripts
- Opening applications and documents from shell scripts
- Wrapping a shell script into an application bundle
- Specifying an application as a "background-only" app
- Using the defaults command to read application preferences
- Giving an application bundle a custom icon
- Taking advantage of Apple's preferences hierarchy
I hope you find some of these useful for your own environment.
__________
Greg Neagle | Last Updated ( Thursday, 07 April 2005 )
|
|
|