├── .gitattributes ├── .gitignore ├── LICENSE.md ├── README.md ├── Tests ├── DiskDetectorTests.cs └── Tests.csproj ├── diskdetector-net.sln └── diskdetector-net ├── Detector.cs ├── Exceptions └── DetectionFailedException.cs ├── Models ├── DriveInfoExtended.cs ├── HardwareType.cs └── QueryType.cs ├── Tools └── Pathing.cs └── diskdetector-net.csproj /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | # DNX 42 | project.lock.json 43 | artifacts/ 44 | 45 | *_i.c 46 | *_p.c 47 | *_i.h 48 | *.ilk 49 | *.meta 50 | *.obj 51 | *.pch 52 | *.pdb 53 | *.pgc 54 | *.pgd 55 | *.rsp 56 | *.sbr 57 | *.tlb 58 | *.tli 59 | *.tlh 60 | *.tmp 61 | *.tmp_proj 62 | *.log 63 | *.vspscc 64 | *.vssscc 65 | .builds 66 | *.pidb 67 | *.svclog 68 | *.scc 69 | 70 | # Chutzpah Test files 71 | _Chutzpah* 72 | 73 | # Visual C++ cache files 74 | ipch/ 75 | *.aps 76 | *.ncb 77 | *.opensdf 78 | *.sdf 79 | *.cachefile 80 | 81 | # Visual Studio profiler 82 | *.psess 83 | *.vsp 84 | *.vspx 85 | 86 | # TFS 2012 Local Workspace 87 | $tf/ 88 | 89 | # Guidance Automation Toolkit 90 | *.gpState 91 | 92 | # ReSharper is a .NET coding add-in 93 | _ReSharper*/ 94 | *.[Rr]e[Ss]harper 95 | *.DotSettings.user 96 | 97 | # JustCode is a .NET coding add-in 98 | .JustCode 99 | 100 | # TeamCity is a build add-in 101 | _TeamCity* 102 | 103 | # DotCover is a Code Coverage Tool 104 | *.dotCover 105 | 106 | # NCrunch 107 | _NCrunch_* 108 | .*crunch*.local.xml 109 | 110 | # MightyMoose 111 | *.mm.* 112 | AutoTest.Net/ 113 | 114 | # Web workbench (sass) 115 | .sass-cache/ 116 | 117 | # Installshield output folder 118 | [Ee]xpress/ 119 | 120 | # DocProject is a documentation generator add-in 121 | DocProject/buildhelp/ 122 | DocProject/Help/*.HxT 123 | DocProject/Help/*.HxC 124 | DocProject/Help/*.hhc 125 | DocProject/Help/*.hhk 126 | DocProject/Help/*.hhp 127 | DocProject/Help/Html2 128 | DocProject/Help/html 129 | 130 | # Click-Once directory 131 | publish/ 132 | 133 | # Publish Web Output 134 | *.[Pp]ublish.xml 135 | *.azurePubxml 136 | ## TODO: Comment the next line if you want to checkin your 137 | ## web deploy settings but do note that will include unencrypted 138 | ## passwords 139 | #*.pubxml 140 | 141 | *.publishproj 142 | 143 | # NuGet Packages 144 | *.nupkg 145 | # The packages folder can be ignored because of Package Restore 146 | **/packages/* 147 | # except build/, which is used as an MSBuild target. 148 | !**/packages/build/ 149 | # Uncomment if necessary however generally it will be regenerated when needed 150 | #!**/packages/repositories.config 151 | 152 | # Windows Azure Build Output 153 | csx/ 154 | *.build.csdef 155 | 156 | # Windows Store app package directory 157 | AppPackages/ 158 | 159 | # Visual Studio cache files 160 | # files ending in .cache can be ignored 161 | *.[Cc]ache 162 | # but keep track of directories ending in .cache 163 | !*.[Cc]ache/ 164 | 165 | # Others 166 | ClientBin/ 167 | [Ss]tyle[Cc]op.* 168 | ~$* 169 | *~ 170 | *.dbmdl 171 | *.dbproj.schemaview 172 | *.pfx 173 | *.publishsettings 174 | node_modules/ 175 | orleans.codegen.cs 176 | 177 | # RIA/Silverlight projects 178 | Generated_Code/ 179 | 180 | # Backup & report files from converting an old project file 181 | # to a newer Visual Studio version. Backup files are not needed, 182 | # because we have git ;-) 183 | _UpgradeReport_Files/ 184 | Backup*/ 185 | UpgradeLog*.XML 186 | UpgradeLog*.htm 187 | 188 | # SQL Server files 189 | *.mdf 190 | *.ldf 191 | 192 | # Business Intelligence projects 193 | *.rdl.data 194 | *.bim.layout 195 | *.bim_*.settings 196 | 197 | # Microsoft Fakes 198 | FakesAssemblies/ 199 | 200 | # Node.js Tools for Visual Studio 201 | .ntvs_analysis.dat 202 | 203 | # Visual Studio 6 build log 204 | *.plg 205 | 206 | # Visual Studio 6 workspace options file 207 | *.opt 208 | 209 | # LightSwitch generated files 210 | GeneratedArtifacts/ 211 | _Pvt_Extensions/ 212 | ModelManifest.xml 213 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 - 2019 Christian Hermann & Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # diskdetector-net [![NuGet Version](https://img.shields.io/nuget/v/diskdetector-net.svg?style=flat-square)](https://www.nuget.org/packages/diskdetector-net/) [![License](http://img.shields.io/badge/license-MIT-green.svg?style=flat-square)](https://github.com/bitbeans/diskdetector-net/blob/master/LICENSE.md) 2 | 3 | 4 | Class to detect the hardware type (SSD or HDD) of a hard disk on windows based systems. 5 | 6 | This implementation is based on [emoacht.wordpress.com](https://emoacht.wordpress.com/2012/11/06/csharp-ssd/). 7 | 8 | 9 | 10 | ## Installation 11 | 12 | There is a [NuGet package](https://www.nuget.org/packages/diskdetector-net/) available. 13 | 14 | ## Example 15 | 16 | ```csharp 17 | public void DetectFixedDrivesTest() 18 | { 19 | var detectedDrives = Detector.DetectFixedDrives(QueryType.SeekPenalty); 20 | if (detectedDrives.Count != 0) 21 | { 22 | foreach (var detectedDrive in detectedDrives) 23 | { 24 | Console.WriteLine("Drive {0}", detectedDrive.Name); 25 | Console.WriteLine(" File type: {0}", detectedDrive.DriveType); 26 | 27 | Console.WriteLine(" Volume label: {0}", detectedDrive.VolumeLabel); 28 | Console.WriteLine(" File system: {0}", detectedDrive.DriveFormat); 29 | Console.WriteLine(" Letter: {0}", detectedDrive.DriveLetter); 30 | Console.WriteLine(" HardwareType: {0}", detectedDrive.HardwareType); 31 | Console.WriteLine(" Id: {0}", detectedDrive.Id); 32 | Console.WriteLine( 33 | " Available space to current user:{0, 15} bytes", 34 | detectedDrive.AvailableFreeSpace); 35 | 36 | Console.WriteLine( 37 | " Total available space: {0, 15} bytes", 38 | detectedDrive.TotalFreeSpace); 39 | 40 | Console.WriteLine( 41 | " Total size of drive: {0, 15} bytes ", 42 | detectedDrive.TotalSize); 43 | } 44 | } 45 | 46 | /* 47 | Drive C:\ 48 | File type: Fixed 49 | Volume label: Windows 50 | File system: NTFS 51 | Letter: C 52 | HardwareType: Ssd 53 | Id: 0 54 | Available space to current user: 23861460992 bytes 55 | Total available space: 23861460992 bytes 56 | Total size of drive: 255505461248 bytes 57 | Drive F:\ 58 | File type: Fixed 59 | Volume label: Data 60 | File system: NTFS 61 | Letter: F 62 | HardwareType: Hdd 63 | Id: 1 64 | Available space to current user: 1250781491200 bytes 65 | Total available space: 1250781491200 bytes 66 | Total size of drive: 2000397791232 bytes 67 | */ 68 | 69 | ``` 70 | 71 | ## License 72 | [MIT](https://en.wikipedia.org/wiki/MIT_License) -------------------------------------------------------------------------------- /Tests/DiskDetectorTests.cs: -------------------------------------------------------------------------------- 1 | using DiskDetector; 2 | using DiskDetector.Models; 3 | 4 | using Xunit; 5 | using Xunit.Abstractions; 6 | 7 | namespace Tests 8 | { 9 | /// 10 | /// Class to test the DiskDetector. 11 | /// Note: As every device has a different hardware specification, 12 | /// it`s currently hard to write usable tests. 13 | /// 14 | public class DiskDetectorTests 15 | { 16 | private readonly ITestOutputHelper output; 17 | 18 | // As recommended by https://xunit.net/docs/capturing-output 19 | public DiskDetectorTests(ITestOutputHelper output) 20 | { 21 | this.output = output; 22 | } 23 | 24 | [Theory] 25 | [InlineData("C")] 26 | [InlineData("F")] 27 | public void DetectFixedDriveTest(string driveName) 28 | { 29 | var detectedDrive = Detector.DetectFixedDrive(driveName, QueryType.RotationRate, true); 30 | output.WriteLine("Drive {0}", detectedDrive.Name); 31 | output.WriteLine(" File type: {0}", detectedDrive.DriveType); 32 | output.WriteLine(" Volume label: {0}", detectedDrive.VolumeLabel); 33 | output.WriteLine(" UNC Path: {0}", detectedDrive.UncPath); 34 | output.WriteLine(" File system: {0}", detectedDrive.DriveFormat); 35 | output.WriteLine(" Letter: {0}", detectedDrive.DriveLetter); 36 | output.WriteLine(" HardwareType: {0}", detectedDrive.HardwareType); 37 | output.WriteLine(" Id: {0}", detectedDrive.Id); 38 | output.WriteLine( 39 | " Available space to current user:{0, 15} bytes", 40 | detectedDrive.AvailableFreeSpace); 41 | 42 | output.WriteLine( 43 | " Total available space: {0, 15} bytes", 44 | detectedDrive.TotalFreeSpace); 45 | 46 | output.WriteLine( 47 | " Total size of drive: {0, 15} bytes ", 48 | detectedDrive.TotalSize); 49 | 50 | Assert.NotNull(detectedDrive); 51 | } 52 | 53 | 54 | [Fact] 55 | public void DetectFixedDrivesTest() 56 | { 57 | var detectedDrives = Detector.DetectFixedDrives(QueryType.RotationRate, true); 58 | if (detectedDrives.Count != 0) 59 | { 60 | foreach (var detectedDrive in detectedDrives) 61 | { 62 | output.WriteLine("Drive {0}", detectedDrive.Name); 63 | output.WriteLine(" File type: {0}", detectedDrive.DriveType); 64 | output.WriteLine(" Volume label: {0}", detectedDrive.VolumeLabel); 65 | output.WriteLine(" UNC Path: {0}", detectedDrive.UncPath); 66 | output.WriteLine(" File system: {0}", detectedDrive.DriveFormat); 67 | output.WriteLine(" Letter: {0}", detectedDrive.DriveLetter); 68 | output.WriteLine(" HardwareType: {0}", detectedDrive.HardwareType); 69 | output.WriteLine(" Id: {0}", detectedDrive.Id); 70 | output.WriteLine( 71 | " Available space to current user:{0, 15} bytes", 72 | detectedDrive.AvailableFreeSpace); 73 | 74 | output.WriteLine( 75 | " Total available space: {0, 15} bytes", 76 | detectedDrive.TotalFreeSpace); 77 | 78 | output.WriteLine( 79 | " Total size of drive: {0, 15} bytes ", 80 | detectedDrive.TotalSize); 81 | } 82 | } 83 | Assert.True(detectedDrives.Count > 0); 84 | /* 85 | Drive C:\ 86 | File type: Fixed 87 | Volume label: Windows 88 | File system: NTFS 89 | Letter: C 90 | HardwareType: Ssd 91 | Id: 0 92 | Available space to current user: 23861460992 bytes 93 | Total available space: 23861460992 bytes 94 | Total size of drive: 255505461248 bytes 95 | Drive F:\ 96 | File type: Fixed 97 | Volume label: Data 98 | File system: NTFS 99 | Letter: F 100 | HardwareType: Hdd 101 | Id: 1 102 | Available space to current user: 1250781491200 bytes 103 | Total available space: 1250781491200 bytes 104 | Total size of drive: 2000397791232 bytes 105 | Drive Z:\ 106 | File type: Network 107 | Volume label: hubiC 108 | UNC Path: \\ExpanDrive\hubiC 109 | File system: EXFS 110 | Letter: Z 111 | HardwareType: Unknown 112 | Id: -1 113 | Available space to current user: 50000000000 bytes 114 | Total available space: 50000000000 bytes 115 | Total size of drive: 100000000000 bytes 116 | */ 117 | } 118 | 119 | [Fact] 120 | public void DetectDriveTest() 121 | { 122 | var detectedDrive = Detector.DetectDrive("Z", QueryType.RotationRate, true); 123 | output.WriteLine("Drive {0}", detectedDrive.Name); 124 | output.WriteLine(" File type: {0}", detectedDrive.DriveType); 125 | output.WriteLine(" Volume label: {0}", detectedDrive.VolumeLabel); 126 | output.WriteLine(" UNC Path: {0}", detectedDrive.UncPath); 127 | output.WriteLine(" File system: {0}", detectedDrive.DriveFormat); 128 | output.WriteLine(" Letter: {0}", detectedDrive.DriveLetter); 129 | output.WriteLine(" HardwareType: {0}", detectedDrive.HardwareType); 130 | output.WriteLine(" Id: {0}", detectedDrive.Id); 131 | output.WriteLine( 132 | " Available space to current user:{0, 15} bytes", 133 | detectedDrive.AvailableFreeSpace); 134 | 135 | output.WriteLine( 136 | " Total available space: {0, 15} bytes", 137 | detectedDrive.TotalFreeSpace); 138 | 139 | output.WriteLine( 140 | " Total size of drive: {0, 15} bytes ", 141 | detectedDrive.TotalSize); 142 | 143 | Assert.NotNull(detectedDrive); 144 | } 145 | 146 | [Fact] 147 | public void DetectDrivesTest() 148 | { 149 | var detectedDrives = Detector.DetectDrives(QueryType.RotationRate, true); 150 | if (detectedDrives.Count != 0) 151 | { 152 | foreach (var detectedDrive in detectedDrives) 153 | { 154 | output.WriteLine("Drive {0}", detectedDrive.Name); 155 | output.WriteLine(" File type: {0}", detectedDrive.DriveType); 156 | output.WriteLine(" Volume label: {0}", detectedDrive.VolumeLabel); 157 | output.WriteLine(" UNC Path: {0}", detectedDrive.UncPath); 158 | output.WriteLine(" File system: {0}", detectedDrive.DriveFormat); 159 | output.WriteLine(" Letter: {0}", detectedDrive.DriveLetter); 160 | output.WriteLine(" HardwareType: {0}", detectedDrive.HardwareType); 161 | output.WriteLine(" Id: {0}", detectedDrive.Id); 162 | output.WriteLine( 163 | " Available space to current user:{0, 15} bytes", 164 | detectedDrive.AvailableFreeSpace); 165 | 166 | output.WriteLine( 167 | " Total available space: {0, 15} bytes", 168 | detectedDrive.TotalFreeSpace); 169 | 170 | output.WriteLine( 171 | " Total size of drive: {0, 15} bytes ", 172 | detectedDrive.TotalSize); 173 | } 174 | } 175 | Assert.True(detectedDrives.Count > 0); 176 | } 177 | 178 | [Fact] 179 | public void DetectHardwareTypeByRotationRate1Test() 180 | { 181 | // Note: Test requires administrator privileges. 182 | var ssd = Detector.DetectHardwareTypeByRotationRate(0); 183 | var hdd = Detector.DetectHardwareTypeByRotationRate(1); 184 | Assert.Equal(HardwareType.Ssd, ssd); 185 | Assert.Equal(HardwareType.Hdd, hdd); 186 | } 187 | 188 | [Fact] 189 | public void DetectHardwareTypeByRotationRate2Test() 190 | { 191 | var driveLetterStringSsd = "C"; 192 | char driveLetterSsd = driveLetterStringSsd[0]; 193 | 194 | var driveLetterStringHdd = "E"; 195 | char driveLetterHdd = driveLetterStringHdd[0]; 196 | // Note: Test requires administrator privileges. 197 | var ssd = Detector.DetectHardwareTypeByRotationRate(driveLetterSsd); 198 | var hdd = Detector.DetectHardwareTypeByRotationRate(driveLetterHdd); 199 | Assert.Equal(HardwareType.Ssd, ssd); 200 | Assert.Equal(HardwareType.Hdd, hdd); 201 | } 202 | 203 | [Fact] 204 | public void DetectHardwareTypeBySeekPenalty1Test() 205 | { 206 | var ssd = Detector.DetectHardwareTypeBySeekPenalty(0); 207 | var hdd = Detector.DetectHardwareTypeBySeekPenalty(1); 208 | Assert.Equal(HardwareType.Ssd, ssd); 209 | Assert.Equal(HardwareType.Hdd, hdd); 210 | } 211 | 212 | [Fact] 213 | public void DetectHardwareTypeBySeekPenalty2Test() 214 | { 215 | var driveLetterStringSsd = "C"; 216 | char driveLetterSsd = driveLetterStringSsd[0]; 217 | 218 | var driveLetterStringHdd = "E"; 219 | char driveLetterHdd = driveLetterStringHdd[0]; 220 | var ssd = Detector.DetectHardwareTypeBySeekPenalty(driveLetterSsd); 221 | var hdd = Detector.DetectHardwareTypeBySeekPenalty(driveLetterHdd); 222 | Assert.Equal(HardwareType.Ssd, ssd); 223 | Assert.Equal(HardwareType.Hdd, hdd); 224 | } 225 | 226 | [Fact] 227 | public void IsAdministratorTest() 228 | { 229 | var isAdministrator = Detector.IsAdministrator(); 230 | Assert.True(isAdministrator); 231 | } 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /Tests/Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /diskdetector-net.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2036 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{97365F76-5C9F-445C-BFBD-58B54F17ED53}" 7 | ProjectSection(SolutionItems) = preProject 8 | LICENSE.md = LICENSE.md 9 | README.md = README.md 10 | EndProjectSection 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "diskdetector-net", "diskdetector-net\diskdetector-net.csproj", "{8D92F7E8-ACB7-4711-8364-F62D2E3D0247}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{A00FF10D-4EA9-4774-9332-D121BCEEC24F}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {8D92F7E8-ACB7-4711-8364-F62D2E3D0247}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {8D92F7E8-ACB7-4711-8364-F62D2E3D0247}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {8D92F7E8-ACB7-4711-8364-F62D2E3D0247}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {8D92F7E8-ACB7-4711-8364-F62D2E3D0247}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {A00FF10D-4EA9-4774-9332-D121BCEEC24F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {A00FF10D-4EA9-4774-9332-D121BCEEC24F}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {A00FF10D-4EA9-4774-9332-D121BCEEC24F}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {A00FF10D-4EA9-4774-9332-D121BCEEC24F}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {9AE50727-AD0A-441C-AF8B-7F8A416278DA} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /diskdetector-net/Detector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.IO; 5 | using System.Runtime.InteropServices; 6 | using System.Security; 7 | using System.Security.Principal; 8 | using System.Text; 9 | using DiskDetector.Exceptions; 10 | using DiskDetector.Models; 11 | using DiskDetector.Tools; 12 | using Microsoft.Win32.SafeHandles; 13 | 14 | namespace DiskDetector 15 | { 16 | /// 17 | /// Class to detect the hardware type of a disk. 18 | /// 19 | /// 20 | public static class Detector 21 | { 22 | #region DeviceIoControl (nominal media rotation rate) 23 | 24 | private const uint AtaFlagsDataIn = 0x02; 25 | 26 | #endregion 27 | 28 | /// 29 | /// Check if the application is running as administrator. 30 | /// 31 | /// true if the application is running as administrator otherwise, false 32 | /// 33 | /// 34 | /// 35 | public static bool IsAdministrator() 36 | { 37 | var identity = WindowsIdentity.GetCurrent(); 38 | if (identity == null) return false; 39 | var principal = new WindowsPrincipal(identity); 40 | return principal.IsInRole(WindowsBuiltInRole.Administrator); 41 | } 42 | 43 | /// 44 | /// Detect a fixed drive by letter. 45 | /// 46 | /// A valid drive letter. 47 | /// The QueryType. 48 | /// Use QueryType.SeekPenalty as fallback. 49 | /// A list of DriveInfoExtended. 50 | /// 51 | /// 52 | /// 53 | /// 54 | /// 55 | /// 56 | /// 57 | /// 58 | public static DriveInfoExtended DetectFixedDrive(string driveName, QueryType queryType = QueryType.SeekPenalty, 59 | bool useFallbackQuery = true) 60 | { 61 | var driveInfoExtended = new DriveInfoExtended(); 62 | var logicalDrive = new DriveInfo(driveName); 63 | if (logicalDrive.DriveType == DriveType.Fixed) 64 | { 65 | if (logicalDrive.IsReady) 66 | { 67 | var tmp = new DriveInfoExtended 68 | { 69 | DriveFormat = logicalDrive.DriveFormat, 70 | VolumeLabel = logicalDrive.VolumeLabel, 71 | Name = logicalDrive.Name, 72 | UncPath = Pathing.GetUNCPath(logicalDrive.Name), 73 | DriveType = logicalDrive.DriveType, 74 | AvailableFreeSpace = logicalDrive.AvailableFreeSpace, 75 | TotalSize = logicalDrive.TotalSize, 76 | TotalFreeSpace = logicalDrive.TotalFreeSpace, 77 | RootDirectory = logicalDrive.RootDirectory, 78 | DriveLetter = logicalDrive.Name.Substring(0, 1).ToCharArray()[0] 79 | }; 80 | 81 | var driveId = GetDiskId(tmp.DriveLetter); 82 | if (driveId != -1) 83 | { 84 | tmp.Id = driveId; 85 | if (queryType == QueryType.SeekPenalty) 86 | { 87 | tmp.HardwareType = DetectHardwareTypeBySeekPenalty(driveId); 88 | } 89 | else 90 | { 91 | if (IsAdministrator()) 92 | { 93 | tmp.HardwareType = DetectHardwareTypeByRotationRate(driveId); 94 | } 95 | else 96 | { 97 | if (useFallbackQuery) 98 | { 99 | tmp.HardwareType = DetectHardwareTypeBySeekPenalty(driveId); 100 | } 101 | else 102 | { 103 | throw new SecurityException( 104 | "DetectHardwareTypeBySeekPenalty needs administrative access."); 105 | } 106 | } 107 | } 108 | if (tmp.HardwareType != HardwareType.Unknown) 109 | { 110 | driveInfoExtended = tmp; 111 | } 112 | } 113 | } 114 | } 115 | return driveInfoExtended; 116 | } 117 | 118 | /// 119 | /// Detect all fixed drives. 120 | /// 121 | /// The QueryType. 122 | /// Use QueryType.SeekPenalty as fallback. 123 | /// A list of DriveInfoExtended. 124 | /// 125 | /// 126 | /// 127 | /// 128 | /// 129 | /// 130 | /// 131 | /// 132 | public static List DetectFixedDrives(QueryType queryType = QueryType.SeekPenalty, 133 | bool useFallbackQuery = true) 134 | { 135 | var driveInfoExtended = new List(); 136 | var logicalDrives = DriveInfo.GetDrives(); 137 | 138 | foreach (var logicalDrive in logicalDrives) 139 | { 140 | if (logicalDrive.DriveType == DriveType.Fixed) 141 | { 142 | if (logicalDrive.IsReady) 143 | { 144 | var tmp = new DriveInfoExtended 145 | { 146 | DriveFormat = logicalDrive.DriveFormat, 147 | VolumeLabel = logicalDrive.VolumeLabel, 148 | Name = logicalDrive.Name, 149 | UncPath = Pathing.GetUNCPath(logicalDrive.Name), 150 | DriveType = logicalDrive.DriveType, 151 | AvailableFreeSpace = logicalDrive.AvailableFreeSpace, 152 | TotalSize = logicalDrive.TotalSize, 153 | TotalFreeSpace = logicalDrive.TotalFreeSpace, 154 | RootDirectory = logicalDrive.RootDirectory, 155 | DriveLetter = logicalDrive.Name.Substring(0, 1).ToCharArray()[0] 156 | }; 157 | 158 | var driveId = GetDiskId(tmp.DriveLetter); 159 | if (driveId != -1) 160 | { 161 | tmp.Id = driveId; 162 | if (queryType == QueryType.SeekPenalty) 163 | { 164 | tmp.HardwareType = DetectHardwareTypeBySeekPenalty(driveId); 165 | } 166 | else 167 | { 168 | if (IsAdministrator()) 169 | { 170 | tmp.HardwareType = DetectHardwareTypeByRotationRate(driveId); 171 | } 172 | else 173 | { 174 | if (useFallbackQuery) 175 | { 176 | tmp.HardwareType = DetectHardwareTypeBySeekPenalty(driveId); 177 | } 178 | else 179 | { 180 | throw new SecurityException( 181 | "DetectHardwareTypeBySeekPenalty needs administrative access."); 182 | } 183 | } 184 | } 185 | if (tmp.HardwareType != HardwareType.Unknown) 186 | { 187 | driveInfoExtended.Add(tmp); 188 | } 189 | } 190 | } 191 | } 192 | } 193 | return driveInfoExtended; 194 | } 195 | 196 | /// 197 | /// Detect a fixed or removable drive. 198 | /// 199 | /// A valid drive letter. 200 | /// The QueryType. 201 | /// Use QueryType.SeekPenalty as fallback. 202 | /// A list of DriveInfoExtended. 203 | /// 204 | /// 205 | /// 206 | /// 207 | /// 208 | /// 209 | /// 210 | /// 211 | public static DriveInfoExtended DetectDrive(string driveName, QueryType queryType = QueryType.SeekPenalty, 212 | bool useFallbackQuery = true) 213 | { 214 | var driveInfoExtended = new DriveInfoExtended(); 215 | var logicalDrive = new DriveInfo(driveName); 216 | if (logicalDrive.DriveType == DriveType.Fixed) 217 | { 218 | if (logicalDrive.IsReady) 219 | { 220 | var tmp = new DriveInfoExtended 221 | { 222 | DriveFormat = logicalDrive.DriveFormat, 223 | VolumeLabel = logicalDrive.VolumeLabel, 224 | Name = logicalDrive.Name, 225 | UncPath = Pathing.GetUNCPath(logicalDrive.Name), 226 | DriveType = logicalDrive.DriveType, 227 | AvailableFreeSpace = logicalDrive.AvailableFreeSpace, 228 | TotalSize = logicalDrive.TotalSize, 229 | TotalFreeSpace = logicalDrive.TotalFreeSpace, 230 | RootDirectory = logicalDrive.RootDirectory, 231 | DriveLetter = logicalDrive.Name.Substring(0, 1).ToCharArray()[0] 232 | }; 233 | 234 | var driveId = GetDiskId(tmp.DriveLetter); 235 | if (driveId != -1) 236 | { 237 | tmp.Id = driveId; 238 | if (queryType == QueryType.SeekPenalty) 239 | { 240 | tmp.HardwareType = DetectHardwareTypeBySeekPenalty(driveId); 241 | } 242 | else 243 | { 244 | if (IsAdministrator()) 245 | { 246 | tmp.HardwareType = DetectHardwareTypeByRotationRate(driveId); 247 | } 248 | else 249 | { 250 | if (useFallbackQuery) 251 | { 252 | tmp.HardwareType = DetectHardwareTypeBySeekPenalty(driveId); 253 | } 254 | else 255 | { 256 | throw new SecurityException( 257 | "DetectHardwareTypeBySeekPenalty needs administrative access."); 258 | } 259 | } 260 | } 261 | driveInfoExtended = tmp; 262 | } 263 | } 264 | } 265 | else 266 | { 267 | if (logicalDrive.IsReady) 268 | { 269 | 270 | var tmp = new DriveInfoExtended 271 | { 272 | DriveFormat = logicalDrive.DriveFormat, 273 | VolumeLabel = logicalDrive.VolumeLabel, 274 | Name = logicalDrive.Name, 275 | UncPath = Pathing.GetUNCPath(logicalDrive.Name), 276 | DriveType = logicalDrive.DriveType, 277 | AvailableFreeSpace = logicalDrive.AvailableFreeSpace, 278 | TotalSize = logicalDrive.TotalSize, 279 | TotalFreeSpace = logicalDrive.TotalFreeSpace, 280 | RootDirectory = logicalDrive.RootDirectory, 281 | DriveLetter = logicalDrive.Name.Substring(0, 1).ToCharArray()[0], 282 | HardwareType = HardwareType.Unknown, 283 | Id = -1 284 | }; 285 | driveInfoExtended = tmp; 286 | } 287 | } 288 | return driveInfoExtended; 289 | } 290 | 291 | /// 292 | /// Detect fixed and removable drives. 293 | /// 294 | /// The QueryType. 295 | /// Use QueryType.SeekPenalty as fallback. 296 | /// A list of DriveInfoExtended. 297 | /// 298 | /// 299 | /// 300 | /// 301 | /// 302 | /// 303 | /// 304 | /// 305 | public static List DetectDrives(QueryType queryType = QueryType.SeekPenalty, 306 | bool useFallbackQuery = true) 307 | { 308 | var driveInfoExtended = new List(); 309 | var logicalDrives = DriveInfo.GetDrives(); 310 | 311 | foreach (var logicalDrive in logicalDrives) 312 | { 313 | if (logicalDrive.DriveType == DriveType.Fixed) 314 | { 315 | if (logicalDrive.IsReady) 316 | { 317 | var tmp = new DriveInfoExtended 318 | { 319 | DriveFormat = logicalDrive.DriveFormat, 320 | VolumeLabel = logicalDrive.VolumeLabel, 321 | Name = logicalDrive.Name, 322 | UncPath = Pathing.GetUNCPath(logicalDrive.Name), 323 | DriveType = logicalDrive.DriveType, 324 | AvailableFreeSpace = logicalDrive.AvailableFreeSpace, 325 | TotalSize = logicalDrive.TotalSize, 326 | TotalFreeSpace = logicalDrive.TotalFreeSpace, 327 | RootDirectory = logicalDrive.RootDirectory, 328 | DriveLetter = logicalDrive.Name.Substring(0, 1).ToCharArray()[0] 329 | }; 330 | 331 | var driveId = GetDiskId(tmp.DriveLetter); 332 | if (driveId != -1) 333 | { 334 | tmp.Id = driveId; 335 | if (queryType == QueryType.SeekPenalty) 336 | { 337 | tmp.HardwareType = DetectHardwareTypeBySeekPenalty(driveId); 338 | } 339 | else 340 | { 341 | if (IsAdministrator()) 342 | { 343 | tmp.HardwareType = DetectHardwareTypeByRotationRate(driveId); 344 | } 345 | else 346 | { 347 | if (useFallbackQuery) 348 | { 349 | tmp.HardwareType = DetectHardwareTypeBySeekPenalty(driveId); 350 | } 351 | else 352 | { 353 | throw new SecurityException( 354 | "DetectHardwareTypeBySeekPenalty needs administrative access."); 355 | } 356 | } 357 | } 358 | driveInfoExtended.Add(tmp); 359 | } 360 | } 361 | } 362 | else 363 | { 364 | if (logicalDrive.IsReady) 365 | { 366 | var tmp = new DriveInfoExtended 367 | { 368 | DriveFormat = logicalDrive.DriveFormat, 369 | VolumeLabel = logicalDrive.VolumeLabel, 370 | Name = logicalDrive.Name, 371 | UncPath = Pathing.GetUNCPath(logicalDrive.Name), 372 | DriveType = logicalDrive.DriveType, 373 | AvailableFreeSpace = logicalDrive.AvailableFreeSpace, 374 | TotalSize = logicalDrive.TotalSize, 375 | TotalFreeSpace = logicalDrive.TotalFreeSpace, 376 | RootDirectory = logicalDrive.RootDirectory, 377 | DriveLetter = logicalDrive.Name.Substring(0, 1).ToCharArray()[0], 378 | HardwareType = HardwareType.Unknown, 379 | Id = -1 380 | }; 381 | driveInfoExtended.Add(tmp); 382 | } 383 | } 384 | } 385 | return driveInfoExtended; 386 | } 387 | 388 | /// 389 | /// DeviceIoControl to get disk extents 390 | /// 391 | /// 392 | /// 393 | /// 394 | /// 395 | /// 396 | /// 397 | /// 398 | /// 399 | /// 400 | [DllImport("kernel32.dll", EntryPoint = "DeviceIoControl", 401 | SetLastError = true)] 402 | [return: MarshalAs(UnmanagedType.Bool)] 403 | private static extern bool DeviceIoControl( 404 | SafeFileHandle hDevice, 405 | uint dwIoControlCode, 406 | IntPtr lpInBuffer, 407 | uint nInBufferSize, 408 | ref VolumeDiskExtents lpOutBuffer, 409 | uint nOutBufferSize, 410 | out uint lpBytesReturned, 411 | IntPtr lpOverlapped); 412 | 413 | 414 | /// 415 | /// Gets the device ID by drive letter. 416 | /// 417 | /// A valid drive letter. 418 | /// The device ID. 419 | /// 420 | /// 421 | /// 422 | private static int GetDiskId(char driveLetter) 423 | { 424 | var di = new DriveInfo(driveLetter.ToString()); 425 | if (di.DriveType != DriveType.Fixed) 426 | { 427 | throw new DetectionFailedException(string.Format("This drive is not fixed drive: {0}", driveLetter)); 428 | } 429 | 430 | var sDrive = "\\\\.\\" + driveLetter + ":"; 431 | 432 | var hDrive = CreateFileW( 433 | sDrive, 434 | 0, // No access to drive 435 | FileShareRead | FileShareWrite, 436 | IntPtr.Zero, 437 | OpenExisting, 438 | FileAttributeNormal, 439 | IntPtr.Zero); 440 | 441 | if (hDrive == null || hDrive.IsInvalid) 442 | { 443 | int lastError = Marshal.GetLastWin32Error(); 444 | throw new DetectionFailedException(string.Format("Could not detect Disk Id of {0}", 445 | driveLetter), 446 | new Win32Exception(lastError) 447 | ); 448 | } 449 | 450 | var ioctlVolumeGetVolumeDiskExtents = CTL_CODE( 451 | IoctlVolumeBase, 0, 452 | MethodBuffered, FileAnyAccess); // From winioctl.h 453 | 454 | var queryDiskExtents = 455 | new VolumeDiskExtents(); 456 | 457 | uint returnedQueryDiskExtentsSize; 458 | 459 | var queryDiskExtentsResult = DeviceIoControl( 460 | hDrive, 461 | ioctlVolumeGetVolumeDiskExtents, 462 | IntPtr.Zero, 463 | 0, 464 | ref queryDiskExtents, 465 | (uint) Marshal.SizeOf(queryDiskExtents), 466 | out returnedQueryDiskExtentsSize, 467 | IntPtr.Zero); 468 | 469 | hDrive.Close(); 470 | 471 | if (!queryDiskExtentsResult) 472 | { 473 | int lastError = Marshal.GetLastWin32Error(); 474 | const int ERROR_MORE_DATA = 234; //(0xEA) More data is available. 475 | if (lastError != ERROR_MORE_DATA 476 | || (queryDiskExtents.Extents.Length < 1) // We need at least 1 477 | ) 478 | { 479 | throw new DetectionFailedException(string.Format("Could not detect Disk Id of {0}", 480 | driveLetter), 481 | new Win32Exception(lastError) 482 | ); 483 | } 484 | } 485 | 486 | return (int) queryDiskExtents.Extents[0].DiskNumber; 487 | } 488 | 489 | /// 490 | /// Detect the HardwareType by SeekPenalty. 491 | /// 492 | /// A valid drive letter. 493 | /// The detected HardwareType. 494 | public static HardwareType DetectHardwareTypeBySeekPenalty(char driveLetter) 495 | { 496 | try 497 | { 498 | return DetectHardwareTypeBySeekPenalty(GetDiskId(driveLetter)); 499 | } 500 | catch (DetectionFailedException) 501 | { 502 | return HardwareType.Unknown; 503 | } 504 | } 505 | 506 | /// 507 | /// Detect the HardwareType by SeekPenalty. 508 | /// 509 | /// A valid drive Id. 510 | /// The detected HardwareType. 511 | public static HardwareType DetectHardwareTypeBySeekPenalty(int driveId) 512 | { 513 | var physicalDriveName = "\\\\.\\PhysicalDrive" + driveId; 514 | 515 | try 516 | { 517 | return HasDriveSeekPenalty(physicalDriveName) ? HardwareType.Hdd : HardwareType.Ssd; 518 | } 519 | catch (DetectionFailedException) 520 | { 521 | return HardwareType.Unknown; 522 | } 523 | } 524 | 525 | /// 526 | /// Detect the HardwareType by RotationRate. 527 | /// 528 | /// A valid drive letter. 529 | /// The detected HardwareType. 530 | public static HardwareType DetectHardwareTypeByRotationRate(char driveLetter) 531 | { 532 | try 533 | { 534 | return DetectHardwareTypeByRotationRate(GetDiskId(driveLetter)); 535 | } 536 | catch (DetectionFailedException) 537 | { 538 | return HardwareType.Unknown; 539 | } 540 | } 541 | 542 | /// 543 | /// Detect the HardwareType by RotationRate. 544 | /// 545 | /// A valid drive Id. 546 | /// The detected HardwareType. 547 | /// Administrative privilege is required! 548 | public static HardwareType DetectHardwareTypeByRotationRate(int driveId) 549 | { 550 | var physicalDriveName = "\\\\.\\PhysicalDrive" + driveId; 551 | 552 | try 553 | { 554 | return HasDriveNominalMediaRotationRate(physicalDriveName) ? HardwareType.Hdd : HardwareType.Ssd; 555 | } 556 | catch (DetectionFailedException) 557 | { 558 | return HardwareType.Unknown; 559 | } 560 | } 561 | 562 | [DllImport("mpr.dll", CharSet = CharSet.Auto, SetLastError = true)] 563 | public static extern int WNetGetConnection([MarshalAs(UnmanagedType.LPTStr)] string localName, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder remoteName, ref int length); 564 | 565 | private static object strToUnc(string path) 566 | { 567 | // This sample code assumes you currently have a drive mapped to p: 568 | 569 | // Find out what remote device a local mapping is to 570 | 571 | int rc = 0; 572 | 573 | // Size for the buffer we will use 574 | 575 | int bsize = 200; 576 | 577 | // Create a new stringbuilder, pre-sized as above 578 | 579 | StringBuilder rname = new StringBuilder(bsize); 580 | 581 | // Call the function 582 | 583 | rc = WNetGetConnection("Z:", rname, ref bsize); 584 | 585 | //https://stackoverflow.com/questions/1088752/how-to-programmatically-discover-mapped-network-drives-on-system-and-their-serve 586 | //http://www.pinvoke.net/default.aspx/mpr/WNetGetConnection.html 587 | int length = 255; 588 | /*2250 (0x8CA) 589 | This network connection does not exist. 590 | 1200 (0x4B0) 591 | The specified device name is invalid.*/ 592 | System.Text.StringBuilder UNC = new System.Text.StringBuilder(length); 593 | int q = WNetGetConnection("Z:", UNC, ref length); 594 | return UNC.ToString(); 595 | } 596 | 597 | //to get the UNC-Path of a network-drive use something like: 598 | 599 | 600 | 601 | /// 602 | /// CreateFile to get handle to drive. 603 | /// 604 | /// 605 | /// 606 | /// 607 | /// 608 | /// 609 | /// 610 | /// 611 | /// 612 | [DllImport("kernel32.dll", SetLastError = true)] 613 | private static extern SafeFileHandle CreateFileW( 614 | [MarshalAs(UnmanagedType.LPWStr)] string lpFileName, 615 | uint dwDesiredAccess, 616 | uint dwShareMode, 617 | IntPtr lpSecurityAttributes, 618 | uint dwCreationDisposition, 619 | uint dwFlagsAndAttributes, 620 | IntPtr hTemplateFile); 621 | 622 | private static uint CTL_CODE(uint DeviceType, uint Function, 623 | uint Method, uint Access) 624 | { 625 | return ((DeviceType << 16) | (Access << 14) | 626 | (Function << 2) | Method); 627 | } 628 | 629 | /// 630 | /// DeviceIoControl to check no seek penalty. 631 | /// 632 | /// 633 | /// 634 | /// 635 | /// 636 | /// 637 | /// 638 | /// 639 | /// 640 | /// 641 | [DllImport("kernel32.dll", EntryPoint = "DeviceIoControl", 642 | SetLastError = true)] 643 | [return: MarshalAs(UnmanagedType.Bool)] 644 | private static extern bool DeviceIoControl( 645 | SafeFileHandle hDevice, 646 | uint dwIoControlCode, 647 | ref StoragePropertyQuery lpInBuffer, 648 | uint nInBufferSize, 649 | ref DeviceSeekPenaltyDescriptor lpOutBuffer, 650 | uint nOutBufferSize, 651 | out uint lpBytesReturned, 652 | IntPtr lpOverlapped); 653 | 654 | /// 655 | /// DeviceIoControl to check nominal media rotation rate. 656 | /// 657 | /// 658 | /// 659 | /// 660 | /// 661 | /// 662 | /// 663 | /// 664 | /// 665 | /// 666 | [DllImport("kernel32.dll", EntryPoint = "DeviceIoControl", 667 | SetLastError = true)] 668 | [return: MarshalAs(UnmanagedType.Bool)] 669 | private static extern bool DeviceIoControl( 670 | SafeFileHandle hDevice, 671 | uint dwIoControlCode, 672 | ref AtaIdentifyDeviceQuery lpInBuffer, 673 | uint nInBufferSize, 674 | ref AtaIdentifyDeviceQuery lpOutBuffer, 675 | uint nOutBufferSize, 676 | out uint lpBytesReturned, 677 | IntPtr lpOverlapped); 678 | 679 | /// 680 | /// Check if the drive has a seek penalty. 681 | /// 682 | /// A valid physicalDriveName. 683 | /// true if the drive has a seek penalty otherwise, false 684 | /// Administrative privilege is required! 685 | /// 686 | private static bool HasDriveSeekPenalty(string physicalDriveName) 687 | { 688 | var hDrive = CreateFileW( 689 | physicalDriveName, 690 | 0, // No access to drive 691 | FileShareRead | FileShareWrite, 692 | IntPtr.Zero, 693 | OpenExisting, 694 | FileAttributeNormal, 695 | IntPtr.Zero); 696 | 697 | if (hDrive == null || hDrive.IsInvalid) 698 | { 699 | int lastError = Marshal.GetLastWin32Error(); 700 | throw new DetectionFailedException(string.Format("Could not detect SeekPenalty of {0}", 701 | physicalDriveName), 702 | new Win32Exception(lastError) 703 | ); 704 | } 705 | 706 | var ioctlStorageQueryProperty = CTL_CODE( 707 | IoctlStorageBase, 0x500, 708 | MethodBuffered, FileAnyAccess); // From winioctl.h 709 | 710 | var querySeekPenalty = 711 | new StoragePropertyQuery 712 | { 713 | PropertyId = StorageDeviceSeekPenaltyProperty, 714 | QueryType = PropertyStandardQuery 715 | }; 716 | 717 | var querySeekPenaltyDesc = 718 | new DeviceSeekPenaltyDescriptor(); 719 | 720 | uint returnedQuerySeekPenaltySize; 721 | 722 | var querySeekPenaltyResult = DeviceIoControl( 723 | hDrive, 724 | ioctlStorageQueryProperty, 725 | ref querySeekPenalty, 726 | (uint) Marshal.SizeOf(querySeekPenalty), 727 | ref querySeekPenaltyDesc, 728 | (uint) Marshal.SizeOf(querySeekPenaltyDesc), 729 | out returnedQuerySeekPenaltySize, 730 | IntPtr.Zero); 731 | 732 | hDrive.Close(); 733 | 734 | if (querySeekPenaltyResult == false) 735 | { 736 | int lastError = Marshal.GetLastWin32Error(); 737 | throw new DetectionFailedException(string.Format("Could not detect SeekPenalty of {0}", 738 | physicalDriveName), 739 | new Win32Exception(lastError) 740 | ); 741 | } 742 | if (querySeekPenaltyDesc.IncursSeekPenalty == false) 743 | { 744 | //This drive has NO SEEK penalty 745 | return false; 746 | } 747 | //This drive has SEEK penalty 748 | return true; 749 | } 750 | 751 | /// 752 | /// Check if the drive has a nominal media rotation rate. 753 | /// 754 | /// A valid physicalDriveName. 755 | /// true if the drive has a media rotation rate otherwise, false 756 | /// Administrative privilege is required! 757 | /// 758 | private static bool HasDriveNominalMediaRotationRate(string physicalDriveName) 759 | { 760 | var hDrive = CreateFileW( 761 | physicalDriveName, 762 | GenericRead | GenericWrite, // Administrative privilege is required 763 | FileShareRead | FileShareWrite, 764 | IntPtr.Zero, 765 | OpenExisting, 766 | FileAttributeNormal, 767 | IntPtr.Zero); 768 | 769 | if (hDrive == null || hDrive.IsInvalid) 770 | { 771 | int lastError = Marshal.GetLastWin32Error(); 772 | throw new DetectionFailedException(string.Format("Could not detect NominalMediaRotationRate of {0}", 773 | physicalDriveName), 774 | new Win32Exception(lastError) 775 | ); 776 | } 777 | 778 | var ioctlAtaPassThrough = CTL_CODE( 779 | IoctlScsiBase, 0x040b, MethodBuffered, 780 | FileReadAccess | FileWriteAccess); // From ntddscsi.h 781 | 782 | var idQuery = new AtaIdentifyDeviceQuery {data = new ushort[256]}; 783 | 784 | idQuery.header.Length = (ushort) Marshal.SizeOf(idQuery.header); 785 | idQuery.header.AtaFlags = (ushort) AtaFlagsDataIn; 786 | idQuery.header.DataTransferLength = 787 | (uint) (idQuery.data.Length*2); // Size of "data" in bytes 788 | idQuery.header.TimeOutValue = 3; // Sec 789 | idQuery.header.DataBufferOffset = Marshal.OffsetOf( 790 | typeof (AtaIdentifyDeviceQuery), "data"); 791 | idQuery.header.PreviousTaskFile = new byte[8]; 792 | idQuery.header.CurrentTaskFile = new byte[8]; 793 | idQuery.header.CurrentTaskFile[6] = 0xec; // ATA IDENTIFY DEVICE 794 | 795 | uint retvalSize; 796 | 797 | var result = DeviceIoControl( 798 | hDrive, 799 | ioctlAtaPassThrough, 800 | ref idQuery, 801 | (uint) Marshal.SizeOf(idQuery), 802 | ref idQuery, 803 | (uint) Marshal.SizeOf(idQuery), 804 | out retvalSize, 805 | IntPtr.Zero); 806 | 807 | hDrive.Close(); 808 | 809 | if (result == false) 810 | { 811 | int lastError = Marshal.GetLastWin32Error(); 812 | throw new DetectionFailedException(string.Format("Could not detect NominalMediaRotationRate of {0}", 813 | physicalDriveName), 814 | new Win32Exception(lastError) 815 | ); 816 | } 817 | // Word index of nominal media rotation rate 818 | // (1 means non-rotate device) 819 | const int kNominalMediaRotRateWordIndex = 217; 820 | 821 | if (idQuery.data[kNominalMediaRotRateWordIndex] == 1) 822 | { 823 | return false; 824 | } 825 | return true; 826 | } 827 | 828 | /// 829 | /// For DeviceIoControl to get disk extents 830 | /// 831 | [StructLayout(LayoutKind.Sequential)] 832 | private struct DiskExtent 833 | { 834 | public readonly uint DiskNumber; 835 | public readonly long StartingOffset; 836 | public readonly long ExtentLength; 837 | } 838 | 839 | [StructLayout(LayoutKind.Sequential)] 840 | private struct VolumeDiskExtents 841 | { 842 | public readonly uint NumberOfDiskExtents; 843 | [MarshalAs(UnmanagedType.ByValArray)] public readonly DiskExtent[] Extents; 844 | } 845 | 846 | [StructLayout(LayoutKind.Sequential)] 847 | private struct StoragePropertyQuery 848 | { 849 | public uint PropertyId; 850 | public uint QueryType; 851 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] public readonly byte[] AdditionalParameters; 852 | } 853 | 854 | [StructLayout(LayoutKind.Sequential)] 855 | private struct DeviceSeekPenaltyDescriptor 856 | { 857 | public readonly uint Version; 858 | public readonly uint Size; 859 | [MarshalAs(UnmanagedType.U1)] public readonly bool IncursSeekPenalty; 860 | } 861 | 862 | [StructLayout(LayoutKind.Sequential)] 863 | private struct AtaPassThroughEx 864 | { 865 | public ushort Length; 866 | public ushort AtaFlags; 867 | public readonly byte PathId; 868 | public readonly byte TargetId; 869 | public readonly byte Lun; 870 | public readonly byte ReservedAsUchar; 871 | public uint DataTransferLength; 872 | public uint TimeOutValue; 873 | public readonly uint ReservedAsUlong; 874 | public IntPtr DataBufferOffset; 875 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public byte[] PreviousTaskFile; 876 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public byte[] CurrentTaskFile; 877 | } 878 | 879 | [StructLayout(LayoutKind.Sequential)] 880 | private struct AtaIdentifyDeviceQuery 881 | { 882 | public AtaPassThroughEx header; 883 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)] public ushort[] data; 884 | } 885 | 886 | #region CreateFile (get handle to drive) 887 | 888 | private const uint GenericRead = 0x80000000; 889 | private const uint GenericWrite = 0x40000000; 890 | private const uint FileShareRead = 0x00000001; 891 | private const uint FileShareWrite = 0x00000002; 892 | private const uint OpenExisting = 3; 893 | private const uint FileAttributeNormal = 0x00000080; 894 | 895 | #endregion 896 | 897 | #region Control Codes 898 | 899 | private const uint FileDeviceMassStorage = 0x0000002d; 900 | private const uint IoctlStorageBase = FileDeviceMassStorage; 901 | private const uint FileDeviceController = 0x00000004; 902 | private const uint IoctlScsiBase = FileDeviceController; 903 | private const uint MethodBuffered = 0; 904 | private const uint FileAnyAccess = 0; 905 | private const uint FileReadAccess = 0x00000001; 906 | private const uint FileWriteAccess = 0x00000002; 907 | private const uint IoctlVolumeBase = 0x00000056; 908 | 909 | #endregion 910 | 911 | #region DeviceIoControl (seek penalty) 912 | 913 | private const uint StorageDeviceSeekPenaltyProperty = 7; 914 | private const uint PropertyStandardQuery = 0; 915 | 916 | #endregion 917 | } 918 | 919 | internal class NativeMethods 920 | { 921 | /// 922 | /// The type of structure that the function stores in the buffer. 923 | /// 924 | public enum InfoLevel 925 | { 926 | /// 927 | /// The function stores a structure in the 928 | /// buffer. 929 | /// 930 | UniversalName = 1, 931 | 932 | /// 933 | /// The function stores a REMOTE_NAME_INFO structure in the buffer. 934 | /// 935 | /// 936 | /// Using this level will throw an . 937 | /// 938 | RemoteName = 2 939 | } 940 | 941 | /// 942 | /// The function 943 | /// takes a drive-based path for a network resource and returns an information 944 | /// structure that contains a more universal form of the name. 945 | /// 946 | /// A pointer to a constant null-terminated string that 947 | /// is a drive-based path for a network resource. 948 | /// The type of structure that the function stores in 949 | /// the buffer pointed to by the parameter. 950 | /// A pointer to a buffer that receives the structure 951 | /// specified by the parameter. 952 | /// A pointer to a variable that specifies the size, 953 | /// in bytes, of the buffer pointed to by the parameter. 954 | /// If the function succeeds, the return value is . 955 | [DllImport("mpr.dll", CharSet = CharSet.Auto)] 956 | public static extern int WNetGetUniversalName( 957 | string lpLocalPath, 958 | InfoLevel dwInfoLevel, 959 | ref UNIVERSAL_NAME_INFO lpBuffer, 960 | ref int lpBufferSize); 961 | 962 | /// 963 | /// The function 964 | /// takes a drive-based path for a network resource and returns an information 965 | /// structure that contains a more universal form of the name. 966 | /// 967 | /// A pointer to a constant null-terminated string that 968 | /// is a drive-based path for a network resource. 969 | /// The type of structure that the function stores in 970 | /// the buffer pointed to by the parameter. 971 | /// A pointer to a buffer that receives the structure 972 | /// specified by the parameter. 973 | /// A pointer to a variable that specifies the size, 974 | /// in bytes, of the buffer pointed to by the parameter. 975 | /// If the function succeeds, the return value is . 976 | [DllImport("mpr.dll", CharSet = CharSet.Auto)] 977 | public static extern int WNetGetUniversalName( 978 | string lpLocalPath, 979 | InfoLevel dwInfoLevel, 980 | IntPtr lpBuffer, 981 | ref int lpBufferSize); 982 | 983 | /// 984 | /// The structure contains a pointer to a 985 | /// Universal Naming Convention (UNC) name string for a network resource. 986 | /// 987 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] 988 | public struct UNIVERSAL_NAME_INFO 989 | { 990 | /// 991 | /// Pointer to the null-terminated UNC name string that identifies a 992 | /// network resource. 993 | /// 994 | [MarshalAs(UnmanagedType.LPTStr)] 995 | public string lpUniversalName; 996 | } 997 | } 998 | } -------------------------------------------------------------------------------- /diskdetector-net/Exceptions/DetectionFailedException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DiskDetector.Exceptions 4 | { 5 | public class DetectionFailedException : Exception 6 | { 7 | public DetectionFailedException() 8 | { 9 | } 10 | 11 | public DetectionFailedException(string message) 12 | : base(message) 13 | { 14 | } 15 | 16 | public DetectionFailedException(string message, Exception inner) 17 | : base(message, inner) 18 | { 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /diskdetector-net/Models/DriveInfoExtended.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace DiskDetector.Models 4 | { 5 | public class DriveInfoExtended 6 | { 7 | public string Name { get; set; } 8 | public char DriveLetter { get; set; } 9 | public DriveType DriveType { get; set; } 10 | public int Id { get; set; } 11 | public string VolumeLabel { get; set; } 12 | public string DriveFormat { get; set; } 13 | public long TotalFreeSpace { get; set; } 14 | public long TotalSize { get; set; } 15 | public long AvailableFreeSpace { get; set; } 16 | public HardwareType HardwareType { get; set; } 17 | public DirectoryInfo RootDirectory { get; set; } 18 | public string UncPath { get; set; } 19 | } 20 | } -------------------------------------------------------------------------------- /diskdetector-net/Models/HardwareType.cs: -------------------------------------------------------------------------------- 1 | namespace DiskDetector.Models 2 | { 3 | /// 4 | /// Possible HardwareTypes. 5 | /// 6 | public enum HardwareType 7 | { 8 | /// 9 | /// Unknown hardware type. 10 | /// 11 | Unknown, 12 | 13 | /// 14 | /// Hard Disk Drive. 15 | /// 16 | Hdd, 17 | 18 | /// 19 | /// Solid State Drive. 20 | /// 21 | Ssd 22 | } 23 | } -------------------------------------------------------------------------------- /diskdetector-net/Models/QueryType.cs: -------------------------------------------------------------------------------- 1 | namespace DiskDetector.Models 2 | { 3 | /// 4 | /// Possible QueryTypes. 5 | /// 6 | public enum QueryType 7 | { 8 | /// 9 | /// Detect the HardwareType by SeekPenalty. 10 | /// 11 | SeekPenalty, 12 | 13 | /// 14 | /// Detect the HardwareType by RotationRate. 15 | /// 16 | RotationRate 17 | } 18 | } -------------------------------------------------------------------------------- /diskdetector-net/Tools/Pathing.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Runtime.InteropServices; 3 | using System.Text; 4 | 5 | namespace DiskDetector.Tools 6 | { 7 | public static class Pathing 8 | { 9 | [DllImport("mpr.dll", CharSet = CharSet.Unicode, SetLastError = true)] 10 | public static extern int WNetGetConnection( 11 | [MarshalAs(UnmanagedType.LPTStr)] string localName, 12 | [MarshalAs(UnmanagedType.LPTStr)] StringBuilder remoteName, 13 | ref int length); 14 | 15 | /// 16 | /// Given a path, returns the UNC path or the original. (No exceptions 17 | /// are raised by this function directly). For example, "P:\2008-02-29" 18 | /// might return: "\\networkserver\Shares\Photos\2008-02-09" 19 | /// 20 | /// The path to convert to a UNC Path 21 | /// 22 | /// A UNC path. If a network drive letter is specified, the 23 | /// drive letter is converted to a UNC or network path. If the 24 | /// originalPath cannot be converted, it is returned unchanged. 25 | /// 26 | /// 27 | public static string GetUNCPath(string originalPath) 28 | { 29 | var sb = new StringBuilder(512); 30 | var size = sb.Capacity; 31 | 32 | // look for the {LETTER}: combination ... 33 | if (originalPath.Length > 2 && originalPath[1] == ':') 34 | { 35 | // don't use char.IsLetter here - as that can be misleading 36 | // the only valid drive letters are a-z && A-Z. 37 | var c = originalPath[0]; 38 | if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) 39 | { 40 | var error = WNetGetConnection(originalPath.Substring(0, 2), 41 | sb, ref size); 42 | if (error == 0) 43 | { 44 | var dir = new DirectoryInfo(originalPath); 45 | 46 | var path = Path.GetFullPath(originalPath) 47 | .Substring(Path.GetPathRoot(originalPath).Length); 48 | return Path.Combine(sb.ToString().TrimEnd(), path); 49 | } 50 | } 51 | } 52 | 53 | return originalPath; 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /diskdetector-net/diskdetector-net.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | DiskDetector 6 | DiskDetector 7 | 0.3.2.0 8 | 0.3.2.0 9 | Copyright © Christian Hermann 2015 - 2019 10 | diskdetector-net 11 | 0.3.3 12 | https://github.com/bitbeans/diskdetector-net/blob/master/LICENSE.md 13 | https://github.com/bitbeans/diskdetector-net 14 | https://github.com/bitbeans/diskdetector-net 15 | disk detection hdd ssd driveinfo 16 | diskdetector-net 17 | 18 | Christian Hermann & Contributors 19 | true 20 | Class to detect the hardware type (SSD or HDD) of a hard disk on windows based systems. 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | --------------------------------------------------------------------------------