验证硬盘 SSD 类型

var letter = "D";
var ext = DiskDetectionUtils.DetectDrive(letter);
if (ext.HardwareType != HardwareType.Ssd)
{
XtraMessageBox.Show("The drive letter " + letter + " is not SSD, please check it again!", "Error");
}

public static class DiskDetectionUtils
{
    #region DeviceIoControl (nominal media rotation rate)

    /// <summary>
    /// The ata flags data in
    /// </summary>
    private const uint AtaFlagsDataIn = 0x02;

    #endregion

    /// <summary>
    /// Check if the application is running as administrator.
    /// </summary>
    /// <returns><c>true</c> if the application is running as administrator otherwise, <c>false</c></returns>
    /// <exception cref="SecurityException"></exception>
    /// <exception cref="ArgumentException"></exception>
    /// <exception cref="ArgumentNullException"></exception>
    public static bool IsAdministrator()
    {
        var identity = WindowsIdentity.GetCurrent();
        if (identity == null) return false;
        var principal = new WindowsPrincipal(identity);
        return principal.IsInRole(WindowsBuiltInRole.Administrator);
    }

    /// <summary>
    /// Detect a fixed drive by letter.
    /// </summary>
    /// <param name="driveName">A valid drive letter.</param>
    /// <param name="queryType">The QueryType.</param>
    /// <param name="useFallbackQuery">Use QueryType.SeekPenalty as fallback.</param>
    /// <returns>A list of DriveInfoExtended.</returns>
    /// <exception cref="SecurityException">DetectHardwareTypeBySeekPenalty needs administrative access.</exception>
    /// <exception cref="IOException"></exception>
    /// <exception cref="UnauthorizedAccessException"></exception>
    /// <exception cref="DriveNotFoundException"></exception>
    /// <exception cref="ArgumentException"></exception>
    /// <exception cref="DetectionFailedException"></exception>
    /// <exception cref="ArgumentNullException"></exception>
    /// <exception cref="ArgumentOutOfRangeException"></exception>
    public static DriveInfoExtended DetectFixedDrive(string driveName, QueryType queryType = QueryType.SeekPenalty,
        bool useFallbackQuery = true)
    {
        var driveInfoExtended = new DriveInfoExtended();
        var logicalDrive = new DriveInfo(driveName);
        if (logicalDrive.DriveType == DriveType.Fixed)
        {
            if (logicalDrive.IsReady)
            {
                var tmp = new DriveInfoExtended
                {
                    DriveFormat = logicalDrive.DriveFormat,
                    VolumeLabel = logicalDrive.VolumeLabel,
                    Name = logicalDrive.Name,
                    UncPath = Pathing.GetUNCPath(logicalDrive.Name),
                    DriveType = logicalDrive.DriveType,
                    AvailableFreeSpace = logicalDrive.AvailableFreeSpace,
                    TotalSize = logicalDrive.TotalSize,
                    TotalFreeSpace = logicalDrive.TotalFreeSpace,
                    RootDirectory = logicalDrive.RootDirectory,
                    DriveLetter = logicalDrive.Name.Substring(0, 1).ToCharArray()[0]
                };

                var driveId = GetDiskId(tmp.DriveLetter);
                if (driveId != -1)
                {
                    tmp.Id = driveId;
                    if (queryType == QueryType.SeekPenalty)
                    {
                        tmp.HardwareType = DetectHardwareTypeBySeekPenalty(driveId);
                    }
                    else
                    {
                        if (IsAdministrator())
                        {
                            tmp.HardwareType = DetectHardwareTypeByRotationRate(driveId);
                        }
                        else
                        {
                            if (useFallbackQuery)
                            {
                                tmp.HardwareType = DetectHardwareTypeBySeekPenalty(driveId);
                            }
                            else
                            {
                                throw new SecurityException(
                                    "DetectHardwareTypeBySeekPenalty needs administrative access.");
                            }
                        }
                    }
                    if (tmp.HardwareType != HardwareType.Unknown)
                    {
                        driveInfoExtended = tmp;
                    }
                }
            }
        }
        return driveInfoExtended;
    }

    /// <summary>
    /// Detect all fixed drives.
    /// </summary>
    /// <param name="queryType">The QueryType.</param>
    /// <param name="useFallbackQuery">Use QueryType.SeekPenalty as fallback.</param>
    /// <returns>A list of DriveInfoExtended.</returns>
    /// <exception cref="SecurityException">DetectHardwareTypeBySeekPenalty needs administrative access.</exception>
    /// <exception cref="IOException"></exception>
    /// <exception cref="UnauthorizedAccessException"></exception>
    /// <exception cref="DriveNotFoundException"></exception>
    /// <exception cref="ArgumentException"></exception>
    /// <exception cref="DetectionFailedException"></exception>
    /// <exception cref="ArgumentNullException"></exception>
    /// <exception cref="ArgumentOutOfRangeException"></exception>
    public static List<DriveInfoExtended> DetectFixedDrives(QueryType queryType = QueryType.SeekPenalty,
        bool useFallbackQuery = true)
    {
        var driveInfoExtended = new List<DriveInfoExtended>();
        var logicalDrives = DriveInfo.GetDrives();

        foreach (var logicalDrive in logicalDrives)
        {
            if (logicalDrive.DriveType == DriveType.Fixed)
            {
                if (logicalDrive.IsReady)
                {
                    var tmp = new DriveInfoExtended
                    {
                        DriveFormat = logicalDrive.DriveFormat,
                        VolumeLabel = logicalDrive.VolumeLabel,
                        Name = logicalDrive.Name,
                        UncPath = Pathing.GetUNCPath(logicalDrive.Name),
                        DriveType = logicalDrive.DriveType,
                        AvailableFreeSpace = logicalDrive.AvailableFreeSpace,
                        TotalSize = logicalDrive.TotalSize,
                        TotalFreeSpace = logicalDrive.TotalFreeSpace,
                        RootDirectory = logicalDrive.RootDirectory,
                        DriveLetter = logicalDrive.Name.Substring(0, 1).ToCharArray()[0]
                    };

                    var driveId = GetDiskId(tmp.DriveLetter);
                    if (driveId != -1)
                    {
                        tmp.Id = driveId;
                        if (queryType == QueryType.SeekPenalty)
                        {
                            tmp.HardwareType = DetectHardwareTypeBySeekPenalty(driveId);
                        }
                        else
                        {
                            if (IsAdministrator())
                            {
                                tmp.HardwareType = DetectHardwareTypeByRotationRate(driveId);
                            }
                            else
                            {
                                if (useFallbackQuery)
                                {
                                    tmp.HardwareType = DetectHardwareTypeBySeekPenalty(driveId);
                                }
                                else
                                {
                                    throw new SecurityException(
                                        "DetectHardwareTypeBySeekPenalty needs administrative access.");
                                }
                            }
                        }
                        if (tmp.HardwareType != HardwareType.Unknown)
                        {
                            driveInfoExtended.Add(tmp);
                        }
                    }
                }
            }
        }
        return driveInfoExtended;
    }

    /// <summary>
    /// Detect a fixed or removable drive.
    /// </summary>
    /// <param name="driveName">A valid drive letter.</param>
    /// <param name="queryType">The QueryType.</param>
    /// <param name="useFallbackQuery">Use QueryType.SeekPenalty as fallback.</param>
    /// <returns>A list of DriveInfoExtended.</returns>
    /// <exception cref="SecurityException">DetectHardwareTypeBySeekPenalty needs administrative access.</exception>
    /// <exception cref="IOException"></exception>
    /// <exception cref="UnauthorizedAccessException"></exception>
    /// <exception cref="DriveNotFoundException"></exception>
    /// <exception cref="ArgumentException"></exception>
    /// <exception cref="DetectionFailedException"></exception>
    /// <exception cref="ArgumentNullException"></exception>
    /// <exception cref="ArgumentOutOfRangeException"></exception>
    public static DriveInfoExtended DetectDrive(string driveName, QueryType queryType = QueryType.SeekPenalty,
        bool useFallbackQuery = true)
    {
        var driveInfoExtended = new DriveInfoExtended();
        var logicalDrive = new DriveInfo(driveName);
        if (logicalDrive.DriveType == DriveType.Fixed)
        {
            if (logicalDrive.IsReady)
            {
                var tmp = new DriveInfoExtended
                {
                    DriveFormat = logicalDrive.DriveFormat,
                    VolumeLabel = logicalDrive.VolumeLabel,
                    Name = logicalDrive.Name,
                    UncPath = Pathing.GetUNCPath(logicalDrive.Name),
                    DriveType = logicalDrive.DriveType,
                    AvailableFreeSpace = logicalDrive.AvailableFreeSpace,
                    TotalSize = logicalDrive.TotalSize,
                    TotalFreeSpace = logicalDrive.TotalFreeSpace,
                    RootDirectory = logicalDrive.RootDirectory,
                    DriveLetter = logicalDrive.Name.Substring(0, 1).ToCharArray()[0]
                };

                var driveId = GetDiskId(tmp.DriveLetter);
                if (driveId != -1)
                {
                    tmp.Id = driveId;
                    if (queryType == QueryType.SeekPenalty)
                    {
                        tmp.HardwareType = DetectHardwareTypeBySeekPenalty(driveId);
                    }
                    else
                    {
                        if (IsAdministrator())
                        {
                            tmp.HardwareType = DetectHardwareTypeByRotationRate(driveId);
                        }
                        else
                        {
                            if (useFallbackQuery)
                            {
                                tmp.HardwareType = DetectHardwareTypeBySeekPenalty(driveId);
                            }
                            else
                            {
                                throw new SecurityException(
                                    "DetectHardwareTypeBySeekPenalty needs administrative access.");
                            }
                        }
                    }
                    driveInfoExtended = tmp;
                }
            }
        }
        else
        {
            if (logicalDrive.IsReady)
            {

                var tmp = new DriveInfoExtended
                {
                    DriveFormat = logicalDrive.DriveFormat,
                    VolumeLabel = logicalDrive.VolumeLabel,
                    Name = logicalDrive.Name,
                    UncPath = Pathing.GetUNCPath(logicalDrive.Name),
                    DriveType = logicalDrive.DriveType,
                    AvailableFreeSpace = logicalDrive.AvailableFreeSpace,
                    TotalSize = logicalDrive.TotalSize,
                    TotalFreeSpace = logicalDrive.TotalFreeSpace,
                    RootDirectory = logicalDrive.RootDirectory,
                    DriveLetter = logicalDrive.Name.Substring(0, 1).ToCharArray()[0],
                    HardwareType = HardwareType.Unknown,
                    Id = -1
                };
                driveInfoExtended = tmp;
            }
        }
        return driveInfoExtended;
    }

    /// <summary>
    /// Detect fixed and removable drives.
    /// </summary>
    /// <param name="queryType">The QueryType.</param>
    /// <param name="useFallbackQuery">Use QueryType.SeekPenalty as fallback.</param>
    /// <returns>A list of DriveInfoExtended.</returns>
    /// <exception cref="SecurityException">DetectHardwareTypeBySeekPenalty needs administrative access.</exception>
    /// <exception cref="IOException"></exception>
    /// <exception cref="UnauthorizedAccessException"></exception>
    /// <exception cref="DriveNotFoundException"></exception>
    /// <exception cref="ArgumentException"></exception>
    /// <exception cref="DetectionFailedException"></exception>
    /// <exception cref="ArgumentNullException"></exception>
    /// <exception cref="ArgumentOutOfRangeException"></exception>
    public static List<DriveInfoExtended> DetectDrives(QueryType queryType = QueryType.SeekPenalty,
        bool useFallbackQuery = true)
    {
        var driveInfoExtended = new List<DriveInfoExtended>();
        var logicalDrives = DriveInfo.GetDrives();

        foreach (var logicalDrive in logicalDrives)
        {
            if (logicalDrive.DriveType == DriveType.Fixed)
            {
                if (logicalDrive.IsReady)
                {
                    var tmp = new DriveInfoExtended
                    {
                        DriveFormat = logicalDrive.DriveFormat,
                        VolumeLabel = logicalDrive.VolumeLabel,
                        Name = logicalDrive.Name,
                        UncPath = Pathing.GetUNCPath(logicalDrive.Name),
                        DriveType = logicalDrive.DriveType,
                        AvailableFreeSpace = logicalDrive.AvailableFreeSpace,
                        TotalSize = logicalDrive.TotalSize,
                        TotalFreeSpace = logicalDrive.TotalFreeSpace,
                        RootDirectory = logicalDrive.RootDirectory,
                        DriveLetter = logicalDrive.Name.Substring(0, 1).ToCharArray()[0]
                    };

                    var driveId = GetDiskId(tmp.DriveLetter);
                    if (driveId != -1)
                    {
                        tmp.Id = driveId;
                        if (queryType == QueryType.SeekPenalty)
                        {
                            tmp.HardwareType = DetectHardwareTypeBySeekPenalty(driveId);
                        }
                        else
                        {
                            if (IsAdministrator())
                            {
                                tmp.HardwareType = DetectHardwareTypeByRotationRate(driveId);
                            }
                            else
                            {
                                if (useFallbackQuery)
                                {
                                    tmp.HardwareType = DetectHardwareTypeBySeekPenalty(driveId);
                                }
                                else
                                {
                                    throw new SecurityException(
                                        "DetectHardwareTypeBySeekPenalty needs administrative access.");
                                }
                            }
                        }
                        driveInfoExtended.Add(tmp);
                    }
                }
            }
            else
            {
                if (logicalDrive.IsReady)
                {
                    var tmp = new DriveInfoExtended
                    {
                        DriveFormat = logicalDrive.DriveFormat,
                        VolumeLabel = logicalDrive.VolumeLabel,
                        Name = logicalDrive.Name,
                        UncPath = Pathing.GetUNCPath(logicalDrive.Name),
                        DriveType = logicalDrive.DriveType,
                        AvailableFreeSpace = logicalDrive.AvailableFreeSpace,
                        TotalSize = logicalDrive.TotalSize,
                        TotalFreeSpace = logicalDrive.TotalFreeSpace,
                        RootDirectory = logicalDrive.RootDirectory,
                        DriveLetter = logicalDrive.Name.Substring(0, 1).ToCharArray()[0],
                        HardwareType = HardwareType.Unknown,
                        Id = -1
                    };
                    driveInfoExtended.Add(tmp);
                }
            }
        }
        return driveInfoExtended;
    }

    /// <summary>
    /// DeviceIoControl to get disk extents
    /// </summary>
    /// <param name="hDevice">The h device.</param>
    /// <param name="dwIoControlCode">The dw io control code.</param>
    /// <param name="lpInBuffer">The lp in buffer.</param>
    /// <param name="nInBufferSize">Size of the n in buffer.</param>
    /// <param name="lpOutBuffer">The lp out buffer.</param>
    /// <param name="nOutBufferSize">Size of the n out buffer.</param>
    /// <param name="lpBytesReturned">The lp bytes returned.</param>
    /// <param name="lpOverlapped">The lp overlapped.</param>
    /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
    [DllImport("kernel32.dll", EntryPoint = "DeviceIoControl",
        SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool DeviceIoControl(
        SafeFileHandle hDevice,
        uint dwIoControlCode,
        IntPtr lpInBuffer,
        uint nInBufferSize,
        ref VolumeDiskExtents lpOutBuffer,
        uint nOutBufferSize,
        out uint lpBytesReturned,
        IntPtr lpOverlapped);


    /// <summary>
    /// Gets the device ID by drive letter.
    /// </summary>
    /// <param name="driveLetter">A valid drive letter.</param>
    /// <returns>The device ID.</returns>
    /// <exception cref="BluePrint.Common.Utility.DetectionFailedException"></exception>
    /// <exception cref="BluePrint.Common.Utility.DetectionFailedException"></exception>
    /// <exception cref="BluePrint.Common.Utility.DetectionFailedException"></exception>
    /// <exception cref="Win32Exception"></exception>
    /// <exception cref="Win32Exception"></exception>
    /// <exception cref="DetectionFailedException"></exception>
    /// <exception cref="ArgumentNullException"></exception>
    /// <exception cref="ArgumentException"></exception>
    private static int GetDiskId(char driveLetter)
    {
        var di = new DriveInfo(driveLetter.ToString());
        if (di.DriveType != DriveType.Fixed)
        {
            throw new DetectionFailedException(string.Format("This drive is not fixed drive: {0}", driveLetter));
        }

        var sDrive = "\\\\.\\" + driveLetter + ":";

        var hDrive = CreateFileW(
            sDrive,
            0, // No access to drive
            FileShareRead | FileShareWrite,
            IntPtr.Zero,
            OpenExisting,
            FileAttributeNormal,
            IntPtr.Zero);

        if (hDrive == null || hDrive.IsInvalid)
        {
            int lastError = Marshal.GetLastWin32Error();
            throw new DetectionFailedException(string.Format("Could not detect Disk Id of {0}",
                driveLetter),
                new Win32Exception(lastError)
                );
        }

        var ioctlVolumeGetVolumeDiskExtents = CTL_CODE(
            IoctlVolumeBase, 0,
            MethodBuffered, FileAnyAccess); // From winioctl.h

        var queryDiskExtents =
            new VolumeDiskExtents();

        uint returnedQueryDiskExtentsSize;

        var queryDiskExtentsResult = DeviceIoControl(
            hDrive,
            ioctlVolumeGetVolumeDiskExtents,
            IntPtr.Zero,
            0,
            ref queryDiskExtents,
            (uint)Marshal.SizeOf(queryDiskExtents),
            out returnedQueryDiskExtentsSize,
            IntPtr.Zero);

        hDrive.Close();

        if (!queryDiskExtentsResult)
        {
            int lastError = Marshal.GetLastWin32Error();
            const int ERROR_MORE_DATA = 234; //(0xEA) More data is available.
            if (lastError != ERROR_MORE_DATA
                || (queryDiskExtents.Extents.Length < 1)    // We need at least 1
            )
            {
                throw new DetectionFailedException(string.Format("Could not detect Disk Id of {0}",
                        driveLetter),
                    new Win32Exception(lastError)
                );
            }
        }

        return (int)queryDiskExtents.Extents[0].DiskNumber;
    }

    /// <summary>
    /// Detect the HardwareType by SeekPenalty.
    /// </summary>
    /// <param name="driveLetter">A valid drive letter.</param>
    /// <returns>The detected HardwareType.</returns>
    public static HardwareType DetectHardwareTypeBySeekPenalty(char driveLetter)
    {
        try
        {
            return DetectHardwareTypeBySeekPenalty(GetDiskId(driveLetter));
        }
        catch (DetectionFailedException)
        {
            return HardwareType.Unknown;
        }
    }

    /// <summary>
    /// Detect the HardwareType by SeekPenalty.
    /// </summary>
    /// <param name="driveId">A valid drive Id.</param>
    /// <returns>The detected HardwareType.</returns>
    public static HardwareType DetectHardwareTypeBySeekPenalty(int driveId)
    {
        var physicalDriveName = "\\\\.\\PhysicalDrive" + driveId;

        try
        {
            return HasDriveSeekPenalty(physicalDriveName) ? HardwareType.Hdd : HardwareType.Ssd;
        }
        catch (DetectionFailedException)
        {
            return HardwareType.Unknown;
        }
    }

    /// <summary>
    /// Detect the HardwareType by RotationRate.
    /// </summary>
    /// <param name="driveLetter">A valid drive letter.</param>
    /// <returns>The detected HardwareType.</returns>
    public static HardwareType DetectHardwareTypeByRotationRate(char driveLetter)
    {
        try
        {
            return DetectHardwareTypeByRotationRate(GetDiskId(driveLetter));
        }
        catch (DetectionFailedException)
        {
            return HardwareType.Unknown;
        }
    }

    /// <summary>
    /// Detect the HardwareType by RotationRate.
    /// </summary>
    /// <param name="driveId">A valid drive Id.</param>
    /// <returns>The detected HardwareType.</returns>
    /// <remarks>Administrative privilege is required!</remarks>
    public static HardwareType DetectHardwareTypeByRotationRate(int driveId)
    {
        var physicalDriveName = "\\\\.\\PhysicalDrive" + driveId;

        try
        {
            return HasDriveNominalMediaRotationRate(physicalDriveName) ? HardwareType.Hdd : HardwareType.Ssd;
        }
        catch (DetectionFailedException)
        {
            return HardwareType.Unknown;
        }
    }

    /// <summary>
    /// ws the net get connection.
    /// </summary>
    /// <param name="localName">Name of the local.</param>
    /// <param name="remoteName">Name of the remote.</param>
    /// <param name="length">The length.</param>
    /// <returns>System.Int32.</returns>
    [DllImport("mpr.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern int WNetGetConnection([MarshalAs(UnmanagedType.LPTStr)] string localName, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder remoteName, ref int length);

    /// <summary>
    /// Strings to unc.
    /// </summary>
    /// <param name="path">The path.</param>
    /// <returns>System.Object.</returns>
    private static object strToUnc(string path)
    {
        // This sample code assumes you currently have a drive mapped to p:
        // Find out what remote device a local mapping is to
        int rc = 0;

        // Size for the buffer we will use
        int bsize = 200;

        // Create a new stringbuilder, pre-sized as above
        StringBuilder rname = new StringBuilder(bsize);

        // Call the function
        rc = WNetGetConnection("Z:", rname, ref bsize);

        //https://stackoverflow.com/questions/1088752/how-to-programmatically-discover-mapped-network-drives-on-system-and-their-serve
        //http://www.pinvoke.net/default.aspx/mpr/WNetGetConnection.html
        int length = 255;
        /*2250 (0x8CA)
        This network connection does not exist.
        1200 (0x4B0)
        The specified device name is invalid.*/
        System.Text.StringBuilder UNC = new System.Text.StringBuilder(length);
        int q = WNetGetConnection("Z:", UNC, ref length);
        return UNC.ToString();
    }

    //to get the UNC-Path of a network-drive use something like:
    /// <summary>
    /// CreateFile to get handle to drive.
    /// </summary>
    /// <param name="lpFileName">Name of the lp file.</param>
    /// <param name="dwDesiredAccess">The dw desired access.</param>
    /// <param name="dwShareMode">The dw share mode.</param>
    /// <param name="lpSecurityAttributes">The lp security attributes.</param>
    /// <param name="dwCreationDisposition">The dw creation disposition.</param>
    /// <param name="dwFlagsAndAttributes">The dw flags and attributes.</param>
    /// <param name="hTemplateFile">The h template file.</param>
    /// <returns>SafeFileHandle.</returns>
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern SafeFileHandle CreateFileW(
        [MarshalAs(UnmanagedType.LPWStr)] string lpFileName,
        uint dwDesiredAccess,
        uint dwShareMode,
        IntPtr lpSecurityAttributes,
        uint dwCreationDisposition,
        uint dwFlagsAndAttributes,
        IntPtr hTemplateFile);

    /// <summary>
    /// Controls the code.
    /// </summary>
    /// <param name="DeviceType">Type of the device.</param>
    /// <param name="Function">The function.</param>
    /// <param name="Method">The method.</param>
    /// <param name="Access">The access.</param>
    /// <returns>System.UInt32.</returns>
    private static uint CTL_CODE(uint DeviceType, uint Function,
        uint Method, uint Access)
    {
        return ((DeviceType << 16) | (Access << 14) |
                (Function << 2) | Method);
    }

    /// <summary>
    /// DeviceIoControl to check no seek penalty.
    /// </summary>
    /// <param name="hDevice">The h device.</param>
    /// <param name="dwIoControlCode">The dw io control code.</param>
    /// <param name="lpInBuffer">The lp in buffer.</param>
    /// <param name="nInBufferSize">Size of the n in buffer.</param>
    /// <param name="lpOutBuffer">The lp out buffer.</param>
    /// <param name="nOutBufferSize">Size of the n out buffer.</param>
    /// <param name="lpBytesReturned">The lp bytes returned.</param>
    /// <param name="lpOverlapped">The lp overlapped.</param>
    /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
    [DllImport("kernel32.dll", EntryPoint = "DeviceIoControl",
        SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool DeviceIoControl(
        SafeFileHandle hDevice,
        uint dwIoControlCode,
        ref StoragePropertyQuery lpInBuffer,
        uint nInBufferSize,
        ref DeviceSeekPenaltyDescriptor lpOutBuffer,
        uint nOutBufferSize,
        out uint lpBytesReturned,
        IntPtr lpOverlapped);

    /// <summary>
    /// DeviceIoControl to check nominal media rotation rate.
    /// </summary>
    /// <param name="hDevice">The h device.</param>
    /// <param name="dwIoControlCode">The dw io control code.</param>
    /// <param name="lpInBuffer">The lp in buffer.</param>
    /// <param name="nInBufferSize">Size of the n in buffer.</param>
    /// <param name="lpOutBuffer">The lp out buffer.</param>
    /// <param name="nOutBufferSize">Size of the n out buffer.</param>
    /// <param name="lpBytesReturned">The lp bytes returned.</param>
    /// <param name="lpOverlapped">The lp overlapped.</param>
    /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
    [DllImport("kernel32.dll", EntryPoint = "DeviceIoControl",
        SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool DeviceIoControl(
        SafeFileHandle hDevice,
        uint dwIoControlCode,
        ref AtaIdentifyDeviceQuery lpInBuffer,
        uint nInBufferSize,
        ref AtaIdentifyDeviceQuery lpOutBuffer,
        uint nOutBufferSize,
        out uint lpBytesReturned,
        IntPtr lpOverlapped);

    /// <summary>
    /// Check if the drive has a seek penalty.
    /// </summary>
    /// <param name="physicalDriveName">A valid physicalDriveName.</param>
    /// <returns><c> true </c> if the drive has a seek penalty otherwise, <c> false </c></returns>
    /// <exception cref="BluePrint.Common.Utility.DetectionFailedException"></exception>
    /// <exception cref="BluePrint.Common.Utility.DetectionFailedException"></exception>
    /// <exception cref="Win32Exception"></exception>
    /// <exception cref="Win32Exception"></exception>
    /// <exception cref="DetectionFailedException"></exception>
    /// <remarks>Administrative privilege is required!</remarks>
    private static bool HasDriveSeekPenalty(string physicalDriveName)
    {
        var hDrive = CreateFileW(
            physicalDriveName,
            0, // No access to drive
            FileShareRead | FileShareWrite,
            IntPtr.Zero,
            OpenExisting,
            FileAttributeNormal,
            IntPtr.Zero);

        if (hDrive == null || hDrive.IsInvalid)
        {
            int lastError = Marshal.GetLastWin32Error();
            throw new DetectionFailedException(string.Format("Could not detect SeekPenalty of {0}",
                physicalDriveName),
                new Win32Exception(lastError)
                );
        }

        var ioctlStorageQueryProperty = CTL_CODE(
            IoctlStorageBase, 0x500,
            MethodBuffered, FileAnyAccess); // From winioctl.h

        var querySeekPenalty =
            new StoragePropertyQuery
            {
                PropertyId = StorageDeviceSeekPenaltyProperty,
                QueryType = PropertyStandardQuery
            };

        var querySeekPenaltyDesc =
            new DeviceSeekPenaltyDescriptor();

        uint returnedQuerySeekPenaltySize;

        var querySeekPenaltyResult = DeviceIoControl(
            hDrive,
            ioctlStorageQueryProperty,
            ref querySeekPenalty,
            (uint)Marshal.SizeOf(querySeekPenalty),
            ref querySeekPenaltyDesc,
            (uint)Marshal.SizeOf(querySeekPenaltyDesc),
            out returnedQuerySeekPenaltySize,
            IntPtr.Zero);

        hDrive.Close();

        if (querySeekPenaltyResult == false)
        {
            int lastError = Marshal.GetLastWin32Error();
            throw new DetectionFailedException(string.Format("Could not detect SeekPenalty of {0}",
                physicalDriveName),
                new Win32Exception(lastError)
                );
        }
        if (querySeekPenaltyDesc.IncursSeekPenalty == false)
        {
            //This drive has NO SEEK penalty
            return false;
        }
        //This drive has SEEK penalty
        return true;
    }

    /// <summary>
    /// Check if the drive has a nominal media rotation rate.
    /// </summary>
    /// <param name="physicalDriveName">A valid physicalDriveName.</param>
    /// <returns><c> true </c> if the drive has a media rotation rate otherwise, <c> false </c></returns>
    /// <exception cref="BluePrint.Common.Utility.DetectionFailedException"></exception>
    /// <exception cref="BluePrint.Common.Utility.DetectionFailedException"></exception>
    /// <exception cref="Win32Exception"></exception>
    /// <exception cref="Win32Exception"></exception>
    /// <exception cref="DetectionFailedException"></exception>
    /// <remarks>Administrative privilege is required!</remarks>
    private static bool HasDriveNominalMediaRotationRate(string physicalDriveName)
    {
        var hDrive = CreateFileW(
            physicalDriveName,
            GenericRead | GenericWrite, // Administrative privilege is required
            FileShareRead | FileShareWrite,
            IntPtr.Zero,
            OpenExisting,
            FileAttributeNormal,
            IntPtr.Zero);

        if (hDrive == null || hDrive.IsInvalid)
        {
            int lastError = Marshal.GetLastWin32Error();
            throw new DetectionFailedException(string.Format("Could not detect NominalMediaRotationRate of {0}",
                physicalDriveName),
                new Win32Exception(lastError)
                );
        }

        var ioctlAtaPassThrough = CTL_CODE(
            IoctlScsiBase, 0x040b, MethodBuffered,
            FileReadAccess | FileWriteAccess); // From ntddscsi.h

        var idQuery = new AtaIdentifyDeviceQuery { data = new ushort[256] };

        idQuery.header.Length = (ushort)Marshal.SizeOf(idQuery.header);
        idQuery.header.AtaFlags = (ushort)AtaFlagsDataIn;
        idQuery.header.DataTransferLength =
            (uint)(idQuery.data.Length * 2); // Size of "data" in bytes
        idQuery.header.TimeOutValue = 3; // Sec
        idQuery.header.DataBufferOffset = Marshal.OffsetOf(
            typeof(AtaIdentifyDeviceQuery), "data");
        idQuery.header.PreviousTaskFile = new byte[8];
        idQuery.header.CurrentTaskFile = new byte[8];
        idQuery.header.CurrentTaskFile[6] = 0xec; // ATA IDENTIFY DEVICE

        uint retvalSize;

        var result = DeviceIoControl(
            hDrive,
            ioctlAtaPassThrough,
            ref idQuery,
            (uint)Marshal.SizeOf(idQuery),
            ref idQuery,
            (uint)Marshal.SizeOf(idQuery),
            out retvalSize,
            IntPtr.Zero);

        hDrive.Close();

        if (result == false)
        {
            int lastError = Marshal.GetLastWin32Error();
            throw new DetectionFailedException(string.Format("Could not detect NominalMediaRotationRate of {0}",
                physicalDriveName),
                new Win32Exception(lastError)
                );
        }
        // Word index of nominal media rotation rate
        // (1 means non-rotate device)
        const int kNominalMediaRotRateWordIndex = 217;

        if (idQuery.data[kNominalMediaRotRateWordIndex] == 1)
        {
            return false;
        }
        return true;
    }

    /// <summary>
    /// For DeviceIoControl to get disk extents
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    private struct DiskExtent
    {
        /// <summary>
        /// The disk number
        /// </summary>
        public readonly uint DiskNumber;
        /// <summary>
        /// The starting offset
        /// </summary>
        public readonly long StartingOffset;
        /// <summary>
        /// The extent length
        /// </summary>
        public readonly long ExtentLength;
    }

    /// <summary>
    /// Struct VolumeDiskExtents
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    private struct VolumeDiskExtents
    {
        /// <summary>
        /// The number of disk extents
        /// </summary>
        public readonly uint NumberOfDiskExtents;
        /// <summary>
        /// The extents
        /// </summary>
        [MarshalAs(UnmanagedType.ByValArray)] public readonly DiskExtent[] Extents;
    }

    /// <summary>
    /// Struct StoragePropertyQuery
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    private struct StoragePropertyQuery
    {
        /// <summary>
        /// The property identifier
        /// </summary>
        public uint PropertyId;
        /// <summary>
        /// The query type
        /// </summary>
        public uint QueryType;
        /// <summary>
        /// The additional parameters
        /// </summary>
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] public readonly byte[] AdditionalParameters;
    }

    /// <summary>
    /// Struct DeviceSeekPenaltyDescriptor
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    private struct DeviceSeekPenaltyDescriptor
    {
        /// <summary>
        /// The version
        /// </summary>
        public readonly uint Version;
        /// <summary>
        /// The size
        /// </summary>
        public readonly uint Size;
        /// <summary>
        /// The incurs seek penalty
        /// </summary>
        [MarshalAs(UnmanagedType.U1)] public readonly bool IncursSeekPenalty;
    }

    /// <summary>
    /// Struct AtaPassThroughEx
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    private struct AtaPassThroughEx
    {
        /// <summary>
        /// The length
        /// </summary>
        public ushort Length;
        /// <summary>
        /// The ata flags
        /// </summary>
        public ushort AtaFlags;
        /// <summary>
        /// The path identifier
        /// </summary>
        public readonly byte PathId;
        /// <summary>
        /// The target identifier
        /// </summary>
        public readonly byte TargetId;
        /// <summary>
        /// The lun
        /// </summary>
        public readonly byte Lun;
        /// <summary>
        /// The reserved as uchar
        /// </summary>
        public readonly byte ReservedAsUchar;
        /// <summary>
        /// The data transfer length
        /// </summary>
        public uint DataTransferLength;
        /// <summary>
        /// The time out value
        /// </summary>
        public uint TimeOutValue;
        /// <summary>
        /// The reserved as ulong
        /// </summary>
        public readonly uint ReservedAsUlong;
        /// <summary>
        /// The data buffer offset
        /// </summary>
        public IntPtr DataBufferOffset;
        /// <summary>
        /// The previous task file
        /// </summary>
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public byte[] PreviousTaskFile;
        /// <summary>
        /// The current task file
        /// </summary>
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public byte[] CurrentTaskFile;
    }

    /// <summary>
    /// Struct AtaIdentifyDeviceQuery
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    private struct AtaIdentifyDeviceQuery
    {
        /// <summary>
        /// The header
        /// </summary>
        public AtaPassThroughEx header;
        /// <summary>
        /// The data
        /// </summary>
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)] public ushort[] data;
    }

    #region CreateFile (get handle to drive)

    /// <summary>
    /// The generic read
    /// </summary>
    private const uint GenericRead = 0x80000000;
    /// <summary>
    /// The generic write
    /// </summary>
    private const uint GenericWrite = 0x40000000;
    /// <summary>
    /// The file share read
    /// </summary>
    private const uint FileShareRead = 0x00000001;
    /// <summary>
    /// The file share write
    /// </summary>
    private const uint FileShareWrite = 0x00000002;
    /// <summary>
    /// The open existing
    /// </summary>
    private const uint OpenExisting = 3;
    /// <summary>
    /// The file attribute normal
    /// </summary>
    private const uint FileAttributeNormal = 0x00000080;

    #endregion

    #region Control Codes

    /// <summary>
    /// The file device mass storage
    /// </summary>
    private const uint FileDeviceMassStorage = 0x0000002d;
    /// <summary>
    /// The ioctl storage base
    /// </summary>
    private const uint IoctlStorageBase = FileDeviceMassStorage;
    /// <summary>
    /// The file device controller
    /// </summary>
    private const uint FileDeviceController = 0x00000004;
    /// <summary>
    /// The ioctl scsi base
    /// </summary>
    private const uint IoctlScsiBase = FileDeviceController;
    /// <summary>
    /// The method buffered
    /// </summary>
    private const uint MethodBuffered = 0;
    /// <summary>
    /// The file any access
    /// </summary>
    private const uint FileAnyAccess = 0;
    /// <summary>
    /// The file read access
    /// </summary>
    private const uint FileReadAccess = 0x00000001;
    /// <summary>
    /// The file write access
    /// </summary>
    private const uint FileWriteAccess = 0x00000002;
    /// <summary>
    /// The ioctl volume base
    /// </summary>
    private const uint IoctlVolumeBase = 0x00000056;

    #endregion

    #region DeviceIoControl (seek penalty)

    /// <summary>
    /// The storage device seek penalty property
    /// </summary>
    private const uint StorageDeviceSeekPenaltyProperty = 7;
    /// <summary>
    /// The property standard query
    /// </summary>
    private const uint PropertyStandardQuery = 0;

    #endregion
}

/// <summary>
/// Class NativeMethods.
/// </summary>
public partial class NativeMethods
{
    /// <summary>
    /// The type of structure that the function stores in the buffer.
    /// </summary>
    public enum InfoLevel
    {
        /// <summary>
        /// The function stores a <see cref="UNIVERSAL_NAME_INFO" /> structure in the
        /// buffer.
        /// </summary>
        UniversalName = 1,

        /// <summary>
        /// The function stores a <c>REMOTE_NAME_INFO</c> structure in the buffer.
        /// </summary>
        RemoteName = 2
    }


    /// <summary>
    /// ws the name of the net get universal.
    /// </summary>
    /// <param name="lpLocalPath">The lp local path.</param>
    /// <param name="dwInfoLevel">The dw information level.</param>
    /// <param name="lpBuffer">The lp buffer.</param>
    /// <param name="lpBufferSize">Size of the lp buffer.</param>
    /// <returns>System.Int32.</returns>
    [DllImport("mpr.dll", CharSet = CharSet.Auto)]
    public static extern int WNetGetUniversalName(
        string lpLocalPath,
        InfoLevel dwInfoLevel,
        ref UNIVERSAL_NAME_INFO lpBuffer,
        ref int lpBufferSize);

    /// <summary>
    /// ws the name of the net get universal.
    /// </summary>
    /// <param name="lpLocalPath">The lp local path.</param>
    /// <param name="dwInfoLevel">The dw information level.</param>
    /// <param name="lpBuffer">The lp buffer.</param>
    /// <param name="lpBufferSize">Size of the lp buffer.</param>
    /// <returns>System.Int32.</returns>
    [DllImport("mpr.dll", CharSet = CharSet.Auto)]
    public static extern int WNetGetUniversalName(
        string lpLocalPath,
        InfoLevel dwInfoLevel,
        IntPtr lpBuffer,
        ref int lpBufferSize);

    /// <summary>
    /// The <see cref="UNIVERSAL_NAME_INFO" /> structure contains a pointer to a
    /// Universal Naming Convention (UNC) name string for a network resource.
    /// </summary>
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    public struct UNIVERSAL_NAME_INFO
    {
        /// <summary>
        /// Pointer to the null-terminated UNC name string that identifies a
        /// network resource.
        /// </summary>
        [MarshalAs(UnmanagedType.LPTStr)]
        public string lpUniversalName;
    }
}

/// <summary>
/// Class DetectionFailedException.
/// Implements the <see cref="System.Exception" />
/// </summary>
/// <seealso cref="System.Exception" />
public class DetectionFailedException : Exception
{
    /// <summary>
    /// Initializes a new instance of the <see cref="DetectionFailedException"/> class.
    /// </summary>
    public DetectionFailedException()
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="DetectionFailedException"/> class.
    /// </summary>
    /// <param name="message">描述错误的消息。</param>
    public DetectionFailedException(string message)
        : base(message)
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="DetectionFailedException"/> class.
    /// </summary>
    /// <param name="message">The message.</param>
    /// <param name="inner">The inner.</param>
    public DetectionFailedException(string message, Exception inner)
        : base(message, inner)
    {
    }
}

/// <summary>
/// Class DriveInfoExtended.
/// </summary>
public class DriveInfoExtended
{
    /// <summary>
    /// Gets or sets the name.
    /// </summary>
    /// <value>The name.</value>
    public string Name { get; set; }
    /// <summary>
    /// Gets or sets the drive letter.
    /// </summary>
    /// <value>The drive letter.</value>
    public char DriveLetter { get; set; }
    /// <summary>
    /// Gets or sets the type of the drive.
    /// </summary>
    /// <value>The type of the drive.</value>
    public DriveType DriveType { get; set; }
    /// <summary>
    /// Gets or sets the identifier.
    /// </summary>
    /// <value>The identifier.</value>
    public int Id { get; set; }
    /// <summary>
    /// Gets or sets the volume label.
    /// </summary>
    /// <value>The volume label.</value>
    public string VolumeLabel { get; set; }
    /// <summary>
    /// Gets or sets the drive format.
    /// </summary>
    /// <value>The drive format.</value>
    public string DriveFormat { get; set; }
    /// <summary>
    /// Gets or sets the total free space.
    /// </summary>
    /// <value>The total free space.</value>
    public long TotalFreeSpace { get; set; }
    /// <summary>
    /// Gets or sets the total size.
    /// </summary>
    /// <value>The total size.</value>
    public long TotalSize { get; set; }
    /// <summary>
    /// Gets or sets the available free space.
    /// </summary>
    /// <value>The available free space.</value>
    public long AvailableFreeSpace { get; set; }
    /// <summary>
    /// Gets or sets the type of the hardware.
    /// </summary>
    /// <value>The type of the hardware.</value>
    public HardwareType HardwareType { get; set; }
    /// <summary>
    /// Gets or sets the root directory.
    /// </summary>
    /// <value>The root directory.</value>
    public DirectoryInfo RootDirectory { get; set; }
    /// <summary>
    /// Gets or sets the unc path.
    /// </summary>
    /// <value>The unc path.</value>
    public string UncPath { get; set; }
}

/// <summary>
/// Possible HardwareTypes.
/// </summary>
public enum HardwareType
{
    /// <summary>
    /// Unknown hardware type.
    /// </summary>
    Unknown,

    /// <summary>
    /// Hard Disk Drive.
    /// </summary>
    Hdd,

    /// <summary>
    /// Solid State Drive.
    /// </summary>
    Ssd
}

/// <summary>
/// Possible QueryTypes.
/// </summary>
public enum QueryType
{
    /// <summary>
    /// Detect the HardwareType by SeekPenalty.
    /// </summary>
    SeekPenalty,

    /// <summary>
    /// Detect the HardwareType by RotationRate.
    /// </summary>
    RotationRate
}

/// <summary>
/// Class Pathing.
/// </summary>
internal static class Pathing
{
    [DllImport("mpr.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int WNetGetConnection(
        [MarshalAs(UnmanagedType.LPTStr)] string localName,
        [MarshalAs(UnmanagedType.LPTStr)] StringBuilder remoteName,
        ref int length);

    /// <summary>
    ///     Given a path, returns the UNC path or the original. (No exceptions
    ///     are raised by this function directly). For example, "P:\2008-02-29"
    ///     might return: "\\networkserver\Shares\Photos\2008-02-09"
    ///     http://www.wiredprairie.us/blog/index.php/archives/22
    /// </summary>
    /// <param name="originalPath">The path to convert to a UNC Path</param>
    /// <returns>
    ///     A UNC path. If a network drive letter is specified, the
    ///     drive letter is converted to a UNC or network path. If the
    ///     originalPath cannot be converted, it is returned unchanged.
    /// </returns>        
    public static string GetUNCPath(string originalPath)
    {
        var sb = new StringBuilder(512);
        var size = sb.Capacity;

        // look for the {LETTER}: combination ...
        if (originalPath.Length > 2 && originalPath[1] == ':')
        {
            // don't use char.IsLetter here - as that can be misleading
            // the only valid drive letters are a-z && A-Z.
            var c = originalPath[0];
            if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
            {
                var error = WNetGetConnection(originalPath.Substring(0, 2),
                    sb, ref size);
                if (error == 0)
                {
                    var dir = new DirectoryInfo(originalPath);

                    var path = Path.GetFullPath(originalPath)
                        .Substring(Path.GetPathRoot(originalPath).Length);
                    return Path.Combine(sb.ToString().TrimEnd(), path);
                }
            }
        }

        return originalPath;
    }
}
posted @ 2026-06-09 23:09  wesson2019  阅读(12)  评论(0)    收藏  举报