如何通过C#.net中的DeviceIoControl使用IOCTL_SCSI_MINIPORT?

问题描述:

我的任务是实施一个可靠的解决方案来检索硬盘的序列号。不幸的是the WMI method根本不可靠。所以我正在寻找另一个解决方案。如何通过C#.net中的DeviceIoControl使用IOCTL_SCSI_MINIPORT?

我发现这个小piecesoftware,这正是我想要在C#.net中实现的。幸运的是,源代码也是available

基本上我想从C#中的diskid32实现函数ReadIdeDriveAsScsiDriveInNT

如何与设备进行通信:

[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] 
private static extern bool DeviceIoControl(
    SafeFileHandle device, 
    int inputOutputControlCode, 
    [In] ref sbyte[] inputBuffer, 
    int inputBufferSize, 
    [In] [Out] ref sbyte[] outputBuffer, 
    int outputBufferSize, 
    ref uint bytesCount, 
    int overlapped); 

public static string GetSerialNumberUsingMiniportDriver(int deviceNumber) 
{ 
    using (var device = OpenScsi(2)) 
    { 
     var bytesReturned = default(uint); 
     var sio = new ScsiRequestBlockInputOutputControl(); 
     var sop = new SendCommandOutParameters(); 
     var sip = new SendCommandInParameters(); 
     var buffer = new byte[Marshal.SizeOf(sio) + Marshal.SizeOf(sop) + IdentifyBufferSize]; 

     sio.HeaderLength = Marshal.SizeOf(sio); 
     sio.Timeout = 10000; 
     sio.Length = Marshal.SizeOf(sop) + IdentifyBufferSize; 
     sio.ControlCode = InputOutputControlSCSIMiniportIdentify; 
     sio.Signature = Encoding.ASCII.GetBytes("SCSIDISK".ToCharArray()); 

     var ptr = Marshal.AllocHGlobal(Marshal.SizeOf(sio)); 
     Marshal.StructureToPtr(sio, ptr, true); 
     Marshal.Copy(ptr, buffer, 0, Marshal.SizeOf(sio)); 

     sip.DriveRegister.CommandRegister = IDEATAIdentify; 
     sip.DriveNumber = 0; 

     ptr = Marshal.AllocHGlobal(Marshal.SizeOf(sip)); 
     Marshal.StructureToPtr(sip, ptr, true); 
     Marshal.Copy(ptr, buffer, Marshal.SizeOf(sio), Marshal.SizeOf(sip)); 

     var signedBuffer = new sbyte[buffer.Length]; 
     Buffer.BlockCopy(buffer, 0, signedBuffer, 0, buffer.Length); 

     if (
      !DeviceIoControl(
       device, 
       InputOutputControlSCSIMiniport, 
       ref signedBuffer, 
       Marshal.SizeOf(sio) + Marshal.SizeOf(sip) - 1, 
       ref signedBuffer, 
       Marshal.SizeOf(sio) + Marshal.SizeOf(sop) + IdentifyBufferSize, 
       ref bytesReturned, 
       0)) 
     { 
      throw new Win32Exception(Marshal.GetLastWin32Error()); 
     } 

     var result = new StringBuilder(); 

     result.Append(buffer); 

     return result.ToString(); 
    } 
} 

我如何创建句柄:

[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] 
private static extern SafeFileHandle CreateFile(
    string fileName, 
    int desiredAccess, 
    FileShare shareMode, 
    IntPtr securityAttributes, 
    FileMode creationDisposition, 
    FileAttributes flagsAndAttributes, 
    IntPtr templateFile); 

private static SafeFileHandle OpenScsi(int scsiNumber) 
{ 
    var device = CreateFile(string.Format(@"\\.\Scsi{0}:", scsiNumber), 0, FileShare.ReadWrite, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero); 
    if (device.IsInvalid) 
    { 
     throw new Win32Exception(Marshal.GetLastWin32Error()); 
    } 

    return device; 
} 

signedBuffer包含完全相同的字节作为diskid32例如缓冲区! Diskid32返回那个句柄\\.\Scsi2:DriveNumber = 0的结果,所以我使用相同的参数。

创建手柄时存在差异。我也试过在diskid32中做了什么。没有任何成功。

当我在C#中调用DeviceIoControl时,我总是会得到一个Win32Exception,这就是Access denied。有人有个想法吗?

主要问题是我访问设备的权限不正确,而其中一个结构布局错误。

首先,我下载了WinDDK以了解结构体的外观如何。我将使用过的结构翻译为C#

下面一个例子来自WinDDK\7600.16385.1\inc\api\ntddscsi.h

/// <summary> 
/// The SRB_IO_CONTROL. 
/// Define header for I/O control SRB. 
/// </summary> 
[StructLayout(LayoutKind.Sequential)] 
internal struct SRB_IO_CONTROL 
{ 
    /// <summary> 
    /// The HeaderLength. 
    /// </summary> 
    public uint HeaderLength; 

    /// <summary> 
    /// The Signature. 
    /// </summary> 
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] 
    public byte[] Signature; 

    /// <summary> 
    /// The Timeout. 
    /// </summary> 
    public uint Timeout; 

    /// <summary> 
    /// The ControlCode. 
    /// </summary> 
    public uint ControlCode; 

    /// <summary> 
    /// The ReturnCode. 
    /// </summary> 
    public uint ReturnCode; 

    /// <summary> 
    /// The Length. 
    /// </summary> 
    public uint Length; 
} 

我已经改变了允许值,我进入到设备0x80000000 | 0x40000000。这是我的方法来打开一个设备句柄:

/// <summary> 
/// The open scsi. 
/// </summary> 
/// <param name="scsiNumber"> 
/// The scsi number. 
/// </param> 
/// <returns> 
/// The <see cref="SafeFileHandle"/>. 
/// </returns> 
/// <exception cref="Win32Exception"> 
/// Will be thrown, when the safe file handle is not valid. 
/// </exception> 
private static SafeFileHandle OpenScsi(int scsiNumber) 
{ 
    var device = FileManagement.CreateFile(
     string.Format(@"\\.\Scsi{0}:", scsiNumber), 
     WinNT.GENERIC_READ | WinNT.GENERIC_WRITE, 
     FileShare.ReadWrite, 
     IntPtr.Zero, 
     FileMode.Open, 
     0, 
     IntPtr.Zero); 
    if (device.IsInvalid) 
    { 
     throw new NativeException(string.Format(@"Error during the creation of a safe file handle for \\.\Scsi{0}:", scsiNumber)); 
    } 

    return device; 
} 

最后我的代码看起来像采集设备序列号:

/// <summary> 
/// The get serial number using miniport driver. 
/// </summary> 
/// <param name="busNumber"> 
/// The bus number. 
/// </param> 
/// <param name="deviceNumber"> 
/// The device number. 
/// </param> 
/// <returns> 
/// The <see cref="string"/>. 
/// </returns> 
/// <exception cref="NativeException"> 
/// Throws an excpetion, if the device io control couldn't execute successfully! 
/// </exception> 
internal static string GetSerialNumberUsingMiniportDriver(int busNumber, byte deviceNumber = 0) 
{ 
    using (var device = OpenScsi(busNumber)) 
    { 
     var bytesReturned = default(uint); 
     var sic = new SCSI.SRB_IO_CONTROL(); 
     var sop = new Disk.SENDCMDOUTPARAMS(); 
     var sip = new Disk.SENDCMDINPARAMS(); 
     var id = new ATA.IDENTIFY_DEVICE_DATA(); 
     var buffer = new byte[Marshal.SizeOf(sic) + Marshal.SizeOf(sop) + Marshal.SizeOf(id)]; 

     sic.HeaderLength = (uint)Marshal.SizeOf(sic); 
     sic.Timeout = 10000; 
     sic.Length = (uint)(Marshal.SizeOf(sop) + Marshal.SizeOf(id)); 
     sic.ControlCode = SCSI.IOCTL_SCSI_MINIPORT_IDENTIFY; 

     // disk access signature 
     sic.Signature = Encoding.ASCII.GetBytes("SCSIDISK".ToCharArray()); 

     var ptr = Marshal.AllocHGlobal(Marshal.SizeOf(sic)); 
     Marshal.StructureToPtr(sic, ptr, true); 
     Marshal.Copy(ptr, buffer, 0, Marshal.SizeOf(sic)); 

     sip.irDriveRegs.bCommandReg = (byte)ATA.CTRL_CMDS.ATA_IDENTIFY; 
     sip.bDriveNumber = deviceNumber; 

     ptr = Marshal.AllocHGlobal(Marshal.SizeOf(sip)); 
     Marshal.StructureToPtr(sip, ptr, true); 
     Marshal.Copy(ptr, buffer, Marshal.SizeOf(sic), Marshal.SizeOf(sip)); 

     if (
      !FileManagement.DeviceIoControl(
       device, 
       SCSI.IOCTL_SCSI_MINIPORT, 
       buffer, 
       (uint)(Marshal.SizeOf(sic) + Marshal.SizeOf(sip) - 1), 
       buffer, 
       (uint)(Marshal.SizeOf(sic) + Marshal.SizeOf(sop) + Marshal.SizeOf(id)), 
       ref bytesReturned, 
       IntPtr.Zero)) 
     { 
      throw new NativeException("P/invoke error on SCSI MINIPORT IDENTIFY"); 
     } 

     var resultPtr = Marshal.AllocHGlobal(Marshal.SizeOf(id)); 
     Marshal.Copy(buffer, Marshal.SizeOf(sic) + Marshal.SizeOf(sop), resultPtr, Marshal.SizeOf(id)); 

     id = (ATA.IDENTIFY_DEVICE_DATA)Marshal.PtrToStructure(resultPtr, typeof(ATA.IDENTIFY_DEVICE_DATA)); 

     var model = Encoding.ASCII.GetString(id.ModelNumber).Replace('\0', ' ').Trim(); 
     if (!string.IsNullOrEmpty(model)) 
     { 
      model = model.Swap(); 

      Logging.Add(
       Message.Type.INFO, 
       string.Format("Found model definition (via IOCTL_SCSI_MINIPORT) for storage device #{0}/{1}: {2}", busNumber, deviceNumber, model)); 
     } 

     var serial = Encoding.ASCII.GetString(id.SerialNumber).Replace('\0', ' ').Trim(); 
     if (!string.IsNullOrEmpty(serial)) 
     { 
      serial = serial.Swap(); 

      Logging.Add(
       Message.Type.INFO, 
       string.Format("Found serial number (via IOCTL_SCSI_MINIPORT) for storage device #{0}/{1}: {2}", busNumber, deviceNumber, serial)); 
     } 
     else 
     { 
      Logging.Add(
       Message.Type.INFO, 
       string.Format("Couldn't find serial number (via IOCTL_SCSI_MINIPORT) for storage device #{0}/{1}", busNumber, deviceNumber)); 
     } 

     return serial.Trim(); 
    } 
} 

如果你想使用的代码,你需要做的以下:

  • 落实结构(你会在WINDDK找到):SENDCMDOUTPARAMSSENDCMDINPARAMSIDENTIFY_DEVICE_DATASRB_IO_CONTROL

  • 定义常量值:IOCTL_SCSI_MINIPORTIOCTL_SCSI_MINIPORT_IDENTIFYATA_IDENTIFY

  • 我已经实现了一个string延伸到交换字符,其被使用。

我的字符串扩展的交换:

/// <summary> 
/// The swap. 
/// </summary> 
/// <param name="input"> 
/// The input. 
/// </param> 
/// <returns> 
/// The <see cref="string"/>. 
/// </returns> 
/// <exception cref="ArgumentException"> 
/// Can't swap input, which is null, empty or a white space! 
/// </exception> 
public static string Swap(this string input) 
{ 
    if (string.IsNullOrEmpty(input) || string.IsNullOrWhiteSpace(input)) 
    { 
     throw new ArgumentException("Can't swap input, which is null, empty or a white space!"); 
    } 

    var characters = input.ToCharArray(); 
    var returnValue = new StringBuilder(); 

    for (var i = 0; i < characters.Length; i++) 
    { 
     if (i % 2 != 0) 
     { 
      continue; 
     } 

     if ((i + 1) < characters.Length) 
     { 
      returnValue.Append(characters[i + 1]); 
     } 

     returnValue.Append(characters[i]); 
    } 

    return returnValue.ToString(); 
} 
+1

您也可以找到我的图书馆帮助。 http://scsi.codeplex.com/ – Mehrdad

+0

看起来不错@Mehrdad。我会看看。谢谢! :-) – Robin

+0

不客气! – Mehrdad