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

Posted

Have you tested this successfully with Devcon? I was under the impression what you ask cannot be done.

Share this post


Link to post
Share on other sites

Posted (edited)

Have you tested this successfully with Devcon? I was under the impression what you ask cannot be done.

Devcon does work with XP SP2, although the commands are slightly different to what I guessed at earlier.

This is what I did, first I stopped a USB key (F:) using your script

Here is the devcon stuff I tried:

D:\Temp\Manage USB\Devcon\i386>devcon rescan
Scanning for new hardware.
Scanning completed.

Command executed OK, but the USB key did not reappear

Then tried the following:

D:\Temp\Manage USB\Devcon\i386>devcon find usb\*
USB\ROOT_HUB\4&EB80878&0									: USB Root Hub
USB\ROOT_HUB20\4&242750EB&0								 : USB Root Hub
USB\VID_0718&PID_0246\07760D952600						  : USB Mass Storage Device 3 matching device(s) found.

D:\Temp\Manage USB\Devcon\i386>devcon enable *pid_0246*
USB\VID_0718&PID_0246\07760D952600						  : Enabled
1 device(s) enabled.

Again the commands appear to execute OK, but no USB key.

This command indicated why the USB key was not appearing

D:\Temp\Manage USB\Devcon\i386>devcon status *pid_0246*
USB\VID_0718&PID_0246\07760D952600
	Name: USB Mass Storage Device
	Device has a problem: 21.
1 matching device(s) found.

Success! ... this command worked, USB key re-appeared in My Computer without me removing and re-inserting the key

D:\Temp\Manage USB\Devcon\i386>devcon restart *pid_0246*
USB\VID_0718&PID_0246\07760D952600						  : Restarted
1 device(s) restarted.

D:\Temp\Manage USB\Devcon\i386>devcon status *pid_0246*
USB\VID_0718&PID_0246\07760D952600
	Name: USB Mass Storage Device
	Driver is running.
1 matching device(s) found.

So using Devcon, in XP SP2 a USB key and perhaps other USB devices can be re-enabled

VW

Edited by VeeDub

Share this post


Link to post
Share on other sites

Posted (edited)

Very interesting! I have the Windows DDK, so I'll take a look at the Devcon source at some point and see what it's doing with the 'restart' command, and see what I can do.

Edited by wraithdu

Share this post


Link to post
Share on other sites

Posted

WOW THANKZ bRO

Share this post


Link to post
Share on other sites

Posted

Updated, see first post.

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




  • Recently Browsing   0 members

    No registered users viewing this page.