Чтобы программно изменить состояние запоминающего устройства USB на ошибку 47 , нам нужно вызвать функцию CM_Request_Device_Eject
для устройства с битом CM_DEVCAP_REMOVABLE
, установленным в его Capabilities
свойство. Мы можем убедиться, что он является родителем самого USB-накопителя (например, с помощью Config manager ).
Для этого из PowerShell в Windows определите и вызовите собственный API-интерфейс Windows (см., Например, Пример 4 в командлете Add-Type документация ).
Сначала определите тип [Usb.Api]
с помощью следующих основных функций:
CM_Locate_DevNode
- получить корневой узел дерева устройств.
CM_Get_Parent
- чтобы получить идентификатор устройства с установленным битом CM_DEVCAP_REMOVABLE
(важно!).
CM_Request_Device_Eject
- функция клавиш позволяет безопасно извлекать устройство.
Затем используйте его следующим образом (в сценарии 56496356a.ps1
я пропускаю часть disable / enable , так как она требует повышенного сеанса, см. Также сценарии 56496356b.ps1
и 56496356c.ps1
в приложении ниже)
# 56496356a.ps1
Function Remove-USBSafely {
[CmdletBinding()]
param (
[Parameter(Mandatory,ValueFromPipeline)]
[PSObject]$usbDrive # necessary: a `PNPDeviceID` property
)
Begin {
try { $null = [Usb.Api] }
catch { . D:\PShell\tests\Set-UsbApi.ps1 } # change path to match your circumstances
}
Process {
$DevInst = $DevInstParent = 0
# get the root node of the device tree
$DevNodeA = [Usb.Api]::CM_Locate_DevNode(
[ref]$devinst,$usbDrive.PNPDeviceID,0)
# get the device ID; Capabilities bit CM_DEVCAP_REMOVABLE (Important!)
$DevNodeP = [Usb.Api]::CM_Get_Parent(
[ref]$DevInstParent,$devinst,0)
# safely remove the device
try {
$aux = [Usb.Api]::Eject($DevInstParent)
switch ( $aux ) {
"OK" { Write-Host -ForegroundColor Yellow "Success $aux`: $usbDrive";
return 0 } # device removed
default { Write-Host -ForegroundColor Cyan "Fail $aux`: $usbDrive";
return -1 } # failed: maybe some locked files?
}
}
catch {
Write-Host -ForegroundColor Red "Blocked $usbDrive";
return -5 } # failed: maybe some locked files?
}
}
# Identify connected USB disk/s
$usbDrives = @(
Get-CimInstance Win32_DiskDrive -Filter 'InterfaceType = "USB"' -OutVariable DiskDrive -PipelineVariable Disk |
Get-CimAssociatedInstance -ResultClassName Win32_DiskPartition -OutVariable DiskPartition -PipelineVariable Part |
Get-CimAssociatedInstance -ResultClassName Win32_LogicalDisk -OutVariable DiskLogical |
Select-Object `
@{n='DriveLetter'; e={$_.DeviceID}},
@{n='PNPDeviceID'; e={$Disk.PNPDeviceID}},
# collect the following properties merely for debugging purposes
@{n='VolumeName' ; e={$_.VolumeName}},
@{n='DiskModel' ; e={$Disk.model}},
@{n='Disk' ; e={$Disk.deviceid}},
@{n='Partition' ; e={$Part.name}}
)
$usbDrives | ForEach-Object {
$auxResult = $_ | Remove-USBSafely
if ( $auxResult -ne 0 ) {
### eject the drive anyway ?
# $Eject = New-Object -comObject Shell.Application
# $Eject.NameSpace(17).ParseName($_.DriveLetter).InvokeVerb("Eject")
}
}
#Some code...
Тип [Usb.Api]
определяется в скрипте Set-UsbApi.ps1
следующим образом:
Set-StrictMode -Version latest
# D:\PShell\tests\Set-UsbApi.ps1
try { $null = [Usb.Api] }
catch { # C# signature (pinvoke)
$script:UsbApiCode = @"
using System;
using System.Text;
using System.ComponentModel;
using System.Runtime.InteropServices;
namespace Usb
{
public class Api
{
public enum PNP_VETO_TYPE
{
Ok,
TypeUnknown,
LegacyDevice,
PendingClose,
WindowsApp,
WindowsService,
OutstandingOpen,
Device,
Driver,
IllegalDeviceRequest,
InsufficientPower,
NonDisableable,
LegacyDriver,
InsufficientRights
}
[DllImport("setupapi.dll", CharSet = CharSet.Auto)]
static extern int CM_Request_Device_Eject(
IntPtr devinst,
out PNP_VETO_TYPE pVetoType,
System.Text.StringBuilder pszVetoName,
int ulNameLength,
int ulFlags); // 0 (not used)
[DllImport("setupapi.dll", SetLastError=true)]
public static extern int CM_Locate_DevNode(
ref int pdnDevInst,
string pDeviceID,
int ulFlags); // 0..4, 7
[DllImport("setupapi.dll")]
public static extern int CM_Get_Parent(
out UInt32 pdnDevInst,
UInt32 dnDevInst,
int ulFlags); // Not used, must be zero
public static string Eject(int devinst)
{
StringBuilder sb = new StringBuilder(255);
PNP_VETO_TYPE veto;
IntPtr dev = new IntPtr(devinst);
int hr = CM_Request_Device_Eject(dev, out veto, sb, sb.Capacity, 0);
if (hr != 0)
throw new Win32Exception(hr);
return veto.ToString();
}
// JosefZ 2019-06-10
[DllImport("setupapi.dll")]
public static extern int CM_Disable_DevNode(
UInt32 dnDevInst,
int ulFlags); // 0..4, 8, 0xF
[DllImport("setupapi.dll")]
public static extern int CM_Enable_DevNode(
UInt32 dnDevInst,
int ulFlags); // must be 0
/*
The CM_Setup_DevNode function restarts a device instance that is
not running because there is a problem with the device configuration.
*/
[DllImport("setupapi.dll")]
public static extern int CM_Setup_DevNode(
UInt32 dnDevInst,
int ulFlags); // 0 or 4
}
public enum CONFIGRET
{
CR_SUCCESS , // 0x00
CR_DEFAULT , // 0x01
CR_OUT_OF_MEMORY , // 0x02
CR_INVALID_POINTER , // 0x03
CR_INVALID_FLAG , // 0x04
CR_INVALID_DEVNODE , // 0x05 // CR_INVALID_DEVINST
CR_INVALID_RES_DES , // 0x06
CR_INVALID_LOG_CONF , // 0x07
CR_INVALID_ARBITRATOR , // 0x08
CR_INVALID_NODELIST , // 0x09
CR_DEVNODE_HAS_REQS , // 0x0A // CR_DEVINST_HAS_REQS
CR_INVALID_RESOURCEID , // 0x0B
CR_DLVXD_NOT_FOUND , // 0x0C // WIN 95 ONLY
CR_NO_SUCH_DEVNODE , // 0x0D // CR_NO_SUCH_DEVINST
CR_NO_MORE_LOG_CONF , // 0x0E
CR_NO_MORE_RES_DES , // 0x0F
CR_ALREADY_SUCH_DEVNODE , // 0x10 // CR_ALREADY_SUCH_DEVINST
CR_INVALID_RANGE_LIST , // 0x11
CR_INVALID_RANGE , // 0x12
CR_FAILURE , // 0x13
CR_NO_SUCH_LOGICAL_DEV , // 0x14
CR_CREATE_BLOCKED , // 0x15
CR_NOT_SYSTEM_VM , // 0x16 // WIN 95 ONLY
CR_REMOVE_VETOED , // 0x17
CR_APM_VETOED , // 0x18
CR_INVALID_LOAD_TYPE , // 0x19
CR_BUFFER_SMALL , // 0x1A
CR_NO_ARBITRATOR , // 0x1B
CR_NO_REGISTRY_HANDLE , // 0x1C
CR_REGISTRY_ERROR , // 0x1D
CR_INVALID_DEVICE_ID , // 0x1E
CR_INVALID_DATA , // 0x1F
CR_INVALID_API , // 0x20
CR_DEVLOADER_NOT_READY , // 0x21
CR_NEED_RESTART , // 0x22
CR_NO_MORE_HW_PROFILES , // 0x23
CR_DEVICE_NOT_THERE , // 0x24
CR_NO_SUCH_VALUE , // 0x25
CR_WRONG_TYPE , // 0x26
CR_INVALID_PRIORITY , // 0x27
CR_NOT_DISABLEABLE , // 0x28
CR_FREE_RESOURCES , // 0x29
CR_QUERY_VETOED , // 0x2A
CR_CANT_SHARE_IRQ , // 0x2B
CR_NO_DEPENDENT , // 0x2C
CR_SAME_RESOURCES , // 0x2D
CR_NO_SUCH_REGISTRY_KEY , // 0x2E
CR_INVALID_MACHINENAME , // 0x2F // NT ONLY
CR_REMOTE_COMM_FAILURE , // 0x30 // NT ONLY
CR_MACHINE_UNAVAILABLE , // 0x31 // NT ONLY
CR_NO_CM_SERVICES , // 0x32 // NT ONLY
CR_ACCESS_DENIED , // 0x33 // NT ONLY
CR_CALL_NOT_IMPLEMENTED , // 0x34
CR_INVALID_PROPERTY , // 0x35
CR_DEVICE_INTERFACE_ACTIVE , // 0x36
CR_NO_SUCH_DEVICE_INTERFACE , // 0x37
CR_INVALID_REFERENCE_STRING , // 0x38
CR_INVALID_CONFLICT_LIST , // 0x39
CR_INVALID_INDEX , // 0x3A
CR_INVALID_STRUCTURE_SIZE , // 0x3B
NUM_CR_RESULTS , // 0x3C
}
}
"@
Add-Type -TypeDefinition $script:UsbApiCode
Remove-Variable -Name UsbApiCode -Scope script
}
Приложение - неудачные попытки вернуть USB-устройство в рабочее состояние: сценарии с комментариями :
56496356b.ps1
с использованием команд Disable-PnpDevice
и Enable-PnpDevice
#Requires -RunAsAdministrator
Begin
{
Import-Module PnpDevice
}
Process
{
#Getting USB Mass Storage Devices with error 47 and disable/enable
Get-WmiObject Win32_PNPEntity | Where-Object{$_.ConfigManagerErrorCode -eq 47} |
ForEach-Object {
Write-Host $_.Caption -ForegroundColor Cyan
$_ | Disable-PnpDevice -Confirm:$false; ### #Requires -RunAsAdministrator
Write-Host $_.DeviceID -ForegroundColor Yellow
pause # Device manager shows `Enable Device` in the right click menu
# this means that the device was succesfully disabled
$_ | Enable-PnpDevice -Confirm:$false;
# Device manager shows `Disable Device` in the right click menu
}
}
56496356c.ps1
с использованием функций CM_Disable_DevNode
, CM_Enable_DevNode
и CM_Setup_DevNode
класса [Usb.Api]
:
#Requires -RunAsAdministrator
begin {
try { $null = [Usb.Api] }
catch { . D:\PShell\tests\Set-UsbApi.ps1 }
Import-Module PnpDevice
}
Process {
#Getting USB Mass Storage Devices with error 47 and disable/enable
Get-WmiObject Win32_PNPEntity | Where-Object{$_.ConfigManagerErrorCode -eq 47} |
ForEach-Object {
Write-Host $_.Caption -ForegroundColor Cyan
$DevInst = 0
[Usb.CONFIGRET]([Usb.Api]::CM_Locate_DevNode( [ref]$DevInst, $_.PNPDeviceID, 0))
[Usb.CONFIGRET]([Usb.Api]::CM_Disable_DevNode( $DevInst, 0)) # using any supported flag
Write-Host $_.DeviceID -ForegroundColor Yellow
# Device manager shows `Disable Device` constantly in the right click menu
# this means that the device was NOT disabled keeping its `Enable` state
# pause
[Usb.CONFIGRET]([Usb.Api]::CM_Enable_DevNode( $DevInst, 0))
# pause
[Usb.CONFIGRET]([Usb.Api]::CM_Setup_DevNode( $DevInst, 8)) # using any supported flag
}
}
Подтверждение . Огромное спасибо Александру AKA Kazun за его сложный операционный скрипт в Безопасное извлечение USB-флешки ( Безопасное извлечение USB Flash на русском языке) статья.