(http://www.codeproject.com/system/Remov ... Letter.asp)
Enable "Create Unicode executable" option, and then run.
It seems to work fine.
Tested on XP SP2, PB v4.02/4.10 beta 2
Code: Select all
;
; RemoveDriveByLetter.cpp by Uwe Sieber - www.uwe-sieber.de
;
; Simple demonstration how to prepare a disk drive for save removal
;
; Works with removable and fixed drives under W2K, XP, W2K3, Vista
;
; Console application - expects the drive letter of the drive to remove as parameter
;
; you are free to use this code in your projects
;
EnableExplicit
Macro CTL_CODE(DeviceType, Function, Method, Access)
(((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))
EndMacro
#FILE_DEVICE_MASS_STORAGE = $0000002d
#IOCTL_STORAGE_BASE = #FILE_DEVICE_MASS_STORAGE
#FILE_ANY_ACCESS = 0
#METHOD_BUFFERED = 0
#IOCTL_STORAGE_GET_DEVICE_NUMBER = CTL_CODE(#IOCTL_STORAGE_BASE, $0420, #METHOD_BUFFERED, #FILE_ANY_ACCESS)
#CM_REMOVE_NO_RESTART = $00000002
#DIGCF_PRESENT = $00000002
#DIGCF_ALLCLASSES = $00000004
#DIGCF_PROFILE = $00000008
#DIGCF_DEVICEINTERFACE = $00000010
#PNP_VetoTypeUnknown = 0
#CR_SUCCESS = 0
Structure PSP_DEVICE_INTERFACE_DETAIL_DATA
cbSize.l
DevicePath.s{1024}
EndStructure
Structure SP_DEVINFO_DATA
cbSize.l
ClassGuid.GUID
DevInst.l
Reserved.l
EndStructure
Structure SP_DEVICE_INTERFACE_DATA
cbSize.l
InterfaceClassGuid.GUID
Flags.l
Reserved.l
EndStructure
Structure STORAGE_DEVICE_NUMBER
DeviceType.l ;// The FILE_DEVICE_XXX type For this device.
DeviceNumber.l ;// The number of this device
PartitionNumber.l ;// If the device is partitionable, the partition number of the device. Otherwise -1
EndStructure
Prototype.l CM_Get_Parent(*DevInst, DevInst, Flags)
; CM_Get_Parent(
; OUT PDEVINST pdnDevInst,
; IN DEVINST dnDevInst,
; IN ULONG ulFlags
; );
Prototype.l CM_Get_Device_IDW(DevInst, DeviceIdString.s, BufferLen, Flags)
; CM_Get_Device_IDW(
; IN DEVINST dnDevInst,
; OUT PWCHAR Buffer,
; IN ULONG BufferLen,
; IN ULONG ulFlags
; );
Prototype.l CM_Request_Device_EjectW(DevInst, *VetoType, VetoName.s, NameLength, Flags)
; CM_Request_Device_EjectW(
; IN DEVINST dnDevInst,
; OUT PPNP_VETO_TYPE pVetoType,
; OUT LPWSTR pszVetoName,
; IN ULONG ulNameLength,
; IN ULONG ulFlags
; );
Prototype.l CM_Query_And_Remove_SubTreeW(Ancestor, *VetoType, VetoName.s, NameLength, Flags)
; CM_Query_And_Remove_SubTreeW(
; IN DEVINST dnAncestor,
; OUT PPNP_VETO_TYPE pVetoType,
; OUT LPWSTR pszVetoName,
; IN ULONG ulNameLength,
; IN ULONG ulFlags
; );
Prototype.l SetupDiEnumDeviceInterfaces(DeviceInfoSet, DeviceInfoData, *InterfaceClassGuid, MemberIndex, *DeviceInterfaceData)
; SetupDiEnumDeviceInterfaces(
; IN HDEVINFO DeviceInfoSet,
; IN PSP_DEVINFO_DATA DeviceInfoData, OPTIONAL
; IN CONST GUID *InterfaceClassGuid,
; IN DWORD MemberIndex,
; OUT PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData
; );
Prototype.l SetupDiGetDeviceInterfaceDetailW(DeviceInfoSet, DeviceInterfaceData, *DeviceInterfaceDetailData, DeviceInterfaceDetailDataSize, *RequiredSize, *DeviceInfoData)
; SetupDiGetDeviceInterfaceDetailW(
; IN HDEVINFO DeviceInfoSet,
; IN PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData,
; OUT PSP_DEVICE_INTERFACE_DETAIL_DATA_W DeviceInterfaceDetailData, OPTIONAL
; IN DWORD DeviceInterfaceDetailDataSize,
; OUT PDWORD RequiredSize, OPTIONAL
; OUT PSP_DEVINFO_DATA DeviceInfoData OPTIONAL
; );
Global CM_Get_Parent.CM_Get_Parent
Global CM_Get_Device_ID.CM_Get_Device_IDW
Global CM_Request_Device_EjectW.CM_Request_Device_EjectW
Global CM_Query_And_Remove_SubTreeW.CM_Query_And_Remove_SubTreeW
Global SetupDiEnumDeviceInterfaces.SetupDiEnumDeviceInterfaces
Global SetupDiGetDeviceInterfaceDetail.SetupDiGetDeviceInterfaceDetailW
; ----------------------------------------------------------------------
; returns the device instance handle of a storage volume or 0 on error
; ----------------------------------------------------------------------
Procedure.l GetDrivesDevInstByDeviceNumber(DeviceNumber, DriveType, DosDeviceName.s)
Protected IsFloppy, *guid, Index, res, Size, hDevInfo
Protected pspdidd.PSP_DEVICE_INTERFACE_DETAIL_DATA, spdid.SP_DEVICE_INTERFACE_DATA, spdd.SP_DEVINFO_DATA
Protected sdn.STORAGE_DEVICE_NUMBER
Protected hDrive, BytesReturned
IsFloppy = FindString(DosDeviceName, "\Floppy", 1) ; // who knows a better way?
Select DriveType
Case #DRIVE_REMOVABLE
If IsFloppy
*guid = ?GUID_DEVINTERFACE_FLOPPY
Else
*guid = ?GUID_DEVINTERFACE_DISK
EndIf
Case #DRIVE_FIXED
*guid = ?GUID_DEVINTERFACE_DISK
Case #DRIVE_CDROM
*guid = ?GUID_DEVINTERFACE_CDROM
Default
ProcedureReturn 0
EndSelect
; Get device interface info set handle for all devices attached to system
hDevInfo = SetupDiGetClassDevs_(*guid, 0, 0, #DIGCF_PRESENT|#DIGCF_DEVICEINTERFACE)
If hDevInfo = #INVALID_HANDLE_VALUE
ProcedureReturn 0
EndIf
; Retrieve a context structure for a device interface of a device information set
spdid\cbSize = SizeOf(SP_DEVICE_INTERFACE_DATA)
Repeat
res = SetupDiEnumDeviceInterfaces(hDevInfo, 0, *guid, Index, @spdid)
If res = 0
Break
EndIf
SetupDiGetDeviceInterfaceDetail(hDevInfo, @spdid, 0, 0, @Size, 0) ; check the buffer size
If Size<>0 And Size<=SizeOf(PSP_DEVICE_INTERFACE_DETAIL_DATA)
pspdidd\cbSize = 6 ;For Unicode. ASCII=5
spdd\cbSize = SizeOf(SP_DEVINFO_DATA)
;res = SetupDiGetDeviceInterfaceDetail(hDevInfo, @spdid, @pspdidd, Size, @Size, @spdd)
res = SetupDiGetDeviceInterfaceDetail(hDevInfo, @spdid, @pspdidd, Size, 0, @spdd)
If res
; in case you are interested in the USB serial number:
; the device id string contains the serial number if the device has one,
; otherwise a generated id that contains the '&' char...
; Protected DevInstParent, DeviceIdString.s=Space(#MAX_PATH)
; CM_Get_Parent(@DevInstParent, spdd\DevInst, 0)
; CM_Get_Device_ID(DevInstParent, DeviceIdString, #MAX_PATH, 0)
; Debug "DeviceId= " + DeviceIdString
; open the disk or cdrom or floppy
hDrive = CreateFile_(@pspdidd\DevicePath, 0, #FILE_SHARE_READ|#FILE_SHARE_WRITE, 0, #OPEN_EXISTING, 0, 0)
If hDrive <> #INVALID_HANDLE_VALUE
; get its device number
BytesReturned = 0
res = DeviceIoControl_(hDrive, #IOCTL_STORAGE_GET_DEVICE_NUMBER, 0, 0, @sdn, SizeOf(STORAGE_DEVICE_NUMBER), @BytesReturned, 0)
If res
If DeviceNumber = sdn\DeviceNumber ; match the given device number with the one of the current device
CloseHandle_(hDrive)
SetupDiDestroyDeviceInfoList_(hDevInfo)
ProcedureReturn spdd\DevInst
EndIf
EndIf
CloseHandle_(hDrive)
EndIf
EndIf
EndIf
Index + 1
ForEver
SetupDiDestroyDeviceInfoList_(hDevInfo)
ProcedureReturn 0
EndProcedure
Procedure RemoveDrive(drive$)
Protected RootPath.s, DevicePath.s, VolumeAccessPath.s, hVolume
Protected sdn.STORAGE_DEVICE_NUMBER, res, DriveType, DeviceNumber=-1
Protected DosDeviceName.s
Protected DevInst, BytesReturned, DevInstParent
Protected VetoType, VetoNameW.s, tries, bSuccess=#False
drive$ = Left(UCase(drive$), 1)
If drive$<"A" Or drive$>"Z" : Goto exit : EndIf
DevicePath = drive$ + ":" ; "X:" -> for QueryDosDevice
RootPath = DevicePath + "\" ; "X:\" -> for GetDriveType
VolumeAccessPath = "\\.\" + DevicePath ; "\\.\X:" -> to open the volume
; open the storage volume
hVolume = CreateFile_(VolumeAccessPath, 0, #FILE_SHARE_READ|#FILE_SHARE_WRITE, 0, #OPEN_EXISTING, 0, 0)
If hVolume <> #INVALID_HANDLE_VALUE
; get the volume's device number
res = DeviceIoControl_(hVolume, #IOCTL_STORAGE_GET_DEVICE_NUMBER, 0, 0, @sdn, SizeOf(STORAGE_DEVICE_NUMBER), @BytesReturned, 0)
If res
DeviceNumber = sdn\DeviceNumber
EndIf
CloseHandle_(hVolume)
If DeviceNumber = -1
Goto exit
EndIf
; get the drive type which is required To match the device numbers correctely
DriveType = GetDriveType_(RootPath)
; get the dos device name (like \device\floppy0) to decide if it's a floppy or not - who knows a better way?
DosDeviceName = Space(#MAX_PATH)
res = QueryDosDevice_(DevicePath, DosDeviceName, #MAX_PATH)
If res = 0
Goto exit
EndIf
; get the device instance handle of the storage volume by means of a SetupDi enum and matching the device number
DevInst = GetDrivesDevInstByDeviceNumber(DeviceNumber, DriveType, DosDeviceName)
If DevInst = 0
Goto exit
EndIf
;VetoType = #PNP_VetoTypeUnknown ;#PNP_VetoTypeUnknown=0
VetoNameW = Space(#MAX_PATH)
; get drives's parent, e.g. the USB bridge, the SATA port, an IDE channel with two drives!
res = CM_Get_Parent(@DevInstParent, DevInst, 0)
For tries = 1 To 3 ; sometimes we need some tries...
; CM_Query_And_Remove_SubTree doesn't work for restricted users
;res = CM_Query_And_Remove_SubTreeW(DevInstParent, @VetoType, VetoNameW, #MAX_PATH, #CM_REMOVE_NO_RESTART); ; CM_Query_And_Remove_SubTreeA is not implemented under W2K!
;res = CM_Query_And_Remove_SubTreeW(DevInstParent, 0, #NUL$, 0, #CM_REMOVE_NO_RESTART); ; with messagebox (W2K, Vista) or balloon (XP)
res = CM_Request_Device_EjectW(DevInstParent, @VetoType, VetoNameW, #MAX_PATH, 0)
;res = CM_Request_Device_EjectW(DevInstParent, 0, #NUL$, 0, 0); ; with messagebox (W2K, Vista) or balloon (XP)
If res=#CR_SUCCESS And VetoType=#PNP_VetoTypeUnknown
bSuccess = 1
Break
Else
bSuccess = 0
EndIf
Sleep_(500) ; required to give the next tries a chance!
Next
If bSuccess
Debug "Success"
Else
Debug "failed"
Debug res
Debug VetoNameW
EndIf
EndIf
exit:
ProcedureReturn
EndProcedure
; -----------------------
; test code
; -----------------------
Define drive$
#setupapi = 1
If OpenLibrary(#setupapi, "setupapi.dll")
CM_Get_Parent = GetFunction(#setupapi, "CM_Get_Parent")
CM_Get_Device_ID = GetFunction(#setupapi, "CM_Get_Device_IDW")
CM_Request_Device_EjectW = GetFunction(#setupapi, "CM_Request_Device_EjectW")
CM_Query_And_Remove_SubTreeW = GetFunction(#setupapi, "CM_Query_And_Remove_SubTreeW")
SetupDiEnumDeviceInterfaces = GetFunction(#setupapi, "SetupDiEnumDeviceInterfaces")
SetupDiGetDeviceInterfaceDetail = GetFunction(#setupapi, "SetupDiGetDeviceInterfaceDetailW")
drive$ = "z" ;any drive letter to want to remove.
RemoveDrive(drive$)
CloseLibrary(#setupapi)
EndIf
End
DataSection
GUID_DEVINTERFACE_FLOPPY:
Data.l $53f56311
Data.w $b6bf, $11d0
Data.b $94, $f2, $00, $a0, $c9, $1e, $fb, $8b
GUID_DEVINTERFACE_DISK:
Data.l $53f56307
Data.w $b6bf, $11d0
Data.b $94, $f2, $00, $a0, $c9, $1e, $fb, $8b
GUID_DEVINTERFACE_CDROM:
Data.l $53f56308
Data.w $b6bf, $11d0
Data.b $94, $f2, $00, $a0, $c9, $1e, $fb, $8b
EndDataSection