This piece of Delphi code should be easily transformed to C# using P/Invoke calls and does exactly what you want. (and a bit more) The importand call is to DeviceIOControl.
type
STORAGE_QUERY_TYPE = DWORD;
const
PropertyStandardQuery = 0; // Retrieves the descriptor
PropertyExistsQuery = 1; // Used to test whether the descriptor is supported
PropertyMaskQuery = 2; // Used to retrieve a mask of writeable fields in the descriptor
type
STORAGE_PROPERTY_ID = DWORD;
const
StorageDeviceProperty = 0;
// Query structure - additional parameters for specific queries can follow the header
type
STORAGE_PROPERTY_QUERY = packed record
PropertyId: STORAGE_PROPERTY_ID;
QueryType: STORAGE_QUERY_TYPE;
AdditionalParameters: Longword;
end;
const
FILE_DEVICE_MASS_STORAGE = $0000002d;
IOCTL_STORAGE_BASE = FILE_DEVICE_MASS_STORAGE;
FILE_ANY_ACCESS = 0;
METHOD_BUFFERED = 0;
IOCTL_STORAGE_QUERY_PROPERTY = ( IOCTL_STORAGE_BASE shl 16 ) or ( $500 shl 2 ) or METHOD_BUFFERED or ( FILE_ANY_ACCESS shl 14 );
type
STORAGE_BUS_TYPE = DWORD;
const
BusTypeUnknown = $00;
BusTypeScsi = $01;
BusTypeAtapi = $02;
BusTypeAta = $03;
BusType1394 = $04;
BusTypeSsa = $05;
BusTypeFibre = $06;
BusTypeUsb = $07;
BusTypeRAID = $08;
BusTypeiScsi = $09;
BusTypeSas = $0A;
BusTypeSata = $0B;
BusTypeSd = $0C;
BusTypeMmc = $0D;
BusTypeVirtual = $0E;
BusTypeFileBackedVirtual = $0F;
BusTypeMax = $10;
BusTypeMaxReserved = $7F;
type
STORAGE_DEVICE_DESCRIPTOR = packed record
// sizeof( STORAGE_DEVICE_DESCRIPTOR )
Version: DWORD;
// Total size of the descriptor, including the space for additional data and id strings
Size: DWORD;
// The SCSI-2 device type
DeviceType: BYTE;
// The SCSI-2 device type modifier (if any) - this may be zero
DeviceTypeModifier: BYTE;
// Flag indicating whether the device's media (if any) is removable. This field should be ignored for media-less devices
RemovableMedia: BOOLEAN;
// Flag indicating whether the device can support multiple outstanding commands.
// The actual synchronization in this case is the responsibility of the port driver.
CommandQueueing: BOOLEAN;
// Byte offset to the zero-terminated ascii string containing the device's vendor id string.
// For devices with no such ID this will be zero
VendorIdOffset: DWORD;
// Byte offset to the zero-terminated ascii string containing the device's product id string.
// For devices with no such ID this will be zero
ProductIdOffset: DWORD;
// Byte offset to the zero-terminated ascii string containing the device's product revision string.
// For devices with no such string this will be zero
ProductRevisionOffset: DWORD;
// Byte offset to the zero-terminated ascii string containing the device's serial number.
// For devices with no serial number this will be zero
SerialNumberOffset: DWORD;
// Contains the bus type (as defined above) of the device. It should be used to interpret the raw device
// properties at the end of this structure (if any)
BusType: STORAGE_BUS_TYPE;
// The number of bytes of bus-specific data which have been appended to this descriptor
RawPropertiesLength: DWORD;
// Place holder for the first byte of the bus specific property data
RawDeviceProperties: DWORD;
end;
PSTORAGE_DEVICE_DESCRIPTOR = ^STORAGE_DEVICE_DESCRIPTOR;
STORAGE_DEVICE_NUMBER = packed record
DeviceType: LONGWORD; // DEVICE_TYPE
DeviceNumber: ULONG;
PartitionNumber: ULONG;
end;
PSTORAGE_DEVICE_NUMBER = ^STORAGE_DEVICE_NUMBER;
const
IOCTL_STORAGE_GET_DEVICE_NUMBER = ( IOCTL_STORAGE_BASE shl 16 ) or ( $420 shl 2 ) or METHOD_BUFFERED or ( FILE_ANY_ACCESS shl 14 );
type
TDriveBusType = (
dbtUnknown,
dbtScsi,
dbtAtapi,
dbtAta,
dbt1394,
dbtSsa,
dbtFibre,
dbtUsb,
dbtRAID,
dbtiScsi,
dbtSas,
dbtSata,
dbtSd,
dbtMmc,
dbtVirtual,
dbtFileBackedVirtual );
TDeviceType = (
dtUnknown ); // todo: implement
TDriveInfoResult = record
//
Drive: string;
VendorID: string;
ProductID: string;
Revision: string;
Serial: string;
BusType: TDriveBusType;
Removable: Boolean;
//
DeviceType: TDeviceType;
DeviceNumber: Integer;
Partition: Integer;
end;
const
BusTypes: array [ TDriveBusType ] of AnsiString = (
'Unknown',
'Scsi',
'Atapi',
'Ata',
'1394',
'Ssa',
'Fibre',
'Usb',
'RAID',
'iScsi',
'Sas',
'Sata',
'Sd',
'Mmc',
'Virtual',
'FileBackedVirtual' );
function DriveInfo( const Drive: string ): TDriveInfoResult;
var
H: THandle;
N: Longword;
Query: STORAGE_PROPERTY_QUERY;
Buffer: array [ 0..1023 ] of Byte;
Desc: PSTORAGE_DEVICE_DESCRIPTOR;
S, X: AnsiString;
i: Integer;
Info: PSTORAGE_DEVICE_NUMBER;
begin
// Clear out old data
Result.Drive := Drive;
Result.VendorID := '';
Result.ProductID := '';
Result.Revision := '';
Result.Serial := '';
// Open drive for querying
H := CreateFile( PChar( '\\.\' + Drive ), 0, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0 );
if H = INVALID_HANDLE_VALUE then Exit;
try
// Query device.
FillChar( Query, sizeof( Query ), 0 );
Query.PropertyId := StorageDeviceProperty;
Query.QueryType := PropertyStandardQuery;
FillChar( Buffer, sizeof( Buffer ), 0 );
if not DeviceIoControl ( H, IOCTL_STORAGE_QUERY_PROPERTY, @Query, sizeof( Query ), @Buffer, sizeof( Buffer ), N, nil ) then Exit;
// Sanity checks.
if N < sizeof( STORAGE_DEVICE_DESCRIPTOR ) then Exit;
Desc := @Buffer;
if Desc^.Version < sizeof( STORAGE_DEVICE_DESCRIPTOR ) then Exit;
// And obtain result.
if Desc^.VendorIdOffset <> 0 then Result.VendorID := Trim( PAnsiChar( @Buffer[ Desc^.VendorIdOffset ] ) );
if Desc^.ProductIdOffset <> 0 then Result.ProductID := Trim( PAnsiChar( @Buffer[ Desc^.ProductIdOffset ] ) );
if Desc^.ProductRevisionOffset <> 0 then Result.Revision := Trim( PAnsiChar( @Buffer[ Desc^.ProductRevisionOffset ] ) );
if Desc^.SerialNumberOffset <> 0 then begin
// The serial number is encoded in HEX and with each two characters encoded swapped. ER ABCD -> BADC -> '42414443'
S := PAnsiChar( @Buffer[ Desc^.SerialNumberOffset ] );
X := '';
for i := 1 to Length( S ) do if S[ i ] in [ '0'..'9', 'A'..'F', 'a'..'f' ] then X := X + S[ i ];
S := '';
SetLength( S, Length( X ) div 2 );
// i = 1,2,3,4,5,6 -> 3,1,7,5,11,9
for i := 1 to Length( S ) do S[ i ] := AnsiChar( StrToInt( '$' + Copy( X, 1 + ( ( ( i - 1 ) div 2 ) * 4 ) + 2 * ( i and 1 ), 2 ) ) );
Result.Serial := Trim( S );
end;
if Desc^.BusType <= Longword( High( TDriveBusType ) ) then Result.BusType := TDriveBusType( Desc^.BusType );
Result.Removable := Desc^.RemovableMedia;
System.FillChar( Buffer, sizeof( Buffer ), 0 );
if DeviceIoControl ( H, IOCTL_STORAGE_GET_DEVICE_NUMBER, nil, 0, @Buffer, sizeof( Buffer ), N, nil ) then begin
Info := @Buffer;
Result.DeviceType := dtUnknown;
Result.DeviceNumber := Integer( Info^.DeviceNumber );
Result.Partition := Integer( Info^.PartitionNumber );
end;
finally
CloseHandle( H );
end;
end;
function GetLogicalDrives(): TStringDynArray;
var
Buffer: array [ 0..1023 ] of Char;
N, i: Integer;
begin
SetLength( Result, 0 );
N := GetLogicalDriveStrings( High( Buffer ), @Buffer );
if N >= Length( Buffer ) then raise Exception.Create( 'Oops' );
i := 0;
while ( i <= N ) and ( Buffer[ i ] <> #0 ) do begin
SetLength( Result, Length( Result ) + 1 );
Result[ High( Result ) ] := PChar( @( Buffer[ i ] ) );
Inc( i, Length( Result[ High( Result ) ] ) + 1 );
end;
end;
function RemoveTrailingPathDelimiter( const Path: string ): string;
begin
if ( Length( Path ) = 0 ) or ( Path[ Length( Path ) ] <> PathDelim ) then Result := Path else Result := Copy( Path, 1, Length( Path ) - 1 );
end;
procedure TForm7.Button1Click( Sender: TObject );
var
Drives: TStringDynArray;
Drive: string;
Res: TDriveInfoResult;
begin
Memo1.Lines.BeginUpdate();
try
Memo1.Lines.Clear();
Drives := GetLogicalDrives();
for Drive in Drives do begin
Res := DriveInfo( RemoveTrailingPathDelimiter( Drive ) );
Memo1.Lines.Add( 'DRIVE: ' + Drive );
Memo1.Lines.Add( 'VendorID = ' + Res.VendorID );
Memo1.Lines.Add( 'ProductID = ' + Res.ProductID );
Memo1.Lines.Add( 'Revision = ' + Res.Revision );
Memo1.Lines.Add( 'Serial = ' + Res.Serial );
Memo1.Lines.Add( 'BusType = ' + BusTypes[ Res.BusType ] );
Memo1.Lines.Add( 'Removable = ' + IntToStr( Ord( Res.Removable ) ) );
// device type.
Memo1.Lines.Add( 'Device = ' + IntToStr( Res.DeviceNumber ) );
Memo1.Lines.Add( 'Partition = ' + IntToStr( Res.Partition ) );
Memo1.Lines.Add( '' );
end;
finally
Memo1.Lines.EndUpdate();
end;
end;