Apple - How to start an application when a specific disk is mounted
I didn't downvote wch1zpink's answer, but it's very much not the way that I would solve the problem.
Having an app run an AppleScript every 5 seconds is a very inefficient way to handle this situation, especially since Mac OS already has a built-in feature to do this, namely, launchd.
Save this as ~/Library/LaunchAgents/com.tjluoma.itunes-on-mount.plist
(you can change the "com.tjluoma.itunes-on-mount" part to whatever you want):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.tjluoma.itunes-on-mount</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>test -d /Volumes/Media && open -j -g -a iTunes</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>WatchPaths</key>
<array>
<string>/Volumes</string>
</array>
</dict>
</plist>
Then either reboot your Mac or do
launchctl load ~/Library/LaunchAgents/com.tjluoma.itunes-on-mount.plist
in Terminal. Then, anytime /Volumes/ changes, launchd
will look to see if /Volumes/Media
exists, and if it does, it will launch iTunes.
A stay-open application (Polling)
Although this question has already been marked as having a satisfactory solution, I'm compelled to provide an alternative. I completely agree with @TJ Luoma, who has provided, what I believe to be, by far the optimal solution, noting that a Stay Open application that polls for disk names every 5 seconds, and has to call out to an external application to do this, is not the way to approach this problem. It's inefficient, resource intensive, and entirely cumbersome to have an applet live permanently in the dock, or even just invisibly in the background, performing only one function in a terrible way, when macOS has a built-in means to monitor file system events, and a service manager to respond to these events and perform tasks in a scheduled and efficient manner.
launchd
TJ Luoma's method takes advantage of launchd
to solve the problem in what is, as far as I am aware, the recommended way. It monitors for changes in the /Volumes
folderpath, and acts accordingly. It's the method I would choose and know I didn't have to worry about what was going on behind the scenes.
Folder Actions
Some might not be aware that macOS's Folder Actions can also monitor the /Volumes
folder, and if AppleScript feels more at home to someone than a .plist
and a shell script, then this solution may be more appealing. As far as I'm aware, folder actions actually run as a process under launchd
, so although they operate in overtly different ways to the user, the underlying mechanisms are possibly the same, or similar, and one is not likely to see any performance differences between the two.
Implementation
Setting up is very easy, even if you haven't done it before. It involves two steps:
Save the following AppleScript, naming it anything you want, and saving it in the Folder Action Scripts folder located at
~/Library/Scripts/Folder Action Scripts
. I saved mine as a plain.applescript
text file using Script Editor, and named itNew Volume Mounted.applescript
:property diskName : "Media" -- whatever the disk is named property _objRef : a reference to reference script disks property path : missing value property kind : "public.volume" property list : missing value on _get(mountPoints) local mountPoints set my list to mountPoints tell application id "com.apple.systemevents" to ¬ repeat with fURL in (a reference to my list) if fURL's type identifier = my kind ¬ then set fURL's contents to ¬ the disk named fURL end repeat return every reference in my list end _get on _eval(key) if my list = missing value then return tell application id "com.apple.systemevents" tell {} repeat with _disk in (a reference to my list) set _objRef to _disk set the end to contents of the key end repeat return it end tell end tell end _eval end script on adding folder items to mountedVolumes after receiving mountPoints local mountedVolumes, mountPoints disks's _get(mountPoints) set propertyKey to a reference to the name of my _objRef set disknames to disks's _eval(propertyKey) if the diskname is in the disknames then exec() end adding folder items to on removing folder items from mountedVolumes after losing diskNames local mountedVolumes, diskNames -- display alert "Disk removed: " & diskNames end removing folder items from on exec() -- display alert "Disk attached" run application id "com.apple.iTunes" end exec
There is a conventional method to pair a folder action script with a watched folder, but by far the simplest† is to copy and paste the script below into Script Editor and, if necessary, edit the third line the declares
property script
. This wants to be set to the same name (with file extension) as your script above. If you chose the same name as I did in the first step, you don't need to make any adjustments at all. Simply run the script and everything will be set up, taking immediate effect. There's no need to save this script, and no need to restart the computer.use sys : application id "com.apple.systemevents" property path : "/Volumes" property script : "New Volume Mounted.applescript" property name : a reference to the name of folder path property folder action : a reference to folder action named (my name) property folder : a reference to Folder Action scripts folder property file : a reference to the file named (my script) in my folder set folder actions enabled to true if not (my file exists) then return open my folder tell my folder action if (exists) then return its scripts make new folder action with properties my {name:name, path:path} make new script with properties my file's ¬ {name:name, POSIX path:POSIX path} set [its enabled, its scripts's enabled] to [true, true] end tell
†This script has only been tested in High Sierra
Postscript
The script in step 1 above is one I wrote a while back as a generalised script that those who are comfortable with AppleScript could easily adapt and extend to perform whatever tasks one wishes, and already have available a list of all new mount points created in /Volumes
in the form of a validated list of disk
objects.
It is, however, actually more than is necessary for this task at hand. You are welcome to stick to it, and it ought to work just fine. But, if you would prefer a simpler script, then this one below performs exactly the same function as the one above, but with all the fat trimmed away:
property diskName : "Media" -- whatever the disk is named
property path : "/Volumes"
property diskVolumePath : [path, "/", diskName, "/"] as text
on adding folder items to mountedVolumes after receiving mountPaths
local mountedVolumes, mountPoints
repeat with mountPath in mountPaths
set mountPath's contents to ¬
mountPath's POSIX path
end repeat
if the mountPaths contains the diskVolumePath then exec()
end adding folder items to
on removing folder items from mountedVolumes after losing diskNames
local mountedVolumes, diskNames
-- display alert "Disk removed: " & diskNames
end removing folder items from
on exec()
-- display alert "Disk attached"
run application id "com.apple.iTunes"
end exec
This is a follow-up to TJ Luoma's answer, which I hope will fix the issues with the .plist
definition that stops the job from being run in the correct manner.
From man
launchd.plist
:
WatchPaths <array of strings>
This optional key causes the job to be started if any one of the listed paths are modified.
IMPORTANT: Use of this key is highly discouraged, as filesystem event monitoring is highly race-prone, and it is entirely possible for modifications to be missed. When modifications are caught, there is no guarantee that the file will be in a consistent state when the job is launched.
StartOnMount <boolean>
This optional key causes the job to be started every time a filesystem is mounted.
Here is a revised .plist
definition incorporating StartOnMount
in favour of WatchPaths
. It is currently labelled "local.startOnMount.iTunes"
, which, if unchanged, means the file should be saved at ~/Library/LaunchAgents/local.StartOnMount.iTunes.plist
:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>local.StartOnMount.iTunes</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/osascript</string>
<string>-e</string>
<string>if (list disks) contains "Media" then ¬
run application id "com.apple.iTunes"</string>
</array>
<key>RunAtLoad</key>
<false/>
<key>StandardErrorPath</key>
<string>/tmp/local.StartOnMount.iTunes.stderr</string>
<key>StandardOutPath</key>
<string>/tmp/local.StartOnMount.iTunes.stdout</string>
<key>StartOnMount</key>
<true/>
</dict>
</plist>
The ProgramArguments
key can alternatively be more akin to TJ's original bash
command:
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>[[ -d "/Volumes/Media" ]] && open -jg -b "com.apple.iTunes"</string>
</array>
However, in testing, I found that, if iTunes is already running and is hidden, then callng the open
command—even with the -j
and/or -g
option(s)—brings iTunes to the foreground. So I elected to do use osascript
to perform the same actions, but in a more consistent manner.
This new .plist
appears to resolve the issues, and only runs the job when a filesystem is mounted, which in turn only performs an action if it's the correct disk name. To replace:
cd ~/Library/LaunchAgents
launchctl unload com.tjluoma.itunes-on-mount.plist
launchctl load local.StartOnMount.iTunes.plist
However, if anything is not performing as expected, and it's not immediately obvious why, then look in the /tmp/
folder for file(s) named local.StartOnMount.iTunes.stderr
and/or local.StartOnMount.iTunes.stdout
:
cat local.StartOnMount.iTunes.stderr