Code : Tout sélectionner
;
; 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_IDA(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_EjectA(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_SubTreeA(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 SetupDiGetDeviceInterfaceDetailA(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_IDA
Global CM_Request_Device_EjectA.CM_Request_Device_EjectA
Global CM_Query_And_Remove_SubTreeA.CM_Query_And_Remove_SubTreeA
Global SetupDiEnumDeviceInterfaces.SetupDiEnumDeviceInterfaces
Global SetupDiGetDeviceInterfaceDetail.SetupDiGetDeviceInterfaceDetailA
; ----------------------------------------------------------------------
; 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 = 5 ; Unicode=6, 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
Debug pspdidd\DevicePath
; 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)
Debug DosDeviceName
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!
If CM_Get_Parent(@DevInstParent, DevInst, 0)
CM_Request_Device_EjectA(DevInstParent, 0, "", 0, 0)
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_IDA")
CM_Request_Device_EjectA = GetFunction(#setupapi, "CM_Request_Device_EjectA")
CM_Query_And_Remove_SubTreeA = GetFunction(#setupapi, "CM_Query_And_Remove_SubTreeA")
SetupDiEnumDeviceInterfaces = GetFunction(#setupapi, "SetupDiEnumDeviceInterfaces")
SetupDiGetDeviceInterfaceDetail = GetFunction(#setupapi, "SetupDiGetDeviceInterfaceDetailA")
drive$ = "h" ;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