No internet connection
  1. Home
  2. How to

macOS - Eject hard drives - Macros?

By Fannar Magnusson @Fannar_Magnusson
    2021-09-07 13:10:12.741Z

    Hi. I have a ton of external hard drives attached to my Mac but sometimes I want to eject them (some of them) to get rid of the noise they generate and when I'm not backing them up via BackBlaze. So is there a way to create some sort of macro/shortcut and then with a press of a button on my StreamDeck eject them all or one by one? Like is it possible to create a macro and include the name of the hard drive and only eject that one? I would then duplicate that macro and just change the name of the drive to get that going for all of them?

    • Fannar
    • 6 replies
    1. Dustin Harris @Dustin_Harris
        2021-09-08 03:12:24.349Z

        I think I wrote something like this a while ago, I’ll check in the morning!

        1. Dustin Harris @Dustin_Harris
            2021-09-09 13:07:57.833Z

            Update: I made a 'dynamic deck' which means I can't copy/paste the code here, I'll have to upload it to the store. Let me tweak a few things as it's old code, but I'll let you know once it's up.

            1. Dustin Harris @Dustin_Harris
                2021-09-15 00:30:06.233Z

                Turns out I'm going to need to do a lot more work on the dynamic deck version of the Eject Script, as it needs to be refreshed automatically every time an action is taken, or else you can end up trying to eject a drive that has already been ejected.

                Here's a script for an on-screen list though, maybe that will be useful?

                function getStorageDeviceInfo() {
                    let storageDevicesJSON = sf.system.exec({ commandLine: "system_profiler SPStorageDataType -json" }).result
                    let storageDevices = JSON.parse(storageDevicesJSON).SPStorageDataType
                
                    //get drive names that are not system data partitions
                    let drives = storageDevices.filter(n => !n._name.match(" - Data") && n.mount_point.match("/Volumes/")).map(n => ({
                        name: n._name,
                        bsdName: n.bsd_name,
                    }));
                
                    return drives
                }
                
                
                /**
                * @param {string} message
                */
                function notify(message) {
                    sf.interaction.notify({
                        title: 'SF Eject Drives',
                        message
                    })
                }
                
                
                /**
                * @param {{ name: string; bsdName: string; }} driveToEject
                */
                function doEjectDrive(driveToEject) {
                
                    let {name, bsdName} = driveToEject;
                
                    let unmountResult = sf.system.exec({ commandLine: `diskutil unmount ${bsdName}` });
                    
                    if (unmountResult.exitCode === 0) {
                        notify(`${name} Ejected`)
                    }
                
                    if (unmountResult.exitCode === 1) {
                        alert('One or more programs prevented the drive from unmounting')
                    }
                
                    if (unmountResult.exitCode > 1) {
                        alert('An unknown error occurred')
                    }
                }
                
                
                /**
                * @param {{name: string, bsdName: string}[]} drives
                */
                function getDriveName(drives) {
                    return sf.interaction.popupSearch({
                        items: drives.map(n => ({ name: n.name })),
                    }).item.name
                }
                
                
                
                function main() {
                    let drives = getStorageDeviceInfo();
                    let driveNameToEject = getDriveName(drives);
                    let driveToEject = drives.filter(n => n.name === driveNameToEject)[0];
                    doEjectDrive(driveToEject)
                }
                
                main();
                

                And here's a script I wrote that will eject all removable drives:

                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
                }
                
                
                /**
                * @param {{ name: string; isRemovable: boolean; bsdName: string; }} drive
                */
                function ejectRemovableDrives(drive) {
                    if (drive.isRemovable) {
                        sf.system.exec({ commandLine: `diskutil unmount ${drive.bsdName}` }).result
                    }
                }
                
                
                function main() {
                    let drives = getStorageDeviceInfo();
                    drives.forEach(ejectRemovableDrives);
                }
                
                
                main();
                

                Hope these are helpful!

                1. Dustin Harris @Dustin_Harris
                    2021-09-15 13:22:42.279Z

                    Here's one more version, where you can specify the drive you want to eject. It will notify you if the drive you're trying to eject isn't mounted.

                    
                    let driveName = "Your Drive Name Here"
                    
                    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 main() {
                    
                        let drives = getStorageDeviceInfo();
                        let listOfMountedDrives = drives.map(n => n.name)
                    
                        if (listOfMountedDrives.indexOf(driveName) == -1) {
                            log(`${driveName} is not mounted`)
                            throw 0;
                        }
                    
                        let {name, bsdName} = drives.filter(n => n.name.match(driveName))[0]
                        let unmountResult = sf.system.exec({ commandLine: `diskutil unmount ${bsdName}` });
                    
                        if (unmountResult.exitCode === 0) {
                            log(`${name} Ejected`)
                        } else if (unmountResult.exitCode === 1) {
                            alert('One or more programs prevented the drive from unmounting')
                        } else {
                            alert('An unknown error occurred')
                        }
                    }
                    
                    
                    main();
                    
                    1. MMasayoshi Sugai @Masayoshi_Sugai
                        2021-09-29 14:56:39.578Z

                        Thank you, Dustin!
                        If the storage built into the TC Clarity M Loudness Meter is mounted, the script that mounts and ejects all other drives from soundflow will cause an unexplained error.
                        I tried to work around this problem by registering the App created by Automator in the Mac's "Login Items" and forcing the Mac to eject the Crality M storage upon startup.
                        I'll try the script you gave me!

                        1. Dustin Harris @Dustin_Harris
                            2021-09-29 15:00:04.395Z

                            Yeah, try the last one, it should tell you if an application is using the drive or not.., let me know how it goes :)