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/*`
});
- Dustin Harris @Dustin_Harris
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...
Christian Scheuer @chrscheuer2021-04-02 13:21:01.140Z
I think there may also be a force option that you may need to specify.
Christian Scheuer @chrscheuer2021-04-02 13:22:14.278Z
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.
- In reply toDustin_Harrisā¬:AAndrew Sherman @Andrew_Sherman
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", });
Dustin Harris @Dustin_Harris
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?
Christian Scheuer @chrscheuer2021-04-02 13:45:02.193Z
Wow!!!
Christian Scheuer @chrscheuer2021-04-02 13:50:56.359Z
Beautiful code, Dustin. Really impressive.
Dustin Harris @Dustin_Harris
That means a ton, coming from you š
- In reply toDustin_Harrisā¬:
Dustin Harris @Dustin_Harris
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);
Daniel Perez @daniel_perez
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!
Daniel Perez @daniel_perez
Dustin Harris @Dustin_Harris
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();
- AIn reply toAndrew_Shermanā¬:Andrew Sherman @Andrew_Sherman
...rubs eyes in disbelief... that was so fast!
Thanks, Dustin, that works perfectly.
Dustin Harris @Dustin_Harris
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 :)
- AAndrew Sherman @Andrew_Sherman
Aah, still it's very impressive!
Dustin Harris @Dustin_Harris
I'll take the compliment! :)
- In reply toAndrew_Shermanā¬:
Dustin Harris @Dustin_Harris
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 :)Ryan DeRemer @Ryan_DeRemer
@Dustin_Harris I'm curious as to why you named
GetStorageDeviceInfo()
starting with an uppercase character in stead on camel case. Any specific reason?Dustin Harris @Dustin_Harris
Oh that was just bad coding. I originally thought functions that returned objects should be capitalized⦠that was before I learned about Classes :)