views:

444

answers:

2

Hey.

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.

+6  A: 

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#.

Christian Vik
+2  A: 

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;
Ritsaert Hornstra