12 February 2011

Sending the SCSI INQUIRY command with DeviceIOControl

Option Strict On
Option Explicit On

Imports System.Runtime.InteropServices
Imports Microsoft.Win32.SafeHandles
Imports System.Security
Imports System.ComponentModel
Imports System.Text

Module Module1

    <SuppressUnmanagedCodeSecurity()> _
    Private Class NativeMethods
        <DllImport("kernel32", SetLastError:=True)> _
        Public Shared Function CreateFile( _
         ByVal FileName As String, _
         ByVal DesiredAccess As Integer, _
         ByVal ShareMode As Integer, _
         ByVal SecurityAttributes As IntPtr, _
         ByVal CreationDisposition As Integer, _
         ByVal FlagsAndAttributes As Integer, _
         ByVal hTemplateFile As IntPtr) As SafeFileHandle
        End Function

        <DllImport("kernel32.dll", SetLastError:=True)> _
        Friend Shared Function DeviceIoControl( _
            ByVal deviceHandle As SafeFileHandle, _
            ByVal controlCode As Integer, _
            ByRef inBuffer As ScsiPassThroughWithBuffers, _
            ByVal inBufferSize As Integer, _
            ByRef outBuffer As ScsiPassThroughWithBuffers, _
            ByVal outBufferSize As Integer, _
            ByRef bytesReturned As Integer, _
            ByVal overlapped1 As IntPtr) As Boolean
        End Function
    End Class

    <StructLayout(LayoutKind.Sequential, pack:=8)> _
    Private Structure ScsiPassThrough
        Public Length As Short
        Public ScsiStatus As Byte
        Public PathId As Byte          ' port / bus
        Public TargetId As Byte        ' controller
        Public Lun As Byte             ' Lun
        Public CdbLength As Byte
        Public SenseInfoLength As Byte
        Public DataIn As Byte
        Public DataTransferLength As Integer
        Public TimeOutValue As Integer
        Public DataBufferOffset As IntPtr
        Public SenseInfoOffset As Integer
        <MarshalAs(UnmanagedType.ByValArray, SizeConst:=16)> _
        Public Cdb() As Byte
        Public Sub Init()
            Me.Length = CShort(Marshal.SizeOf(GetType(ScsiPassThrough)))
            CdbLength = 6                   ' Size of our command structure
            SenseInfoLength = 32            ' Size of our buffer that may be filled with error/status info
            DataIn = 1                      ' SCSI_IOCTL_DATA_IN 
            DataTransferLength = 128        ' Size of our buffer that gets filled with informaion
            TimeOutValue = 10
            ' TargetId, PathId and Lun are filled on return. Don't mean anything on the way out.
            DataBufferOffset = Marshal.OffsetOf(GetType(ScsiPassThroughWithBuffers), "Data")
            SenseInfoOffset = Marshal.OffsetOf(GetType(ScsiPassThroughWithBuffers), "Sense").ToInt32
            Cdb = New Byte(15) {}
            Cdb(0) = &H12
            Cdb(4) = 128
        End Sub
    End Structure

    <StructLayout(LayoutKind.Sequential, pack:=8)> _
    Private Structure ScsiPassThroughWithBuffers
        Public Spt As ScsiPassThrough
        <MarshalAs(UnmanagedType.ByValArray, SizeConst:=32)> _
        Public Sense() As Byte
        <MarshalAs(UnmanagedType.ByValArray, SizeConst:=128)> _
        Public Data() As Byte
        Public Sub Init()
            Sense = New Byte(31) {}
            Data = New Byte(127) {}
            Spt = New ScsiPassThrough
            Spt.Init()
        End Sub
    End Structure

    Private Enum PeripheralDeviceType
        Sbc2 = 0 ' direct access block device (disk)
        Ssc2 = 1 ' sequential access block device (tape)
        Ssc = 2  ' Printer
        Spc2 = 3 ' Processor
        Sbc = 4  ' Write once
        Mmc = 5  ' CD/DVD etc
        SbcOptical = 7 ' Optical memory device
        Smc2 = 8 ' Medium changer
        scc2 = &HC ' storage array controller (raid)
        Ses = &HD  ' enclosure services device
        Rbc = &HE  ' simple direct access device
        Ocrw = &HF ' optical card reader
        Bcc = &H10 ' bridge controller 
        Osd = &H11 ' object based storage device
        Adc = &H12 ' automation / drive interface
        WellKnown = &H1E
        Unknown = &H1F
    End Enum

    Sub Main()
        Console.WriteLine("Enter a drive letter")
        Dim letter As Char = Console.ReadKey.KeyChar
        Console.WriteLine()
        Const GenericRead As Integer = &H80000000
        Const GenericWrite As Integer = &H40000000
        Const FileShareRead As Integer = 1
        Const FileShareWrite As Integer = 2
        Const OpenExisting As Integer = 3
        Dim drivePath As String = String.Concat("\\.\" & letter & ":")
        Console.WriteLine("Trying path: " & drivePath)
        Using driveHandle As SafeFileHandle = NativeMethods.CreateFile( _
         drivePath, _
         GenericRead Or GenericWrite, _
         FileShareRead Or FileShareWrite, _
         IntPtr.Zero, _
         OpenExisting, _
         0, _
         IntPtr.Zero)
            If driveHandle.IsInvalid Then
                Console.WriteLine("CreateFile ERROR: " & (New Win32Exception).Message)
                Console.ReadKey()
                Return
            End If
            Dim sptwb As New ScsiPassThroughWithBuffers
            sptwb.Init()

            Dim inBufferSize As Integer = Marshal.SizeOf(GetType(ScsiPassThroughWithBuffers))
            Dim bytesReturned As Integer
            Const IOCTL_SCSI_PASS_THROUGH As Integer = &H4D004
            Dim result As Boolean = NativeMethods.DeviceIoControl(driveHandle, IOCTL_SCSI_PASS_THROUGH, _
                sptwb, inBufferSize, sptwb, inBufferSize, bytesReturned, IntPtr.Zero)
            If result = False Then
                Console.WriteLine("DeviceIOControl ERROR: {0} {1}", Marshal.GetLastWin32Error.ToString("x"), (New Win32Exception).Message)
                Console.ReadKey()
                Return
            End If
            Dim bytes() As Byte = sptwb.Data
            Dim peripheralQualifier As Integer = bytes(0) >> 5
            If peripheralQualifier = 1 Then Console.WriteLine("No peripheral is attached to the device")
            Dim peripheralDevice As PeripheralDeviceType = CType(bytes(0) And &H1F, PeripheralDeviceType)
            Console.WriteLine("Peripheral device type: " & peripheralDevice.ToString)
            Console.WriteLine("Version of the standard:" & sptwb.Data(2))
            Console.WriteLine("Additional Length: " & sptwb.Data(4))
            DumpString("Vendor Id: ", sptwb.Data, 8, 8)
            DumpString("Product Id: ", sptwb.Data, 16, 16)
            DumpString("Product Revision Level: ", sptwb.Data, 32, 4)
            DumpString("Serial no: ", sptwb.Data, 36, 8)
            Console.WriteLine("Press any key")
            Console.ReadKey()
        End Using
    End Sub

    Private Sub DumpString(msg As String, bytes() As Byte, offset As Integer, length As Integer)
        Console.WriteLine(String.Format("{0} :'{1}'", msg, ASCIIEncoding.ASCII.GetString(bytes, offset, length)))
    End Sub

End Module

The serial number is unreliable. The peripheral device type tells you which standard the device is using. I’m trying to work out how to get the serial no of my raided or USB drives. This command is getting back the serial number of my controller card for my raid array when I ask it for \\.\C:

Enter a drive letter
c
Trying path: \\.\c:
Peripheral device type: Sbc2
Version of the standard:4
Additional Length: 43
Vendor Id:  :'AMD     '
Product Id:  :'2+0 Stripe/RAID0'
Product Revision Level:  :'1.10'
Serial no:  :'98031612'
Press any key

No comments:

Post a Comment