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