Introduction
Ever wonder how things work? I do. It’s a blessing, and a curse…. It is sad how we developers take everyday things for granted. Things such as getting volume information from hard disks or even just a list of drives on a computer may seem complicated, and yes, some elements may be difficult, but it is an essential, must-have skill to have at your disposal. Today, you will learn the complicated way of getting disk information from your computer.
Our Project
Design
Start a new Visual Basic Windows Forms project. Once the form is loaded, add a ListView object onto your form. You may name the ListView anything you like, but, as always, keep in mind that my object names may differ from yours. Your design should resemble Figure 1.
Figure 1: Our Design
Code
Add a class to your project by selecting Project, Add Class. I have named mine clsVolume.
Add the following Namespaces to your class:
Imports Microsoft.Win32.SafeHandles Imports System.IO Imports System.Runtime.InteropServices Imports System.ComponentModel
MSDN explains the preceding Namespaces in the following links:
Add the following variables to your class:
Private Const intGenRead As Integer = &H80000000 Private Const intShareRead As Integer = 1 Private Const intShareWrite As Integer = 2 Private Const intExisting As Integer = 3 Private Const IoctlVolumeGetVolumeDiskExtents As _ Integer = &H560000 Private Const intIncorrectFunc As Integer = 1 Private Const intInsufficientBuffer As Integer = 122
Add the following two Structures to clsVolume:
<StructLayout(LayoutKind.Sequential)> _ Private Structure DiskExtent Public DiskNumber As Integer Public StartingOffset As Long Public ExtentLength As Long End Structure <StructLayout(LayoutKind.Sequential)> _ Private Structure DiskExtents Public intExtents As Integer Public intExtent As DiskExtent End Structure
These two structures will assist in getting the data in the correct format from the system APIs, which we will add next.
Add a subclass and the next few API Declarations to it:
Private Class NativeMethods <DllImport("kernel32", CharSet:=CharSet.Unicode, _ SetLastError:=True)> _ Public Shared Function CreateFile( _ ByVal strFileName As String, _ ByVal intDesiredAccess As Integer, _ ByVal intShareMode As Integer, _ ByVal iptrSecurity As IntPtr, _ ByVal intCreationDispos As Integer, _ ByVal intFlags As Integer, _ ByVal iptrTemplateFile As IntPtr) As SafeFileHandle End Function <DllImport("kernel32", SetLastError:=True)> _ Public Shared Function DeviceIoControl( _ ByVal hVol As SafeFileHandle, _ ByVal controlCode As Integer, _ ByVal inBuffer As IntPtr, _ ByVal inBufferSize As Integer, _ ByRef outBuffer As DiskExtents, _ ByVal outBufferSize As Integer, _ ByRef bytesReturned As Integer, _ ByVal overlapped As IntPtr) As _ <MarshalAs(UnmanagedType.Bool)> Boolean End Function <DllImport("kernel32", SetLastError:=True)> _ Public Shared Function DeviceIoControl( _ ByVal hVol As SafeFileHandle, _ ByVal controlCode As Integer, _ ByVal inBuffer As IntPtr, _ ByVal inBufferSize As Integer, _ ByVal outBuffer As IntPtr, _ ByVal outBufferSize As Integer, _ ByRef bytesReturned As Integer, _ ByVal overlapped As IntPtr) As _ <MarshalAs(UnmanagedType.Bool)> Boolean End Function End Class
Add the GetDriveStrings function to clsVolume:
Public Function GetDriveStrings(ByVal diDriveInfo As DriveInfo) _ As List(Of String) Dim sfhFile As SafeFileHandle = Nothing Dim lstPhysicalDrives As New List(Of String)(1) Dim strPath As String = "\\.\" & _ diDriveInfo.RootDirectory.ToString.TrimEnd("\"c) Try sfhFile = NativeMethods.CreateFile(strPath, intGenRead, _ intShareRead Or intShareWrite, IntPtr.Zero, _ intExisting, 0, IntPtr.Zero) Dim intBytes As Integer Dim intDiskExtents As Integer = 0 Dim deExtents As DiskExtents = Nothing Dim blnResult As Boolean = NativeMethods.DeviceIoControl(sfhFile, _ IoctlVolumeGetVolumeDiskExtents, IntPtr.Zero, _ 0, deExtents, Marshal.SizeOf(deExtents), intBytes, IntPtr.Zero) If blnResult = True Then lstPhysicalDrives.Add("\\.\PhysicalDrive" & _ deExtents.intExtent.DiskNumber.ToString) Return lstPhysicalDrives End If If Marshal.GetLastWin32Error = intIncorrectFunc Then Return lstPhysicalDrives End If Dim intSize As Integer = Marshal.SizeOf(GetType(DiskExtents)) + _ (deExtents.intExtents - 1) * _ Marshal.SizeOf(GetType(DiskExtent)) Dim iptrBlob As IntPtr = Marshal.AllocHGlobal(intSize) blnResult = NativeMethods.DeviceIoControl(sfhFile, _ IoctlVolumeGetVolumeDiskExtents, IntPtr.Zero, 0, _ iptrBlob, intSize, intBytes, IntPtr.Zero) Dim iptrNext As New IntPtr(iptrBlob.ToInt32 + 4) For i As Integer = 0 To deExtents.intExtents - 1 Dim dediskExtent As DiskExtent = _ DirectCast(Marshal.PtrToStructure(iptrNext, _ GetType(DiskExtent)), DiskExtent) lstPhysicalDrives.Add("\\.\PhysicalDrive" & _ dediskExtent.DiskNumber.ToString) iptrNext = New IntPtr(iptrNext.ToInt32 + _ Marshal.SizeOf(GetType(DiskExtent))) Next Return lstPhysicalDrives Finally If sfhFile IsNot Nothing Then If sfhFile.IsInvalid = False Then sfhFile.Close() End If sfhFile.Dispose() End If End Try End Function
This function simply makes use of the APIs to extract the volume information from all the disks on the system and add this information to a list which then gets returned to the calling function—which we will add in the form.
Add the following Namespaces to your form’s code:
Imports HTG_HDStuff.clsVolume Imports System.IO Imports System.Text
Our class gets imported and becomes part of our form. Here is more information regarding the System.Text Namespace.
Create a new clsVolume object:
Private cVolume As New clsVolume
Add the last piece of code to your Form’s Load event:
Private Sub Form1_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load For Each diDrives As DriveInfo In DriveInfo.GetDrives Dim lstDrives As List(Of String) = _ cVolume.GetDriveStrings(diDrives) Dim sbDrives As New StringBuilder If lstDrives.Count > 0 Then For Each s As String In lstDrives sbDrives.Append(s) sbDrives.Append(", ") Next sbDrives.Remove(sbDrives.Length - 2, 2) Else sbDrives.Append("N / A") End If Dim lviItem As New _ ListViewItem(diDrives.RootDirectory.ToString) lviItem.SubItems.Add(sbDrives.ToString) lvListView.Items.Add(lviItem) Next End Sub
This code makes use of the clsVolume class’ GetDriveStrings function to return the obtained volume information to the ListView. Your running app should resemble Figure 2.
Figure 2: Running your application
Conclusion
There are numerous ways to obtain disk information. This was just one of them, albeit somewhat more complicated than others.