' ***********************************************************************
' Author : Elektro
' Modified : 02-17-2014
' ***********************************************************************
' <copyright file="DriveWatcher.vb" company="Elektro Studios">
' Copyright (c) Elektro Studios. All rights reserved.
' </copyright>
' ***********************************************************************
#Region " Usage Examples "
' ''' <summary>
' ''' The DriveWatcher instance to monitor USB devices.
' ''' </summary>
'Friend WithEvents USBMonitor As New DriveWatcher(form:=Me)
' ''' <summary>
' ''' Handles the DriveInserted event of the USBMonitor object.
' ''' </summary>
' ''' <param name="sender">The source of the event.</param>
' ''' <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
'Private Sub USBMonitor_DriveInserted(ByVal sender As Object, ByVal e As DriveWatcher.DriveWatcherInfo) Handles USBMonitor.DriveInserted
' If e.DriveType = IO.DriveType.Removable Then ' If it's a removable media then...
' Dim sb As New System.Text.StringBuilder
' sb.AppendLine("DRIVE CONNECTED!")
' sb.AppendLine()
' sb.AppendLine(String.Format("Drive Name: {0}", e.Name))
' sb.AppendLine(String.Format("Drive Type: {0}", e.DriveType))
' sb.AppendLine(String.Format("FileSystem: {0}", e.DriveFormat))
' sb.AppendLine(String.Format("Is Ready? : {0}", e.IsReady))
' sb.AppendLine(String.Format("Root Dir. : {0}", e.RootDirectory))
' sb.AppendLine(String.Format("Vol. Label: {0}", e.VolumeLabel))
' sb.AppendLine(String.Format("Total Size: {0}", e.TotalSize))
' sb.AppendLine(String.Format("Free Space: {0}", e.TotalFreeSpace))
' sb.AppendLine(String.Format("Ava. Space: {0}", e.AvailableFreeSpace))
' MessageBox.Show(sb.ToString, "USBMonitor", MessageBoxButtons.OK, MessageBoxIcon.Information)
' End If
'End Sub
' ''' <summary>
' ''' Handles the DriveRemoved event of the USBMonitor object.
' ''' </summary>
' ''' <param name="sender">The source of the event.</param>
' ''' <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
'Private Sub USBMonitor_DriveRemoved(ByVal sender As Object, ByVal e As DriveWatcher.DriveWatcherInfo) Handles USBMonitor.DriveRemoved
' If e.DriveType = IO.DriveType.Removable Then ' If it's a removable media then...
' Dim sb As New System.Text.StringBuilder
' sb.AppendLine("DRIVE DISCONNECTED!")
' sb.AppendLine()
' sb.AppendLine(String.Format("Drive Name: {0}", e.Name))
' sb.AppendLine(String.Format("Drive Type: {0}", e.DriveType))
' sb.AppendLine(String.Format("FileSystem: {0}", e.DriveFormat))
' sb.AppendLine(String.Format("Is Ready? : {0}", e.IsReady))
' sb.AppendLine(String.Format("Root Dir. : {0}", e.RootDirectory))
' sb.AppendLine(String.Format("Vol. Label: {0}", e.VolumeLabel))
' sb.AppendLine(String.Format("Total Size: {0}", e.TotalSize))
' sb.AppendLine(String.Format("Free Space: {0}", e.TotalFreeSpace))
' sb.AppendLine(String.Format("Ava. Space: {0}", e.AvailableFreeSpace))
' MessageBox.Show(sb.ToString, "USBMonitor", MessageBoxButtons.OK, MessageBoxIcon.Information)
' End If
'End Sub
#End Region
#Region " Imports "
Imports System.IO
Imports System.Runtime.InteropServices
Imports System.ComponentModel
#End Region
''' <summary>
''' Device insertion/removal monitor.
''' </summary>
Public Class DriveWatcher : Inherits NativeWindow : Implements IDisposable
#Region " Objects "
''' <summary>
''' The current connected drives.
''' </summary>
Private CurrentDrives
As New Dictionary(Of Char, DriveWatcherInfo
)
''' <summary>
''' Indicates the drive letter of the current device.
''' </summary>
Private DriveLetter As Char = Nothing
''' <summary>
''' Indicates the current Drive information.
''' </summary>
Private CurrentDrive As DriveWatcherInfo = Nothing
''' <summary>
''' The form to manage their Windows Messages.
''' </summary>
Private WithEvents form As Form = Nothing
#End Region
#Region " Events "
''' <summary>
''' Occurs when a drive is inserted.
''' </summary>
Public Event DriveInserted(ByVal sender As Object, ByVal e As DriveWatcherInfo)
''' <summary>
''' Occurs when a drive is removed.
''' </summary>
Public Event DriveRemoved(ByVal sender As Object, ByVal e As DriveWatcherInfo)
#End Region
#Region " Enumerations "
''' <summary>
''' Notifies an application of a change to the hardware configuration of a device or the computer.
''' A window receives this message through its WindowProc function.
''' For more info, see here:
''' http://msdn.microsoft.com/en-us/library/windows/desktop/aa363480%28v=vs.85%29.aspx
''' http://msdn.microsoft.com/en-us/library/windows/desktop/aa363232%28v=vs.85%29.aspx
''' </summary>
Private Enum DeviceEvents As Integer
''' <summary>
''' The current configuration has changed, due to a dock or undock.
''' </summary>
Change = &H219
''' <summary>
''' A device or piece of media has been inserted and becomes available.
''' </summary>
Arrival = &H8000
''' <summary>
''' Request permission to remove a device or piece of media.
''' This message is the last chance for applications and drivers to prepare for this removal.
''' However, any application can deny this request and cancel the operation.
''' </summary>
QueryRemove = &H8001
''' <summary>
''' A request to remove a device or piece of media has been canceled.
''' </summary>
QueryRemoveFailed = &H8002
''' <summary>
''' A device or piece of media is being removed and is no longer available for use.
''' </summary>
RemovePending = &H8003
''' <summary>
''' A device or piece of media has been removed.
''' </summary>
RemoveComplete = &H8004
''' <summary>
''' The type volume
''' </summary>
TypeVolume = &H2
End Enum
#End Region
#Region " Structures "
''' <summary>
''' Indicates information related of a Device.
''' ( Replic of System.IO.DriveInfo )
''' </summary>
Public Structure DriveWatcherInfo
''' <summary>
''' Indicates the name of a drive, such as 'C:\'.
''' </summary>
Public Name As String
''' <summary>
''' Indicates the amount of available free space on a drive, in bytes.
''' </summary>
Public AvailableFreeSpace As Long
''' <summary>
''' Indicates the name of the filesystem, such as 'NTFS', 'FAT32', 'UDF', etc...
''' </summary>
Public DriveFormat As String
''' <summary>
''' Indicates the the drive type, such as 'CD-ROM', 'removable', 'fixed', etc...
''' </summary>
Public DriveType As DriveType
''' <summary>
''' Indicates whether a drive is ready.
''' </summary>
Public IsReady As Boolean
''' <summary>
''' Indicates the root directory of a drive.
''' </summary>
Public RootDirectory As String
''' <summary>
''' Indicates the total amount of free space available on a drive, in bytes.
''' </summary>
Public TotalFreeSpace As Long
''' <summary>
''' Indicates the total size of storage space on a drive, in bytes.
''' </summary>
Public TotalSize As Long
''' <summary>
''' Indicates the volume label of a drive.
''' </summary>
Public VolumeLabel As String
''' <summary>
''' Initializes a new instance of the <see cref="DriveWatcherInfo"/> struct.
''' </summary>
''' <param name="e">The e.</param>
Public Sub New(ByVal e As DriveInfo)
Name = e.Name
Select Case e.IsReady
Case True ' Drive is formatted and ready.
IsReady = True
DriveFormat = e.DriveFormat
DriveType = e.DriveType
RootDirectory = e.RootDirectory.FullName
VolumeLabel = e.VolumeLabel
TotalSize = e.TotalSize
TotalFreeSpace = e.TotalFreeSpace
AvailableFreeSpace = e.AvailableFreeSpace
Case False ' Drive is not formatted so can't retrieve data.
IsReady = False
DriveFormat = Nothing
DriveType = e.DriveType
RootDirectory = e.RootDirectory.FullName
VolumeLabel = Nothing
TotalSize = 0
TotalFreeSpace = 0
AvailableFreeSpace = 0
End Select ' e.IsReady
End Sub
End Structure
''' <summary>
''' Contains information about a logical volume.
''' For more info, see here:
''' http://msdn.microsoft.com/en-us/library/windows/desktop/aa363249%28v=vs.85%29.aspx
''' </summary>
<StructLayout(LayoutKind.Sequential)>
Private Structure DEV_BROADCAST_VOLUME
''' <summary>
''' The size of this structure, in bytes.
''' </summary>
Public Size As UInteger
''' <summary>
''' Set to DBT_DEVTYP_VOLUME (2).
''' </summary>
Public Type As UInteger
''' <summary>
''' Reserved parameter; do not use this.
''' </summary>
Public Reserved As UInteger
''' <summary>
''' The logical unit mask identifying one or more logical units.
''' Each bit in the mask corresponds to one logical drive.
''' Bit 0 represents drive A, bit 1 represents drive B, and so on.
''' </summary>
Public Mask As UInteger
''' <summary>
''' This parameter can be one of the following values:
''' '0x0001': Change affects media in drive. If not set, change affects physical device or drive.
''' '0x0002': Indicated logical volume is a network volume.
''' </summary>
Public Flags As UShort
End Structure
#End Region
#Region " Constructor "
''' <summary>
''' Initializes a new instance of this class.
''' </summary>
''' <param name="form">The form to assign.</param>
Public Sub New(ByVal form As Form)
' Assign the Formulary.
Me.form = form
End Sub
#End Region
#Region " Event Handlers "
''' <summary>
''' Assign the handle of the target Form to this NativeWindow,
''' necessary to override target Form's WndProc.
''' </summary>
Private Sub SetFormHandle() _
Handles form.HandleCreated, form.Load, form.Shown
If Not MyBase.Handle.Equals(Me.form.Handle) Then
MyBase.AssignHandle(Me.form.Handle)
End If
End Sub
''' <summary>
''' Releases the Handle.
''' </summary>
Private Sub OnHandleDestroyed() _
Handles form.HandleDestroyed
MyBase.ReleaseHandle()
End Sub
#End Region
#Region " Private Methods "
''' <summary>
''' Gets the drive letter stored in a 'DEV_BROADCAST_VOLUME' structure object.
''' </summary>
''' <param name="Device">
''' Indicates the 'DEV_BROADCAST_VOLUME' object containing the Device mask.
''' </param>
''' <returns>System.Char.</returns>
Private Function GetDriveLetter(ByVal Device As DEV_BROADCAST_VOLUME) As Char
Dim DriveLetters As Char() =
{
"A", "B", "C", "D", "E", "F", "G", "H", "I",
"J", "K", "L", "M", "N", "O", "P", "Q", "R",
"S", "T", "U", "V", "W", "X", "Y", "Z"
}
Dim DeviceID As New BitArray(BitConverter.GetBytes(Device.Mask))
For X As Integer = 0 To DeviceID.Length
If DeviceID(X) Then
Return DriveLetters(X)
End If
Next X
Return Nothing
End Function
#End Region
#Region " WndProc"
''' <summary>
''' Invokes the default window procedure associated with this window to process messages for this Window.
''' </summary>
''' <param name="m">
''' A <see cref="T:System.Windows.Forms.Message" /> that is associated with the current Windows message.
''' </param>
Protected Overrides Sub WndProc(ByRef m As Message)
Select Case m.Msg
Case DeviceEvents.Change ' The hardware has changed.
' Transform the LParam pointer into the data structure.
Dim CurrentWDrive As DEV_BROADCAST_VOLUME =
CType(Marshal.PtrToStructure(m.LParam, GetType(DEV_BROADCAST_VOLUME)), DEV_BROADCAST_VOLUME)
Select Case m.WParam.ToInt32
Case DeviceEvents.Arrival ' The device is connected.
' Get the drive letter of the connected device.
DriveLetter = GetDriveLetter(CurrentWDrive)
' Get the drive information of the connected device.
CurrentDrive = New DriveWatcherInfo(New DriveInfo(DriveLetter))
' If it's an storage device then...
If Marshal.ReadInt32(m.LParam, 4) = DeviceEvents.TypeVolume Then
' Inform that the device is connected by raising the 'DriveConnected' event.
RaiseEvent DriveInserted(Me, CurrentDrive)
' Add the connected device to the dictionary, to retrieve info.
If Not CurrentDrives.ContainsKey(DriveLetter) Then
CurrentDrives.Add(DriveLetter, CurrentDrive)
End If ' Not CurrentDrives.ContainsKey(DriveLetter)
End If ' Marshal.ReadInt32(m.LParam, 4) = DeviceEvents.TypeVolume
Case DeviceEvents.QueryRemove ' The device is preparing to be removed.
' Get the letter of the current device being removed.
DriveLetter = GetDriveLetter(CurrentWDrive)
' If the current device being removed is not in the dictionary then...
If Not CurrentDrives.ContainsKey(DriveLetter) Then
' Get the device information of the current device being removed.
CurrentDrive = New DriveWatcherInfo(New DriveInfo(DriveLetter))
' Add the current device to the dictionary,
' to retrieve info before lost it after fully-removal.
CurrentDrives.Add(DriveLetter, New DriveWatcherInfo(New DriveInfo(DriveLetter)))
End If ' Not CurrentDrives.ContainsKey(DriveLetter)
Case DeviceEvents.RemoveComplete
' Get the letter of the removed device.
DriveLetter = GetDriveLetter(CurrentWDrive)
' Inform that the device is disconnected by raising the 'DriveDisconnected' event.
RaiseEvent DriveRemoved(Me, CurrentDrive)
' If the removed device is in the dictionary then...
If CurrentDrives.ContainsKey(DriveLetter) Then
' Remove the device from the dictionary.
CurrentDrives.Remove(DriveLetter)
End If ' CurrentDrives.ContainsKey(DriveLetter)
End Select ' m.WParam.ToInt32
End Select ' m.Msg
MyBase.WndProc(m) ' Return Message to base message handler.
End Sub
#End Region
#Region " Hidden methods "
' These methods and properties are purposely hidden from Intellisense just to look better without unneeded methods.
' NOTE: The methods can be re-enabled at any-time if needed.
''' <summary>
''' Assigns a handle to this window.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Sub AssignHandle()
End Sub
''' <summary>
''' Creates a window and its handle with the specified creation parameters.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Sub CreateHandle()
End Sub
''' <summary>
''' Creates an object that contains all the relevant information required
''' to generate a proxy used to communicate with a remote object.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Sub CreateObjRef()
End Sub
''' <summary>
''' Invokes the default window procedure associated with this window.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Sub DefWndProc()
End Sub
''' <summary>
''' Destroys the window and its handle.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Sub DestroyHandle()
End Sub
''' <summary>
''' Determines whether the specified object is equal to the current object.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Sub Equals()
End Sub
''' <summary>
''' Serves as the default hash function.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Sub GetHashCode()
End Sub
''' <summary>
''' Retrieves the current lifetime service object that controls the lifetime policy for this instance.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Sub GetLifetimeService()
End Sub
''' <summary>
''' Obtains a lifetime service object to control the lifetime policy for this instance.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Sub InitializeLifetimeService()
End Sub
''' <summary>
''' Releases the handle associated with this window.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Sub ReleaseHandle()
End Sub
''' <summary>
''' Gets the handle for this window.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Property Handle()
#End Region
#Region " IDisposable "
''' <summary>
''' To detect redundant calls when disposing.
''' </summary>
Private IsDisposed As Boolean = False
''' <summary>
''' Prevent calls to methods after disposing.
''' </summary>
''' <exception cref="System.ObjectDisposedException"></exception>
Private Sub DisposedCheck()
If Me.IsDisposed Then
Throw New ObjectDisposedException(Me.GetType().FullName)
End If
End Sub
''' <summary>
''' Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
''' </summary>
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
''' <summary>
''' Releases unmanaged and - optionally - managed resources.
''' </summary>
''' <param name="IsDisposing">
''' <c>true</c> to release both managed and unmanaged resources;
''' <c>false</c> to release only unmanaged resources.
''' </param>
Protected Sub Dispose(ByVal IsDisposing As Boolean)
If Not Me.IsDisposed Then
If IsDisposing Then
Me.form = Nothing
MyBase.ReleaseHandle()
MyBase.DestroyHandle()
End If
End If
Me.IsDisposed = True
End Sub
#End Region
End Class