C# 检查硬盘分区是ssd还是hdd

C# detect driver ssd/hdd

来自github的代码,略做了一丢丢修改。

using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;

/// <summary>
    /// Class DiskDetectionUtils.
    /// reference : https://github.com/bitbeans/diskdetector-net
    /// </summary>
    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;
        }
    }

 

调用接口DiskDetectionUtils.DetectDrive即可,第一个参数是a-z的字符。比如:

var ext = DiskDetectionUtils.DetectDrive(targetDirectory.Substring(0, 1));

if(ext.HardwareType != HardwareType.Ssd)

{

   // do sht.

}

posted @ 2021-06-17 11:21  bodong  阅读(437)  评论(0编辑  收藏  举报