86

I'm constructing a backup script for Windows 7, and the last action I want it to perform is to safely "remove" the USB drive that it is backing up to. I am under the impression that plugging the drive into the same USB port all the time will keep the same DEV_ID (correct me if I'm wrong). With a command line (or PowerShell), how can I tell Windows to safely remove the hardware automatically without user input?

Just as a place holder, other OSes that may have a way to do this would be great to know as well.

Canadian Luke
  • 24,640

9 Answers9

83

Besides Uwe Sieber's RemoveDrive mentioned in the other answer, there are a whole bunch of utilities that can accomplish this. A small list follows:

  • USB Disk Ejector is primarily a GUI-based utility but can be used equally well from the command-line to eject the drive that the program is running from, or any drive by specifying the drive letter / (partial) drive name / mountpoint etc. Free and open source.

    USB Disk Ejector

  • USB Safely Remove is not free but it's a disk removal utility on steroids, with lots of advanced features, including of course command-line support. Zentimo is its bigger brother, with even more features.

    USB Safely Remove

  • Microsoft's own DevCon is the command-line version of Device Manager. Besides the original Win2K/XP-era version available from the KB page, there are newer releases (both 32 and 64-bit) available from various MS sources as mentioned in this Where to find DevCon.exe article. DevCon.exe for Windows 7 (and probably Windows 8 as well) can be found buried in the appropriate Windows Driver Kit (WDK), as mentioned in this thread (which also contains download links to the extracted executable).

    devcon status * or devcon hwids * or devcon findall =usb (for a more compact listing) should tell you the hardware ID of the device. For example:

    USB\VID_0781&PID_7113\0001162825
    Name: USB Mass Storage Device
    Driver is running.

    You can then try removing the device with devcon remove "USB\VID_0781&PID_7113" (wildcards like * are allowed, but be careful or you might end up removing something else entirely!)


Someone asked "Is there a DOS prompt (cmd.exe from Win7) command to eject a thumb drive?" which was unfortunately closed as a duplicate of this thread. However, the question was about ejecting USB drives while in the Windows Recovery Console / System Recovery Command Prompt, so it is unlikely any of the utilities above will help. In such a situation, the following method using Diskpart should work:

  1. Type diskpart and wait for the diskpart prompt (DISKPART>)

  2. Type list volume

  3. Note the volume number of the USB drive carefully (use listed properties such as drive letter, label, type and size for help)

  4. Type select volume <number>, where <number> is the volume number noted above

    Diskpart

  5. Type remove all dismount

  6. Type exit to quit Diskpart

Now you should be able to safely remove your USB drive without fear of data loss.

Karan
  • 57,289
37

RemoveDrive has served me well in the past

stijn
  • 2,087
27

To answer to this question... You don't need third party stuff.

With a Command Line (or PowerShell), how can I tell Windows to safely remove the hardware automatically without user input?

Run this command: RunDll32.exe shell32.dll,Control_RunDLL hotplug.dll to bring up the Safely Remove Hardware dialog box:

The Safely Remove Hardware Dialog

climenole
  • 3,516
  • 1
  • 22
  • 30
10

Since it's a backup device, it means it's a storage device, therefore it can be done from powershell - just replace X: with your desired drive letter:

$driveEject = New-Object -comObject Shell.Application
$driveEject.Namespace(17).ParseName("X:").InvokeVerb("Eject")
Overmind
  • 10,308
3

Couldn't find an internal command (thanks MS), not a single script out there worked either, removing letter is a poor way to do eject, interactive way is also not cool, and hate to use 3rd party tools. In my case, using something that's on my drive for 20years: the inhouse Microsoft Windows Sysinternals helps the case:

sync -e x:
  • flushes usb drive
  • ejects usb drive
  • keeps letter
  • doesn't distort USB tree

in case of locks, use Sysinternals handle or procexp to find out.

2

try with ejectjs.bat - it does not require external binaries.

Example usage:
::eject drive
call ejectjs.bat G
::eject all applicable drives
call ejectjs.bat *
npocmaka
  • 1,313
1

This option works in DOS and WSL, and ejects the device without asking any questions or popping open UI dialogues etc:-

https://gist.github.com/gitcnd/0fcc98e2dd2b18b844770666d95e8bf7

And yes, IT DOES WORK PERFECTLY (even if you're in "dos" on the "D:" drive - it still ejects fine!).

It is better to use the above original source of this answers, instead of my copy below (because copies don't get updated or have maintenance etc applied to them, like github does)... but here is all is:-

  1. Create a WSL "bash" script: /usr/local/bin/ejectusb
#!/bin/bash

cmd.exe /c start python3 C:\windows\ejectusb.py

-or-

  1. Create a DOS .bat file: C:\windows\ejectusb.bat
python3 C:\windows\ejectusb.py
  1. Create the ejection code: C:\windows\ejectusb.py
#!/usr/bin/python3

import string import ctypes from ctypes import wintypes # Using ctypes.wintypes in the code below does not seem to work

Ignore windows error popups. Fixes the whole "Can't open drive X" when user has an SD card reader.

ctypes.windll.kernel32.SetErrorMode(1) #type: ignore

WinAPI Constants that we need

Hardcoded here due to stupid WinDLL stuff that does not give us access to these values.

DRIVE_REMOVABLE = 2 # [CodeStyle: Windows Enum value]

GENERIC_READ = 2147483648 # [CodeStyle: Windows Enum value] GENERIC_WRITE = 1073741824 # [CodeStyle: Windows Enum value]

FILE_SHARE_READ = 1 # [CodeStyle: Windows Enum value] FILE_SHARE_WRITE = 2 # [CodeStyle: Windows Enum value]

IOCTL_STORAGE_EJECT_MEDIA = 2967560 # [CodeStyle: Windows Enum value]

OPEN_EXISTING = 3 # [CodeStyle: Windows Enum value]

Setup the DeviceIoControl function arguments and return type.

See ctypes documentation for details on how to call C functions from python, and why this is important.

ctypes.windll.kernel32.DeviceIoControl.argtypes = [ #type: ignore wintypes.HANDLE, # In HANDLE hDevice wintypes.DWORD, # In DWORD dwIoControlCode wintypes.LPVOID, # In_opt LPVOID lpInBuffer wintypes.DWORD, # In DWORD nInBufferSize wintypes.LPVOID, # Out_opt LPVOID lpOutBuffer wintypes.DWORD, # In DWORD nOutBufferSize ctypes.POINTER(wintypes.DWORD), # Out_opt LPDWORD lpBytesReturned wintypes.LPVOID # Inout_opt LPOVERLAPPED lpOverlapped ] ctypes.windll.kernel32.DeviceIoControl.restype = wintypes.BOOL #type: ignore

def checkRemovableDrives(): drives = {}

# The currently available disk drives, e.g.: bitmask = ...1100 &lt;-- ...DCBA
bitmask = ctypes.windll.kernel32.GetLogicalDrives()
# Since we are ignoring drives A and B, the bitmask has has to shift twice to the right
bitmask &gt;&gt;= 2
# Check possible drive letters, from C to Z
# Note: using ascii_uppercase because we do not want this to change with locale!
# Skip A and B, since those drives are typically reserved for floppy disks.
# Those drives can theoretically be reassigned but it's safer to not check them for removable drives.
# Windows will also behave weirdly even with some of its internal functions if you do this (e.g. search indexing doesn't search it).
# Users that have removable drives in A or B will just have to save to file and select the drive there.
for letter in string.ascii_uppercase[2:]:
    drive = &quot;{0}:/&quot;.format(letter)

    # Do we really want to skip A and B?
    # GetDriveTypeA explicitly wants a byte array of type ascii. It will accept a string, but this wont work
    if bitmask &amp; 1 and ctypes.windll.kernel32.GetDriveTypeA(drive.encode(&quot;ascii&quot;)) == DRIVE_REMOVABLE:
        volume_name = &quot;&quot;
        name_buffer = ctypes.create_unicode_buffer(1024)
        filesystem_buffer = ctypes.create_unicode_buffer(1024)
        error = ctypes.windll.kernel32.GetVolumeInformationW(ctypes.c_wchar_p(drive), name_buffer, ctypes.sizeof(name_buffer), None, None, None, filesystem_buffer, ctypes.sizeof(filesystem_buffer))

        if error != 0:
            volume_name = name_buffer.value

        if not volume_name:
            volume_name = &quot;Removable Drive&quot;

        # Certain readers will report themselves as a volume even when there is no card inserted, but will show an
        # &quot;No volume in drive&quot; warning when trying to call GetDiskFreeSpace. However, they will not report a valid
        # filesystem, so we can filter on that. In addition, this excludes other things with filesystems Windows
        # does not support.
        if filesystem_buffer.value == &quot;&quot;:
            continue

        # Check for the free space. Some card readers show up as a drive with 0 space free when there is no card inserted.
        free_bytes = ctypes.c_longlong(0)
        if ctypes.windll.kernel32.GetDiskFreeSpaceExA(drive.encode(&quot;ascii&quot;), ctypes.byref(free_bytes), None, None) == 0:
            continue

        if free_bytes.value &lt; 1:
            continue

        drives[drive] = &quot;{0} ({1}:)&quot;.format(volume_name, letter)
    bitmask &gt;&gt;= 1

return drives

def performEjectDevice(device): # Magic WinAPI stuff # First, open a handle to the Device #handle = ctypes.windll.kernel32.CreateFileA("\\.\{0}".format(device.getId()[:-1]).encode("ascii"), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, None, OPEN_EXISTING, 0, None ) handle = ctypes.windll.kernel32.CreateFileA("\\.\{0}".format(device[:-1]).encode("ascii"), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, None, OPEN_EXISTING, 0, None ) #handle = ctypes.windll.kernel32.CreateFileA("E:/".encode("ascii"), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, None, OPEN_EXISTING, 0, None )

if handle == -1:
    # ctypes.WinError sets up an GetLastError API call for windows as an Python OSError exception.
    # So we use this to raise the error to our caller.
    raise ctypes.WinError()

# The DeviceIoControl requires a bytes_returned pointer to be a valid pointer.
# So create a ctypes DWORD to reference. (Without this pointer the DeviceIoControl function will crash with an access violation after doing its job.
bytes_returned = wintypes.DWORD(0)

error = None

# Then, try and tell it to eject
return_code = ctypes.windll.kernel32.DeviceIoControl(handle, IOCTL_STORAGE_EJECT_MEDIA, None, 0, None, 0, ctypes.pointer(bytes_returned), None)
# DeviceIoControl with IOCTL_STORAGE_EJECT_MEDIA return 0 on error.
if return_code == 0:
    # ctypes.WinError sets up an GetLastError API call for windows as an Python OSError exception.
    # So we use this to raise the error to our caller.
    error = ctypes.WinError()
    # Do not raise an error here yet, so we can properly close the handle.

# Finally, close the handle
ctypes.windll.kernel32.CloseHandle(handle)

# If an error happened in the DeviceIoControl, raise it now.
if error:
    raise error

# Return success
return True


mydrives=checkRemovableDrives() if mydrives: #print(mydrives) for drive in mydrives: print("Ejecting drive {0} {1}".format(drive,mydrives[drive])) if performEjectDevice(drive): print("Success") exit(0) else: print("Failed") exit(0) else: print("No removable drives")

Note that I have not installed "python" on my Windows-11, it's just magically there somehow (I do use WSL (Windows Services for Linux) and Fusion360, both of which include python)

1

For those of you trying/struggling to make a functional powershell script out of the answer of Overmind, consider this:

The following sentence:

$driveEject.Namespace(17).ParseName("X:").InvokeVerb("Eject")

seems to be, somehow, asynchrounous (at least, my tests show that). This implies that if the script ends before the InvokeVerb is completed, the USB won't be removed.

To solve this, you should add a Start-Sleep -Seconds 3 instruction after the InvokeVerb("Eject") call. "3 seconds" seems to be enough time to allow the InvokeVerb("Eject") to finish. Or,

You can run the script with the -NoExit flag; however, this solution is far from perfect because such flag will leave the session open and the only way to terminate it is to type down "exit" yourself in the powershell console, killing the purpose of the script (which is to automate things!) (Important: adding exit to your script will make no difference!).

It is important to mention that, using the powershell command described previously, will unmount the flash drive (USB), but its icon will be still visible in the "This PC" window!

1

According to this 7tutorials-article, you can enable safe unplugging by setting "Quick removal". This will disable write caching per device, of which the performance impact is "negligible"? Their steps for Windows 7 are below.

(Edit) According to this howtogeek-article, you should still be careful when setting "Quick removal". This will disable write caching, and will thus prevent most problems. But some program(s) may still be writing stuff 'live', until explicitly ejected/removed. (End of edit)

Steps:

  • plug in the device in the USB drive
  • open Device Manager
  • expand Disk Drives
  • right-click your removable drive, e.g. "USB2.0 Flash Disk USB Device."
  • select Properties
  • click the Policies tab
  • enable "Quick removal" (disable "Better performance")

(Edit) Note that you need to use Device Manager to change the setting, it can not be done from the File Explorer. (At least in my Windows 10 edition.)