Удалить USB-устройство из командной строки

Я создаю сценарий резервного копирования для Windows 7, и последнее действие, которое я хочу выполнить, - это безопасно "извлечь" USB-накопитель, на который он выполняет резервное копирование. У меня сложилось впечатление, что при постоянном подключении накопителя к одному и тому же порту USB будет сохраняться один и тот же DEV_ID (поправьте меня, если я ошибаюсь). С помощью командной строки (или PowerShell), как я могу сказать Windows, чтобы безопасно удалить оборудование автоматически без участия пользователя?

Точно так же было бы полезно знать о других ОС, у которых есть способ сделать это.

9 ответов

Решение

RemoveDrive хорошо послужил мне в прошлом

Помимо RemoveDrive Уве Зибера, упомянутого в другом ответе, существует целый ряд утилит, которые могут это сделать. Вот небольшой список:

  • USB Disk Ejector - это, прежде всего, утилита на основе графического интерфейса, но она может использоваться с равной эффективностью из командной строки для извлечения диска, с которого запускается программа, или любого диска, указав букву диска / (частичное) имя диска / точку монтирования и т. Д. Бесплатный и открытый исходный код.

    USB дисковый эжектор

  • USB Safely Remove не бесплатен, но это утилита для удаления дисков на стероидах, с множеством расширенных функций, включая поддержку командной строки. Zentimo - его старший брат, с еще большим количеством функций.

    USB безопасно удалить

  • Microsoft DevCon - это версия диспетчера устройств для командной строки. Помимо исходной версии эпохи Windows 2000 /XP, доступной на странице KB, существуют новые версии (как 32-разрядные, так и 64-разрядные), доступные из различных источников MS, как упомянуто в этой статье " Где найти DevCon.exe". DevCon.exe для Windows 7 (и, возможно, также для Windows 8) находится в соответствующем наборе драйверов Windows (WDK), как указано в этой теме (который также содержит ссылки для загрузки извлеченного исполняемого файла).

    devcon status * или же devcon hwids * или же devcon findall =usb (для более компактного перечисления) должен сообщить вам аппаратный идентификатор устройства. Например:

    USB \ VID_0781 & PID_7113 \ 0001162825
    Имя: USB-накопитель
    Драйвер работает.

    Затем вы можете попробовать удалить устройство с devcon remove "USB\VID_0781&PID_7113" (Подстановочные знаки, такие как *, разрешены, но будьте осторожны, иначе вы можете полностью удалить что-то еще!)


Кто-то спросил: " Существует ли команда DOS prompt (cmd.exe из Win7) для извлечения флэш-диска? ", Которая, к сожалению, была закрыта как дубликат этого потока. Однако вопрос был об извлечении USB-накопителей во время работы в консоли восстановления Windows / командной строке восстановления системы, поэтому вряд ли какая-либо из приведенных выше утилит поможет. В такой ситуации должен работать следующий метод с использованием Diskpart:

  1. Тип diskpart и дождаться приглашения diskpart (DISKPART>)

  2. Тип list volume

  3. Внимательно запишите номер тома USB-накопителя (для справки используйте перечисленные свойства, такие как буква диска, метка, тип и размер)

  4. Тип select volume <number>, где <number> номер тома, указанный выше

    Diskpart

  5. Тип remove all dismount

  6. Тип exit выйти из Diskpart

Теперь вы сможете безопасно удалить USB-накопитель, не опасаясь потери данных.

Чтобы ответить на этот вопрос... Вам не нужны сторонние вещи.

С помощью командной строки (или PowerShell), как я могу сказать Windows, чтобы безопасно удалить оборудование автоматически без участия пользователя?

Запустите эту команду: RunDll32.exe shell32.dll,Control_RunDLL hotplug.dll чтобы открыть диалоговое окно Safely Remove Hardware:

Поскольку это устройство резервного копирования, это означает, что это устройство хранения, поэтому это можно сделать из powershell - просто замените X: на желаемую букву диска:

$driveEject = New-Object -comObject Shell.Application
$driveEject.Namespace(17).ParseName("X:").InvokeVerb("Eject")

Не удалось найти внутреннюю команду (спасибо MS), ни один скрипт там не сработал, удаление букв - плохой способ извлечь, интерактивный способ - тоже не круто, и ненавистно использовать сторонние инструменты. В моем случае, использование чего-то, что находится на моем диске в течение 20 лет: внутренняя версия Microsoft Windows Sysinternals помогает:

sync -e x:
  • очищает USB-накопитель
  • извлекает USB-накопитель
  • держит письмо
  • не искажает дерево USB

в случае блокировок используйте ручку Sysinternals или procxp, чтобы выяснить это.

Попробуйте с ejectjs.bat - он не требует внешних двоичных файлов.

Example usage:
::eject drive
call ejectjs.bat G
::eject all applicable drives
call ejectjs.bat *

Эта опция работает в DOS и WSL и извлекает устройство, не задавая никаких вопросов, не открывая диалоговые окна пользовательского интерфейса и т. д.:

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

И да, ОНО РАБОТАЕТ ОТЛИЧНО (даже если вы находитесь в «dos» на диске «D:» — он все равно извлекается нормально!).

Лучше использовать приведенный выше первоначальный источник этих ответов вместо моей копии ниже (потому что копии не обновляются и не подвергаются обслуживанию и т. д., как это делает github)... но вот все:-

  1. Создайте сценарий WSL «bash»: /usr/local/bin/ejectusb.
      #!/bin/bash

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

-или-

  1. Создайте файл .bat для DOS: C:\windows\ejectusb.bat.
      python3 C:\windows\ejectusb.py
  1. Создайте код извлечения: 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 <-- ...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 >>= 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 = "{0}:/".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 & 1 and ctypes.windll.kernel32.GetDriveTypeA(drive.encode("ascii")) == DRIVE_REMOVABLE:
            volume_name = ""
            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 = "Removable Drive"

            # Certain readers will report themselves as a volume even when there is no card inserted, but will show an
            # "No volume in drive" 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 == "":
                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("ascii"), ctypes.byref(free_bytes), None, None) == 0:
                continue

            if free_bytes.value < 1:
                continue

            drives[drive] = "{0} ({1}:)".format(volume_name, letter)
        bitmask >>= 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")

Обратите внимание, что я не установил «python» на свою Windows-11, он каким-то волшебным образом там есть (я использую WSL (Windows Services для Linux) и Fusion360, оба из которых включают Python)

Согласно этой статье-учебникам, вы можете включить безопасное отключение, установив "Быстрое удаление". Это отключит кеширование записи для каждого устройства, влияние на производительность которого "незначительно"? Их шаги для Windows 7 ниже.

(Правка) Согласно этой статье, вы должны быть осторожны при установке "Быстрое удаление". Это отключит кэширование записи и, таким образом, предотвратит большинство проблем. Но некоторые программы могут по-прежнему писать материал "вживую", пока он не будет извлечен / удален. (Конец редактирования)

шаги:

  • подключите устройство к USB-накопителю
  • открыть диспетчер устройств
  • расширить дисководы
  • щелкните правой кнопкой мыши на съемном диске, например, "USB2.0 Flash Disk USB Device".
  • выберите Свойства
  • щелкните вкладку Policies
  • включить "Быстрое удаление" (отключить "Лучшая производительность")

(Изменить) Обратите внимание, что вам нужно использовать диспетчер устройств, чтобы изменить настройку, это не может быть сделано из проводника. (По крайней мере, в моем выпуске Windows 10.)

Для тех из вас, кто пытается/пытается создать функциональный сценарий PowerShell на основе ответа Overmind, подумайте об этом:

Следующее предложение:

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

кажется каким-то асинхронным (по крайней мере, мои тесты это показывают). Это означает, что если сценарий завершится до завершения InvokeVerb, USB не будет удален.

Чтобы решить эту проблему, вам следует добавитьStart-Sleep -Seconds 3инструкция после звонка. «3 секунды» кажутся достаточным временем, чтобы позволитьInvokeVerb("Eject")заканчивать. Или,

Вы можете запустить скрипт с помощью-NoExitфлаг; однако это решение далеко от совершенства, поскольку такой флаг оставит сеанс открытым, и единственный способ завершить его — это ввести «выход» самостоятельно в консоли PowerShell, убивая цель сценария (а именно автоматизировать работу!) ( Важно : добавлениеexitдля вашего сценария не будет иметь никакого значения!).

Важно отметить, что при использовании описанной ранее команды powershell флешка (USB) будет отключена, но ее значок все равно будет виден в окне «Этот компьютер»!

Другие вопросы по тегам