This Delphi code snippet should be easily converted to C # using P / Invoke calls and does exactly what you want. (and a little more). Import and call - 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 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 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 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 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 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 ] <>