No internet connection
  1. Home
  2. How to

Eject all hard drives

By Andrew Sherman @Andrew_Sherman
    2021-04-02 12:30:05.529Z

    I'm trying to eject all attached hard drives (for example when I'm wanting to grab my laptop and unplug it from the multiple USB docks and attached hard drives etc).

    This is what I have so far, but it doesn't seem to work reliably. Sometimes it does nothing, sometimes it ejects one or two drives and then I need to trigger it again to eject a few more, and there's always one pesky USB hard drive that it refuses to eject.

    I'm trying to get something that I trigger once, and it ejects all the hard drives, repeating if necessary.

    Do you have any suggestions?

    
    // Eject all ejectable media
    
    sf.system.exec({
    commandLine: `diskutil eject /Volumes/*`
    });
    
    // Eject all hard drives not in use
    
    sf.system.exec({
    commandLine: `diskutil unmountDisk /Volumes/*`
    });
    
    Solved in post #14, click to view
    • 19 replies
    1. Dustin Harris @Dustin_Harris
        2021-04-02 12:55:06.422Z

        You might get a better result by getting the names of all attached disks in an array, filtering out your startup disk, then ejecting them one by one in quick succession. Let me see what I can come up with...

        1. I think there may also be a force option that you may need to specify.

          1. For example:

            diskutil unmountDisk force /dev/disk2
            

            Then followed by eject.

            But obviously it's important to not force unmount any disk that is actually not movable.

          2. AAndrew Sherman @Andrew_Sherman
              2021-04-02 13:30:20.291Z

              Thanks for the replies guys,

              This is the other script I was using but it didn't seem very efficient or elegant.

              
              sf.directory.open({
                  path: "/Volumes/MP-005",
              });
              
              sf.ui.finder.menuClick({
                  menuPath: ["File","Eject"],
              });
              
              sf.directory.open({
                  path: "/Volumes/MP-001",
              });
              
              sf.ui.finder.menuClick({
                  menuPath: ["File","Eject"],
              });
              
              sf.directory.open({
                  path: "/Volumes/Backblaze_MacEx8TB31044614",
              });
              
              sf.ui.finder.menuClick({
                  menuPath: ["File","Eject"],
              });
              
              sf.directory.open({
                  path: "/Volumes/Personal",
              });
              
              sf.ui.finder.menuClick({
                  menuPath: ["File","Eject"],
              });
              
              sf.directory.open({
                  path: "/Volumes/Clone-1",
              });
              
              sf.ui.finder.menuClick({
                  menuPath: ["File","Eject"],
              });
              
              sf.directory.open({
                  path: "/Volumes/Clone-1\xa0-\xa0Data",
              });
              
              sf.ui.finder.menuClick({
                  menuPath: ["File","Eject"],
              });
              
              sf.keyboard.press({
                  keys: "return",
              });
              
              1. Dustin Harris @Dustin_Harris
                  2021-04-02 13:36:39.445Z

                  here's something I've come up with; it doesn't have the force parameter (yet) as that can be dangerous, but this should unmount any drive whatsoever that isn't internal:

                  function GetStorageDeviceInfo() {
                      let storageDevicesJSON = sf.system.exec({ commandLine: "system_profiler SPStorageDataType -json" }).result
                      let storageDevices = JSON.parse(storageDevicesJSON).SPStorageDataType
                  
                      let drives = [];
                  
                      for (let i = 0; i < storageDevices.length; i++) {
                          let obj = {}
                          obj.name = storageDevices[i]._name
                          obj.bsdName = storageDevices[i].bsd_name
                          obj.isInternal = storageDevices[i].physical_drive.is_internal_disk
                          drives.push(obj)
                      }
                  
                      return drives
                  }
                  
                  function doEjectDrive(bsdName) {
                      let unmountResult = sf.system.exec({ commandLine: `diskutil unmount ${bsdName}` }).result
                  }
                  
                  let drives = GetStorageDeviceInfo()
                  
                  for (let i = 0; i < drives.length; i++) {
                  
                      if (drives[i].isInternal === "no") {
                          doEjectDrive(drives[i].bsdName)
                      }
                  }
                  

                  Maybe it's useful?

                  1. Wow!!!

                    1. Beautiful code, Dustin. Really impressive.

                      1. Dustin Harris @Dustin_Harris
                          2021-04-02 13:51:55.951Z

                          That means a ton, coming from you šŸ˜

                  2. Dustin Harris @Dustin_Harris
                      2021-04-02 14:14:20.893Z2021-04-02 19:01:19.335Z

                      I updated the script a little, a couple of conventions were bugging me :) (edit: refactored yet again)

                      function GetStorageDeviceInfo() {
                      
                          let storageDevicesJSON = sf.system.exec({ commandLine: "system_profiler SPStorageDataType -json" }).result
                          let storageDevices = JSON.parse(storageDevicesJSON).SPStorageDataType
                      
                          let drives = storageDevices.map(n => ({ name: n._name, bsdName: n.bsd_name, isRemovable: n.physical_drive.is_internal_disk === "no" }))
                      
                          return drives
                      }
                      
                      function ejectRemovableDrives(drive) {
                      
                          if (drive.isRemovable) {
                      
                              sf.system.exec({ commandLine: `diskutil unmount ${drive.bsdName}` }).result
                          }
                      }
                      
                      let drives = GetStorageDeviceInfo();
                      drives.forEach(ejectRemovableDrives);
                      
                      Reply1 LikeSolution
                      1. Daniel Perez @daniel_perez
                          2022-11-05 10:13:37.332Z

                          could you add a pop for me? drive ejected successfully, but without the need to confirm with "ok" button?

                          also a second script to mount all connected drives?, with a confirmation pop "all connected drives mounted successfully".

                          thanks alot!

                          1. Daniel Perez @daniel_perez
                              2022-11-08 16:39:35.605Z
                              1. Dustin Harris @Dustin_Harris
                                  2022-11-08 18:26:10.707Z

                                  Hi @daniel_perez
                                  here's a quick one, I don't have time at the moment to do super thorough error handling, so let me know how this works. I'll have to figure out a different way to re-mount unmounted drives as they don't actually show up in the SPStorageDataType JSON data :)

                                  function getStorageDeviceInfo() {
                                      const storageDevicesJSON = sf.system.exec({ commandLine: "system_profiler SPStorageDataType -json" }).result
                                      const storageDevices = JSON.parse(storageDevicesJSON).SPStorageDataType
                                      
                                      const drives = storageDevices.map(n => ({ name: n._name, bsdName: n.bsd_name, isRemovable: n.physical_drive.is_internal_disk === "no" }))
                                      return drives
                                  }
                                  
                                  
                                  /**
                                  * @param {{ isRemovable: boolean; bsdName: string; name: string }} drive
                                  */
                                  function ejectRemovableDrives(drive) {
                                      let standardError = null;
                                  
                                      if (drive.isRemovable) {
                                          standardError = sf.system.exec({ commandLine: `diskutil unmount ${drive.bsdName}` }).standardError
                                      }
                                  
                                      if (standardError) throw `There was an error unmounting ${drive.name}`;
                                  }
                                  
                                  
                                  function main() {
                                      let drives = getStorageDeviceInfo();
                                      drives.forEach(ejectRemovableDrives);
                                      log('All removable drives unmounted successfully')
                                  }
                                  
                                  
                                  main();
                                  
                          2. A
                            Andrew Sherman @Andrew_Sherman
                              2021-04-02 13:47:30.027Z

                              ...rubs eyes in disbelief... that was so fast!

                              Thanks, Dustin, that works perfectly.

                              1. Dustin Harris @Dustin_Harris
                                  2021-04-02 13:48:20.236Z

                                  Oh I'm not THAT fast, I had made something similar to eject drives from a stream deck so I had most of it written already :)

                                  1. AAndrew Sherman @Andrew_Sherman
                                      2021-04-02 13:49:45.217Z

                                      Aah, still it's very impressive!

                                      1. Dustin Harris @Dustin_Harris
                                          2021-04-02 13:50:44.346Z

                                          I'll take the compliment! :)

                                      2. Dustin Harris @Dustin_Harris
                                          2021-04-02 15:10:23.115Z

                                          Another super handy thing: if you have a drive that won't eject for some reason (the force-eject message pops up) open the terminal window, type lsof <name of the drive, just drag it into the window> and hit enter. It will tell you what applications are still trying to use the drive. 99% of the time it's mds_worker (spotlight) or quickpreview has crashed :)

                                          1. Ryan DeRemer @Ryan_DeRemer
                                              2022-06-03 00:25:10.479Z

                                              @Dustin_Harris I'm curious as to why you named GetStorageDeviceInfo() starting with an uppercase character in stead on camel case. Any specific reason?

                                              1. Dustin Harris @Dustin_Harris
                                                  2022-06-03 00:55:17.022Z

                                                  Oh that was just bad coding. I originally thought functions that returned objects should be capitalized… that was before I learned about Classes :)