MS EnumCD example converted to PureBasic

Share your advanced PureBasic knowledge/code with the community.
nalor
Enthusiast
Enthusiast
Posts: 121
Joined: Thu Apr 02, 2009 9:48 pm

MS EnumCD example converted to PureBasic

Post by nalor »

Just in case it helps someone, I've converted the EnumCD example from Microsoft to PureBasic (the original can be found here: https://support.microsoft.com/en-us/kb/305184 )

Code: Select all

EnableExplicit

; Purebasic Conversion of EnumCD from Microsoft - https://support.microsoft.com/en-us/kb/305184

; ###################################################################
;- ~ Prototypes
; ###################################################################

Prototype ProtoSetupDiGetClassDevs(*ClassGuid, *Enumerator, hwndParent.i, Flags.l)
If OpenLibrary(0, "setupapi.dll")
CompilerIf #PB_Compiler_Unicode=0
	Global SetupDiGetClassDevs.ProtoSetupDiGetClassDevs = GetFunction(0, "SetupDiGetClassDevsA")
CompilerElse
	Global SetupDiGetClassDevs.ProtoSetupDiGetClassDevs = GetFunction(0, "SetupDiGetClassDevsW")
CompilerEndIf
	CloseLibrary(0)
EndIf 


; ###################################################################
;- ~ Constants
; ###################################################################
#SPDRP_HARDWAREID = 1

DataSection
	GUID_DEVCLASS_CDROM:
	Data.l $4D36E965 : Data.w $E325, $11CE : Data.b $BF, $C1, $08, $00, $2B, $E1, $03, $18
	
	GUID_DEVINTERFACE_CDROM:
	Data.l $53f56308 : Data.w $b6bf, $11d0 : Data.b $94, $f2, $00, $a0, $c9, $1e, $fb, $8b
EndDataSection

#StorageAdapterProperty = 1 ; https://msdn.microsoft.com/en-us/library/windows/desktop/ff800840%28v=vs.85%29.aspx
#StorageDeviceProperty  = 0 ; https://msdn.microsoft.com/en-us/library/windows/desktop/ff800840%28v=vs.85%29.aspx
#PropertyStandardQuery  = 0 ; https://msdn.microsoft.com/en-us/library/windows/desktop/ff800840%28v=vs.85%29.aspx

#FILE_DEVICE_CD_ROM = 02
#FILE_DEVICE_DVD    = 51

#SCSI_MAX_CDB_LEN=16
#SCSI_MAX_SENSE_LEN=24; 64
#SCSI_MAX_INDIRECT_DATA=$FFFF ; 16384

#IOCTL_SCSI_PASS_THROUGH          = $0004D004
#IOCTL_STORAGE_QUERY_PROPERTY     = $002D1400
#IOCTL_STORAGE_GET_MEDIA_TYPES_EX = $002D0C04

#SCSI_IOCTL_DATA_IN = 1

; from EnumCD.h file:

; Command Descriptor Block constants.
#CDB6GENERIC_LENGTH    =6

; SCSI CDB operation codes
#SCSIOP_INQUIRY        =$12
#SCSIOP_MODE_SENSE     =$1A

#MODE_PAGE_CAPABILITIES=$2A

#DebugLevel = 1
;  0 = Suppress All Messages
;  1 = Display & Fatal Error Message
;  2 = Warning & Debug Messages
;  3 = Informational Messages



; ###################################################################
;- ~ Structures
; ###################################################################

Structure SP_DEVINFO_DATA Align #PB_Structure_AlignC
  cbSize.l
  ClassGuid.GUID
  DevInst.l;    // DEVINST handle
  Reserved.l;
EndStructure

Structure SP_DEVICE_INTERFACE_DETAIL_DATA Align #PB_Structure_AlignC
  cbSize.l                    ; DWORD cbSize
  DevicePath.s{1024}          ; TCHAR DevicePath[ANYSIZE_ARRAY]
EndStructure

Structure STORAGE_PROPERTY_QUERY Align #PB_Structure_AlignC
  PropertyId.l;STORAGE_PROPERTY_ID
  QueryType.l;STORAGE_QUERY_TYPE
  AdditionalParameters.l
EndStructure

Structure STORAGE_ADAPTER_DESCRIPTOR Align #PB_Structure_AlignC
	Version.l         ; DWORD   Version;
	Size.l				; DWORD   Size;
	MaximumTransferLength.l ; DWORD   MaximumTransferLength;
	MaximumPhysicalPages.l	; DWORD   MaximumPhysicalPages;
	AlignmentMask.l			; DWORD   AlignmentMask;
	AdapterUsesPio.a			; BOOLEAN AdapterUsesPio;
	AdapterScansDown.a		; BOOLEAN AdapterScansDown;
	CommandQueueing.a			; BOOLEAN CommandQueueing;
	AcceleratedTransfer.a	; BOOLEAN AcceleratedTransfer;
	BusType.a					; BYTE    BusType;
	BusMajorVersion.u			; WORD    BusMajorVersion;
	BusMinorVersion.u			; WORD    BusMinorVersion;
EndStructure

; https://msdn.microsoft.com/en-us/library/windows/desktop/ff800835%28v=vs.85%29.aspx
Structure STORAGE_DEVICE_DESCRIPTOR Align #PB_Structure_AlignC
  Version.l                ; DWORD            Version
  Size.l                   ; DWORD            Size
  DeviceType.a             ; BYTE             DeviceType
  DeviceTypeModifier.a     ; BYTE             DeviceTypeModifier
  RemovableMedia.a         ; BOOLEAN          RemovableMedia
  CommandQueueing.a        ; BOOLEAN          CommandQueueing
  VendorIdOffset.l         ; DWORD            VendorIdOffset
  ProductIdOffset.l        ; DWORD            ProductIdOffset
  ProductRevisionOffset.l  ; DWORD            ProductRevisionOffset
  SerialNumberOffset.l     ; DWORD            SerialNumberOffset
  BusType.u                ; STORAGE_BUS_TYPE BusType  >> Wert aus ENUM STORAGE_BUS_TYPE
  RawPropertiesLength.l    ; DWORD            RawPropertiesLength
  RawDeviceProperties.a    ; BYTE             RawDeviceProperties[1] >> Contains an array of length one that serves as a place holder for the first byte of the bus specific property data
EndStructure

Structure GET_MEDIA_TYPES Align #PB_Structure_AlignC
	DeviceType.l      ;   DWORD             DeviceType;
	MediaInfoCount.l  ;   DWORD             MediaInfoCount;
	MediaInfo.i       ;   DEVICE_MEDIA_INFO MediaInfo[1];
EndStructure


; https://msdn.microsoft.com/en-us/library/windows/hardware/ff565345%28v=vs.85%29.aspx
Structure SCSI_PASS_THROUGH  Align #PB_Structure_AlignC
	Length.u ; USHORT 	Length
	ScsiStatus.a ; UCHAR 	ScsiStatus
	PathId.a		 ; UCHAR 	PathId
	TargetId.a	 ; UCHAR 	TargetId
	Lun.a			 ; UCHAR 	Lun
	CdbLength.a	 ; UCHAR 	CdbLength
	SenseInfoLength.a ; UCHAR 	SenseInfoLength
	DataIn.a				; UCHAR 	DataIn
	DataTransferLength.l ; ULONG 	DataTransferLength
	TimeOutValue.l			; ULONG 	TimeOutValue
	DataBufferOffset.l	; ULONG_PTR 	DataBufferOffset
	SenseInfoOffset.l		; ULONG 	SenseInfoOffset
	Cdb.a[#SCSI_MAX_CDB_LEN]  ; UCHAR 	Cdb [SCSI_MAX_CDB_LEN]
EndStructure

Structure SCSI_PASS_THROUGH_WITH_BUFFERS Align #PB_Structure_AlignC
	spt.SCSI_PASS_THROUGH
; 	Filler.l                      ; ULONG 	Filler
	ucSenseBuf.a[#SCSI_MAX_SENSE_LEN]   ; UCHAR 	ucSenseBuf [SCSI_MAX_SENSE_LEN]
	ucDataBuf.a[#SCSI_MAX_INDIRECT_DATA]; UCHAR 	ucDataBuf [SCSI_MAX_INDIRECT_DATA]
EndStructure

; ###################################################################
;- ~ Procedures
; ###################################################################

Procedure.s HexL(Val.i, Len.a)
	ProcedureReturn RSet(Hex(Val), Len, "0")
EndProcedure

Procedure.s StrL(Val.i, Len.a)
	ProcedureReturn RSet(Str(Val), Len, "0")
EndProcedure

Procedure.s BusType(Type.u)
	; Bus Type

	Select Type
		Case $00 : ProcedureReturn "UNKNOWN"
		Case $01 : ProcedureReturn "SCSI"
		Case $02 : ProcedureReturn "ATAPI"
		Case $03 : ProcedureReturn "ATA"
		Case $04 : ProcedureReturn "IEEE 1394"
		Case $05 : ProcedureReturn "SSA"
		Case $06 : ProcedureReturn "FIBRE"
		Case $07 : ProcedureReturn "USB"
		Case $08 : ProcedureReturn "RAID"
; additional types not available in original MS EnumCD:
		Case $09 : ProcedureReturn "iScsi"
		Case $0A : ProcedureReturn "SAS"
		Case $0B : ProcedureReturn "SATA"
		Case $0C : ProcedureReturn "SD"
		Case $0D : ProcedureReturn "MMC"
		Case $0E : ProcedureReturn "Virtual"
		Case $0F : ProcedureReturn "FileBackedVirtual"
			
		Default  : ProcedureReturn "Bustype=" + Str(Type)
			
	EndSelect
		
EndProcedure


Procedure.s DeviceType(Type.i)
	; SCSI Device Type
	
	Select Type
		Case $00 : ProcedureReturn "Direct Access Device"
		Case $01 : ProcedureReturn "Tape Device"
		Case $02 : ProcedureReturn "Printer Device"
		Case $03 : ProcedureReturn "Processor Device"
		Case $04 : ProcedureReturn "WORM Device"
		Case $05 : ProcedureReturn "CDROM Device"
		Case $06 : ProcedureReturn "Scanner Device"
		Case $07 : ProcedureReturn "Optical Disk"
		Case $08 : ProcedureReturn "Media Changer"
		Case $09 : ProcedureReturn "Comm. Device"
		Case $0A : ProcedureReturn "ASCIT8"
		Case $0B : ProcedureReturn "ASCIT8"
		Case $0C : ProcedureReturn "Array Device"
		Case $0D : ProcedureReturn "Enclosure Device"
		Case $0E : ProcedureReturn "RBC Device"
		Default  : ProcedureReturn "Unknown Device "+ Str(Type)
	EndSelect
	
EndProcedure

Procedure.s YesNo(Bool.a)
	
	Select Bool
		Case #False : ProcedureReturn "No"
		Default     : ProcedureReturn "Yes"
	EndSelect
		
EndProcedure

Procedure DebugPrint( aDebugPrintLevel.a, sDebugMessage.s)
; Routine Description:  This routine print the given string, If given Debug level is <= To the current Debug level.
; Arguments:            DebugPrintLevel - Debug level of the given message
;                       DebugMessage    - Message To be printed
; Return Value:         None
	
	Protected iCnt.i

	If aDebugPrintLevel<=#DebugLevel
		Debug sDebugMessage
		Print(sDebugMessage)
	EndIf
	
EndProcedure


Procedure GetRegistryProperty( iDevInfo.i, iIndex.i )
; Routine Description:  This routine enumerates the CD devices using the Setup class Interface
;                       GUID GUID_DEVCLASS_CDROM. Gets the Device ID from the Registry
;                       property.
; Arguments:            DevInfo - Handles To the device information List
;                       Index   - Device member
; Return Value:         TRUE / FALSE. This decides whether To Continue Or Not
	
	Protected deviceInfoData.SP_DEVINFO_DATA
	Protected iStatus.i
	Protected iErrorCode.i
	Protected dataType.l
	Protected *Buffer=#Null
	Protected iBufferSize.i=0

	deviceInfoData\cbSize = SizeOf(SP_DEVINFO_DATA)
	iStatus = SetupDiEnumDeviceInfo_(iDevInfo, iIndex, @deviceInfoData)
	
	If iStatus=#False
		iErrorCode=GetLastError_()
		If iErrorCode=#ERROR_NO_MORE_ITEMS
			DebugPrint( 2, ~"\nNo more devices.\n")
		Else
			DebugPrint( 1, "SetupDiEnumDeviceInfo failed with error: "+Str(iErrorCode)+~"\n")
		EndIf
		
		ProcedureReturn #False
	EndIf
	
; We won't know the size of the HardwareID buffer until we call
; this function. So call it with a null To begin with, and then 
; use the required buffer size to alloc the necessary space.
; Keep calling we have success Or an unknown failure.
	
	iStatus=SetupDiGetDeviceRegistryProperty_(iDevInfo, @deviceInfoData, #SPDRP_HARDWAREID, @dataType, *Buffer, iBufferSize, @iBufferSize)
	
	If iStatus=#False
		iErrorCode=GetLastError_()
		If iErrorCode<>#ERROR_INSUFFICIENT_BUFFER
			If iErrorCode=#ERROR_INVALID_DATA
				; May be a Legacy Device With no HardwareID. Continue.
				ProcedureReturn #True
			Else
				DebugPrint( 1, "SetupDiGetDeviceInterfaceDetail failed with error: "+Str(iErrorCode)+~"\n")
				ProcedureReturn #False
			EndIf
		EndIf
	EndIf
	
	; We need To change the buffer size.
	
	*Buffer=AllocateMemory(iBufferSize)
	
	iStatus=SetupDiGetDeviceRegistryProperty_(iDevInfo, @deviceInfoData, #SPDRP_HARDWAREID, @dataType, *Buffer, iBufferSize, @iBufferSize)
	If iStatus=#False
		iErrorCode=GetLastError_()
		If iErrorCode=#ERROR_INVALID_DATA
			; May be a Legacy Device With no HardwareID. Continue.
			ProcedureReturn #True
		Else
			DebugPrint( 1, "SetupDiGetDeviceInterfaceDetail failed with error: "+Str(iErrorCode)+~"\n")
			ProcedureReturn #False
		EndIf
	EndIf
	
	DebugPrint( 1, ~"\n\nDevice ID: "+PeekS(*Buffer)+~"\n")
	
	If *Buffer
		FreeMemory(*Buffer)
	EndIf
	
	ProcedureReturn #True
	
EndProcedure

Procedure GetMediaType(hDevice.i, *cdTypeString.STRING)
	
	Protected iStatus.i
	Protected Dim buffer.a(2048)    ;   // Must be big enough hold DEVICE_MEDIA_INFO
	Protected returned.i
	Protected *mediaTypes.GET_MEDIA_TYPES
	Protected iTmp.i
	
	; Get the Media type.
	
	iStatus=DeviceIoControl_(hDevice,
	                         #IOCTL_STORAGE_GET_MEDIA_TYPES_EX,
	                         #Null,
	                         0,
	                         @buffer(),
	                         ArraySize(buffer()),
	                         @returned,
	                         #False)
	
	If Not iStatus
		iTmp=GetLastError_()
		DebugPrint( 2, "IOCTL_STORAGE_GET_MEDIA_TYPES_EX failed with error code "+Str(iTmp)+~"\n\n")
		ProcedureReturn 0
	EndIf
	
	
	*mediaTypes=@buffer()
	
	Select *mediaTypes\DeviceType
		Case #FILE_DEVICE_CD_ROM
			*cdTypeString\s="CD-ROM"
			
		Case #FILE_DEVICE_DVD
			*cdTypeString\s="DVD"
			
		Default
			*cdTypeString\s="Unknown"
			
	EndSelect
	
	
	ProcedureReturn *mediaTypes\DeviceType
	
EndProcedure

Procedure PrintError(ErrorCode.i)
; Routine Description:  Prints formated error message
; Arguments:            ErrorCode   - Error code To print
; Return Value:         None
	
	Protected count.i
	Protected Dim errorBuffer.a(80)
	Protected iTmp.i
	
	count=FormatMessage_(#FORMAT_MESSAGE_FROM_SYSTEM, #Null, ErrorCode, 0, @errorBuffer(0), 80, #Null)
	
	If count <> 0
		DebugPrint( 1, PeekS(@errorBuffer(0))+~"\n")
	Else
		iTmp=GetLastError_()
		DebugPrint( 1, "Format message failed.  Error: "+Str(iTmp)+~"\n")
	EndIf
	
EndProcedure

Procedure PrintSenseInfo(*sptwb.SCSI_PASS_THROUGH_WITH_BUFFERS)
; Routine Description:  Prints the SCSI status And Sense Info from the device
; Arguments:            sptwb      - Pass Through buffer that contains the Sense info
; Return Value:         None
	
	Protected iCnt.i
	Protected sTmp.s
	
	DebugPrint( 1, "Scsi status: "+HexL(*sptwb\spt\ScsiStatus, 2)+~"h\n\n")
	
	If *sptwb\spt\SenseInfoLength=0
		ProcedureReturn
	EndIf
	
	DebugPrint( 3, ~"Sense Info -- consult SCSI spec for details\n")
	DebugPrint( 3, ~"-------------------------------------------------------------\n")
	
	For iCnt=0 To *sptwb\spt\SenseInfoLength-1
		DebugPrint( 3, HexL(*sptwb\ucSenseBuf[iCnt], 2)+" ")
	Next
	
	DebugPrint( 1, ~"\n\n")
	
EndProcedure


Procedure PrintDataBuffer(*DataBuffer, iBufferLength.i)
; Routine Description:  Prints the formated Data buffer
; Arguments:            DataBuffer      - Buffer To be printed
;                       BufferLength    - Length of the buffer
; Return Value:         None
	
	Protected iCnt.i
	Protected sTmp.s
	
	DebugPrint( 3, ~"      00  01  02  03  04  05  06  07   08  09  0A  0B  0C  0D  0E  0F\n")
	DebugPrint( 3, ~"      ---------------------------------------------------------------\n")
	
	sTmp=""
	For iCnt=0 To iBufferLength-1
		If iCnt % 16 = 0
			DebugPrint( 3, " "+HexL(iCnt, 3)+"  ")
		EndIf
		
		DebugPrint( 3, HexL(PeekA(*DataBuffer+iCnt), 2)+"  ")
		
		If ((iCnt+1) % 8 = 0)
			DebugPrint( 3, " ")
		EndIf
		
		If ((iCnt+1) % 16 = 0)
			DebugPrint( 3, ~"\n")
			sTmp=""
		EndIf	
	Next
	DebugPrint( 3, ~"\n\n")
	
EndProcedure


Procedure PrintStatusResults(iStatus.i, iReturned.i, *sptwb.SCSI_PASS_THROUGH_WITH_BUFFERS)
; Routine Description:  Prints the SCSI Inquiry Data from the device
; Arguments:            Status      - Status of the DeviceIOControl
;                       Returned    - Number of bytes returned
;                       Psptwb      - SCSI pass through Structure
; Return Value:         None
	
	Protected iTmp.i
	Protected iCnt.i
	Protected errorCode.i
	Protected devType.u
	
	DebugPrint( 1, ~"\nInquiry Data from Pass Through\n")
	DebugPrint( 1, ~"------------------------------\n")
	
	If Not iStatus
		iTmp=GetLastError_()
		DebugPrint( 1, "Error: "+Str(iTmp))
		PrintError(errorCode)
		ProcedureReturn
	EndIf
	
	If *sptwb\spt\ScsiStatus
		PrintSenseInfo(*sptwb)
		ProcedureReturn
	Else
		devType=*sptwb\ucDataBuf[0] & $1f
		
		; Our Device Table can handle only 16 devices.
		
		DebugPrint( 1, "Device Type: "+DeviceType(devType)+" (0x"+Hex(devType)+~")\n")
		
		DebugPrint( 1, "Vendor ID  : ")
		For iCnt=8 To 15
			DebugPrint( 1, Chr(*sptwb\ucDataBuf[iCnt]))
		Next
		
		DebugPrint( 1, ~"\nProduct ID : ")
		For iCnt=16 To 31
			DebugPrint( 1, Chr(*sptwb\ucDataBuf[iCnt]))
		Next
		
		DebugPrint( 1, ~"\nProduct Rev: ")
		For iCnt=32 To 35
			DebugPrint( 1, Chr(*sptwb\ucDataBuf[iCnt]))
		Next
		
		DebugPrint( 1, ~"\nVendor Str : ")
		For iCnt=36 To 55
			DebugPrint( 1, Chr(*sptwb\ucDataBuf[iCnt]))
		Next
		DebugPrint( 1, ~"\n\n")
		
		DebugPrint( 3, "Scsi status: "+HexL(*sptwb\spt\ScsiStatus, 2)+"h , Bytes returned: "+Hex(iReturned) + "h, ")
		DebugPrint( 3, "Data buffer length: "+Hex(*sptwb\spt\DataTransferLength)+~"h\n\n\n")
		
		PrintDataBuffer(@*sptwb\ucDataBuf[0], 192)
		
	EndIf
	
EndProcedure


Procedure PrintCapResults(iStatus.i, iReturned.i, *sptwb.SCSI_PASS_THROUGH_WITH_BUFFERS)
; Routine Description:  Prints the CD Drive Capabilities
; Arguments:            Status      - Status of the DeviceIOControl
;                       Returned    - Number of bytes returned
;                       Psptwb      - SCSI pass through Structure
; Return Value:         None
	
	Protected iTmp.i
	Protected CDReader.a
	Protected CDwriter.a
	Protected DVDReader.a
	Protected DVDwriter.a
	
	DebugPrint( 1, ~"\nCD Drive Capabilities from Pass Through\n")
	DebugPrint( 1, ~"---------------------------------------\n")
	
	If Not iStatus
		iTmp=GetLastError_()
		DebugPrint( 1, "Error: "+Str(iTmp))
		PrintError(iTmp)
		ProcedureReturn
	EndIf
	
	If (*sptwb\spt\ScsiStatus)
		PrintSenseInfo(*sptwb)
		ProcedureReturn
	Else
		; Notes:
		; 1. The header of 6-byte MODE commands is 4 bytes long. 
		; 2. No Block Descriptors returned before parameter page As was specified when building the Mode command.
		; 3. First two bytes of a parameter page are the Page Code And Page Length bytes.
		; Therefore, our useful Data starts at the 7th byte in the Data buffer. 
		
		CDReader=( *sptwb\ucDataBuf[6] & $01) | (*sptwb\ucDataBuf[6] & $02)
		
		If (CDReader)
			DebugPrint( 1, ~"CD Reader    : Yes\n")
			DebugPrint( 1, "   CD-R disc : "+YesNo(*sptwb\ucDataBuf[6] & $01)+~"\n")
			DebugPrint( 1, "   CD-RW disc: "+YesNo(*sptwb\ucDataBuf[6] & $02)+~"\n")
		Else
			DebugPrint( 1, ~"CD Reader    : No\n")
		EndIf
		
		CDwriter=(*sptwb\ucDataBuf[7] & $01) | (*sptwb\ucDataBuf[7] & $02)
		
		If (CDwriter)
			DebugPrint( 1, ~"\nCD writer    : Yes\n")
			DebugPrint( 1, "   CD-R disc : "+YesNo(*sptwb\ucDataBuf[7] & $01)+~"\n")
			DebugPrint( 1, "   CD-RW disc: "+YesNo(*sptwb\ucDataBuf[7] & $02)+~"\n")
		Else
			DebugPrint( 1, ~"\nCD writer    : No\n")
		EndIf
		
		DVDReader=( *sptwb\ucDataBuf[6] & $08) | ( *sptwb\ucDataBuf[6] & $10) | (*sptwb\ucDataBuf[6] & $20)
		
		If (DVDReader)
			DebugPrint( 1, ~"\nDVD Reader     : Yes\n")
			DebugPrint( 1, "   DVD-ROM disc: "+YesNo(*sptwb\ucDataBuf[6] & $08)+~"\n")
			DebugPrint( 1, "   DVD-R disc  : "+YesNo(*sptwb\ucDataBuf[6] & $10)+~"\n")
			DebugPrint( 1, "   DVD-RAM disc: "+YesNo(*sptwb\ucDataBuf[6] & $20)+~"\n")
			
		Else
			DebugPrint( 1, ~"\nDVD Reader     : No\n")
		EndIf
		
		DVDwriter=( *sptwb\ucDataBuf[7] & $10) | ( *sptwb\ucDataBuf[7] & $20)
		
		If (DVDwriter)
			DebugPrint( 1, ~"\nDVD writer     : Yes\n")
			DebugPrint( 1, "   DVD-R disc  : "+YesNo(*sptwb\ucDataBuf[7] & $10)+~"\n")
			DebugPrint( 1, "   DVD-RAM disc: "+YesNo(*sptwb\ucDataBuf[7] & $20)+~"\n")
		Else
			DebugPrint( 1, ~"\nDVD writer     : No\n")
		EndIf
		
		DebugPrint( 1, ~"\n\n")
		
		DebugPrint( 3, "Scsi status: "+HexL(*sptwb\spt\ScsiStatus, 2)+"h, Bytes returned: "+Hex(iReturned)+"h, ")
		DebugPrint( 3, "Data buffer length: "+Hex(*sptwb\spt\DataTransferLength)+~"h\n\n\n")
		
		PrintDataBuffer(@*sptwb\ucDataBuf[0], 192)
		
		DebugPrint( 1, ~"\n\n")
		
	EndIf
	
EndProcedure


Procedure GetDeviceProperty(iIntDevInfo.i, iIndex.i)
; Routine Description:  This routine enumerates the CD devices using the Device Interface
;                       GUID CdRomClassGuid. Gets the Adapter & Device property from the port
;                       driver. Then sends IOCTL through SPTI To get the device Inquiry Data.
; Arguments:            IntDevInfo - Handles To the Interface device information List
;                       Index      - Device member
; Return Value:         TRUE / FALSE. This decides whether To Continue Or Not
	
	Protected interfaceData.SP_DEVICE_INTERFACE_DATA
	Protected iStatus.i
	Protected iErrorCode.i
	Protected iReqSize.i
	Protected iInterfaceDetailDataSize.i
	Protected *InterfaceDetailData.SP_DEVICE_INTERFACE_DETAIL_DATA=#Null
	Protected hDevice.i
	Protected query.STORAGE_PROPERTY_QUERY
	Protected *adpDesc.STORAGE_ADAPTER_DESCRIPTOR
	Protected Dim outBuf.a(512)
	Protected iReturnedLength.i
	Protected iCdType.i=0
	Protected CdTypeString.STRING
	Protected *devDesc.STORAGE_DEVICE_DESCRIPTOR
	Protected iCnt.i
	Protected iTmp.i
	Protected sptwb.SCSI_PASS_THROUGH_WITH_BUFFERS
	Protected iLength.i
	Protected iReturned.i
	
	
	interfaceData\cbSize=SizeOf(SP_DEVICE_INTERFACE_DATA)
	
	iStatus=SetupDiEnumDeviceInterfaces_(iIntDevInfo,              ; Interface Device Info handle
	                                     0,								; Device Info Data
	                                     ?GUID_DEVINTERFACE_CDROM,	; Interface registered by driver
	                                     iIndex,							; Member
	                                     @interfaceData)				; Device Interface Data
	
	If iStatus=#False
		iErrorCode=GetLastError_()
		If iErrorCode=#ERROR_NO_MORE_ITEMS
			DebugPrint( 2, ~"\nNo more interfaces\n")
		Else
			DebugPrint( 1, "SetupDiEnumDeviceInterfaces failed with error: "+Str(iErrorCode)+~"\n")
		EndIf
		ProcedureReturn #False
	EndIf
	
	; Find out required buffer size, so pass NULL
	
	iStatus=SetupDiGetDeviceInterfaceDetail_(iIntDevInfo,         ; Interface Device info handle
	                                         @interfaceData,		  ; Interface Data for the event class
	                                         #Null,					  ; Checking for buffer size
	                                         0,						  ; Checking for buffer size
	                                         @iReqSize,			  ; Buffer size required to get the detail Data
	                                         #Null)					; Checking for buffer size
	
	; This call returns ERROR_INSUFFICIENT_BUFFER With reqSize
	; set To the required buffer size. Ignore the above error And
	; pass a bigger buffer To get the detail Data
	If iStatus=#False
		iErrorCode=GetLastError_()
		If iErrorCode<>#ERROR_INSUFFICIENT_BUFFER
			DebugPrint( 1, "SetupDiGetDeviceInterfaceDetail failed with error: "+Str(iErrorCode)+~"\n")
			ProcedureReturn #False
		EndIf
	EndIf
	
	; Allocate memory To get the Interface detail Data
	; This contains the devicepath we need To open the device
	
	iInterfaceDetailDataSize=iReqSize
	
	*InterfaceDetailData=AllocateMemory(iInterfaceDetailDataSize)
	
	If Not *InterfaceDetailData
		DebugPrint( 1, ~"Unable to allocate memory to get the interface detail data.\n")
		ProcedureReturn #False
	EndIf
	
	*InterfaceDetailData\cbSize=4+SizeOf(CHARACTER) ; SizeOf(SP_DEVICE_INTERFACE_DETAIL_DATA) >> 4Byte Long + Size of 1 Char
	
	iStatus=SetupDiGetDeviceInterfaceDetail_(iIntDevInfo,               ; Interface Device info handle
	                                         @interfaceData,				  ; Interface Data For the event class
	                                         *InterfaceDetailData,		  ; Interface detail Data
	                                         iInterfaceDetailDataSize,  ; Interface detail Data size
	                                         @iReqSize,					  ; Buffer size required To get the detail Data
	                                         #Null)							  ; Interface device info
	
	If iStatus=#False
		iTmp=GetLastError_()
		DebugPrint( 1, "Error in SetupDiGetDeviceInterfaceDetail failed with error: "+Str(iTmp)+~"\n")
		ProcedureReturn #False
	EndIf
	
	
	; Now we have the device path. Open the device Interface
	; To send Pass Through command
	
	DebugPrint( 2, "Interface: "+ *InterfaceDetailData\DevicePath+~"\n")
	
	hDevice=CreateFile_(*InterfaceDetailData\DevicePath,          ; device Interface name
	                   #GENERIC_READ | #GENERIC_WRITE,				 ; dwDesiredAccess
	                   #FILE_SHARE_READ | #FILE_SHARE_WRITE,		 ; dwShareMode
	                   #Null,												 ; lpSecurityAttributes
	                   #OPEN_EXISTING,									 ; dwCreationDistribution
	                   0,													 ; dwFlagsAndAttributes
	                   #Null)												 ; hTemplateFile
	
	
	; We have the handle To talk To the device.
	; So we can release the interfaceDetailData buffer
	
	FreeMemory(*InterfaceDetailData)
	
	If hDevice=#INVALID_HANDLE_VALUE
		iTmp=GetLastError_()
		DebugPrint( 1, "CreateFile failed with error: "+Str(iTmp)+~"\n")
		ProcedureReturn #True
	EndIf
	
	query\PropertyId=#StorageAdapterProperty
	query\QueryType= #PropertyStandardQuery
	
	iStatus=DeviceIoControl_(hDevice,
	                         #IOCTL_STORAGE_QUERY_PROPERTY,
	                         @query,
	                         SizeOf(STORAGE_PROPERTY_QUERY),
	                         @outBuf(),
	                         512,
	                         @iReturnedLength,
	                         #Null)
	
	If Not iStatus
		iTmp=GetLastError_()
		DebugPrint( 1, "IOCTL failed with error code "+Str(iTmp)+~"\n\n")
	Else
		*adpDesc=@outBuf()
		
		DebugPrint( 1, ~"\nAdapter Properties\n")
		DebugPrint( 1, ~"------------------\n")
		DebugPrint( 1, "Bus Type       : "+BusType(*adpDesc\BusType)+~"\n")
		DebugPrint( 1, "Max. Tr. Length: 0x"+Hex(*adpDesc\MaximumTransferLength)+~"\n")
		DebugPrint( 1, "Max. Phy. Pages: 0x"+Hex(*adpDesc\MaximumPhysicalPages)+~"\n")
		DebugPrint( 1, "Alignment Mask : 0x"+Hex(*adpDesc\AlignmentMask)+~"\n")
		
		iCdType=GetMediaType(hDevice, @CdTypeString)
		
		query\PropertyId=#StorageDeviceProperty
		query\QueryType=#PropertyStandardQuery
		
		iStatus=DeviceIoControl_(hDevice,
		                         #IOCTL_STORAGE_QUERY_PROPERTY,
		                         @query,
		                         SizeOf( STORAGE_PROPERTY_QUERY ),
		                         @outBuf(),
		                         512,
		                         @iReturnedLength,
		                         #Null)
		
		
		If Not iStatus
			iTmp=GetLastError_()
			DebugPrint( 1, "IOCTL failed with error code "+Str(iTmp)+~"\n\n")
		Else
			DebugPrint( 1, ~"\nDevice Properties\n")
			DebugPrint( 1, ~"-----------------\n")
			
			*devDesc=@outBuf()
			
			; Our device table can handle only 16 devices.
			
			DebugPrint( 1, "SCSI Device Type: "+DeviceType(*devDesc\DeviceType)+" (0x"+Hex(*devDesc\DeviceType)+~")\n")
			
			If iCdType
				DebugPrint( 1, "Media Type      : "+CdTypeString\s+" (0x"+Hex(iCdType)+~")\n")
			EndIf
			
			If *devDesc\DeviceTypeModifier
				DebugPrint( 1, "Device Modifier : 0x"+Hex(*devDesc\DeviceTypeModifier)+~"\n")
			EndIf
			
			DebugPrint( 1, "Removable Media : "+YesNo(*devDesc\RemovableMedia)+~"\n")
			
			If *devDesc\VendorIdOffset And PeekA(@outBuf()+*devDesc\VendorIdOffset)
				DebugPrint( 1, "Vendor ID       : " )
				iCnt=*devDesc\VendorIdOffset
				While PeekA(@outBuf()+iCnt)<>#Null And iCnt<iReturnedLength
					DebugPrint( 1, Chr(PeekA(@outBuf()+iCnt)))
					iCnt+1
				Wend
				DebugPrint( 1, ~"\n")
			EndIf
			
			If *devDesc\ProductIdOffset And PeekA(@outBuf()+*devDesc\ProductIdOffset)
				DebugPrint( 1, "Product ID      : " )
				iCnt=*devDesc\ProductIdOffset
				While PeekA(@outBuf()+iCnt)<>#Null And iCnt<iReturnedLength
					DebugPrint( 1, Chr(PeekA(@outBuf()+iCnt)))
					iCnt+1
				Wend
				DebugPrint( 1, ~"\n")
			EndIf
			
			If *devDesc\ProductRevisionOffset And PeekA(@outBuf()+*devDesc\ProductRevisionOffset)
				DebugPrint( 1, "Product Revision: " )
				iCnt=*devDesc\ProductRevisionOffset
				While PeekA(@outBuf()+iCnt)<>#Null And iCnt<iReturnedLength
					DebugPrint( 1, Chr(PeekA(@outBuf()+iCnt)))
					iCnt+1
				Wend
				DebugPrint( 1, ~"\n")
			EndIf
			
			If *devDesc\SerialNumberOffset And PeekA(@outBuf()+*devDesc\SerialNumberOffset)
				DebugPrint( 1, "Serial Number   : " )
				iCnt=*devDesc\SerialNumberOffset
				While PeekA(@outBuf()+iCnt)<>#Null And iCnt<iReturnedLength
					DebugPrint( 1, Chr(PeekA(@outBuf()+iCnt)))
					iCnt+1
				Wend
				DebugPrint( 1, ~"\n")
			EndIf
			
		EndIf
		
	EndIf
	
	
	FillMemory(@sptwb, SizeOf(SCSI_PASS_THROUGH_WITH_BUFFERS))
	
	sptwb\spt\Length=SizeOf(SCSI_PASS_THROUGH)
	sptwb\spt\PathId=0
	sptwb\spt\TargetId=1
	sptwb\spt\Lun=0
	sptwb\spt\CdbLength=#CDB6GENERIC_LENGTH
	sptwb\spt\SenseInfoLength=24
	sptwb\spt\DataIn=#SCSI_IOCTL_DATA_IN
	sptwb\spt\DataTransferLength=192
	sptwb\spt\TimeOutValue=2
	sptwb\spt\DataBufferOffset=OffsetOf(SCSI_PASS_THROUGH_WITH_BUFFERS\ucDataBuf)
	sptwb\spt\SenseInfoOffset=OffsetOf(SCSI_PASS_THROUGH_WITH_BUFFERS\ucSenseBuf)
	sptwb\spt\Cdb[0]=#SCSIOP_INQUIRY
	sptwb\spt\Cdb[4]=192
	iLength=OffsetOf(SCSI_PASS_THROUGH_WITH_BUFFERS\ucDataBuf) + sptwb\spt\DataTransferLength
	
	iStatus=DeviceIoControl_(hDevice,
	                         #IOCTL_SCSI_PASS_THROUGH,
	                         @sptwb,
	                         SizeOf(SCSI_PASS_THROUGH),
	                         @sptwb,
	                         iLength,
	                         @iReturned,
	                         #False)
	
	PrintStatusResults(iStatus, iReturned, @sptwb)
	
	; If device supports SCSI-3, then we can get the CD drive capabilities, i.e. ability To 
	; Read/write To CD-ROM/R/RW Or/And Read/write To DVD-ROM/R/RW.
	; Use the previous spti Structure, only modify the command To "mode sense"
	
	sptwb\spt\Cdb[0]=#SCSIOP_MODE_SENSE
	sptwb\spt\Cdb[1]=$08                ; target shall not return any block descriptors
	sptwb\spt\Cdb[2]=#MODE_PAGE_CAPABILITIES
	
	iStatus=DeviceIoControl_(hDevice,
	                         #IOCTL_SCSI_PASS_THROUGH,
	                         @sptwb,
	                         SizeOf(SCSI_PASS_THROUGH),
	                         @sptwb,
	                         iLength,
	                         @iReturned,
	                         #False)
	
	PrintCapResults(iStatus, iReturned, @sptwb)
	
	; Close handle the driver
	
	If Not CloseHandle_(hDevice)
		DebugPrint( 2, ~"Failed to close device.\n")
	EndIf
	
	ProcedureReturn #True
	
 EndProcedure
 




;- ~ Main
; Routine Description:  This is the main function. It takes no arguments from the user.
; Arguments:            None
; Return Value:         Status

Define hDevInfo.i
Define hIntDevInfo.i
Define iIndex.i
Define iStatus.i
Define iTmp.i

If Not OpenConsole("EnumCD Purebasic")
	End(1)
EndIf

hDevInfo = SetupDiGetClassDevs_( ?GUID_DEVCLASS_CDROM, #Null, #Null, #DIGCF_PRESENT  ) ; All devices present on system

If hDevInfo = #INVALID_HANDLE_VALUE
	iTmp=GetLastError_()
	DebugPrint( 1, "SetupDiGetClassDevs failed with error: "+Str(iTmp)+~"\n")
	End(1)
EndIf

; Open the device using device Interface registered by the driver

; Get the Interface device information set that contains all devices of event class.

hIntDevInfo = SetupDiGetClassDevs_( ?GUID_DEVINTERFACE_CDROM,                ; https://msdn.microsoft.com/en-us/library/windows/hardware/ff537883%28v=vs.85%29.aspx
                                    #Null,                                   ; Enumerator
                                    #Null,											  ; Parent Window
                                    (#DIGCF_PRESENT | #DIGCF_DEVICEINTERFACE )); Only Devices present & Interface class

If hDevInfo=#INVALID_HANDLE_VALUE
	iTmp=GetLastError_()
	DebugPrint( 1, "SetupDiGetClassDevs failed with error: "+Str(iTmp)+~"\n")
	End(1)
EndIf

; Enumerate all the CD devices

iIndex=0
While (#True)
	DebugPrint( 1, "Properties for Device "+Str(iIndex+1))
	iStatus=GetRegistryProperty( hDevInfo, iIndex )
	If iStatus=#False
		Break
	EndIf
	
 	iStatus=GetDeviceProperty( hIntDevInfo, iIndex )
 	If iStatus=#False
		Break
 	EndIf
	iIndex+1
Wend

DebugPrint( 1, ~"\r ***  End of Device List  *** \n")
SetupDiDestroyDeviceInfoList_(hDevInfo)
SetupDiDestroyDeviceInfoList_(hIntDevInfo)

CompilerIf Not #PB_Editor_CreateExecutable
	PrintN("Press return to exit")
	Input()
CompilerEndIf

CloseConsole()

End(0)

User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5494
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: MS EnumCD example converted to PureBasic

Post by Kwai chang caine »

Unfortunately more and more notebook are distribued withoud CD drives now :cry:
It's the case of mine actually
But thanks when even for sharing this code who can be useful 8)
ImageThe happiness is a road...
Not a destination
Post Reply