2013-10-22 87 views
2

我的任務是實施一個可靠的解決方案來檢索硬盤的序列號。不幸的是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。有人有個想法嗎?

回答

3

主要問題是我訪問設備的權限不正確,而其中一個結構佈局錯誤。

首先,我下載了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