Как использовать IOCTL_SCSI_MINIPORT через DeviceIoControl из C # .net?

Моя задача — реализовать надежное решение для получения серийного номера жесткого диска. к несчастью метод WMI не надежен вообще. Поэтому я ищу другое решение.

Я нашел этот маленький кусок из программного обеспечения, который делает именно то, что я хочу реализовать в C # .net. К счастью, исходный код также имеется в наличии.

В основном я хотел бы реализовать функцию ReadIdeDriveAsScsiDriveInNT из diskid32 в C #.

Как я общаюсь с устройством:

[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. Без какого-либо успеха.

Я всегда получаю Win32Exception когда я звоню DeviceIoControl в C #, который говорит Access denied, У кого-нибудь есть идея?

2

Решение

Основные проблемы заключались в том, что я получил доступ к устройству с неверным разрешением, а одна из структур имела неправильную компоновку.

Сначала я скачал 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): SENDCMDOUTPARAMS, SENDCMDINPARAMS, IDENTIFY_DEVICE_DATA а также SRB_IO_CONTROL

  • Определите постоянные значения: IOCTL_SCSI_MINIPORT, IOCTL_SCSI_MINIPORT_IDENTIFY а также ATA_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();
}
3

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]