现在的位置: 首页 > 综合 > 正文

Mac OS X: Running items at login

2013年12月13日 ⁄ 综合 ⁄ 共 13033字 ⁄ 字号 评论关闭

Running items at login

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.


Implementation

This solution has three parts:

  1. Login items directory: all items to be auto-launched at login go here.
  2. 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 downloaded here
  3. /Library/Preferences/loginwindow.plist
     file: 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

The
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:

#!/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 in echo
 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 at 
   LoginLauncher.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 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

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 tricks

You
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:

<plist>
<dict>
   <key>LSBackgroundOnly</key>
   <string>1</string>
</dict>
</plist>

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 preferences

I
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:

<?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>

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>
   <string>/Library/FA/LoginItems</string>


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 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, 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:

<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.plist
 file, 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 in LoginLauncher.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:

We
use 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 that
add 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.
Some
installers set this up for the user that was logged in when the app was
installed, but other users do not get these apps auto-launched.
Or the installer does correctly modify the /Library/Preferences/loginwindow.plist
 file to auto-launch the helper apps for all users.
If
different 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, open
 commands:


Type "man [command name]" at a Terminal prompt.


Conclusion

This
article details a solution to a specific problem facing Mac OS X
administrators: running/opening specific items at logon for all users of
a 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 )

 

抱歉!评论已关闭.