Page 2 of 2

Re: Eject USB drive

Posted: Tue Jan 19, 2010 2:36 pm
by Michael Vogel
doctorized wrote:@ Michael Vogel: your code returns error: "Line 32: Structure not found: SP_DEVINFO_DATA."
To avoid an additional remark, the posted code runs fine under 4.31 and has to be adapted to work on version 4.4 :wink:

Michael

Code: Select all

Structure SP_DEVINFO_DATA
	cbSize.l
	ClassGuid.GUID
	DevInst.l
	Reserved.l
EndStructure

Global DeviceInfoData.SP_DEVINFO_DATA

Re: Eject USB drive

Posted: Wed Jan 20, 2010 9:35 am
by doctorized
Michael Vogel wrote:To avoid an additional remark, the posted code runs fine under 4.31 and has to be adapted to work on version 4.4
You mean the structure SP_DEVINFO_DATA is supported from 4.31 and not supported from 4.40? Is it possible?

(I will test your code in a few hours, when I will be at home.)

Re: Eject USB drive

Posted: Wed Jan 20, 2010 2:58 pm
by doctorized
I tested the code. It is not working. Maybe CallFunctionFast is the reason because:
(from help:) "Note: this function is not very flexible and can't handle string/float/double/quad parameters or string/float/double/quad returns. The prototypes are now strongly recommended instead."
You are trying to pass strings. I passed their pointers instead and didn't work.

Re: Eject USB drive

Posted: Wed Jan 20, 2010 4:21 pm
by Michael Vogel
doctorized wrote:It is now [:?:] working
Hurray :|

So all you have to do now -- and I'm quite sure you'll be able to -- replacing three times the string to a pointer, like here...

Code: Select all

	Global FastString=Drive+"\"
	CallFunctionFast(*GetVolumeNameForVolumeMountPoint,@FastString,*VolumeName,255)

Re: Eject USB drive

Posted: Wed Jan 20, 2010 7:41 pm
by doctorized
Michael Vogel wrote:
doctorized wrote:It is now [:?:] working
Obviously, I wanted to write "not"!
I did the change from strings to pointers, as I said to my previous post, but I did a calling mistake (I know it is a shame for me) and now works fine. Good work, bravo!!

Re: Eject USB drive

Posted: Sun Oct 03, 2010 1:51 pm
by doctorized
I tried today Michael Vogel's code with PB 4.50 x86 and Win 7 x64 and the code fails to eject media.
I found that in line

Code: Select all

(Line 103) If DeviceCapabilities & #CM_DEVCAP_REMOVABLE
DeviceCapabilities = 48 (hex=$30) so the logical AND with #CM_DEVCAP_REMOVABLE is zero.
With PB 4.50 x64:

Code: Select all

(Line 75)  If SetupDiGetDeviceInterfaceDetail_(hDevice, @spdid, @pspdidd, DevicePath_Length, 0, @spdd)
fails.
Any suggestions?

Re: Eject USB drive

Posted: Thu Nov 26, 2015 8:20 pm
by Michael Vogel
doctorized wrote:@Michael: your code returns error: "Line 32: Structure not found: SP_DEVINFO_DATA."
Must have missed this, sorry:

Code: Select all

	Structure SP_DEVINFO_DATA
		cbSize.l
		ClassGuid.GUID
		DevInst.l
		Reserved.l
	EndStructure
Anyhow, most of the snippets do not work on 64 bit here (PB5.41), for instance the following code:

Code: Select all

Procedure USBDriveEjectAll(nil)

	#DIGCF_PRESENT=2
	#DIGCF_ALLCLASSES=4
	#SPDRP_SERVICE=4

	Protected hDeviceInfoSet=SetupDiGetClassDevs_(0,0,0,#DIGCF_PRESENT|#DIGCF_ALLCLASSES)
	Debug hDeviceInfoSet
	
	Structure SP_DEVINFO_DATA
		cbSize.l
		ClassGuid.GUID
		DevInst.l
		Reserved.l
	EndStructure

	Global DeviceInfoData.SP_DEVINFO_DATA
	DeviceInfoData\cbSize=SizeOf(DeviceInfoData)

	Protected *MemoryID=AllocateMemory(500)
	Protected i.l
	Protected devinst

	OpenLibrary(1,"setupapi.dll")

	Protected *F1=GetFunction(1,"SetupDiGetDeviceInstanceIdA")
	Protected *F2=GetFunction(1,"SetupDiGetDeviceRegistryPropertyA")
	Protected *F3=GetFunction(1,"CM_Locate_DevNodeA")
	Protected *F4=GetFunction(1,"CM_Request_Device_Eject_ExA")
	
	Debug SetupDiEnumDeviceInfo_(hDeviceInfoSet,i,@DeviceInfoData)
	While SetupDiEnumDeviceInfo_(hDeviceInfoSet,i,@DeviceInfoData)
		i+1
		CallFunctionFast(*F2,hDeviceInfoSet,DeviceInfoData,#SPDRP_SERVICE,0,*MemoryID,500,0)
		Debug UCase(PeekS(*MemoryID))
		If UCase(PeekS(*MemoryID))="USBSTOR"
			CallFunctionFast(*F1,hDeviceInfoSet,DeviceInfoData,*MemoryID,500,0)
			If CallFunctionFast(*F3,@devinst,*MemoryID,0)=0
				;If CallFunctionFast(*F3,@devinst,PeekS(*MemoryID),0)=0
				CallFunctionFast(*F4,devinst,0,0,0,0,0)
			EndIf
		EndIf
	Wend

	SetupDiDestroyDeviceInfoList_(hDeviceInfoSet)
	CloseLibrary(1)
	FreeMemory(*MemoryID)

EndProcedure

USBDriveEjectAll(0)

Re: Eject USB drive

Posted: Fri Nov 27, 2015 12:02 am
by IdeasVacuum
Win7 x86 PB 5.31

Can't get Michael's code to eject a USB Thumb Drive (nothing special), edit as below runs without PB complaining but does not work:

Code: Select all

   Structure SP_DEVINFO_DATA
      cbSize.i
      ClassGuid.GUID
      DevInst.i
      Reserved.i
   EndStructure

   #SPDRP_CAPABILITIES = $F
   #CM_DEVCAP_REMOVABLE  = $4

   DataSection
      GUID_DEVINTERFACE_FLOPPY:
      Data.i $53F56311
      Data.w $B6BF, $11D0
      Data.b $94, $F2, $00, $A0, $C9, $1E, $FB, $8B
      GUID_DEVINTERFACE_DISK:
      Data.i $53F56307
      Data.w $B6BF, $11D0
      Data.b $94, $F2, $00, $A0, $C9, $1E, $FB, $8B
      GUID_DEVINTERFACE_CDROM:
      Data.i $53F56308
      Data.w $B6BF, $11D0
      Data.b $94, $F2, $00, $A0, $C9, $1E, $FB, $8B
      GUID_DEVINTERFACE_VOLUME:
      Data.i  $53F5630D
      Data.w $B6BF, $11D0
      Data.b $94, $F2, $00, $A0, $C9, $1E, $FB, $8B
   EndDataSection

   Structure PSP_DEVICE_INTERFACE_DETAIL_DATA
      cbSize.i
      DevicePath.s{255}
   EndStructure

Procedure EjectUSBDrive(sDrive.s, iEjectNow.i = #True)
;#----------------------------------------------------
Protected sUsbDrive.s = sDrive + "\"
Protected Index, Retour, *VolumeName, *DeviceInstanceId
Protected DevicePath_Length.i, DeviceLetter.s, DeviceVolume.s, DeviceInstance.i, DeviceID.s
Protected hDevice.i, *Guid, pspdidd.PSP_DEVICE_INTERFACE_DETAIL_DATA, spdid.SP_DEVICE_INTERFACE_DATA, spdd.SP_DEVINFO_DATA
Protected Removable_hDevice, Removable_Index, Removable_spdd.SP_DEVINFO_DATA, Removable_DeviceCapabilities.i, Removable_Service.s, RemovableDevice.i, Removable_DeviceID.s
Protected DeviceCapabilities.i
Protected DriveVolume.s

      dll_Setupapi = OpenLibrary(#PB_Any, "setupapi.dll")
   If dll_Setupapi

         dll_Kernel32 = OpenLibrary(#PB_Any, "Kernel32.dll")
      If dll_Kernel32

         *CM_Locate_DevNode = GetFunction(dll_Setupapi, "CM_Locate_DevNodeA")
         *CM_Request_Device_Eject = GetFunction(dll_Setupapi, "CM_Request_Device_Eject_ExA")
         *CM_Get_Parent = GetFunction(dll_Setupapi, "CM_Get_Parent")
         *CM_Get_Device_ID = GetFunction(dll_Setupapi, "CM_Get_Device_IDA")
         *GetVolumeNameForVolumeMountPoint = GetFunction(dll_Kernel32, "GetVolumeNameForVolumeMountPointA")

         *VolumeName = AllocateMemory(255)
         CallFunctionFast(*GetVolumeNameForVolumeMountPoint, @sUsbDrive, *VolumeName, 255)
         DriveVolume = PeekS(*VolumeName)
         FreeMemory(*VolumeName)

           *Guid = ?GUID_DEVINTERFACE_VOLUME
         hDevice = SetupDiGetClassDevs_(*Guid, 0, 0, #DIGCF_PRESENT|#DIGCF_DEVICEINTERFACE)

         If hDevice <> #INVALID_HANDLE_VALUE

            spdid\cbSize = SizeOf(SP_DEVICE_INTERFACE_DATA)

            Index = 0
            While SetupDiEnumDeviceInterfaces_(hDevice, 0, *Guid, Index, @spdid)

               SetupDiGetDeviceInterfaceDetail_(hDevice, @spdid, 0, 0, @DevicePath_Length, 0)
               If DevicePath_Length <> 0 And DevicePath_Length < 255

                  pspdidd\DevicePath = sUsbDrive
                  pspdidd\cbSize = 5
                  spdd\cbSize = SizeOf(SP_DEVINFO_DATA)

                  If SetupDiGetDeviceInterfaceDetail_(hDevice, @spdid, @pspdidd, DevicePath_Length, 0, @spdd)

                     *VolumeName = AllocateMemory(255)
                     CallFunctionFast(*GetVolumeNameForVolumeMountPoint, @pspdidd\DevicePath, *VolumeName, 255)
                     DeviceVolume =  PeekS(*VolumeName)
                     FreeMemory(*VolumeName)

                     If DriveVolume = DeviceVolume

                        DeviceInstance = spdd\DevInst
                        Repeat
                           If CallFunctionFast(*CM_Get_Parent, @DeviceParent, DeviceInstance, 0) = 0
                              DeviceInstance = DeviceParent
                              *DeviceInstanceId = AllocateMemory(255)
                              CallFunctionFast(*CM_Get_Device_ID, DeviceInstance, *DeviceInstanceId, 255, 0)
                              DeviceID = PeekS(*DeviceInstanceId)
                              FreeMemory(*DeviceInstanceId)

                              RemovableDevice = 0
                              Removable_hDevice = SetupDiGetClassDevs_(0, 0, 0, #DIGCF_PRESENT | #DIGCF_ALLCLASSES)
                              If Removable_hDevice <> #INVALID_HANDLE_VALUE
                                 Removable_spdd\cbSize = SizeOf(SP_DEVINFO_DATA)
                                 Removable_Index = 0

                                 While SetupDiEnumDeviceInfo_(Removable_hDevice, Removable_Index, @Removable_spdd)

                                    *DeviceInstanceId = AllocateMemory(255)
                                    SetupDiGetDeviceInstanceId_(Removable_hDevice, Removable_spdd, *DeviceInstanceId, 255, 0)
                                    Removable_DeviceID = PeekS(*DeviceInstanceId)
                                    FreeMemory(*DeviceInstanceId)

                                    If Removable_DeviceID = DeviceID

                                       SetupDiGetDeviceRegistryProperty_(Removable_hDevice, Removable_spdd, #SPDRP_CAPABILITIES, 0, @DeviceCapabilities, 4, 0)

                                       If DeviceCapabilities & #CM_DEVCAP_REMOVABLE
                                          RemovableDevice = 1
                                          Break
                                       EndIf
                                    EndIf
                                    Removable_Index + 1
                                 Wend

                                 SetupDiDestroyDeviceInfoList_(Removable_hDevice)

                              EndIf

                              If RemovableDevice
                                 Retour = 1
                                 If iEjectNow

                                    If CallFunctionFast(*CM_Locate_DevNode, @DeviceInstance, @DeviceID, 0) = 0
                                       CallFunctionFast(*CM_Request_Device_Eject, DeviceInstance, 0, 0, 0, 0, 0)
                                    EndIf

                                 EndIf
                                 Break 2
                              EndIf

                           EndIf
                        Until DeviceInstance <> DeviceParent

                     EndIf
                  EndIf
               EndIf

               Index + 1
            Wend

            SetupDiDestroyDeviceInfoList_(hDevice)
         EndIf
         CloseLibrary(dll_Kernel32)
      EndIf
      CloseLibrary(dll_Setupapi)
   EndIf

   ProcedureReturn Retour
EndProcedure

EjectUSBDrive("K:", #True)
[/size]

Re: Eject USB drive

Posted: Sat Nov 28, 2015 12:34 am
by CalamityJames
Here is some code I wrote a little while ago. It leans heavily on the sources noted at the beginning. I looks for the last removable USB drive and ejects it. The last drive is defined as the one with the drive letter furthest down the alphabet. This is normally the most recent drive plugged in but it doesn't have to be. Note that it ejects only drives that identify themselves as removable. A hard drive mounted on USB does not identify itself as removable whereas a pen drive does. I wrote it so that I could plug a USB pen drive into the computer, copy files to it (or whatever) and then eject it using this program without affecting any large drives connected via USB which I have permantently attached to the computer. The program closes itself when the drive is removed.

Code: Select all

; Uses http://www.purebasic.fr/english/viewtopic.php?f=12&t=26055
;and http://forums.purebasic.com/english/viewtopic.php?f=12&t=20763
; and http://msdn.microsoft.com/en-us/library/windows/desktop/aa363215%28v=vs.85%29.aspx for Detecting Media Removal
EnableExplicit
Global DriveStringsLen.l, DeviceStr.s, Counter.l, DriveType.l, CommandLinePtr.l, CommandLineStr.s
Global Counter2.l, Inc.l, ModuleFileName.s, RequiredVolumeName.s, LenOfName.l, AllDriveStringsStr.s
Global Dim DeviceStrArray.s(100), Dim VolumeNameArray.s(100)
Global WindowOrigProc.l, EventId.l, *Dbhdr.DEV_BROADCAST_HDR, *Dbv.DEV_BROADCAST_VOLUME
Global DriveRemovedLetter.s, MessageStr.s
Global Dim StdIconHandles(#IDI_WINLOGO - #IDI_APPLICATION)
#WindowWidth = 470
#DBTF_MEDIA = 1
#DBTF_NET = 2
Dim AllDriveStringsArray.b(512)
Define Nsize.l = 512
Global VolumeName.s
Global FileSystemName.s
Global InvComma.s = Chr(34)
Global ReturnValue.l
#FSCTL_LOCK_VOLUME=$90018
#FSCTL_UNLOCK_VOLUME=$9001C
#FSCTL_DISMOUNT_VOLUME=$90020
#IOCTL_STORAGE_EJECT_MEDIA=$2D4808
#IOCTL_STORAGE_QUERY_PROPERTY  = $2D1400
#IOCTL_STORAGE_MEDIA_REMOVAL=$2D4804
#StorageDeviceProperty = 0
#PropertyStandardQuery = 0
#BusTypeUsb = 7
#vbCR = Chr(13)

Structure PREVENT_MEDIA_REMOVAL
  PreventMediaRemoval.w
EndStructure
   
Structure STORAGE_PROPERTY_QUERY
  PropertyId.l
  QueryType.l
  AdditionalParameters.l
EndStructure

Structure STORAGE_DEVICE_DESCRIPTOR
  Version.l
  Size.l;                                    As Long
  DeviceType.b;                              As Byte
  DeviceTypeModifier.b;                      As Byte
  RemovableMedia.b;                          As Byte
  CommandQueueing.b;                         As Byte
  VendorIdOffset.l;                          As Long
  ProductIdOffset.l;                         As Long
  ProductRevisionOffset.l;                   As Long
  SerialNumberOffset.l;                      As Long
  BusType.w;                                 As Integer
  RawPropertiesLength.l;                     As Long
  RawDeviceProperties.b;                     As Byte
  Reserved.b[1024]
EndStructure
    
Procedure GetStdIcons()
Protected Inc.l
For Inc = #IDI_APPLICATION To #IDI_WINLOGO
  StdIconHandles(Inc - #IDI_APPLICATION) = LoadIcon_(0, Inc)
Next
EndProcedure

Procedure DisplayApiError(LastError.l)
Protected MessLen.l, MessString.s
MessLen = 256
MessString = Space(MessLen)
MessLen = FormatMessage_(#FORMAT_MESSAGE_FROM_SYSTEM, 0, LastError, 0, @MessString, MessLen, 0)
MessageRequester("EjectUSB",  Left(MessString, MessLen)) 
EndProcedure

Procedure CentreAnyFormInWorkArea(WindowNo.l)
Protected WindowTop.l, WindowLeft.l, WindowWidth.l, WindowHeight.l, ReturnValue.l, WorkAreaRect.RECT
Protected SpareHeight.l, SpareWidth.l, WorkAreaHeight.l, WorkAreaWidth.l, WindowRect.rect
ReturnValue = SystemParametersInfo_(#SPI_GETWORKAREA, 0, @WorkAreaRect, 0)
GetWindowRect_(WindowID(WindowNo),@WindowRect)
WindowWidth = WindowRect\right - WindowRect\left
WindowHeight = WindowRect\bottom - WindowRect\top
WorkAreaHeight = WorkAreaRect\Bottom - WorkAreaRect\Top
WorkAreaWidth = WorkAreaRect\Right - WorkAreaRect\Left
SpareHeight = Int((WorkAreaHeight - WindowHeight) / 2)
SpareWidth = Int((WorkAreaWidth - WindowWidth) / 2)
WindowTop = WorkAreaRect\Top + SpareHeight
WindowLeft = WorkAreaRect\Left + SpareWidth
ReturnValue = MoveWindow_(WindowID(WindowNo), WindowLeft, WindowTop, WindowWidth, WindowHeight, 0)
EndProcedure

Procedure SendWrongDriveMessage()
Protected LabelText.s, NewMessageText.s
  MessageStr = ReplaceString(MessageStr, "now", "still")
For Inc = 1 To 26
  If Left(DeviceStrArray(Inc), 2) = DriveRemovedLetter
    If VolumeNameArray(Inc) = ""
      LabelText = " (unlabelled)"  
    ElseIf VolumeNameArray(Inc) = "error"  
      LabelText = ""
    Else 
     LabelText = " (labelled " + InvComma + VolumeNameArray(Inc) + InvComma + ")" 
    EndIf
  EndIf  
Next
NewMessageText = "You have just removed drive " + DriveRemovedLetter + LabelText +  ", which is not the one which was made safe for removal." + #vbcr + #vbcr + MessageStr
HideGadget(1, #True)
TextGadget(2, GadgetWidth(0) + 10 , 10, #WindowWidth - 2 * (GadgetWidth(0) + 10), 84, NewMessageText, #PB_Text_Center)
SetGadgetText(1, NewMessageText)
 
EndProcedure


Procedure.l WindSubClasser(hWnd.l, Msg.l, wParam.l, lParam.l)
Protected Mask.l, Inc.l
  Select Msg
  Case #WM_DEVICECHANGE
    Select wparam
        Case #DBT_DEVICEREMOVECOMPLETE
        *DBHDR.DEV_BROADCAST_HDR=lParam    
      Select *dbhdr\dbch_devicetype    
        Case #DBT_DEVTYP_VOLUME
          *Dbv.DEV_BROADCAST_VOLUME = lParam
           If *dbv\dbcv_flags <> #DBTF_MEDIA And  *dbv\dbcv_flags <> #DBTF_NET ; neither should be true for USB drive 
              Mask = *dbv\dbcv_unitmask
              If Mask = 0 
                DriveRemovedLetter = "A:"
              Else
                For Inc = 1 To 26 
                  If Mask & 1 
                    Break
                  EndIf
                  Mask = Mask >> 1
                Next  
                DriveRemovedLetter = Chr(Inc + 64)+ ":"
                If UCase(DriveRemovedLetter) = UCase(DeviceStr)
                  ReturnValue = PostMessage_(hWnd, #WM_CLOSE, 0, 0)   ;closes
                Else 
                  SendWrongDriveMessage()
                EndIf    
              EndIf
          EndIf        
      EndSelect
    EndSelect    
EndSelect
ProcedureReturn CallWindowProc_(WindowOrigProc, hWnd, Msg, wParam, lParam)
EndProcedure


Procedure WaitForRemoval()

If OpenWindow(0, 24, 24, #WindowWidth, 94, "Eject USB", #PB_Window_Invisible | #PB_Window_SystemMenu)  
  GetStdIcons()
  ImageGadget(0,  10, 10, 100, 83, StdIconHandles( #IDI_INFORMATION - #IDI_APPLICATION))
  
  TextGadget(1, GadgetWidth(0) + 10 , 30, #WindowWidth - 2 * (GadgetWidth(0) + 10), 50, MessageStr, #PB_Text_Center)
  CentreAnyFormInWorkArea(0)
  HideWindow(0, #False)
  StickyWindow(0, #True)
  MessageBeep_(#MB_OK)
  WindowOrigProc = GetWindowLong_(WindowID(0), #GWL_WNDPROC)
  SetWindowLong_(WindowID(0), #GWL_WNDPROC, @WindSubClasser())
  Repeat
    EventID = WaitWindowEvent() 
   Until EventId = #PB_Event_CloseWindow ; close window sent in the subclass procedure
EndIf
End
EndProcedure

Procedure DoEject(VolLetter.s)
Protected  DriveHandle.l, bytes.l, ReturnValue.l, PMR.PREVENT_MEDIA_REMOVAL
DriveHandle = CreateFile_("\\.\" + VolLetter, #GENERIC_READ | #GENERIC_WRITE, #FILE_SHARE_READ  | #FILE_SHARE_WRITE, 0, #OPEN_EXISTING, 0, 0)
If DriveHandle <> #INVALID_HANDLE_VALUE
  ReturnValue = DeviceIoControl_(DriveHandle, #FSCTL_LOCK_VOLUME, 0, 0, 0, 0, @bytes, 0);
  If ReturnValue <> 0 
    ReturnValue = DeviceIoControl_(DriveHandle, #FSCTL_DISMOUNT_VOLUME, 0, 0, 0, 0, @bytes, 0);
    If ReturnValue <> 0
      DeviceIoControl_(DriveHandle,#IOCTL_STORAGE_MEDIA_REMOVAL,@PMR,SizeOf(PREVENT_MEDIA_REMOVAL),#Null,0,@Bytes,#Null)
      If ReturnValue <> 0 
        ReturnValue = DeviceIoControl_(DriveHandle, #IOCTL_STORAGE_EJECT_MEDIA, 0, 0, 0, 0, @bytes, 0);
        If ReturnValue <> 0 
          ReturnValue = CloseHandle_(DriveHandle)
          ProcedureReturn #True
        EndIf
      EndIf
    EndIf
    ReturnValue = DeviceIoControl_(DriveHandle, #FSCTL_UNLOCK_VOLUME, 0, 0, 0, 0, @bytes, 0);
   EndIf
  ReturnValue = CloseHandle_(DriveHandle)
EndIf
ProcedureReturn #False
EndProcedure

Procedure IsDriveUsb(VolLetter.s)
Protected  DriveHandle.l, bytes.l, ReturnValue.l, dwOutBytes.l
Protected udtQuery.STORAGE_PROPERTY_QUERY
Protected udtOut.STORAGE_DEVICE_DESCRIPTOR
DriveHandle = CreateFile_("\\.\" + VolLetter, 0, #FILE_SHARE_READ | #FILE_SHARE_WRITE, 0, #OPEN_EXISTING, 0, 0)
If DriveHandle <> #INVALID_HANDLE_VALUE
  ReturnValue = DeviceIoControl_(DriveHandle, #IOCTL_STORAGE_QUERY_PROPERTY, udtQuery, SizeOf(udtQuery), @udtOut, SizeOf(udtout), @dwOutBytes, 0)
  If ReturnValue <> 0 
    If udtOut\Bustype = #BusTypeUsb
     CloseHandle_(DriveHandle)  
      ProcedureReturn #True
    EndIf  
  EndIf  
  CloseHandle_(DriveHandle)  
Else
  DisplayApiError(GetLastError_())
EndIf
ProcedureReturn #False
EndProcedure

Procedure DoByAlphabet()
For Inc = 1 To Counter2
  DeviceStr = DeviceStrArray(Inc)
  If Right(DeviceStr, 1) = "\"
     DriveType = GetDriveType_(DeviceStr)
     If DriveType = #DRIVE_REMOVABLE
        VolumeName = Space(#MAX_PATH + 1)
        FileSystemName = Space(#MAX_PATH + 1)
        ReturnValue = GetVolumeInformation_(DeviceStr, @VolumeName, Len(VolumeName), 0, 0, 0, @FileSystemName, Len(FileSystemName))
        If ReturnValue <> 0
          DeviceStr = Left(DeviceStr, Len(DeviceStr) - 1)
          If IsDriveUsb(DeviceStr) = #True
            Counter = 99999
            If DoEject(DeviceStr)
             MessageStr = "The Usb drive labelled " + InvComma + VolumeName + InvComma + " (Drive " + DeviceStr + ") can now be removed safely."
             WaitForRemoval() ;prgram ends here
               ;MessageRequester("EjectUsb", "The Usb drive labelled " + InvComma + VolumeName + InvComma + " (Drive " + DeviceStr + ") can now be removed safely.", #MB_ICONINFORMATION)  
            Else
              MessageRequester("EjectUsb", "It is NOT safe to remove the Usb drive labelled " + InvComma + VolumeName + InvComma+ " (Drive " + DeviceStr + ")." + Chr(13) + "(A Program must be using the drive.)", #MB_ICONERROR)  
            EndIf
          EndIf
         Break  
        EndIf
      EndIf
  EndIf
Next
If Counter < 99999
  MessageRequester("EjectUsb", "No removable Usb drives found.", #MB_ICONEXCLAMATION)  
EndIf     
EndProcedure

Procedure DoByVolumeLabel()
For Inc = 1 To Counter2
  DeviceStr = DeviceStrArray(Inc)
  If Right(DeviceStr, 1) = "\"
    DriveType = GetDriveType_(DeviceStr)
    If DriveType = #DRIVE_REMOVABLE
      VolumeName = Space(#MAX_PATH + 1)
      FileSystemName = Space(#MAX_PATH + 1)
      GetVolumeInformation_(DeviceStr, @VolumeName, #MAX_PATH + 1, 0, 0, 0, @FileSystemName, Len(FileSystemName))
      DeviceStr = Left(DeviceStr, Len(DeviceStr) - 1)
      If LCase(VolumeName) = LCase(RequiredVolumeName)
        If DoEject(DeviceStr)
          MessageStr = "The Usb drive labelled " + InvComma + VolumeName + InvComma +  " (Drive " + DeviceStr + ") can now be removed safely."
          WaitForRemoval() ;prgram ends here
;          MessageRequester("EjectUsb", "The Usb drive labelled " + InvComma + VolumeName + InvComma +  " (Drive " + DeviceStr + ") can now be removed safely.", #MB_ICONINFORMATION)  
        Else
          MessageRequester("EjectUsb", "It is NOT safe to remove the Usb drive labelled " + InvComma + VolumeName + InvComma+ "." + Chr(13) + "(A Program must be using the drive.)", #MB_ICONERROR)  
        EndIf
  Break  
      EndIf
    EndIf
  EndIf
Next
If LCase(VolumeName) <>  LCase(RequiredVolumeName)
  MessageRequester("EjectUsb", "The removable Usb drive labelled " + InvComma + RequiredVolumeName + InvComma+ " has not been found.",#MB_ICONEXCLAMATION)
EndIf
EndProcedure

Procedure DoByDriveLetter()
If Right(RequiredVolumeName,1) <> "\"
  DeviceStr = RequiredVolumeName + "\"
Else
  DeviceStr = RequiredVolumeName
EndIf
If FindString(UCase(AllDriveStringsStr), UCase(DeviceStr), 1) <> 0
  DriveType = GetDriveType_(DeviceStr)
  If DriveType <> #DRIVE_REMOVABLE
    MessageRequester("EjectUsb", "Drive " + InvComma + RequiredVolumeName + InvComma +  " does not appear to be removable.", #MB_ICONEXCLAMATION)  
  Else  
    VolumeName = Space(#MAX_PATH + 1)
    FileSystemName = Space(#MAX_PATH + 1)
    ReturnValue = GetVolumeInformation_(DeviceStr, @VolumeName, Len(VolumeName), 0, 0, 0, @FileSystemName, Len(FileSystemName))
    If ReturnValue <> 0 ; volume  there
      DeviceStr = Left(DeviceStr, Len(DeviceStr) - 1)
      If IsDriveUsb(DeviceStr) = #True
        If DoEject(DeviceStr)
          MessageStr = "Drive " + InvComma + RequiredVolumeName + InvComma + " (Labelled " + InvComma +  VolumeName + InvComma + ") can now be removed safely."
          WaitForRemoval() ;prgram ends here
          MessageRequester("EjectUsb", "Drive " + InvComma + RequiredVolumeName + InvComma + " (Labelled " + InvComma +  VolumeName + InvComma + ") can now be removed safely.", #MB_ICONINFORMATION)  
        Else
          MessageRequester("EjectUsb", "It is NOT safe to remove the Usb drive labelled " + InvComma + VolumeName + InvComma+ " (Drive " + DeviceStr + ")." + Chr(13) + "(A Program must be using the drive.)", #MB_ICONERROR)  
        EndIf
      Else  
        MessageRequester("EjectUsb", "Drive " + InvComma + RequiredVolumeName + InvComma +  " is not a removable USB Drive.", #MB_ICONEXCLAMATION)  
    EndIf  
    Else  
      MessageRequester("EjectUsb", "Drive " + InvComma + RequiredVolumeName + InvComma +  " not found.", #MB_ICONEXCLAMATION)  
    EndIf  
  EndIf
Else  
  MessageRequester("EjectUsb", "Drive " + InvComma + RequiredVolumeName + InvComma +  " not found.", #MB_ICONEXCLAMATION)  
EndIf
EndProcedure

ModuleFileName = Space(256)
LenOfName = GetModuleFileName_(0,ModuleFileName, 255) 
ModuleFileName = Left(ModuleFileName, LenOfName)

CommandLinePtr = GetCommandLine_()
If CommandLinePtr <> 0
  CommandLineStr = PeekS(GetCommandLine_())
  CommandLineStr = RemoveString(CommandLineStr, ModuleFileName, #PB_String_NoCase, 1, 1)
  CommandLineStr = Trim(CommandLineStr); does spaces
  CommandLineStr = Trim(CommandLineStr, InvComma)
  CommandLineStr = Trim(CommandLineStr); does spaces
  CommandLineStr = Trim(CommandLineStr, InvComma)
  CommandLineStr = Trim(CommandLineStr); does spaces
EndIf
If CommandLineStr <> ""
  RequiredVolumeName = CommandLineStr
EndIf  

DriveStringsLen = GetLogicalDriveStrings_(Nsize, @AllDriveStringsArray(0))
If DriveStringsLen <> 0
  Repeat
    DeviceStr = ""
    Repeat
      If AllDriveStringsArray(Counter) <> 0
        DeviceStr = DeviceStr + Chr(AllDriveStringsArray(Counter))
        AllDriveStringsStr = AllDriveStringsStr + Chr(AllDriveStringsArray(Counter))
        Counter = Counter + 1
      Else
        Break
      EndIf
     ForEver
     Counter = Counter + 1
     Counter2 = Counter2 + 1
     DeviceStrArray(Counter2) = DeviceStr
   Until Counter >= DriveStringsLen
   SortArray(DeviceStrArray(),#PB_Sort_Descending | #PB_Sort_NoCase, 1, Counter2)
  ;Debug GetTickCount_()
   For inc = 0 To counter2
    VolumeName = Space(#MAX_PATH + 1)
    FileSystemName = Space(#MAX_PATH + 1)
    ReturnValue = GetVolumeInformation_(DeviceStrArray(inc), @VolumeName, #MAX_PATH + 1, 0, 0, 0, @FileSystemName, Len(FileSystemName))
    If ReturnValue <> 0
      VolumeNameArray(Inc) = VolumeName    
    Else   
      VolumeNameArray(Inc) = "error"
    EndIf  
    Next
 EndIf
   ;Debug GetTickCount_()

If RequiredVolumeName <> ""
  If FindString(RequiredVolumeName, ":", 1) <> 0 ; it's a drive letter
    DoByDriveLetter()
  Else  
    DoByVolumeLabel()
  EndIf
Else
  DoByAlphabet()
EndIf

Re: Eject USB drive

Posted: Sat Nov 28, 2015 4:34 pm
by Michael Vogel
Thanks CalamityJames,
tried your code (using the version PB5.41 64 bit), but got memory errors multiple times in line 144: ProcedureReturn CallWindowProc_(WindowOrigProc,...). I also tried DoEject which did a partial removement of the USB drive, the drive number will be still kept in the windows explorer list. So I still try to get one of the codes working in 64 bit mode.

Re: Eject USB drive

Posted: Sun Nov 29, 2015 9:36 pm
by CalamityJames
Ah! I did mention the code went back quite a bit. Back to before I had understood that it is much better to use type integer rather than type long in order to give flexibility. It doesn't matter if you are in 32 bit but it does if you transfer to 64. I have change my declarations where necessary and this version works in 32 bit and 64 bit under PB 5.40 on my system.

Code: Select all

; Uses http://www.purebasic.fr/english/viewtopic.php?f=12&t=26055
;and http://forums.purebasic.com/english/viewtopic.php?f=12&t=20763
; and http://msdn.microsoft.com/en-us/library/windows/desktop/aa363215%28v=vs.85%29.aspx for Detecting Media Removal
EnableExplicit
Global DriveStringsLen.l, DeviceStr.s, Counter.i, DriveType.i, CommandLinePtr.i, CommandLineStr.s
Global Counter2.i, Inc.i, ModuleFileName.s, RequiredVolumeName.s, LenOfName.i, AllDriveStringsStr.s
Global Dim DeviceStrArray.s(100), Dim VolumeNameArray.s(100)
Global WindowOrigProc.i, EventId.i, *Dbhdr.DEV_BROADCAST_HDR, *Dbv.DEV_BROADCAST_VOLUME
Global DriveRemovedLetter.s, MessageStr.s
Global Dim StdIconHandles(#IDI_WINLOGO - #IDI_APPLICATION)
#WindowWidth = 470
#DBTF_MEDIA = 1
#DBTF_NET = 2
Dim AllDriveStringsArray.c(512)
Define Nsize.i = 512
Global VolumeName.s
Global FileSystemName.s
Global InvComma.s = Chr(34)
Global ReturnValue.i
#FSCTL_LOCK_VOLUME=$90018
#FSCTL_UNLOCK_VOLUME=$9001C
#FSCTL_DISMOUNT_VOLUME=$90020
#IOCTL_STORAGE_EJECT_MEDIA=$2D4808
#IOCTL_STORAGE_QUERY_PROPERTY  = $2D1400
#IOCTL_STORAGE_MEDIA_REMOVAL=$2D4804
#StorageDeviceProperty = 0
#PropertyStandardQuery = 0
#BusTypeUsb = 7
#vbCR = Chr(13)

Structure PREVENT_MEDIA_REMOVAL
  PreventMediaRemoval.i
EndStructure
   
Structure STORAGE_PROPERTY_QUERY
  PropertyId.i
  QueryType.i
  AdditionalParameters.i
EndStructure

Structure STORAGE_DEVICE_DESCRIPTOR
  Version.l;                                 As Dword
  Size.l;                                    As Dword
  DeviceType.b;                              As Byte
  DeviceTypeModifier.b;                      As Byte
  RemovableMedia.b;                          As Byte
  CommandQueueing.b;                         As Byte
  VendorIdOffset.l;                          As Dword
  ProductIdOffset.l;                         As Dword
  ProductRevisionOffset.l;                   As Dword
  SerialNumberOffset.l;                      As Dword
  BusType.l;                                 As Integer
  RawPropertiesLength.l;                     As Long
  RawDeviceProperties.b;                     As Byte
  Reserved.b[1024]
EndStructure
    
Procedure GetStdIcons()
Protected Inc.i
For Inc = #IDI_APPLICATION To #IDI_WINLOGO
  StdIconHandles(Inc - #IDI_APPLICATION) = LoadIcon_(0, Inc)
Next
EndProcedure

Procedure DisplayApiError(LastError.i)
Protected MessLen.i, MessString.s
MessLen = 256
MessString = Space(MessLen)
MessLen = FormatMessage_(#FORMAT_MESSAGE_FROM_SYSTEM, 0, LastError, 0, @MessString, MessLen, 0)
MessageRequester("EjectUSB",  Left(MessString, MessLen)) 
EndProcedure

Procedure CentreAnyFormInWorkArea(WindowNo.i)
Protected WindowTop.i, WindowLeft.i, WindowWidth.i, WindowHeight.i, ReturnValue.i, WorkAreaRect.RECT
Protected SpareHeight.i, SpareWidth.i, WorkAreaHeight.i, WorkAreaWidth.i, WindowRect.rect
ReturnValue = SystemParametersInfo_(#SPI_GETWORKAREA, 0, @WorkAreaRect, 0)
GetWindowRect_(WindowID(WindowNo),@WindowRect)
WindowWidth = WindowRect\right - WindowRect\left
WindowHeight = WindowRect\bottom - WindowRect\top
WorkAreaHeight = WorkAreaRect\Bottom - WorkAreaRect\Top
WorkAreaWidth = WorkAreaRect\Right - WorkAreaRect\Left
SpareHeight = Int((WorkAreaHeight - WindowHeight) / 2)
SpareWidth = Int((WorkAreaWidth - WindowWidth) / 2)
WindowTop = WorkAreaRect\Top + SpareHeight
WindowLeft = WorkAreaRect\Left + SpareWidth
ReturnValue = MoveWindow_(WindowID(WindowNo), WindowLeft, WindowTop, WindowWidth, WindowHeight, 0)
EndProcedure

Procedure SendWrongDriveMessage()
Protected LabelText.s, NewMessageText.s
  MessageStr = ReplaceString(MessageStr, "now", "still")
For Inc = 1 To 26
  If Left(DeviceStrArray(Inc), 2) = DriveRemovedLetter
    If VolumeNameArray(Inc) = ""
      LabelText = " (unlabelled)"  
    ElseIf VolumeNameArray(Inc) = "error"  
      LabelText = ""
    Else 
     LabelText = " (labelled " + InvComma + VolumeNameArray(Inc) + InvComma + ")" 
    EndIf
  EndIf  
Next
NewMessageText = "You have just removed drive " + DriveRemovedLetter + LabelText +  ", which is not the one which was made safe for removal." + #vbcr + #vbcr + MessageStr
HideGadget(1, #True)
TextGadget(2, GadgetWidth(0) + 10 , 10, #WindowWidth - 2 * (GadgetWidth(0) + 10), 84, NewMessageText, #PB_Text_Center)
SetGadgetText(1, NewMessageText)
 
EndProcedure


Procedure.i WindSubClasser(hWnd.i, Msg.i, wParam.i, lParam.i)
Protected Mask.i, Inc.i
  Select Msg
  Case #WM_DEVICECHANGE
    Select wparam
        Case #DBT_DEVICEREMOVECOMPLETE
        *DBHDR.DEV_BROADCAST_HDR=lParam    
      Select *dbhdr\dbch_devicetype    
        Case #DBT_DEVTYP_VOLUME
          *Dbv.DEV_BROADCAST_VOLUME = lParam
           If *dbv\dbcv_flags <> #DBTF_MEDIA And  *dbv\dbcv_flags <> #DBTF_NET ; neither should be true for USB drive 
              Mask = *dbv\dbcv_unitmask
              If Mask = 0 
                DriveRemovedLetter = "A:"
              Else
                For Inc = 1 To 26 
                  If Mask & 1 
                    Break
                  EndIf
                  Mask = Mask >> 1
                Next  
                DriveRemovedLetter = Chr(Inc + 64)+ ":"
                If UCase(DriveRemovedLetter) = UCase(DeviceStr)
                  ReturnValue = PostMessage_(hWnd, #WM_CLOSE, 0, 0)   ;closes
                Else 
                  SendWrongDriveMessage()
                EndIf    
              EndIf
          EndIf        
      EndSelect
    EndSelect    
EndSelect
ProcedureReturn CallWindowProc_(WindowOrigProc, hWnd, Msg, wParam, lParam)
EndProcedure


Procedure WaitForRemoval()

If OpenWindow(0, 24, 24, #WindowWidth, 94, "Eject USB", #PB_Window_Invisible | #PB_Window_SystemMenu)  
  GetStdIcons()
  ImageGadget(0,  10, 10, 100, 83, StdIconHandles( #IDI_INFORMATION - #IDI_APPLICATION))
  
  TextGadget(1, GadgetWidth(0) + 10 , 30, #WindowWidth - 2 * (GadgetWidth(0) + 10), 50, MessageStr, #PB_Text_Center)
  CentreAnyFormInWorkArea(0)
  HideWindow(0, #False)
  StickyWindow(0, #True)
  MessageBeep_(#MB_OK)
  WindowOrigProc = GetWindowLong_(WindowID(0), #GWL_WNDPROC)
  SetWindowLong_(WindowID(0), #GWL_WNDPROC, @WindSubClasser())
  Repeat
    EventID = WaitWindowEvent() 
   Until EventId = #PB_Event_CloseWindow ; close window sent in the subclass procedure
EndIf
End
EndProcedure

Procedure DoEject(VolLetter.s)
Protected  DriveHandle.i, bytes.i, ReturnValue.i, PMR.PREVENT_MEDIA_REMOVAL
DriveHandle = CreateFile_("\\.\" + VolLetter, #GENERIC_READ | #GENERIC_WRITE, #FILE_SHARE_READ  | #FILE_SHARE_WRITE, 0, #OPEN_EXISTING, 0, 0)
If DriveHandle <> #INVALID_HANDLE_VALUE
  ReturnValue = DeviceIoControl_(DriveHandle, #FSCTL_LOCK_VOLUME, 0, 0, 0, 0, @bytes, 0);
  If ReturnValue <> 0 
    ReturnValue = DeviceIoControl_(DriveHandle, #FSCTL_DISMOUNT_VOLUME, 0, 0, 0, 0, @bytes, 0);
    If ReturnValue <> 0
      DeviceIoControl_(DriveHandle,#IOCTL_STORAGE_MEDIA_REMOVAL,@PMR,SizeOf(PREVENT_MEDIA_REMOVAL),#Null,0,@Bytes,#Null)
      If ReturnValue <> 0 
        ReturnValue = DeviceIoControl_(DriveHandle, #IOCTL_STORAGE_EJECT_MEDIA, 0, 0, 0, 0, @bytes, 0);
        If ReturnValue <> 0 
          ReturnValue = CloseHandle_(DriveHandle)
          ProcedureReturn #True
        EndIf
      EndIf
    EndIf
    ReturnValue = DeviceIoControl_(DriveHandle, #FSCTL_UNLOCK_VOLUME, 0, 0, 0, 0, @bytes, 0);
   EndIf
  ReturnValue = CloseHandle_(DriveHandle)
EndIf
ProcedureReturn #False
EndProcedure

Procedure IsDriveUsb(VolLetter.s)
Protected  DriveHandle.i, bytes.i, ReturnValue.i, dwOutBytes.i
Protected udtQuery.STORAGE_PROPERTY_QUERY
Protected udtOut.STORAGE_DEVICE_DESCRIPTOR
DriveHandle = CreateFile_("\\.\" + VolLetter, 0, #FILE_SHARE_READ | #FILE_SHARE_WRITE, 0, #OPEN_EXISTING, 0, 0)
If DriveHandle <> #INVALID_HANDLE_VALUE
  ReturnValue = DeviceIoControl_(DriveHandle, #IOCTL_STORAGE_QUERY_PROPERTY, @udtQuery, SizeOf(udtQuery), @udtOut, SizeOf(udtout), @dwOutBytes, 0)
  If ReturnValue <> 0 
    If udtOut\Bustype = #BusTypeUsb
     CloseHandle_(DriveHandle)  
      ProcedureReturn #True
    EndIf  
  EndIf  
  CloseHandle_(DriveHandle)  
Else
  DisplayApiError(GetLastError_())
EndIf
ProcedureReturn #False
EndProcedure

Procedure DoByAlphabet()
For Inc = 1 To Counter2
  DeviceStr = DeviceStrArray(Inc)
  If Right(DeviceStr, 1) = "\"
    DriveType = GetDriveType_(DeviceStr)
     If DriveType = #DRIVE_REMOVABLE
        VolumeName = Space(#MAX_PATH + 1)
        FileSystemName = Space(#MAX_PATH + 1)
        ReturnValue = GetVolumeInformation_(DeviceStr, @VolumeName, Len(VolumeName), 0, 0, 0, @FileSystemName, StringByteLength(FileSystemName))
        If ReturnValue <> 0
          DeviceStr = Left(DeviceStr, Len(DeviceStr) - 1)
          If IsDriveUsb(DeviceStr) = #True
            Counter = 99999
            If DoEject(DeviceStr)
             MessageStr = "The Usb drive labelled " + InvComma + VolumeName + InvComma + " (Drive " + DeviceStr + ") can now be removed safely."
             WaitForRemoval() ;prgram ends here
               ;MessageRequester("EjectUsb", "The Usb drive labelled " + InvComma + VolumeName + InvComma + " (Drive " + DeviceStr + ") can now be removed safely.", #MB_ICONINFORMATION)  
            Else
              MessageRequester("EjectUsb", "It is NOT safe to remove the Usb drive labelled " + InvComma + VolumeName + InvComma+ " (Drive " + DeviceStr + ")." + Chr(13) + "(A Program must be using the drive.)", #MB_ICONERROR)  
            EndIf
          EndIf
         Break  
        EndIf
      EndIf
  EndIf
Next
If Counter < 99999
  MessageRequester("EjectUsb", "No removable Usb drives found.", #MB_ICONEXCLAMATION)  
EndIf     
EndProcedure

Procedure DoByVolumeLabel()
For Inc = 1 To Counter2
  DeviceStr = DeviceStrArray(Inc)
  If Right(DeviceStr, 1) = "\"
    DriveType = GetDriveType_(DeviceStr)
    If DriveType = #DRIVE_REMOVABLE
      VolumeName = Space(#MAX_PATH + 1)
      FileSystemName = Space(#MAX_PATH + 1)
      GetVolumeInformation_(DeviceStr, @VolumeName, #MAX_PATH + 1, 0, 0, 0, @FileSystemName, Len(FileSystemName))
      DeviceStr = Left(DeviceStr, Len(DeviceStr) - 1)
      If LCase(VolumeName) = LCase(RequiredVolumeName)
        If DoEject(DeviceStr)
          MessageStr = "The Usb drive labelled " + InvComma + VolumeName + InvComma +  " (Drive " + DeviceStr + ") can now be removed safely."
          WaitForRemoval() ;prgram ends here
;          MessageRequester("EjectUsb", "The Usb drive labelled " + InvComma + VolumeName + InvComma +  " (Drive " + DeviceStr + ") can now be removed safely.", #MB_ICONINFORMATION)  
        Else
          MessageRequester("EjectUsb", "It is NOT safe to remove the Usb drive labelled " + InvComma + VolumeName + InvComma+ "." + Chr(13) + "(A Program must be using the drive.)", #MB_ICONERROR)  
        EndIf
  Break  
      EndIf
    EndIf
  EndIf
Next
If LCase(VolumeName) <>  LCase(RequiredVolumeName)
  MessageRequester("EjectUsb", "The removable Usb drive labelled " + InvComma + RequiredVolumeName + InvComma+ " has not been found.",#MB_ICONEXCLAMATION)
EndIf
EndProcedure

Procedure DoByDriveLetter()
If Right(RequiredVolumeName,1) <> "\"
  DeviceStr = RequiredVolumeName + "\"
Else
  DeviceStr = RequiredVolumeName
EndIf
If FindString(UCase(AllDriveStringsStr), UCase(DeviceStr), 1) <> 0
  DriveType = GetDriveType_(DeviceStr)
  If DriveType <> #DRIVE_REMOVABLE
    MessageRequester("EjectUsb", "Drive " + InvComma + RequiredVolumeName + InvComma +  " does not appear to be removable.", #MB_ICONEXCLAMATION)  
  Else  
    VolumeName = Space(#MAX_PATH + 1)
    FileSystemName = Space(#MAX_PATH + 1)
    ReturnValue = GetVolumeInformation_(DeviceStr, @VolumeName, Len(VolumeName), 0, 0, 0, @FileSystemName, Len(FileSystemName))
    If ReturnValue <> 0 ; volume  there
      DeviceStr = Left(DeviceStr, Len(DeviceStr) - 1)
      If IsDriveUsb(DeviceStr) = #True
        If DoEject(DeviceStr)
          MessageStr = "Drive " + InvComma + RequiredVolumeName + InvComma + " (Labelled " + InvComma +  VolumeName + InvComma + ") can now be removed safely."
          WaitForRemoval() ;prgram ends here
          MessageRequester("EjectUsb", "Drive " + InvComma + RequiredVolumeName + InvComma + " (Labelled " + InvComma +  VolumeName + InvComma + ") can now be removed safely.", #MB_ICONINFORMATION)  
        Else
          MessageRequester("EjectUsb", "It is NOT safe to remove the Usb drive labelled " + InvComma + VolumeName + InvComma+ " (Drive " + DeviceStr + ")." + Chr(13) + "(A Program must be using the drive.)", #MB_ICONERROR)  
        EndIf
      Else  
        MessageRequester("EjectUsb", "Drive " + InvComma + RequiredVolumeName + InvComma +  " is not a removable USB Drive.", #MB_ICONEXCLAMATION)  
    EndIf  
    Else  
      MessageRequester("EjectUsb", "Drive " + InvComma + RequiredVolumeName + InvComma +  " not found.", #MB_ICONEXCLAMATION)  
    EndIf  
  EndIf
Else  
  MessageRequester("EjectUsb", "Drive " + InvComma + RequiredVolumeName + InvComma +  " not found.", #MB_ICONEXCLAMATION)  
EndIf
EndProcedure

ModuleFileName = Space(256)
LenOfName = GetModuleFileName_(0,ModuleFileName, 255) 
ModuleFileName = Left(ModuleFileName, LenOfName)

CommandLinePtr = GetCommandLine_()
If CommandLinePtr <> 0
  CommandLineStr = PeekS(GetCommandLine_())
  CommandLineStr = RemoveString(CommandLineStr, ModuleFileName, #PB_String_NoCase, 1, 1)
  CommandLineStr = Trim(CommandLineStr); does spaces
  CommandLineStr = Trim(CommandLineStr, InvComma)
  CommandLineStr = Trim(CommandLineStr); does spaces
  CommandLineStr = Trim(CommandLineStr, InvComma)
  CommandLineStr = Trim(CommandLineStr); does spaces
EndIf
If CommandLineStr <> ""
  RequiredVolumeName = CommandLineStr
EndIf  

DriveStringsLen = GetLogicalDriveStrings_(Nsize, @AllDriveStringsArray(0))
If DriveStringsLen <> 0
  Repeat
    DeviceStr = ""
    Repeat
      If AllDriveStringsArray(Counter) <> 0
        DeviceStr = DeviceStr + Chr(AllDriveStringsArray(Counter))
        AllDriveStringsStr = AllDriveStringsStr + Chr(AllDriveStringsArray(Counter))
        Counter = Counter + 1
      Else
        Break
      EndIf
    ForEver
    Counter + 1
    Counter2 + 1
    DeviceStrArray(Counter2) = DeviceStr
  Until Counter >= DriveStringsLen
  SortArray(DeviceStrArray(),#PB_Sort_Descending | #PB_Sort_NoCase, 1, Counter2)
  For inc = 0 To counter2
    VolumeName = Space(#MAX_PATH + 1)
    FileSystemName = Space(#MAX_PATH + 1)
    ReturnValue = GetVolumeInformation_(DeviceStrArray(inc), @VolumeName, #MAX_PATH + 1, 0, 0, 0, @FileSystemName, Len(FileSystemName))
    If ReturnValue <> 0
      VolumeNameArray(Inc) = VolumeName    
    Else   
      VolumeNameArray(Inc) = "error"
    EndIf  
  Next
EndIf

If RequiredVolumeName <> ""
  If FindString(RequiredVolumeName, ":", 1) <> 0 ; it's a drive letter
    DoByDriveLetter()
  Else  
    DoByVolumeLabel()
  EndIf
Else
  DoByAlphabet()
EndIf

Re: Eject USB drive

Posted: Wed Dec 02, 2015 5:04 am
by IdeasVacuum
CalamityJames :cry: On Win7 32bit

Fails at:

Code: Select all

ReturnValue = DeviceIoControl_(DriveHandle, #FSCTL_LOCK_VOLUME, 0, 0, 0, 0, @bytes, 0)
ReturnValue = 0
However, the API error message is:
The operation completed successfully.
?

Re: Eject USB drive

Posted: Wed Dec 02, 2015 4:06 pm
by IdeasVacuum
OK, got it to work with the following changes:

Existing

Code: Select all

DriveHandle = CreateFile_("\\.\" + VolLetter, 0, #FILE_SHARE_READ | #FILE_SHARE_WRITE, 0, #OPEN_EXISTING, 0, 0)
[/size]
Replacement

Code: Select all

DriveHandle = CreateFile_("\\.\" + VolLetter, #GENERIC_READ | #GENERIC_WRITE, #FILE_SHARE_READ | #FILE_SHARE_WRITE, 0, #OPEN_EXISTING, #FILE_ATTRIBUTE_NORMAL, 0)
[/size]
After

Code: Select all

If DriveHandle <> #INVALID_HANDLE_VALUE
[/size]
Add

Code: Select all

FlushFileBuffers_(DriveHandle)
[/size]

Re: Eject USB drive

Posted: Wed Dec 02, 2015 9:33 pm
by CalamityJames
I have incorporated IdeasVacuum's changes into the original code and tested a 32bit version (Unicode and Ascii) under Windows 7 64 bit and Windows XP 32 bit). I have tested a version compiled with Purebasic 5.40 64 bit (Unicode and Ascii) under Windows 7 64 bit. IdeasVacuum has tested it under Window 7 32 bit. Hopefully all the wrinkles have now been ironed out. Thanks to IdeasVacuum and all the other people whose code has found its way into this program.

Code: Select all

; Uses http://www.purebasic.fr/english/viewtopic.php?f=12&t=26055
;and http://forums.purebasic.com/english/viewtopic.php?f=12&t=20763
; and http://msdn.microsoft.com/en-us/library/windows/desktop/aa363215%28v=vs.85%29.aspx for Detecting Media Removal
EnableExplicit
Global DriveStringsLen.l, DeviceStr.s, Counter.i, DriveType.i, CommandLinePtr.i, CommandLineStr.s
Global Counter2.i, Inc.i, ModuleFileName.s, RequiredVolumeName.s, LenOfName.i, AllDriveStringsStr.s
Global Dim DeviceStrArray.s(100), Dim VolumeNameArray.s(100)
Global WindowOrigProc.i, EventId.i, *Dbhdr.DEV_BROADCAST_HDR, *Dbv.DEV_BROADCAST_VOLUME
Global DriveRemovedLetter.s, MessageStr.s
Global Dim StdIconHandles(#IDI_WINLOGO - #IDI_APPLICATION)
#WindowWidth = 470
#DBTF_MEDIA = 1
#DBTF_NET = 2
Dim AllDriveStringsArray.c(512)
Define Nsize.i = 512
Global VolumeName.s
Global FileSystemName.s
Global InvComma.s = Chr(34)
Global ReturnValue.i
#FSCTL_LOCK_VOLUME=$90018
#FSCTL_UNLOCK_VOLUME=$9001C
#FSCTL_DISMOUNT_VOLUME=$90020
#IOCTL_STORAGE_EJECT_MEDIA=$2D4808
#IOCTL_STORAGE_QUERY_PROPERTY  = $2D1400
#IOCTL_STORAGE_MEDIA_REMOVAL=$2D4804
#StorageDeviceProperty = 0
#PropertyStandardQuery = 0
#BusTypeUsb = 7
#vbCR = Chr(13)

Structure PREVENT_MEDIA_REMOVAL
  PreventMediaRemoval.i
EndStructure
   
Structure STORAGE_PROPERTY_QUERY
  PropertyId.i
  QueryType.i
  AdditionalParameters.i
EndStructure

Structure STORAGE_DEVICE_DESCRIPTOR
  Version.l;                                 As Dword
  Size.l;                                    As Dword
  DeviceType.b;                              As Byte
  DeviceTypeModifier.b;                      As Byte
  RemovableMedia.b;                          As Byte
  CommandQueueing.b;                         As Byte
  VendorIdOffset.l;                          As Dword
  ProductIdOffset.l;                         As Dword
  ProductRevisionOffset.l;                   As Dword
  SerialNumberOffset.l;                      As Dword
  BusType.l;                                 As Integer
  RawPropertiesLength.l;                     As Long
  RawDeviceProperties.b;                     As Byte
  Reserved.b[1024]
EndStructure
    
Procedure GetStdIcons()
Protected Inc.i
For Inc = #IDI_APPLICATION To #IDI_WINLOGO
  StdIconHandles(Inc - #IDI_APPLICATION) = LoadIcon_(0, Inc)
Next
EndProcedure

Procedure DisplayApiError(LastError.i)
Protected MessLen.i, MessString.s
MessLen = 256
MessString = Space(MessLen)
MessLen = FormatMessage_(#FORMAT_MESSAGE_FROM_SYSTEM, 0, LastError, 0, @MessString, MessLen, 0)
MessageRequester("EjectUSB",  Left(MessString, MessLen)) 
EndProcedure

Procedure CentreAnyFormInWorkArea(WindowNo.i)
Protected WindowTop.i, WindowLeft.i, WindowWidth.i, WindowHeight.i, ReturnValue.i, WorkAreaRect.RECT
Protected SpareHeight.i, SpareWidth.i, WorkAreaHeight.i, WorkAreaWidth.i, WindowRect.rect
ReturnValue = SystemParametersInfo_(#SPI_GETWORKAREA, 0, @WorkAreaRect, 0)
GetWindowRect_(WindowID(WindowNo),@WindowRect)
WindowWidth = WindowRect\right - WindowRect\left
WindowHeight = WindowRect\bottom - WindowRect\top
WorkAreaHeight = WorkAreaRect\Bottom - WorkAreaRect\Top
WorkAreaWidth = WorkAreaRect\Right - WorkAreaRect\Left
SpareHeight = Int((WorkAreaHeight - WindowHeight) / 2)
SpareWidth = Int((WorkAreaWidth - WindowWidth) / 2)
WindowTop = WorkAreaRect\Top + SpareHeight
WindowLeft = WorkAreaRect\Left + SpareWidth
ReturnValue = MoveWindow_(WindowID(WindowNo), WindowLeft, WindowTop, WindowWidth, WindowHeight, 0)
EndProcedure

Procedure SendWrongDriveMessage()
Protected LabelText.s, NewMessageText.s
  MessageStr = ReplaceString(MessageStr, "now", "still")
For Inc = 1 To 26
  If Left(DeviceStrArray(Inc), 2) = DriveRemovedLetter
    If VolumeNameArray(Inc) = ""
      LabelText = " (unlabelled)"  
    ElseIf VolumeNameArray(Inc) = "error"  
      LabelText = ""
    Else 
     LabelText = " (labelled " + InvComma + VolumeNameArray(Inc) + InvComma + ")" 
    EndIf
  EndIf  
Next
NewMessageText = "You have just removed drive " + DriveRemovedLetter + LabelText +  ", which is not the one which was made safe for removal." + #vbcr + #vbcr + MessageStr
HideGadget(1, #True)
TextGadget(2, GadgetWidth(0) + 10 , 10, #WindowWidth - 2 * (GadgetWidth(0) + 10), 84, NewMessageText, #PB_Text_Center)
SetGadgetText(1, NewMessageText)
 
EndProcedure


Procedure.i WindSubClasser(hWnd.i, Msg.i, wParam.i, lParam.i)
Protected Mask.i, Inc.i
  Select Msg
  Case #WM_DEVICECHANGE
    Select wparam
        Case #DBT_DEVICEREMOVECOMPLETE
        *DBHDR.DEV_BROADCAST_HDR=lParam    
      Select *dbhdr\dbch_devicetype    
        Case #DBT_DEVTYP_VOLUME
          *Dbv.DEV_BROADCAST_VOLUME = lParam
           If *dbv\dbcv_flags <> #DBTF_MEDIA And  *dbv\dbcv_flags <> #DBTF_NET ; neither should be true for USB drive 
              Mask = *dbv\dbcv_unitmask
              If Mask = 0 
                DriveRemovedLetter = "A:"
              Else
                For Inc = 1 To 26 
                  If Mask & 1 
                    Break
                  EndIf
                  Mask = Mask >> 1
                Next  
                DriveRemovedLetter = Chr(Inc + 64)+ ":"
                If UCase(DriveRemovedLetter) = UCase(DeviceStr)
                  ReturnValue = PostMessage_(hWnd, #WM_CLOSE, 0, 0)   ;closes
                Else 
                  SendWrongDriveMessage()
                EndIf    
              EndIf
          EndIf        
      EndSelect
    EndSelect    
EndSelect
ProcedureReturn CallWindowProc_(WindowOrigProc, hWnd, Msg, wParam, lParam)
EndProcedure


Procedure WaitForRemoval()

If OpenWindow(0, 24, 24, #WindowWidth, 94, "Eject USB", #PB_Window_Invisible | #PB_Window_SystemMenu)  
  GetStdIcons()
  ImageGadget(0,  10, 10, 100, 83, StdIconHandles( #IDI_INFORMATION - #IDI_APPLICATION))
  
  TextGadget(1, GadgetWidth(0) + 10 , 30, #WindowWidth - 2 * (GadgetWidth(0) + 10), 50, MessageStr, #PB_Text_Center)
  CentreAnyFormInWorkArea(0)
  HideWindow(0, #False)
  StickyWindow(0, #True)
  MessageBeep_(#MB_OK)
  WindowOrigProc = GetWindowLong_(WindowID(0), #GWL_WNDPROC)
  SetWindowLong_(WindowID(0), #GWL_WNDPROC, @WindSubClasser())
  Repeat
    EventID = WaitWindowEvent() 
   Until EventId = #PB_Event_CloseWindow ; close window sent in the subclass procedure
EndIf
End
EndProcedure

Procedure DoEject(VolLetter.s)
Protected  DriveHandle.i, bytes.i, ReturnValue.i, PMR.PREVENT_MEDIA_REMOVAL
DriveHandle = CreateFile_("\\.\" + VolLetter, #GENERIC_READ | #GENERIC_WRITE, #FILE_SHARE_READ | #FILE_SHARE_WRITE, 0, #OPEN_EXISTING, #FILE_ATTRIBUTE_NORMAL, 0)
If DriveHandle <> #INVALID_HANDLE_VALUE
  FlushFileBuffers_(DriveHandle)
  ReturnValue = DeviceIoControl_(DriveHandle, #FSCTL_LOCK_VOLUME, 0, 0, 0, 0, @bytes, 0);
  If ReturnValue <> 0 
    ReturnValue = DeviceIoControl_(DriveHandle, #FSCTL_DISMOUNT_VOLUME, 0, 0, 0, 0, @bytes, 0);
    If ReturnValue <> 0
      DeviceIoControl_(DriveHandle,#IOCTL_STORAGE_MEDIA_REMOVAL,@PMR,SizeOf(PREVENT_MEDIA_REMOVAL),#Null,0,@Bytes,#Null)
      If ReturnValue <> 0 
        ReturnValue = DeviceIoControl_(DriveHandle, #IOCTL_STORAGE_EJECT_MEDIA, 0, 0, 0, 0, @bytes, 0);
        If ReturnValue <> 0 
          ReturnValue = CloseHandle_(DriveHandle)
          ProcedureReturn #True
        EndIf
      EndIf
    EndIf
    ReturnValue = DeviceIoControl_(DriveHandle, #FSCTL_UNLOCK_VOLUME, 0, 0, 0, 0, @bytes, 0);
   EndIf
  ReturnValue = CloseHandle_(DriveHandle)
EndIf
ProcedureReturn #False
EndProcedure

Procedure IsDriveUsb(VolLetter.s)
Protected  DriveHandle.i, bytes.i, ReturnValue.i, dwOutBytes.i
Protected udtQuery.STORAGE_PROPERTY_QUERY
Protected udtOut.STORAGE_DEVICE_DESCRIPTOR
DriveHandle = CreateFile_("\\.\" + VolLetter, 0, #FILE_SHARE_READ | #FILE_SHARE_WRITE, 0, #OPEN_EXISTING, 0, 0)
If DriveHandle <> #INVALID_HANDLE_VALUE
  ReturnValue = DeviceIoControl_(DriveHandle, #IOCTL_STORAGE_QUERY_PROPERTY, @udtQuery, SizeOf(udtQuery), @udtOut, SizeOf(udtout), @dwOutBytes, 0)
  If ReturnValue <> 0 
    If udtOut\Bustype = #BusTypeUsb
     CloseHandle_(DriveHandle)  
      ProcedureReturn #True
    EndIf  
  EndIf  
  CloseHandle_(DriveHandle)  
Else
  DisplayApiError(GetLastError_())
EndIf
ProcedureReturn #False
EndProcedure

Procedure DoByAlphabet()
For Inc = 1 To Counter2
  DeviceStr = DeviceStrArray(Inc)
  If Right(DeviceStr, 1) = "\"
    DriveType = GetDriveType_(DeviceStr)
     If DriveType = #DRIVE_REMOVABLE
        VolumeName = Space(#MAX_PATH + 1)
        FileSystemName = Space(#MAX_PATH + 1)
        ReturnValue = GetVolumeInformation_(DeviceStr, @VolumeName, Len(VolumeName), 0, 0, 0, @FileSystemName, StringByteLength(FileSystemName))
        If ReturnValue <> 0
          DeviceStr = Left(DeviceStr, Len(DeviceStr) - 1)
          If IsDriveUsb(DeviceStr) = #True
            Counter = 99999
            If DoEject(DeviceStr)
             MessageStr = "The Usb drive labelled " + InvComma + VolumeName + InvComma + " (Drive " + DeviceStr + ") can now be removed safely."
             WaitForRemoval() ;prgram ends here
               ;MessageRequester("EjectUsb", "The Usb drive labelled " + InvComma + VolumeName + InvComma + " (Drive " + DeviceStr + ") can now be removed safely.", #MB_ICONINFORMATION)  
            Else
              MessageRequester("EjectUsb", "It is NOT safe to remove the Usb drive labelled " + InvComma + VolumeName + InvComma+ " (Drive " + DeviceStr + ")." + Chr(13) + "(A Program must be using the drive.)", #MB_ICONERROR)  
            EndIf
          EndIf
         Break  
        EndIf
      EndIf
  EndIf
Next
If Counter < 99999
  MessageRequester("EjectUsb", "No removable Usb drives found.", #MB_ICONEXCLAMATION)  
EndIf     
EndProcedure

Procedure DoByVolumeLabel()
For Inc = 1 To Counter2
  DeviceStr = DeviceStrArray(Inc)
  If Right(DeviceStr, 1) = "\"
    DriveType = GetDriveType_(DeviceStr)
    If DriveType = #DRIVE_REMOVABLE
      VolumeName = Space(#MAX_PATH + 1)
      FileSystemName = Space(#MAX_PATH + 1)
      GetVolumeInformation_(DeviceStr, @VolumeName, #MAX_PATH + 1, 0, 0, 0, @FileSystemName, Len(FileSystemName))
      DeviceStr = Left(DeviceStr, Len(DeviceStr) - 1)
      If LCase(VolumeName) = LCase(RequiredVolumeName)
        If DoEject(DeviceStr)
          MessageStr = "The Usb drive labelled " + InvComma + VolumeName + InvComma +  " (Drive " + DeviceStr + ") can now be removed safely."
          WaitForRemoval() ;prgram ends here
;          MessageRequester("EjectUsb", "The Usb drive labelled " + InvComma + VolumeName + InvComma +  " (Drive " + DeviceStr + ") can now be removed safely.", #MB_ICONINFORMATION)  
        Else
          MessageRequester("EjectUsb", "It is NOT safe to remove the Usb drive labelled " + InvComma + VolumeName + InvComma+ "." + Chr(13) + "(A Program must be using the drive.)", #MB_ICONERROR)  
        EndIf
  Break  
      EndIf
    EndIf
  EndIf
Next
If LCase(VolumeName) <>  LCase(RequiredVolumeName)
  MessageRequester("EjectUsb", "The removable Usb drive labelled " + InvComma + RequiredVolumeName + InvComma+ " has not been found.",#MB_ICONEXCLAMATION)
EndIf
EndProcedure

Procedure DoByDriveLetter()
If Right(RequiredVolumeName,1) <> "\"
  DeviceStr = RequiredVolumeName + "\"
Else
  DeviceStr = RequiredVolumeName
EndIf
If FindString(UCase(AllDriveStringsStr), UCase(DeviceStr), 1) <> 0
  DriveType = GetDriveType_(DeviceStr)
  If DriveType <> #DRIVE_REMOVABLE
    MessageRequester("EjectUsb", "Drive " + InvComma + RequiredVolumeName + InvComma +  " does not appear to be removable.", #MB_ICONEXCLAMATION)  
  Else  
    VolumeName = Space(#MAX_PATH + 1)
    FileSystemName = Space(#MAX_PATH + 1)
    ReturnValue = GetVolumeInformation_(DeviceStr, @VolumeName, Len(VolumeName), 0, 0, 0, @FileSystemName, Len(FileSystemName))
    If ReturnValue <> 0 ; volume  there
      DeviceStr = Left(DeviceStr, Len(DeviceStr) - 1)
      If IsDriveUsb(DeviceStr) = #True
        If DoEject(DeviceStr)
          MessageStr = "Drive " + InvComma + RequiredVolumeName + InvComma + " (Labelled " + InvComma +  VolumeName + InvComma + ") can now be removed safely."
          WaitForRemoval() ;prgram ends here
          MessageRequester("EjectUsb", "Drive " + InvComma + RequiredVolumeName + InvComma + " (Labelled " + InvComma +  VolumeName + InvComma + ") can now be removed safely.", #MB_ICONINFORMATION)  
        Else
          MessageRequester("EjectUsb", "It is NOT safe to remove the Usb drive labelled " + InvComma + VolumeName + InvComma+ " (Drive " + DeviceStr + ")." + Chr(13) + "(A Program must be using the drive.)", #MB_ICONERROR)  
        EndIf
      Else  
        MessageRequester("EjectUsb", "Drive " + InvComma + RequiredVolumeName + InvComma +  " is not a removable USB Drive.", #MB_ICONEXCLAMATION)  
    EndIf  
    Else  
      MessageRequester("EjectUsb", "Drive " + InvComma + RequiredVolumeName + InvComma +  " not found.", #MB_ICONEXCLAMATION)  
    EndIf  
  EndIf
Else  
  MessageRequester("EjectUsb", "Drive " + InvComma + RequiredVolumeName + InvComma +  " not found.", #MB_ICONEXCLAMATION)  
EndIf
EndProcedure

ModuleFileName = Space(256)
LenOfName = GetModuleFileName_(0,ModuleFileName, 255) 
ModuleFileName = Left(ModuleFileName, LenOfName)

CommandLinePtr = GetCommandLine_()
If CommandLinePtr <> 0
  CommandLineStr = PeekS(GetCommandLine_())
  CommandLineStr = RemoveString(CommandLineStr, ModuleFileName, #PB_String_NoCase, 1, 1)
  CommandLineStr = Trim(CommandLineStr); does spaces
  CommandLineStr = Trim(CommandLineStr, InvComma)
  CommandLineStr = Trim(CommandLineStr); does spaces
  CommandLineStr = Trim(CommandLineStr, InvComma)
  CommandLineStr = Trim(CommandLineStr); does spaces
EndIf
If CommandLineStr <> ""
  RequiredVolumeName = CommandLineStr
EndIf  

DriveStringsLen = GetLogicalDriveStrings_(Nsize, @AllDriveStringsArray(0))
If DriveStringsLen <> 0
  Repeat
    DeviceStr = ""
    Repeat
      If AllDriveStringsArray(Counter) <> 0
        DeviceStr = DeviceStr + Chr(AllDriveStringsArray(Counter))
        AllDriveStringsStr = AllDriveStringsStr + Chr(AllDriveStringsArray(Counter))
        Counter = Counter + 1
      Else
        Break
      EndIf
    ForEver
    Counter + 1
    Counter2 + 1
    DeviceStrArray(Counter2) = DeviceStr
  Until Counter >= DriveStringsLen
  SortArray(DeviceStrArray(),#PB_Sort_Descending | #PB_Sort_NoCase, 1, Counter2)
  For inc = 0 To counter2
    VolumeName = Space(#MAX_PATH + 1)
    FileSystemName = Space(#MAX_PATH + 1)
    ReturnValue = GetVolumeInformation_(DeviceStrArray(inc), @VolumeName, #MAX_PATH + 1, 0, 0, 0, @FileSystemName, Len(FileSystemName))
    If ReturnValue <> 0
      VolumeNameArray(Inc) = VolumeName    
    Else   
      VolumeNameArray(Inc) = "error"
    EndIf  
  Next
EndIf

If RequiredVolumeName <> ""
  If FindString(RequiredVolumeName, ":", 1) <> 0 ; it's a drive letter
    DoByDriveLetter()
  Else  
    DoByVolumeLabel()
  EndIf
Else
  DoByAlphabet()
EndIf