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