Safely Eject a USB Drive

64 posts in this topic

Posted (edited)

UPDATE 3:

Included a _RestartDrive function (needs testing on other platforms). Call _RestartDrive and pass it the query array from _QueryDrive (called with the eject parameter set to FALSE, which is the default).

UPDATE 2:

General fixes and x64 compatibility.

UPDATE 1:

I updated this script a while ago and never posted it, so here it is. It can be used two ways now. First and foremost it returns a lot of low level info about your attached device. Second, it can be used to eject that device the same way as the old script.

I've seen several solutions on this board and on others, in AutoIt, VB, and C++. Even the one from MSDN. None of them worked correctly. Either nothing happened, or the device was unmounted but the drive letter was still present in explorer and the drive was not "Safely Removed". Well I came across an article on CodeProject, and after finally getting it all translated, it works perfectly. The script is missing some general comments...I'll get around to it eventually, but I'm tired :P

Please test this on WinXP. It works on Vista and should work on XP, but I wanna be sure.

 

#include-once
#include <WinAPI.au3>

; Constants
;~ Global Const $FILE_SHARE_READ = 0x1
;~ Global Const $FILE_SHARE_WRITE = 0x2
;~ Global Const $OPEN_EXISTING = 3
;~ Global Const $INVALID_HANDLE_VALUE = Ptr(0xFFFFFFFF) ; invalid ptr value
Global Const $IOCTL_STORAGE_GET_DEVICE_NUMBER = 0x2D1080
Global Const $DRIVE_REMOVABLE = 2
Global Const $DRIVE_FIXED = 3
Global Const $DRIVE_CDROM = 5
Global Const $DIGCF_PRESENT = 0x2
Global Const $DIGCF_DEVICEINTERFACE = 0x10
Global Const $CR_SUCCESS = 0x0
Global Const $DN_REMOVABLE = 0x4000
Global Const $CM_REMOVE_UI_OK = 0x0
Global Const $CM_REMOVE_UI_NOT_OK = 0x1
Global Const $CM_REMOVE_NO_RESTART = 0x2
Global Const $CM_SETUP_DEVNODE_READY = 0x0
Global Const $CM_SETUP_DEVNODE_RESET = 0x4
Global Const $CR_ACCESS_DENIED = 0x33
Global Const $PNP_VetoTypeUnknown = 0 ; Name is unspecified
Global Const $PNP_VetoLegacyDevice = 1 ; Name is an Instance Path
Global Const $PNP_VetoPendingClose = 2 ; Name is an Instance Path
Global Const $PNP_VetoWindowsApp = 3 ; Name is a Module
Global Const $PNP_VetoWindowsService = 4 ; Name is a Service
Global Const $PNP_VetoOutstandingOpen = 5 ; Name is an Instance Path
Global Const $PNP_VetoDevice = 6 ; Name is an Instance Path
Global Const $PNP_VetoDriver = 7 ; Name is a Driver Service Name
Global Const $PNP_VetoIllegalDeviceRequest = 8 ; Name is an Instance Path
Global Const $PNP_VetoInsufficientPower = 9 ; Name is unspecified
Global Const $PNP_VetoNonDisableable = 10 ; Name is an Instance Path
Global Const $PNP_VetoLegacyDriver = 11 ; Name is a Service
Global Const $PNP_VetoInsufficientRights = 12 ; Name is unspecified

; Structures
Global Const $STORAGE_DEVICE_NUMBER = "ulong DeviceType;ulong DeviceNumber;ulong PartitionNumber"
Global Const $SP_DEV_BUF = "byte[2052]"
Global Const $SP_DEVICE_INTERFACE_DETAIL_DATA = "dword cbSize;wchar DevicePath[1024]" ; created at SP_DEV_BUF ptr
Global Const $SP_DEVICE_INTERFACE_DATA = "dword cbSize;byte InterfaceClassGuid[16];dword Flags;ulong_ptr Reserved" ; GUID struct = 16 bytes
Global Const $SP_DEVINFO_DATA = "dword cbSize;byte ClassGuid[16];dword DevInst;ulong_ptr Reserved"

; GUIDs
; GUID_DEVINTERFACE_DISK =      0x53f56307L, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b
; GUID_DEVINTERFACE_CDROM =     0x53f56308L, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b
; GUID_DEVINTERFACE_FLOPPY =    0x53f56311L, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b
Global Const $guidDisk = DllStructCreate($tagGUID)
DllStructSetData($guidDisk, "Data1", 0x53f56307)
DllStructSetData($guidDisk, "Data2", 0xb6bf)
DllStructSetData($guidDisk, "Data3", 0x11d0)
DllStructSetData($guidDisk, "Data4", Binary("0x94f200a0c91efb8b"))
Global Const $guidCDROM = DllStructCreate($tagGUID)
DllStructSetData($guidCDROM, "Data1", 0x53f56308)
DllStructSetData($guidCDROM, "Data2", 0xb6bf)
DllStructSetData($guidCDROM, "Data3", 0x11d0)
DllStructSetData($guidCDROM, "Data4", Binary("0x94f200a0c91efb8b"))
Global Const $guidFloppy = DllStructCreate($tagGUID)
DllStructSetData($guidFloppy, "Data1", 0x53f56311)
DllStructSetData($guidFloppy, "Data2", 0xb6bf)
DllStructSetData($guidFloppy, "Data3", 0x11d0)
DllStructSetData($guidFloppy, "Data4", Binary("0x94f200a0c91efb8b"))

Func _IsUSBHDD(Const $query)
    ; if drive type is fixed (3), and device path contains 'usbstor', and parent device ID contains 'USB', and IsRemovable
    Return (($query[1] = 3) And StringInStr($query[3], "usbstor") And StringInStr($query[7], "usb") And $query[8])
EndFunc   ;==>_IsUSBHDD

Func _IsUSBFlash(Const $query)
    ; if drive type is removable (2), and device path contains 'usbstor', and parent device ID contains 'USB', and IsRemovable
    Return (($query[1] = 2) And StringInStr($query[3], "usbstor") And StringInStr($query[7], "usb") And $query[8])
EndFunc   ;==>_IsUSBHDD

Func _QueryDrive($drive, $fEject = False)
    ; drive letter only!
    $drive = StringLeft($drive, 1)

    Local $queryarray[9]
    ; [0] = device number (int)
    ; [1] = drive type (int)
    ; [2] = dos device name (string)
    ; [3] = device path (string)
    ; [4] = device instance (int)
    ; [5] = device ID (string)
    ; [6] = device parent instance (int)
    ; [7] = parent device ID (string)
    ; [8] = IsRemovable (boolean)

    ; "X:\"    -> for GetDriveType
    Local $szRootPath = $drive & ":\"
    ; "X:"     -> for QueryDosDevice
    Local $szDevicePath = $drive & ":"
    ; "\\.\X:" -> to open the volume
    Local $szVolumeAccessPath = "\\.\" & $drive & ":"

    Local $DeviceNumber = -1

    Local $hVolume = DllCall("kernel32.dll", "ptr", "CreateFileW", "wstr", $szVolumeAccessPath, "dword", 0, _
            "dword", BitOR($FILE_SHARE_READ, $FILE_SHARE_WRITE), "ptr", 0, "dword", $OPEN_EXISTING, _
            "dword", 0, "ptr", 0)
    $hVolume = $hVolume[0]
    If $hVolume <> $INVALID_HANDLE_VALUE Then
;~      ConsoleWrite("hVolume: " & $hVolume & @CRLF)

        Local $sdn = DllStructCreate($STORAGE_DEVICE_NUMBER)
        Local $res = DllCall("kernel32.dll", "int", "DeviceIoControl", "ptr", $hVolume, "dword", $IOCTL_STORAGE_GET_DEVICE_NUMBER, _
                "ptr", 0, "dword", 0, "ptr", DllStructGetPtr($sdn), "dword", DllStructGetSize($sdn), _
                "dword*", 0, "ptr", 0)
        If $res[0] Then
            $DeviceNumber = DllStructGetData($sdn, "DeviceNumber")
;~          ConsoleWrite("DeviceNumber: " & $DeviceNumber & @CRLF)
            $queryarray[0] = $DeviceNumber

            $res = DllCall("kernel32.dll", "int", "CloseHandle", "ptr", $hVolume)
            If $res[0] And $DeviceNumber <> -1 Then
                Local $DriveType = DllCall("kernel32.dll", "uint", "GetDriveTypeW", "wstr", $szRootPath)
                $DriveType = $DriveType[0]
;~              ConsoleWrite("Drive type: " & $DriveType & @CRLF)
                $queryarray[1] = $DriveType

                ; get the dos device name (like \device\floppy0)
                ; to decide if it's a floppy or not
                $res = DllCall("kernel32.dll", "dword", "QueryDosDeviceW", "wstr", $szDevicePath, "wstr", "", "dword", 260)
                If $res[0] Then
                    Local $szDosDeviceName = $res[2]
;~                  ConsoleWrite("DosDeviceName: " & $szDosDeviceName & @CRLF)
                    $queryarray[2] = $szDosDeviceName

                    Local $DevInst = _GetDrivesDevInstByDeviceNumber($DeviceNumber, $DriveType, $szDosDeviceName, $queryarray)
;~                  ConsoleWrite("DevInst: " & $DevInst & @CRLF)
                    If $DevInst <> 0 Then
                        $queryarray[4] = $DevInst
                        $res = DllCall("setupapi.dll", "dword", "CM_Get_Device_IDW", "ptr", $DevInst, "wstr", "", "ulong", 1024, "ulong", 0)
;~                      ConsoleWrite("DeviceID: " & $res[2] & @CRLF)
                        $queryarray[5] = $res[2]
                        Local $EjectSuccess = _DevInstQuery($DevInst, $queryarray, $fEject)
                    EndIf
                EndIf
            EndIf
        EndIf
    EndIf

    If $fEject Then
        Return $EjectSuccess
    Else
        Return $queryarray
    EndIf
EndFunc   ;==>_QueryDrive

Func _RestartDrive($queryarray)
    Local $res = DllCall("setupapi.dll", "dword", "CM_Setup_DevNode", "dword", $queryarray[6], "ulong", 0)
    Return SetError(Number(Not ($res[0] = 0)), 0, ($res[0] = 0))
EndFunc   ;==>_RestartDrive

Func _GetDrivesDevInstByDeviceNumber($DeviceNumber, $DriveType, $szDosDeviceName, ByRef $queryarray)
    Local $IsFloppy = (StringInStr($szDosDeviceName, "\floppy") <> 0)
;~  ConsoleWrite("Is floppy: " & $IsFloppy & @CRLF)

    Local $GUID
    Switch $DriveType
        Case $DRIVE_REMOVABLE
            If $IsFloppy Then
                $GUID = DllStructGetPtr($guidFloppy)
            Else
                $GUID = DllStructGetPtr($guidDisk)
            EndIf
        Case $DRIVE_FIXED
            $GUID = DllStructGetPtr($guidDisk)
        Case $DRIVE_CDROM
            $GUID = DllStructGetPtr($guidCDROM)
        Case Default
            Return 0
    EndSwitch

    ; Get device interface info set handle
    ; for all devices attached to system
    Local $hDevInfo = DllCall("setupapi.dll", "ptr", "SetupDiGetClassDevsW", "ptr", $GUID, "ptr", 0, "hwnd", 0, _
            "dword", BitOR($DIGCF_PRESENT, $DIGCF_DEVICEINTERFACE))
    $hDevInfo = $hDevInfo[0]
    If $hDevInfo <> $INVALID_HANDLE_VALUE Then
;~      ConsoleWrite("hDevInfo: " & $hDevInfo & @CRLF)

        ; Retrieve a context structure for a device interface
        ; of a device information set.
        Local $dwIndex = 0
        Local $bRet

        Local $buf = DllStructCreate($SP_DEV_BUF)
        Local $pspdidd = DllStructCreate($SP_DEVICE_INTERFACE_DETAIL_DATA, DllStructGetPtr($buf))
        Local $cb_spdidd = 6 ; size of fixed part of structure
        If @AutoItX64 Then $cb_spdidd = 8 ; fix for x64
        Local $spdid = DllStructCreate($SP_DEVICE_INTERFACE_DATA)
        Local $spdd = DllStructCreate($SP_DEVINFO_DATA)

        DllStructSetData($spdid, "cbSize", DllStructGetSize($spdid))

        While True
            $bRet = DllCall("setupapi.dll", "int", "SetupDiEnumDeviceInterfaces", "ptr", $hDevInfo, "ptr", 0, _
                    "ptr", $GUID, "dword", $dwIndex, "ptr", DllStructGetPtr($spdid))
            If Not $bRet[0] Then ExitLoop

            Local $res = DllCall("setupapi.dll", "int", "SetupDiGetDeviceInterfaceDetailW", "ptr", $hDevInfo, "ptr", DllStructGetPtr($spdid), _
                    "ptr", 0, "dword", 0, "dword*", 0, "ptr", 0)
            Local $dwSize = $res[5]
;~          ConsoleWrite("dwSize: " & $dwSize & @CRLF)

            If $dwSize <> 0 And $dwSize <= DllStructGetSize($buf) Then
                DllStructSetData($pspdidd, "cbSize", $cb_spdidd)
                _ZeroMemory(DllStructGetPtr($spdd), DllStructGetSize($spdd))
                DllStructSetData($spdd, "cbSize", DllStructGetSize($spdd))

                $res = DllCall("setupapi.dll", "int", "SetupDiGetDeviceInterfaceDetailW", "ptr", $hDevInfo, "ptr", DllStructGetPtr($spdid), _
                        "ptr", DllStructGetPtr($pspdidd), "dword", $dwSize, "dword*", 0, "ptr", DllStructGetPtr($spdd))

                If $res[0] Then
;~                  ConsoleWrite("DevicePath: " & DllStructGetData($pspdidd, "DevicePath") & @CRLF)
                    $queryarray[3] = DllStructGetData($pspdidd, "DevicePath")

                    Local $hDrive = DllCall("kernel32.dll", "ptr", "CreateFileW", "wstr", DllStructGetData($pspdidd, "DevicePath"), "dword", 0, _
                            "dword", BitOR($FILE_SHARE_READ, $FILE_SHARE_WRITE), "ptr", 0, "dword", $OPEN_EXISTING, _
                            "dword", 0, "ptr", 0)
                    $hDrive = $hDrive[0]
;~                  ConsoleWrite("hDrive: " & $hDrive & @CRLF)

                    If $hDrive <> $INVALID_HANDLE_VALUE Then
                        Local $sdn2 = DllStructCreate($STORAGE_DEVICE_NUMBER)
                        $res = DllCall("kernel32.dll", "int", "DeviceIoControl", "ptr", $hDrive, "dword", $IOCTL_STORAGE_GET_DEVICE_NUMBER, _
                                "ptr", 0, "dword", 0, "ptr", DllStructGetPtr($sdn2), "dword", DllStructGetSize($sdn2), _
                                "dword*", 0, "ptr", 0)

                        If $res[0] Then
                            If $DeviceNumber = DllStructGetData($sdn2, "DeviceNumber") Then
                                $res = DllCall("kernel32.dll", "int", "CloseHandle", "ptr", $hDrive)
;~                              If Not $res[0] Then ConsoleWrite("Error closing volume: " & $hDrive & @CRLF)
                                $res = DllCall("setupapi.dll", "int", "SetupDiDestroyDeviceInfoList", "ptr", $hDevInfo)
;~                              If Not $res[0] Then ConsoleWrite("Destroy error." & @CRLF)
                                Return DllStructGetData($spdd, "DevInst")
                            EndIf
                        EndIf

                        $res = DllCall("kernel32.dll", "int", "CloseHandle", "ptr", $hDrive)
;~                      If Not $res[0] Then ConsoleWrite("Error closing volume: " & $hDrive & @CRLF)
                    EndIf
                EndIf
            EndIf
            $dwIndex += 1
        WEnd

        $res = DllCall("setupapi.dll", "int", "SetupDiDestroyDeviceInfoList", "ptr", $hDevInfo)
;~      If Not $res[0] Then ConsoleWrite("Destroy error." & @CRLF)
    EndIf

    Return 0
EndFunc   ;==>_GetDrivesDevInstByDeviceNumber

Func _DevInstQuery($DevInst, ByRef $queryarray, $fEject)
    ; get drives's parent, e.g. the USB bridge,
    ; the SATA port, an IDE channel with two drives, etc.
    Local $res = DllCall("setupapi.dll", "dword", "CM_Get_Parent", "dword*", 0, "dword", $DevInst, "ulong", 0)
    If $res[0] = $CR_SUCCESS Then
        Local $DevInstParent = $res[1]
;~      ConsoleWrite("DevInstParent: " & $DevInstParent & @CRLF)
        $queryarray[6] = $DevInstParent

        $res = DllCall("setupapi.dll", "dword", "CM_Get_Device_IDW", "ptr", $DevInstParent, "wstr", "", "ulong", 1024, "ulong", 0)
;~      ConsoleWrite("Parent DeviceID: " & $res[2] & @CRLF)
        $queryarray[7] = $res[2]

        $res = DllCall("setupapi.dll", "dword", "CM_Get_DevNode_Status", "ulong*", 0, "ulong*", 0, "ptr", $DevInstParent, "ulong", 0)
        Local $IsRemovable = (BitAND($res[1], $DN_REMOVABLE) <> 0)
;~      ConsoleWrite("IsRemovable: " & $IsRemovable & @CRLF)
        $queryarray[8] = $IsRemovable
    EndIf

    If $fEject Then
        Local $bSuccess = False
        For $tries = 1 To 3
            ; sometimes we need some tries...
;~          ConsoleWrite("Try: " & $tries & @CRLF)
            $res = DllCall("setupapi.dll", "dword", "CM_Query_And_Remove_SubTreeW", "dword", $DevInstParent, _
                    "dword*", 0, "wstr", "", "ulong", 260, "ulong", $CM_REMOVE_UI_OK)
            If $res[0] = $CR_ACCESS_DENIED Then
;~              ConsoleWrite("Trying CM_Request_Device_Eject..." & @CRLF)
                $res = DllCall("setupapi.dll", "dword", "CM_Request_Device_EjectW", "dword", $DevInstParent, _
                        "dword*", 0, "wstr", "", "ulong", 260, "ulong", 0)
            EndIf
;~          ConsoleWrite("VetoType: " & $res[2] & @CRLF) ; success when type = 0
;~          ConsoleWrite("VetoName: " & $res[3] & @CRLF) ; name will be blank on success

            $bSuccess = (($res[0] = $CR_SUCCESS) And ($res[2] = $PNP_VetoTypeUnknown))
            If $bSuccess Then ExitLoop

            Sleep(500) ; required to give the next tries a chance
        Next
        Return $bSuccess
    Else
        Return 0
    EndIf
EndFunc   ;==>_DevInstQuery

Func _ZeroMemory($ptr, $size)
    DllCall("kernel32.dll", "none", "RtlZeroMemory", "ptr", $ptr, "ulong_ptr", $size)
EndFunc   ;==>_ZeroMemory

Example:

#NoTrayIcon
#AutoIt3Wrapper_Au3Check_Parameters=-q -d -w 1 -w 2 -w 3 -w 4 -w 5 -w 6
;;;EXAMPLE
Global $drive = InputBox("Eject Drive", "Enter drive (letter only):", "", " M1", 200, 130)
If @error Then Exit
$drive = StringUpper(StringLeft($drive, 1))
If Not FileExists($drive & ":") Then
MsgBox(0 + 16, "Error", "Drive not found.")
Exit
EndIf

Global $driveInfo[9] = ["Device Number", "Drive Type", "DOS Device Name", "Device Path", "Device ID", "Device Instance", "Device Instance Parent", _
"Parent Device ID", "IsRemovable"]
Global $driveArray = _QueryDrive($drive)
For $i = 0 To UBound($driveArray) - 1
ConsoleWrite("-" & $driveInfo[$i] & ": " & $driveArray[$i] & @CRLF)
Next
ConsoleWrite("-Is USBHDD: " & _IsUSBHDD($driveArray) & @CRLF)

If (6 = MsgBox(4 + 32, "Eject?", "Eject this drive?")) Then ConsoleWrite("Ejecting drive <" & $drive & ":> - " & _QueryDrive($drive, True) & @CRLF)
If (6 = MsgBox(4 + 32, "Restart?", "Restart this drive?")) Then ConsoleWrite("Restarting drive <" & $drive & ":> - " & _RestartDrive($driveArray) & @CRLF)

I'll throw this in here too. It's a small example how to monitor addition and removal of volumes such as USB flash drives / HDDs.

$wmiSink = ObjCreate("WbemScripting.SWbemSink")
ObjEvent($wmiSink , "SINK_")

$Obj_WMIService = ObjGet('winmgmts:localhostrootcimv2')

If Not @error Then
$obj_WMIService.ExecNotificationQueryAsync($wmiSink, "SELECT * FROM __InstanceOperationEvent WITHIN 1 WHERE TargetInstance ISA 'Win32_LogicalDisk'")
ConsoleWrite("Ready and waiting for changes" & @CRLF)
EndIf

While 1
Sleep(1000)
WEnd

Func SINK_OnObjectReady($objLatestEvent, $objAsyncContext)
Switch $objLatestEvent.Path_.Class
Case "__InstanceCreationEvent"
ConsoleWrite("->==========================================" & @CRLF)
ConsoleWrite("> Creation Event" & @CRLF)

Case "__InstanceDeletionEvent"
ConsoleWrite("->==========================================" & @CRLF)
ConsoleWrite("> Deletion Event" & @CRLF)

EndSwitch
; Access:
; 0 - Unknown
; 1 - Readable
; 2 - Writable
; 3 - Read / Write Supported
; 4 - Write Once
Switch $objLatestEvent.Path_.Class
Case "__InstanceCreationEvent", "__InstanceDeletionEvent"
ConsoleWrite("Access: " & $objLatestEvent.TargetInstance.Access & @CRLF)
ConsoleWrite("Caption: " & $objLatestEvent.TargetInstance.Caption & @CRLF)
ConsoleWrite("Description: " & $objLatestEvent.TargetInstance.Description & @CRLF)
ConsoleWrite("DeviceID: " & $objLatestEvent.TargetInstance.DeviceID & @CRLF)
ConsoleWrite("DriveType: " & $objLatestEvent.TargetInstance.DriveType & @CRLF)
ConsoleWrite("FileSystem: " & $objLatestEvent.TargetInstance.FileSystem & @CRLF)
ConsoleWrite("FreeSpace: " & Int($objLatestEvent.TargetInstance.FreeSpace / 1000000) & " MB" & @CRLF)
ConsoleWrite("Name: " & $objLatestEvent.TargetInstance.Name & @CRLF)
ConsoleWrite("Size: " & Int($objLatestEvent.TargetInstance.Size / 1000000) & " MB" & @CRLF)
ConsoleWrite("VolumeSerialNumber: " & $objLatestEvent.TargetInstance.VolumeSerialNumber & @CRLF)
ConsoleWrite("->==========================================" & @CRLF)
EndSwitch
EndFunc;==>SINK_OnObjectReady
Edited by wraithdu

Share this post


Link to post
Share on other sites



Posted (edited)

Hi,

doesn't work :P

>"C:\Programme\AutoIt3\SciTE\AutoIt3Wrapper\AutoIt3Wrapper.exe" /run /prod /ErrorStdOut /in "C:\Downloads\AutoIt-Skripte\Entwicklung\ForumTests\USBEject.au3" /autoit3dir "C:\Programme\AutoIt3" /UserParams	
+>07:26:04 Starting AutoIt3Wrapper v.1.10.1.13	Environment(Language:0407  Keyboard:00000407  OS:WIN_XP/Service Pack 2  CPU:X86  ANSI)
>Running AU3Check (1.54.13.0)  from:C:\Programme\AutoIt3
+>07:26:05 AU3Check ended.rc:0
>Running:(3.2.12.1):C:\Programme\AutoIt3\autoit3.exe "C:\Downloads\AutoIt-Skripte\Entwicklung\ForumTests\USBEject.au3"	
hVolume: 0x00000700
DeviceNumber: 1
Drive type: 2
DosDeviceName: \Device\Harddisk1\DP(1)0-0+5
Is floppy: False
hDevInfo: 0x008A6828
dwSize: 123
DevicePath: \\?\ide#diskwdc_wd1600bevs-08rst2___________________08.01g08#5&1635548a&0&0.0.0#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}
hDrive: 0x000006E4
dwSize: 104
DevicePath: \\?\usbstor#disk&ven_&prod_cnmemory&rev_1100#0004012700243&0#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}
hDrive: 0x000006E4
DevInst: 2204
DevInstParent: 2324
Try: 1
Try: 2
Try: 3
Ejecting drive <E:>  -  False
+>07:26:10 AutoIT3.exe ended.rc:0
+>07:26:11 AutoIt3Wrapper Finished
>Exit code: 0	Time: 6.993

Mega

Edited by Xenobiologist

Share this post


Link to post
Share on other sites

Posted

Error

F:\My documents\My scripts\script tester.au3(156,13) : ERROR: syntax error

$bRet = False

~~~~~~~~~~~~^

F:\My documents\My scripts\script tester.au3(122,34) : ERROR: _DevInstEject(): undefined function.

Return _DevInstEject($DevInst)

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^

F:\My documents\My scripts\script tester.au3 - 2 error(s), 0 warning(s)

I'm using XP

Share this post


Link to post
Share on other sites

Posted (edited)

@Xenobiologist

That means your USB stick had an open handle by the system and could not be safely ejected. What happened when you used the normal tray icon to try and eject the stick?

Another thought, it looks like you might be trying this with a USB HDD, not a flash drive...I haven't tested this yet. Let me get back to you on that.

@farhan879

You have a copy and paste error somewhere if it's saying you're missing the function. Try again and be sure you copied everything.

Edited by wraithdu

Share this post


Link to post
Share on other sites

Posted (edited)

Yep, works on my USB HDD as well.

hVolume: 0x0000015C
DeviceNumber: 1
Drive type: 3
DosDeviceName: \Device\HarddiskVolume2
Is floppy: False
hDevInfo: 0x006BE8B8
dwSize: 123
DevicePath: \\?\ide#diskfujitsu_mhv2080bh_______________________892c____#5&35674a2c&0&0.0.0#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}
hDrive: 0x00000160
dwSize: 125
DevicePath: \\?\usbstor#disk&ven_wd&prod_1200bev_external&rev_1.04#575845583037383238373539&0#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}
hDrive: 0x00000160
DevInst: 2204
DevInstParent: 2364
Try: 1
Ejecting drive <E:>  -  True

Here's a quick snippet from the CodeProject article about your situation -

What Makes the Removal Fail

The prepartion for safe removal fails as long as there is one open handle to the disk or to the storage volume. And of course you cannot run this EXE from the drive to remove it. To do that you would need a temporary copy on another drive. ProcessExplorer is great for discovering which process holds an open handle to a drive. Press Ctrl+F and enter the drive letter, like U:. I've often seen that it cannot resolve drive letters, so you have to search for the DOS device name of the drive. It should be something like \Device\Harddisk4\DP(1)0-0+11. A significant part, such as 'disk4,' is usually good enough. On occasion, however, even the driver-driven ProcessExplorer isn't able to find the nasty handle.
Edited by wraithdu

Share this post


Link to post
Share on other sites

Posted

Added a quick example for monitoring addition and removal of volumes such as USB flash drives / HDDs.

Share this post


Link to post
Share on other sites

Posted (edited)

Made an update to try to work with disks not marked as REMOVABLE (DN_REMOVABLE flag), using a different function. I believe this other func requires admin privileges.

Edited by wraithdu

Share this post


Link to post
Share on other sites

Posted

Doesn't work for me on WinXP SP2 Czech with my USB card reader (looks like flash disk):

hVolume: 0x00000700

DeviceNumber: 1

Drive type: 2

DosDeviceName: \Device\Harddisk1\DP(1)0-0+27

Is floppy: False

hDevInfo: 0x00896938

dwSize: 145

DevicePath: \\?\ide#diskwdc_wd800bb-22jha0______________________05.01c05#4457572d4143394d393136303939203320202020#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}

hDrive: 0x000006F8

dwSize: 111

DevicePath: \\?\usbstor#disk&ven_multi&prod_flash_reader&rev_1.00#058f0o1111b&0#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}

hDrive: 0x000006F8

DevInst: 2248

DevInstParent: 2380

IsRemovable: True

Try: 1

Try: 2

Try: 3

Ejecting drive <D:> - False

No blocked files were opened.

Also your monitor didn't catch event when I manually did "safety remove" of my drive :-(

Share this post


Link to post
Share on other sites

Posted

I'll have to test more on SP2, but it looks like you have an open handle on your drive, same as Xenobiologixt. That's what I think a device name like this means -

\Device\Harddisk1\DP(1)0-0+27

Share this post


Link to post
Share on other sites

Posted

I'll have to test more on SP2, but it looks like you have an open handle on your drive, same as Xenobiologixt. That's what I think a device name like this means -

\Device\Harddisk1\DP(1)0-0+27

No. I'm sure I have no open handles. I know what I'm talking about.

I have just mounted USB device and after it appeard in tray area I have run your script.

When it failed I remved it by hand with no errors immediatelly.

Share this post


Link to post
Share on other sites

Posted

Hmmm...I'll try a few sticks I have on XP and see what happens.

Share this post


Link to post
Share on other sites

Posted

ahhh great script.

Seems to be the source code of deveject.exe, wanted to do this.

BTW it has several limitations since you eject ROOT controllers by their IDs so this method maybe doesn't work for HDD or Flash Card ( i don't think they use the same driver or root driver ).

Share this post


Link to post
Share on other sites

Posted (edited)

Ok, so I got the same problems in XP. In the _DevInstEject() function, switch the versions to use the ones that DO NOT issue the popup message from the OS. I'm not sure why, but those versions worked on my XP test VM when the others failed.

@arcker

I'm not sure. I did test with a USB HDD and it worked correctly.

Edited by wraithdu

Share this post


Link to post
Share on other sites

Posted

Updated function in first post. I removed the version that would show the normal windows popup message, as it wasn't working on XP. This current one is tested on XP and Vista.

Share this post


Link to post
Share on other sites

Posted

Updated function in first post. I removed the version that would show the normal windows popup message, as it wasn't working on XP. This current one is tested on XP and Vista.

Yes. This works fine for me.

Share this post


Link to post
Share on other sites

Posted

Great!

I'm at a loss why the Sink isn't working on XP though. I coded in an error handler, and there aren't any COM errors happening. MSDN says that all the methods and classes being used are supported in XP. Any ideas from the COM experts here?

Share this post


Link to post
Share on other sites

Posted

Updated first post with a version of the WMI monitor that works for XP and Vista. Thanks to arcker for the nudge in the right direction.

Share this post


Link to post
Share on other sites

Posted

After speaking with the CodeProject author, I slightly changed the logic for the device removal. Now, CM_Query_And_Remove_SubTreeW is always tried first, and only if it returns CR_ACCESS_DENIED (from a limited user account usually) is CM_Request_Device_EjectW used. Apparently, this is the Microsoft recommended procedure.

Share this post


Link to post
Share on other sites

Posted

@wraithdu

Do you have any plans to add code to re-enable the device if it has not been physically removed?

That way if you had a USB drive attached to a workstation for backup purposes, after the backup has been done you could "eject" the drive - so that the user could safely remove the drive anytime that they want.

But when next backup about to start, if drive still present, then you can re-enable - do the backup - and then un-plug again.

I can see that re-enable could possibly be done with Devcon but a pure AutoIt solution would be easier to manage

VW

Share this post


Link to post
Share on other sites

Posted

Possibly a rescan of devices will "re-enable" the device if it is still attached.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now