My strategy for managing SD! jobs via AppleScript is this:
First, schedule each desired SD! job from within SD!, with any schedule that won't execute in the near future. This creates a settings package in the folder
Library/Application Support/SuperDuper!/Scheduled Copies/, with a name such as
Smart Update aOSX from OSX.sdsp. Move these settings packages to your own folder; this allows them to run independently of SD!'s built-in scheduling feature, as I noticed by reading their AppleScript code. Now go back in SD! and delete these scheduled jobs. The settings packages will survive intact; for the moment, SD! has no idea where they went.
Each of these settings packages contains a compiled AppleScript called
Copy Job. One can
Show Package Contents and then manually launch
Copy Job, to run the corresponding SD! job. The log files for these jobs are also stored in these settings packages.
Alternatively, one can call these
Copy Job scripts from within AppleScript. The calling script can then be run manually, or scheduled using
cron or a GUI for cron such as
CronniX. I describe below how I resolved the issues that arose for me in writing and testing such a script.
For a variety of reasons, I don't want my backup hard drives mounted when I'm not backing up to them. I also rotate hard drives as backup media, and I want a script to gracefully notice when media is unavailable, and move on. So I mount each volume as needed from within AppleScript, and eject it afterwards.
I found that I needed to eject volumes by telling the Finder, but this has the effect of ejecting all volumes on a given physical hard drive. This affects both one's strategy in partitioning one's drives, and one's code: One needs to avoid timing issues where ejecting the last volume used could also eject the next volume needed.
Along the same lines, all available volumes are mounted whenever a user logs in, so it is necessary to write another AppleScript (not shown, but similar) to eject unwanted volumes on login.
My AppleScript
mount routine is written to be as robust as possible for my purposes, rather than to be as concise as possible:
Some code fragments that I found for mounting partitions were vulnerable to volume name issues. The
egrep pattern matches an entire line of the
diskutil list output, and
sed removes the volume name, so
awk can find the disk locator in a consistent column. This allows for volume names with embedded spaces, and volume names that contain one another. Still, it is poor form to have two identical volume names available, and this code will behave unpredictably in that event.
I also want to return the status of mount attempts as quickly as possible.
mount executes two shell scripts, rather than combining these into one, in order to detect immediately if a volume name isn't even listed in the
diskutil list output. Presumably, this is rotating backup media that is intentionally unavailable.
If SD! is open, my script waits for SD! to quit. Similarly, it waits for SD! to quit before proceeding to subsequent copy jobs. This code is based on the source code for the SD!
Copy Job scripts.
Each top level
copyJob call takes two arguments, the name of the destination volume, and a full AppleScript alias for the SD!
Copy Job script to run. AppleScript aliases need to exist at compile time, but they have the advantage of automatically updating themselves when files are moved. The relative unreadability of this argument is a small price to pay for the convenience and feedback one gains.
Links to related threads:
Mounting an external volume before backup
An (Applescript) hack to automate mounting and unmounting external volumes
Here is my complete AppleScript for managing SD! jobs:
Note: I have revised this code at least once. I am preserving the sequence of posted versions in this thread, so that the discussion makes sense. However, I recommend modifying the latest version to your needs, after you understand the code.
Code:
on mount(diskname)
tell application "Finder"
if (exists the disk diskname) then return true
try
set a to do shell script "diskutil list | egrep '^ +[[:digit:]]+: +[[:graph:]]+ +" & diskname & " +[.[:digit:]]+ GB +disk[[:alnum:]]+$'"
do shell script "diskutil mount `echo '" & a & "' | sed 's/" & diskname & "//' | awk '{print $5}'`"
on error
return false
end try
repeat 12 times
tell application "System Events" to do shell script "sleep 5"
if (exists the disk diskname) then return true
end repeat
return false
end tell
end mount
on eject(diskname)
tell application "Finder"
if not (exists the disk diskname) then return true
try
eject disk diskname
on error
return false
end try
repeat 12 times
tell application "System Events" to do shell script "sleep 5"
if not (exists the disk diskname) then return true
end repeat
return false
end tell
end eject
on isSuperDuperRunning()
tell application "System Events"
return exists process "SuperDuper!"
end tell
end isSuperDuperRunning
on waitForSuperDuper()
repeat while isSuperDuperRunning
tell application "System Events" to do shell script "sleep 5"
end repeat
end waitForSuperDuper
on copyJob(diskname, scriptalias)
if mount(diskname) then
waitForSuperDuper
run script alias scriptalias
waitForSuperDuper
eject(diskname)
else
display dialog "Run Backups: \"" & diskname & "\" is unavailable" giving up after 30
end if
end copyJob
on run
copyJob("aOS9", "User:Users:ad:Scripts:Smart Update aOS9 from OS9.sdsp:Copy Job.app:")
copyJob("aOSX 10.3.9 min", "User:Users:ad:Scripts:Smart Update aOSX 10.3.9 min from OSX 10.3.9 min.sdsp:Copy Job.app:")
copyJob("aOSX 10.4.4 min", "User:Users:ad:Scripts:Smart Update aOSX 10.4.4 min from OSX 10.4.4 min.sdsp:Copy Job.app:")
-- copyJob("aUser", "User:Users:ad:Scripts:Smart Update aUser from User.sdsp:Copy Job.app:")
-- copyJob("aOSX", "User:Users:ad:Scripts:Smart Update aOSX from OSX.sdsp:Copy Job.app:")
end run