卸除/安装连接的USB磁盘

时间:2019-06-07 14:34:59

标签: powershell

我正在编写Powershell脚本来卸除连接的USB磁盘,之后它将运行一些PS代码,然后应该再次安装USB驱动器。

Powershell脚本适用于Windows 7以上版本和MS Server 2012以上版本。我认为安装弹出的USB磁盘的最佳方法是通过在设备管理器中禁用/启用USB大容量存储设备,因为此后可以再次安装USB磁盘。从主面板上卸下USB磁盘后,设备管理器中适当的USB大容量存储设备会将其状态更改为安全移除(错误47),因此根据该“错误”,可以轻松识别USB磁盘。

https://www.thewindowsclub.com/remount-ejected-usb-drive-windows

#Identify connected USB disk/s and dismounting 
$usbDrives = @(Get-CimInstance -Class Win32_DiskDrive -Filter 'InterfaceType = "USB"' -KeyOnly | Get-CimAssociatedInstance - ResultClassName Win32_DiskPartition -KeyOnly | Get-CimAssociatedInstance - ResultClassName Win32_LogicalDisk | ForEach-Object{$_.deviceid})

for ($i = 0; $i -le ($usbDrives.length - 1); $i += 1) {
    $Eject =  New-Object -comObject Shell.Application
    $Eject.NameSpace(17).ParseName($usbDrives[$i]).InvokeVerb("Eject")
}

#Some code...

#Getting USB Mass Storage Devices with error 47 and disable/enable 
Get-WmiObject Win32_PNPEntity | Where-Object{$_.ConfigManagerErrorCode -eq 47} | Select-Object Name, DeviceID | ForEach-Object {
    $_ | Disable-PnpDevice -Confirm:$false;
    $_ | Enable-PnpDevice -Confirm:$false;
}

如果我使用脚本弹出已连接的USB磁盘,则USB磁盘将断开连接,但设备管理器中的USB Mass Storage Device不会将其状态更改为错误47,其状态将保持连接状态,我可以不能挂接连接的USB磁盘。

请认识某人,如何编写或修复此PS代码以卸下和安装连接的USB磁盘?

谢谢

1 个答案:

答案 0 :(得分:0)

要将USB大容量存储设备的状态通过编程方式更改为错误47 ,我们需要为设备的CM_DEVCAP_REMOVABLE位设置为Capabilities的设备调用CM_Request_Device_Eject function {1}}属性。我们可以验证它是USB驱动器本身的父级(例如,使用 Config Manager )。

要从Windows中的 PowerShell 中执行此操作,请定义并调用本机Windows API(例如,参见Example 4 in the Add-Type cmdlet documentation)。

首先,使用以下基本功能定义[Usb.Api]类型:

  • CM_Locate_DevNode-获取设备树的根节点。
  • CM_Get_Parent-获取设置了CM_DEVCAP_REMOVABLE位的设备ID(重要!)。
  • CM_Request_Device_Eject-按键功能可让您安全地卸下设备。

然后,按如下方式使用它(在56496356a.ps1脚本中,我省略了 disable / enable 部分,因为它需要提高会话强度,另请参见56496356b.ps156496356c.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-PnpDeviceEnable-PnpDevice cmdlet

#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_DevNodeCM_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 KazunSafely remove USB flash (Безопасное извлечение USB Flash in Russian original)文章中的操作复杂脚本。