I have those partitions (in Windows) for example:
Hard Disk 1 - Partition C, Partition D
Hard Disk 2 - Partition E
Is there any way in a program language to know if for example partition C and partition D are in one physical hard disk without WMI?
I don't want to use WMI because it's slow - for this example, it took for me 0.5 seconds. I need it to be fast.
Thank you.
I don't know of any other managed way to get disk partition information. You may use the Win32 API using P/Invoke from C#. However, you shouldn't unless it's absolutely necessary.
The Win32 function you'll need is called DeviceIoControl(). The API documentation can be found at http://msdn.microsoft.com/en-us/library/aa363216(VS.85).aspx. Call DeviceIoControl() with the control code IOCTL_STORAGE_GET_DEVICE_NUMBER and you'll get the physical disk drive for the given partition device handle. The device handle for the partition can be retrieved using CreateFile() API.
However, using DeviceIoControl() is cumbersome and you will most likely have to make different versions for the 32-bit and 64-bit versions of Windows.
To retrieve all partitions you may use the managed code System.IO.DriveInfo like this:
var x = from di in DriveInfo.GetDrives()
where (di.DriveType == DriveType.Fixed)
select di;
foreach (DriveInfo di in x)
{
// Call DeviceIoControl() using the partition name from di.Name and the IOCTL_STORAGE_GET_DEVICE_NUMBER control code to retrieve the physical disk
}
It seems pinvoke.net has some signatures for C#.
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;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With