, , ,

Adaptive Startup for the Mac

AppleScript icon

How shall I start thee? Let me count the ways….

Apple offers multiple approaches to launching selected applications when you start up your Mac. You can specify any number of Login Items as part of the Users & Groups panel within your System Preferences. And of course, when shutting down with OS X Lion (10.7), you can leave the option checked to reopen windows when logging back in, so that your Mac starts up with the same apps and windows that were last open.

For me, at least, neither of these options has been entirely satisfactory. I’m in the habit of opening apps and windows and tabs throughout the day, then shutting my Mac down at night before I go to bed; when I start it up in the morning, I want to start fresh — not facing the leftover detritus from the day before.

On most mornings, when I start my Mac, I want it to open a standard set of apps and web pages so they’re ready for me when I sit down to my desk. On the other hand, if I’m traveling with my MacBook, or taking it in to the Genius Bar for assistance, I don’t want my computer to spend five minutes opening a dozen applications before I can wrest control away from it it — in those cases, I’d like a chance to abort the normal startup sequence, and launch apps on my own.

Being dissatisfied with the other solutions available, I finally took matters into my own hands and cobbled together the following AppleScript program. While AppleScript is not for the faint of heart, neither is it only for the use of serious developers. If you’re not familiar with the language, have no fear: just fire up your AppleScript Editor application (found in your Utilities folder), then copy and paste the following code into a new window. You’ll want to customize it for your needs, but such modifications should be straightforward.

You can download the entire script from this location.

So without further preamble, let me walk you through the code, explaining what it does and how to modify it along the way. (Note that the downloaded script also contains comments to explain the operation of the program; I won’t repeat those here, since I’ll be offering a fuller explanation as I go.)

One of the features of this script is that it writes a log file to your desktop explaining exactly what it did during its execution. We’ll store the log file in the user’s Desktop folder, with the name “Adaptive Startup Log.txt”.

So, to start with, let’s specify a few global variables that will be used for the logging operation.

global logFilePath
global logFile
global lf
set lf to (ASCII character 10) as string
set logFilePath to (path to desktop as string) & "Adaptive Startup Log.txt" as string

Now let’s delete the log file, if it already exists in your Desktop folder.

tell application "System Events"
  set logExists to exists file "Adaptive Startup Log.txt" of desktop folder
end tell
if logExists then
  set logFileAlias to logFilePath as alias
  tell application "Finder" to delete logFileAlias
end if

Let’s open the log file so that we can write to it throughout the script.

set logFile to open for access logFilePath with write permission

Ensure that this script is frontmost before we try to engage the user.

activate

Now let’s give the user a chance to abort the script without doing anything else. We’ll start with a verbal announcement, then pop up a dialog box. We’ll give the user 30 seconds to respond to the dialog box, by either giving the OK to proceed, asking for more Choices, or telling us to Cancel. If we haven’t heard anything in 30 seconds, then we’ll assume the user has stepped away and wants us to proceed.

say "Startup sequence about to begin"
set go to true
try
  display dialog "Startup sequence about to begin" buttons {"Cancel", "Choices", "OK"} default button "OK" giving up after 30
  set selectedButton to button returned of the result
  if selectedButton = "OK" or selectedButton = "Choices" or gave up of the result = true then
    set go to true
  else
    set go to false
  end if
on error errStr
  if the errStr contains "User canceled" then
    set go to false
  else
    set go to true
  end if
end try

If the user has asked us to proceed, then see if they want a list of choices about which apps to launch.

set choice to "My Usual Apps"
if go then
  if selectedButton = "Choices" then
    choose from list {"My Usual Apps", "My Writing Apps"}
    if result is false then
      set go to false
    else
      set choices to result
      set choice to item 1 of choices
    end if
  end if
end if

If we got the permission to go forward with the launch sequence, either explicitly or implicitly, then we’ll proceed with the bulk of the script.

if go then

Now let’s write the first line to the log file, indicating the date and time at which we’re starting the script. Note that we’re using a subroutine called “writeLogLine” that will be defined later.

  set eof of logFile to 0
  writeLogLine("Adaptive Startup launching at " & (current date))
  writeLogLine(" ")

As part of my normal daily startup, I like to perform a little automated maintenance. I’ve got several backup folders that get new files added to them on a regular basis. Here’s where I toss out the old ones, keeping only the latest five backups, so that they don’t accumulate into some hellish mess. Modify to meet your needs, or delete this section altogether if you have no need for it.

  set the homeFolder to path to home folder as string
  set the backupsFolder to the homeFolder & "Backups:"

  set the pruneFolder to the backupsFolder & "Bento Backups:"
  set the pruneAlias to the pruneFolder as alias
  deleteOldFilesFromFolder(pruneAlias, false, 5)

  set the pruneFolder to the backupsFolder & "iWisdom Backups:"
  set the pruneAlias to the pruneFolder as alias
  deleteOldFilesFromFolder(pruneAlias, true, 5)

  set the pruneFolder to the backupsFolder & "Moneydance Backups:"
  set the pruneAlias to the pruneFolder as alias
  deleteOldFilesFromFolder(pruneAlias, true, 5)

  set the pruneFolder to the backupsFolder & "OmniFocus Backups:"
  set the pruneAlias to the pruneFolder as alias
  deleteOldFilesFromFolder(pruneAlias, true, 20)

  set the pruneFolder to the backupsFolder & "Two Due Backups:"
  set the pruneAlias to the pruneFolder as alias
  deleteOldFilesFromFolder(pruneAlias, true, 5)

  set the pruneFolder to the backupsFolder & "UMSeattle Backups:"
  set the pruneAlias to the pruneFolder as alias
  deleteOldFilesFromFolder(pruneAlias, true, 5)

  set the pruneFolder to the backupsFolder & "URL Union Backups:"
  set the pruneAlias to the pruneFolder as alias
  deleteOldFilesFromFolder(pruneAlias, true, 5)

  writeLogLine(" ")

Here’s another maintenance task I like to start my day with: empty out the Downloads folder, and take out the Trash. Again, modify or delete altogether, in accordance with your preferences.

  set myDownloadFolder to (path to downloads folder)
  tell application "Finder"
    delete items of myDownloadFolder
    empty trash
  end tell
  writeLogLine("Downloads folder thrown in the trash")
  writeLogLine("Trash emptied")
  writeLogLine(" ")

Now let’s log the choice the user made earlier about which set of apps to launch.

  writeLogLine("Launching " & choice)

Now let’s launch our first application, using a subroutine called “launchApp”. In addition to specifying the name of the application, we’ll pass a true/false indication of whether we want the app to be hidden after it is launched (to avoid cluttering up our screen with too many visible apps).

Note that any apps identified at this point will be launched irregardless of what set the user chose.

  launchApp("LaunchBar", false)

Now let’s check and see whether we have an Internet connection. If not, then we’ll skip opening apps dependent on access to the ‘Net. Otherwise, once we’ve got the apps open, we’ll open a few Web pages we like to view first thing in the morning.

  set internet_status to 1
  try
    do shell script "curl --connect-timeout 5 http://www.amazon.com"
  on error
    set internet_status to 0
  end try

  if (internet_status = 1) then
    writeLogLine("Internet is available")
  else
    writeLogLine("Internet is not available")
  end if

Now we’re going to check for the presence of an external drive (modify the name of the drive to match your setup). If the drive is present, then we’ll assume that we’re in our normal home configuration, and launch apps accordingly. If the drive is missing, then we’ll assume we’re mobile.

  set extdrives to 0
  set file_server_status to 0
  tell application "Finder"
    if "HB HD Spring Clone" exists then
      set extdrives to 1
    end if
  end tell

If external drives are present, then launch apps dependent on them, and mount our home network file server.

  if (extdrives = 1) then
    writeLogLine("External drives are available")
    launchApp("ChronoSync", false)
    tell application "Finder"
      try
        mount volume "afp://10.0.1.1" as user name "Herb Bowie" with password "password"
        set file_server_status to 1
      on error
        set file_server_status to 0
      end try
    end tell

If our file server mounted successfully, then we can launch any apps dependent on files only found there.

    if (file_server_status = 1) then
      writeLogLine("Mounted Home Media Drive")
      launchApp("Moneydance", true)
    end if
  else
    writeLogLine("External drives are not available")
  end if
  writeLogLine(" ")

Now launch our favorite apps and Web pages, depending on the set chosen earlier, and on the other conditions detected. Note that we’re using PowerTunes from Fat Cat Software, rather than launching iTunes directly, so that we can specify which library we want it to use.

  if choice = "My Usual Apps" then
    if (extdrives = 1) then
      launchApp("ChronoSync", true)
    end if

    launchApp("PowerTunes", true)
    tell application "PowerTunes"
      open library "iTunes"
    end tell
    launchApp("iCal", true)
    launchApp("OmniFocus", true)
    launchApp("BBEdit", true)
    launchApp("TextMate", true)
    launchApp("Two Due", false)

    -- Now launch apps dependent on the Internet
    if (internet_status = 1) then
      launchApp("Mail", true)
      launchApp("Safari", false)
      launchApp("App Store", true)
      launchApp("MacUpdate Desktop", true)
      launchApp("URL Union", true)

      open location "http://my.yahoo.com"
      open location "http://facebook.com"
      open location "http://www.swackett.com/app/"
    end if

We’re going to launch NetBeans with a separate try block, since it’s a hefty Java app that typically doesn’t launch quickly enough to be reported as successful by the activate routine.

    try
      tell application "NetBeans 7.1.2"
        activate
      end tell
    end try

    delay 10

    tell application "Finder"
      set visible of process "NetBeans" to false
    end tell
    writeLogLine("Application " & "NetBeans" & " launched and hidden")

I’ve got a folder called “Active Projects” inside my home folder, and I put an alias there to anything I’m currently working on, so that it will open automatically at startup.

    set the projects_folder to path to home folder as string
    set the projects_folder to the projects_folder & "Active Projects:"
    set the projects_folder to the projects_folder as alias

    set the apps_folder to path to applications folder as string
    set omni_outliner to the apps_folder & "OmniOutliner Professional.app"
    set omni_outliner to the omni_outliner as alias

    tell application "System Events" to get every disk item of the projects_folder whose visible is true
    set projects_files to result
    repeat with projects_file in projects_files
      tell application "System Events" to get name extension of projects_file
      if result is "oo3" then
        tell application "Finder"
          open projects_file using omni_outliner
        end tell
      else
        tell application "Finder"
          open projects_file
        end tell
      end if
      tell application "System Events" to get displayed name of projects_file
      writeLogLine("Opened file " & result)
    end repeat
  end if

Here’s a separate routine for launching a more manageable set of apps geared to a particular purpose.

  if choice = "My Writing Apps" then
    launchApp("TextMate", false)
    launchApp("Dictionary", false)
    launchApp("Two Due", false)
  end if

OK, let’s wrap things up. We’ll finish by opening the log file in whatever our default text editor happens to be, which signals that we’re done, and presents us with a nice neat record of what we’ve done.

  writeLogLine(" ")
  writeLogLine("Adaptive Startup finishing at " & (current date))

  close access logFile
  tell application "Finder"
    open logFilePath
  end tell
else
  close access logFile
end if

Aforementioned subroutines follow.

on launchApp(appName, hide)
  try
    activate application ((path to applications folder as text) & appName & ".app")
    -- Let's pause for a few seconds, to give our app a chance to launch before we try to hide it
    delay 5

    if hide then
      tell application "Finder"
        set visible of process appName to false
      end tell
      writeLogLine("Application " & appName & " launched and hidden")
    else
      writeLogLine("Application " & appName & " launched")
    end if
  on error
    writeLogLine("Application " & appName & " could not be launched")
  end try
end launchApp

on deleteOldFilesFromFolder(pth, sortByDate, filesToKeep)
  writeLogLine("Reviewing files in " & (pth))
  tell application "Finder"
    activate
    set filelist to files in pth
    if sortByDate then
      set filelist1 to (sort filelist by modification date)
    else
      set filelist1 to the reverse of (sort filelist by name)
    end if
  end tell
  set fileCount to 0
  repeat with oneFile in filelist1
    tell application "System Events" to get displayed name of oneFile
    set oneFileName to result
    tell application "System Events" to get modification date of oneFile
    set modDate to result
    set fileCount to fileCount + 1
    if fileCount ≤ filesToKeep then
      -- writeLogLine("Keeping " & modDate & " " & oneFileName)
    else
      writeLogLine("Deleting " & modDate & " " & oneFileName)
      tell application "Finder" to delete oneFile
    end if
  end repeat
end deleteOldFilesFromFolder

on writeLogLine(logLine)
  write logLine & lf to logFile
end writeLogLine

There you have it! A fairly complete startup script that you can tailor to your liking, and a nice little tour of AppleScript capabilities along the way!

Once you’ve tweaked this for your situation and preferences, save it somewhere as a script and run it from within the Editor to test it out. When it’s working to your satisfaction, save it as an application within your Applications folder, then add it to your list of login items in System Preferences.

Other AppleScript resources:

July 7, 2012

Next: Tech Titans Compete