Удалить USB-устройство из командной строки
Я создаю сценарий резервного копирования для Windows 7, и последнее действие, которое я хочу выполнить, - это безопасно "извлечь" USB-накопитель, на который он выполняет резервное копирование. У меня сложилось впечатление, что при постоянном подключении накопителя к одному и тому же порту USB будет сохраняться один и тот же DEV_ID (поправьте меня, если я ошибаюсь). С помощью командной строки (или PowerShell), как я могу сказать Windows, чтобы безопасно удалить оборудование автоматически без участия пользователя?
Точно так же было бы полезно знать о других ОС, у которых есть способ сделать это.
9 ответов
Помимо RemoveDrive Уве Зибера, упомянутого в другом ответе, существует целый ряд утилит, которые могут это сделать. Вот небольшой список:
USB Disk Ejector - это, прежде всего, утилита на основе графического интерфейса, но она может использоваться с равной эффективностью из командной строки для извлечения диска, с которого запускается программа, или любого диска, указав букву диска / (частичное) имя диска / точку монтирования и т. Д. Бесплатный и открытый исходный код.
USB Safely Remove не бесплатен, но это утилита для удаления дисков на стероидах, с множеством расширенных функций, включая поддержку командной строки. Zentimo - его старший брат, с еще большим количеством функций.
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:
Тип
diskpart
и дождаться приглашения diskpart (DISKPART>
)Тип
list volume
Внимательно запишите номер тома USB-накопителя (для справки используйте перечисленные свойства, такие как буква диска, метка, тип и размер)
Тип
select volume <number>
, где<number>
номер тома, указанный вышеТип
remove all dismount
Тип
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)... но вот все:-
- Создайте сценарий WSL «bash»: /usr/local/bin/ejectusb.
#!/bin/bash
cmd.exe /c start python3 C:\\windows\\ejectusb.py
-или-
- Создайте файл .bat для DOS: C:\windows\ejectusb.bat.
python3 C:\windows\ejectusb.py
- Создайте код извлечения: 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) будет отключена, но ее значок все равно будет виден в окне «Этот компьютер»!