Search code examples
macosapplescript-objc

How to Mount Multiple EFI Partitions?


I’m trying to create an Applescript-Objc to mount multiple EFI partitions, but I’m not able to mount the EFI partitions on external drives only those on the internal disk, what I've done so far is this:

set a to do shell script "diskutil list | grep EFI | grep -o -e disk[0-9]s[0-9]"
set b to do shell script "diskutil info " & a & " | awk '/Identifier/' | grep -o -e disk[[:digit:]]*"
set c to do shell script "diskutil info " & b & "| grep 'Media Name' | awk /'Name:/{print$5,$6}'"
choose from list c with prompt "Multiple EFI partitions found:"
display alert "Do you want to mount the EFI partition?" buttons {"Yes", "No"} default button 1
set response to button returned of the result
if response = "Yes" then
    do shell script "diskutil mount " & a with administrator privileges
    display alert result
end if

Note: when asked the user I want the disk name to be displayed and not something like: ''disk0s1'' Thanks in advance!


Solution

  • I wrote an EFI mount/unmount utility applet a while ago that uses an NSAlert with a combo box to try to avoid the death by dialog typical with AppleScripts. I extracted the script from that and added disk names to the initial dialog - the alert stuff makes it a bit longer than your snippet, but there should be something in there you can use:

    use AppleScript version "2.4" -- Yosemite (10.10) or later
    use framework "Foundation"
    use scripting additions
    
    property alertHeader : "                          --- CAUTION ---" & return & "This utility works with EFI boot partitions -" & return & "Administrator authorization will be required."
    property resources : "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/"
    property leadText : "                  "
    property identifiers : missing value -- this will be a list of the EFI partition identifiers, e.g. {disk0s1}
    
    on run -- example can be run as app and from Script Editor
        if current application's NSThread's isMainThread() as boolean then
            doStuff()
        else
            my performSelectorOnMainThread:"doStuff" withObject:(missing value) waitUntilDone:true
        end if
    end run
    
    on doStuff() -- the main stuff
        set theDisks to setup() -- set up properties and get disks with EFI partitions, e.g. {/dev/disk0}
        set reply to {button:missing value, answer:""} -- preset a previous empty dialog result
        repeat -- forever
            set theAnswer to answer of reply -- use previous
            set reply to (showAlert given settings:{title:"EFI [un]Mounter", message:alertHeader, information:"EFI partitions have been found on the following disks:" & return & return & theDisks & return & "The EFI partition of the specified disk will be [un]mounted." & return & "Enter the disk identifier or select one from the menu:", answer:theAnswer, icon:(resources & "AlertCautionIcon.icns"), buttons:{"Cancel", "Mount", "Unmount", "More Info"}})
            if button of reply is "Cancel" then error number -128
            set diskID to checkID(answer of reply)
            if diskID is not missing value then -- found it
                if button of reply is "More Info" then
                    set reply to moreInfo(answer of reply) -- result of the moreInfo dialog
                    if button of reply is not "Change" then exit repeat
                else
                    exit repeat
                end if
            else -- try again
                beep
                set answer of reply to ""
            end if
        end repeat
        processEFI(button of reply, diskID)
    end doStuff
    
    to processEFI(command, diskID) -- mount or unmount the specified disk - admin authorization will be needed
        log diskID
        try
            set command to (do shell script "echo " & command & " | tr [:upper:] [:lower:]")
            if command is not in {"mount", "unmount"} then error "The processEFI handler received an invalid command."
    
            set theResult to (do shell script "diskutil " & command & " /dev/" & diskID with administrator privileges)
            if command is "mount" then tell application "Finder" -- show it
                make new Finder window to ("/Volumes/EFI" as POSIX file)
                activate
            end tell
            display notification theResult
            delay 2
        on error errmess
            log errmess
            showAlert given settings:{title:"EFI [un]Mounter", message:return & "There was an error processing the partition.", information:errmess, icon:(resources & "AlertCautionIcon.icns")}
        end try
    end processEFI
    
    on moreInfo(diskID) -- get more information about the specified disk identifier
        set infoText to ""
        set diskName to (do shell script "diskutil info " & diskID & " | grep 'Media Name' | awk '{print substr($0,index($0,$5))}'")
        if diskName is "" then
            set diskName to "the following disk"
        else
            set diskName to "disk " & quoted form of diskName
        end if
        repeat with aParagraph in paragraphs of (do shell script "diskutil list " & diskID)
            if aParagraph starts with leadText then -- trim information text
                if (paragraph -2 of infoText does not start with leadText) then set infoText to infoText & leadText & trimWhitespace(text 1 thru -1 of contents of aParagraph) & return
            else if aParagraph does not start with "   #:" then -- don't include header text
                if length of aParagraph is less than 56 then
                    set infoText to infoText & aParagraph & return
                else -- trim description text
                    set infoText to infoText & space & space & space & text 1 thru 6 of aParagraph & tab & trimWhitespace(text 7 thru 56 of aParagraph)
                    set infoText to infoText & return
                end if
            end if
        end repeat
        set reply to (showAlert given settings:{title:"EFI [un]Mounter", message:alertHeader, information:"The EFI partition of " & diskName & " will be [un]mounted:" & return & return & infoText, icon:(resources & "AlertCautionIcon.icns"), buttons:{"Cancel", "Mount", "Unmount", "Change"}})
        if button of reply is "Cancel" then error number -128
        return {button:button of reply, answer:diskID}
    end moreInfo
    
    to showAlert given settings:arguments -- show a custom alert
        set arguments to arguments & {title:"", message:"Alert", information:"", answer:missing value, icon:"", buttons:{"OK"}} -- a record is used for input parameters, unspecified keys will use default values
        tell current application's NSAlert's alloc's init()
            if (icon of arguments) as text is "Critical" then -- everything else is NSInformationalAlertStyle
                set its alertStyle to current application's NSCriticalAlertStyle
            else -- use the contents of an image file - informational icon will be used if no image or file (missing value)
                set its icon to current application's NSImage's alloc's initByReferencingFile:((icon of arguments) as text)
            end if
            set its |window|'s title to (title of arguments) as text
            set its messageText to (message of arguments) as text -- the bold text
            set its informativeText to (information of arguments) as text -- the normal text
            set buttonList to my (setButtons for it from (buttons of arguments))
            if (answer of arguments) is not missing value then
                set its |window|'s autorecalculatesKeyViewLoop to true
                set accessory to my (makeComboAccessory for it)
                set accessory's stringValue to answer of arguments
            end if
            activate me
            set response to ((its runModal) as integer) - 999 -- get index - 1000 is rightmost button
        end tell
        if answer of arguments is not missing value then set answer of arguments to (accessory's stringValue) as text
        return {button:item response of buttonList, answer:answer of arguments} -- returns a record: {button:(button title), answer:(text field value, or 'missing value' if not used)}
    end showAlert
    
    to setButtons for alert from buttons -- set buttons for the alert - filters for blanks and duplicates; returns a list of the button titles (left-to-right)
        set {buttonList, theButton} to {{}, missing value}
        repeat with aButton in reverse of (buttons as list) -- match dialog order
            set aButton to aButton as text
            if aButton is not in buttonList and aButton is not in {missing value, ""} then -- filter
                set theButton to (alert's addButtonWithTitle:aButton)
                set end of buttonList to aButton
            end if
        end repeat
        if buttonList is {} then -- better have at least one
            set theButton to alert's addButtonWithTitle:"OK"
            set end of buttonList to "OK"
        end if
        set alert's |window|()'s initialFirstResponder to theButton -- the last (leftmost) one
        return buttonList
    end setButtons
    
    to makeComboAccessory for alert -- make and return a comboBox accessory view for the alert
        tell (current application's NSComboBox's alloc's initWithFrame:{{0, 0}, {288, 28}})
            set its completes to true
            set its hasVerticalScroller to true
            set its placeholderString to "a disk identifier, for example disk0" -- arbitrary  
            repeat with anItem in identifiers -- populate the combo box with the disks with EFI partitions
                set here to offset of "s" in (reverse of characters of anItem) as text
                set anItem to text 1 thru -(here + 1) of anItem -- strip off partition
                (its addItemWithObjectValue:anItem)
            end repeat
            set alert's accessoryView to it
            return it
        end tell
    end makeComboAccessory
    
    to checkID(diskID) -- check the disk identifier against the EFI partitions - returns the EFI partition, or missing value if not found
        if diskID is not "" then repeat with anItem in identifiers
            set anItem to anItem as text
            set here to offset of "s" in (reverse of characters of anItem) as text -- partition index
            if text 1 thru -(here + 1) of anItem is first word of diskID then return anItem
        end repeat
        return missing value
    end checkID
    
    to trimWhitespace(someText) -- trim whitespace characters from the beginning and end of a string
        set someText to someText as text
        if someText is "" then return ""
        set whiteSpace to {space, tab, return, linefeed}
        repeat until the first character of someText is not in whiteSpace
            if (count someText) is 1 then return ""
            set someText to text 2 thru -1 of someText
        end repeat
        repeat until the last character of someText is not in whiteSpace
            if (count someText) is 1 then return ""
            set someText to text 1 thru -2 of someText
        end repeat
        return someText
    end trimWhitespace
    
    to setup() -- set up properties and stuff
        set {theDisks, identifiers} to {"", {}}
        try
            repeat with aPartition in paragraphs of (do shell script "diskutil list | grep 'EFI EFI' | grep -E -o 'disk[0-9]?[0-9]s[0-9]?[0-9]'") -- just EFI partitions named EFI
                set aDisk to first paragraph of (do shell script "diskutil list " & aPartition)
                if aDisk does not contain "disk image" then -- don't add disk images
                    set diskName to (do shell script "diskutil info " & text 1 thru -3 of aPartition & " | grep 'Media Name' | awk '{print substr($0,index($0,$5))}'")
                    if diskName is in {"", missing value} then
                        set diskName to ""
                    else
                        set diskName to "(" & diskName & ")"
                    end if
                    set theDisks to theDisks & tab & text 1 thru 11 of aDisk & space & diskName & return
                    set end of my identifiers to aPartition
                end if
            end repeat
            if (count identifiers) < 1 then error "No EFI partitions were found."
            return theDisks -- returns the disks found with EFI partitions (for the initial dialog)
        on error errmess
            log errmess
            showAlert given settings:{title:"EFI [un]Mounter", message:return & errmess, icon:(resources & "AlertStopIcon.icns")}
            error number -128
        end try
    end setup