10.5: Eject stubborn disk images via AppleScript

May 21, '08 07:30:03AM

Contributed by: RickoKid

Leopard sometimes can be a little reluctant to eject disks and disk images in the Finder. It doesn't matter if you click the Eject button in the Finder's sidebar, drag the disk to the Trash, or right-click and choose Eject, the Finder just silently ignores you. You can open Disk Utilty though and eject it fine that way (unless there are files open on the image, of course). This AppleScript will eject those troublesome disks:

(*
    Eject Disks © RickoKid 2008
    Version 0.1
  
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*)

set prefaceMessage to ""
set confirm to false

-- check if current selection in Finder contains any ejectable disks
set ejectables to my findEjectable(selection of application "Finder")
if (count of ejectables) is greater than 0 then
  -- Ejectable disks are selected in Finder, add them to the eject list
  set ejectList to ejectables
  set prefaceMessage to "Note: Ejecting disks selected in Finder." & return & return
else
  -- No ejectable disks in Finder selection; look for all ejectable disks on the system ...
  try
    tell application "Finder" to set allEjectables to name of every disk whose ejectable is true
  on error
    -- Finder throws an error (tantrum?) if it can't match the 'whose' condition, ie when there are no disks to match
    display alert "No ejectable disks" message "Couldn't find any ejectable disks on your system!" as informational buttons {"OK"} giving up after 10
    return "Error: No ejectable disks."
  end try
  
  -- if ejectable disks are found ...
  if (count of allEjectables) is 1 then
    set ejectList to allEjectables
    set confirm to true
  else if (count of allEjectables) is greater than 1 then
    set ejectList to choose from list allEjectables with title "Eject Disk" with prompt "Choose which disk(s) to eject" OK button name "Eject" with multiple selections allowed without empty selection allowed
  end if
end if

my kickoffEject(ejectList, prefaceMessage, confirm)

on open droppedDisks
  set ejectables to findEjectable(droppedDisks)
  my kickoffEject(ejectables, "Note: Ejecting dropped disks." & return & return, false)
end open

on findEjectable(selectedList)
  set ejectables to {}
  tell application "Finder"
    repeat with selectedItem in selectedList
      if class of selectedItem is alias then
        set nameOfItem to name of selectedItem
        set matchingDisks to (every disk whose name is nameOfItem and ejectable is true)
        repeat with matchItem in matchingDisks
          set ejectables to ejectables & {name of matchItem}
        end repeat
      else if class of selectedItem is disk and ejectable of selectedItem then
        set ejectables to ejectables & {name of selectedItem}
      end if
    end repeat
  end tell
  return ejectables
end findEjectable

on kickoffEject(ejectList, preface, confirm)
  set textDelimiters to AppleScript's text item delimiters
  set AppleScript's text item delimiters to return
  -- if disks are chosen (don't try if Cancel is clicked) or 1 is confirmed or disks are selected in Finder
  set messages to {}
  set moreInfo to {}
  
  if (count of ejectList) is greater than 0 then
    if confirm then
      set ejectIt to display alert "Eject" message "Are you sure you want to eject the following disk(s)?" & return & return & (ejectList as text) as warning buttons {"Cancel", "Eject"} default button "Eject" cancel button "Cancel" giving up after 20
      if button returned of ejectIt is "Cancel" then return "User cancelled."
    end if
    
    set AppleScript's text item delimiters to return & tab
    repeat with ejectDisk in ejectList
      try
        set ejectResult to (do shell script "touch /tmp/" & ejectDisk & "-report.txt; hdiutil detach '/Volumes/" & ejectDisk & "' &> /tmp/" & ejectDisk & "-report.txt &")
        repeat while ejectResult is equal to ""
          delay 0.5
          set ejectResult to (do shell script "cat  /tmp/" & ejectDisk & "-report.txt")
        end repeat
        do shell script "rm /tmp/" & ejectDisk & "-report.txt"
        if ejectResult contains "error" then error ejectResult
        set messages to messages & {"Ejected disk \"" & ejectDisk & "\" successfully."}
        set moreInfo to moreInfo & {"Success: disk \"" & ejectDisk & "\": " & return & tab & (paragraphs of ejectResult as text)}
      on error errorMsg
        set messages to messages & {"Error: disk \"" & ejectDisk & "\" could not be ejected."}
        set moreInfo to moreInfo & {"Fail: disk \"" & ejectDisk & "\":" & return & tab & errorMsg}
      end try
    end repeat
    
    set AppleScript's text item delimiters to return
    
    if (count of messages) is not 0 then
      set report to display alert "Ejection Results" message preface & (messages as text) as informational buttons {"More info ...", "Close"} default button "Close" giving up after 60
      if button returned of report is "More info ..." then
        set moreDetailedInfo to {}
        repeat with eachMessage in moreInfo
          if eachMessage contains "error" then
            set diskDev to do shell script "mount | grep " & word 3 of eachMessage & " |awk '{print $1}'"
            set openFiles to paragraphs of (do shell script "lsof -Fc " & diskDev & " |  sed -ne '/c/ s/c//p'")
            if (count of openFiles) is greater than 0 then
              set AppleScript's text item delimiters to return & tab & tab
              set eachMessage to eachMessage & return & tab & "Processes open on disk:" & return & tab & tab & (openFiles as text)
              set AppleScript's text item delimiters to return
            end if
          end if
          set moreDetailedInfo to moreDetailedInfo & {return & eachMessage}
        end repeat
        display alert "Ejection Details" message (moreDetailedInfo as text) as informational buttons {"OK"} default button "OK"
      end if
    end if
  end if
  set AppleScript's text item delimiters to textDelimiters
end kickoffEject
The script will let you eject troublesome disks quickly if the Finder is ignoring your requests. It will also tell you which applications are stopping the disk from being ejected, as opposed to the Finder's generic "something is open on that disk, but I'm not telling you what!" You can find a ready-to-run version of the script in this post on my site.

[robg adds: When you run the AppleScript, a list of mounted disks will appear, including FireWire drives, CD/DVDs, and disk images. Select the one you'd like to eject then click the Eject button. I didn't have any 'stuck' devices to test this with, but it worked fine on my three test runs with a FireWire disk, a CD, and a disk image.]

Comments (16)


Mac OS X Hints
http://hints.macworld.com/article.php?story=20080518161338583