Imports System
Imports System.ComponentModel
Imports System.IO
Imports System.Runtime.CompilerServices
Imports System.Runtime.InteropServices
Imports Microsoft.Win32.SafeHandles
Class Sample
Shared Sub Main()
For Each d As DriveInfo In DriveInfo.GetDrives()
Console.WriteLine("{0,-10} {1}", d.Name, d.DriveType)
Next
Console.Write("テストするドライブを指定してください: ")
Dim drive As New DriveInfo(Console.ReadLine().TrimEnd())
If drive.IsTrayOpened() Then
Console.WriteLine("トレイを閉じます")
drive.Load()
Console.WriteLine("トレイを閉じました")
Else
Console.WriteLine("トレイは閉じています")
End If
Console.WriteLine("メディアをチェックします")
If drive.HasMedia() Then
Console.WriteLine("メディアがセットされています")
Else
Console.WriteLine("メディアはセットされていません")
End If
Console.WriteLine("トレイを開きます")
drive.Eject()
Console.WriteLine("トレイを開きました")
End Sub
End Class
''' <summary>DriveInfoクラスにトレイの開閉を行う拡張メソッドを追加するクラス</summary>
Module DriveInfoEjectLoadExtensions
''' <summary>ドライブにメディアがセットされているかどうかを返す</summary>
<Extension()> _
Public Function HasMedia(ByVal drive As DriveInfo) As Boolean
Using volume As SafeFileHandle = Open(drive, False)
Return IOControl.IOCtl(volume.DangerousGetHandle(), IOControl.IOCTL_STORAGE_CHECK_VERIFY)
End Using
End Function
''' <summary>ドライブのトレイが開いているかどうかを返す</summary>
<Extension()> _
Public Function IsTrayOpened(ByVal drive As DriveInfo) As Boolean
Using volume As SafeFileHandle = Open(drive, True)
'
' http://www.eggheadcafe.com/conversation.aspx?messageid=33820121&threadid=33794406
' http://forum.sources.ru/index.php?showtopic=225102
'
Const dataLength As Integer = 8
Dim sptd As New SCSI_PASS_THROUGH_DIRECT()
Dim size As Integer = Marshal.SizeOf(GetType(SCSI_PASS_THROUGH_DIRECT))
Dim bytesReturned As UInteger
Try
sptd.Length = CUShort(size)
sptd.PathId = 0
sptd.TargetId = 0
sptd.CdbLength = 12
sptd.DataIn = IOControl.SCSI_IOCTL_DATA_IN
sptd.DataTransferLength = dataLength
sptd.TimeOutValue = 5
sptd.DataBuffer = Marshal.AllocCoTaskMem(dataLength)
sptd.Cdb = New Byte(15) {}
sptd.Cdb(0) = &hBD ' mechanism status
sptd.Cdb(9) = 8 ' timeout value
If Not IOControl.DeviceIoControl(volume.DangerousGetHandle(), IOControl.IOCTL_SCSI_PASS_THROUGH_DIRECT, sptd, CUInt(size), sptd, CUInt(size), bytesReturned, IntPtr.Zero) Then
Throw New Win32Exception(Marshal.GetLastWin32Error())
End If
Return (Marshal.ReadByte(sptd.DataBuffer, 1) And &h10) = &h10
Finally
If sptd.DataBuffer <> IntPtr.Zero Then Marshal.FreeCoTaskMem(sptd.DataBuffer)
End Try
End Using
End Function
''' <summary>メディアをセットする、またはドライブのトレイを閉じる</summary>
<Extension()> _
Public Sub Load(ByVal drive As DriveInfo)
Using volume As SafeFileHandle = Open(drive, True)
If Not IOControl.IOCtl(volume.DangerousGetHandle(), IOControl.IOCTL_STORAGE_LOAD_MEDIA) Then
Throw New Win32Exception(Marshal.GetLastWin32Error())
End If
End Using
End Sub
''' <summary>メディアの取り外す、またはドライブのトレイを開く</summary>
<Extension()> _
Public Sub Eject(ByVal drive As DriveInfo)
Using volume As SafeFileHandle = Open(drive, True)
'
' http://support.microsoft.com/kb/165721/
'
Const maxRetry As Integer = 20 ' デバイスロックの最大試行回数
Dim locked As Boolean = False
For t As Integer = 0 To maxRetry - 1
If IOControl.IOCtl(volume.DangerousGetHandle(), IOControl.FSCTL_LOCK_VOLUME) Then
locked = True
Exit For
Else
System.Threading.Thread.Sleep(1000) ' ロックできなければ1秒後に再試行
End If
Next
If Not locked Then Throw New IOException("デバイスがビジー状態です")
If Not IOControl.IOCtl(volume.DangerousGetHandle(), IOControl.FSCTL_DISMOUNT_VOLUME) Then
Throw New Win32Exception(Marshal.GetLastWin32Error())
End If
Dim bytesReturned As UInteger
Dim pmr As PREVENT_MEDIA_REMOVAL
pmr.PreventMediaRemoval = 0 ' FALSE
If Not IOControl.DeviceIoControl(volume.DangerousGetHandle(), IOControl.IOCTL_STORAGE_MEDIA_REMOVAL, pmr, CUInt(Marshal.SizeOf(GetType(PREVENT_MEDIA_REMOVAL))), IntPtr.Zero, CUInt(0), bytesReturned, IntPtr.Zero) Then
Throw New Win32Exception(Marshal.GetLastWin32Error())
End If
If Not IOControl.IOCtl(volume.DangerousGetHandle(), IOControl.IOCTL_STORAGE_EJECT_MEDIA) Then
Throw New Win32Exception(Marshal.GetLastWin32Error())
End If
End Using
End Sub
''' <summary>ドライブを開いてSafeFileHandleを取得する</summary>
Private Function Open(ByVal drive As DriveInfo, ByVal accessWrite As Boolean) As SafeFileHandle
Dim accessFlags As UInteger
If drive.DriveType = DriveType.CDRom Then
accessFlags = GENERIC_READ
Else If drive.DriveType = DriveType.Removable Then
accessFlags = GENERIC_READ Or GENERIC_WRITE
Else
Throw New NotSupportedException("リムーバブルドライブではありません")
End If
If accessWrite Then accessFlags = accessFlags Or GENERIC_WRITE
Dim handle As IntPtr = CreateFile(String.Format("\\.\{0}:", drive.Name(0)), _
accessFlags, _
FILE_SHARE_READ Or FILE_SHARE_WRITE, _
IntPtr.Zero, _
OPEN_EXISTING, _
0, _
IntPtr.Zero)
If handle = INVALID_HANDLE_VALUE Then Throw New Win32Exception(Marshal.GetLastWin32Error())
Return New SafeFileHandle(handle, True)
End Function
<DllImport("kernel32", SetLastError := True, CharSet := CharSet.Auto)> _
Private Function CreateFile(ByVal lpFIleName As String, _
ByVal dwDesiredAccess As UInteger, _
ByVal dwShareMode As UInteger, _
ByVal lpSecurityAttributes As IntPtr, _
ByVal dwCreationDisposition As UInteger, _
ByVal dwFlagsAndAttributes As UInteger, _
ByVal hTemplateFile As IntPtr) As IntPtr
End Function
Private Const GENERIC_READ As UInteger = &h80000000UI
Private Const GENERIC_WRITE As UInteger = &h40000000UI
Private Const FILE_SHARE_READ As UInteger = &h00000001UI
Private Const FILE_SHARE_WRITE As UInteger = &h00000002UI
Private Const OPEN_EXISTING As UInteger = 3
Private ReadOnly INVALID_HANDLE_VALUE As IntPtr = New IntPtr(-1)
' DeviceIoControl呼び出し関連のクラス
Private Class IOControl
<DllImport("kernel32", SetLastError := True)> _
Public Shared Overloads Function DeviceIoControl(ByVal hDevice As IntPtr, _
ByVal dwIoControlCode As UInteger, _
ByVal lpInBuffer As IntPtr, _
ByVal nInBufferSize As UInteger, _
ByVal lpOutBuffer As IntPtr, _
ByVal nOutBufferSize As UInteger, _
<Out> ByRef lpBytesReturned As UInteger, _
ByVal lpOverlapped As IntPtr) As Boolean
End Function
<DllImport("kernel32", SetLastError := True)> _
Public Shared Overloads Function DeviceIoControl(ByVal hDevice As IntPtr, _
ByVal dwIoControlCode As UInteger, _
ByRef lpInBuffer As SCSI_PASS_THROUGH_DIRECT, _
ByVal nInBufferSize As UInteger, _
ByRef lpOutBuffer As SCSI_PASS_THROUGH_DIRECT, _
ByVal nOutBufferSize As UInteger, _
<Out> ByRef lpBytesReturned As UInteger, _
ByVal lpOverlapped As IntPtr) As Boolean
End Function
<DllImport("kernel32", SetLastError := True)> _
Public Shared Overloads Function DeviceIoControl(ByVal hDevice As IntPtr, _
ByVal dwIoControlCode As UInteger, _
ByRef lpInBuffer As PREVENT_MEDIA_REMOVAL, _
ByVal nInBufferSize As UInteger, _
ByVal lpOutBuffer As IntPtr, _
ByVal nOutBufferSize As UInteger, _
<Out> ByRef lpBytesReturned As UInteger, _
ByVal lpOverlapped As IntPtr) As Boolean
End Function
Public Const FILE_ANY_ACCESS As UInteger= &h00000000
Public Const FILE_SPECIAL_ACCESS As UInteger= FILE_ANY_ACCESS
Public Const FILE_READ_ACCESS As UInteger = &h00000001
Public Const FILE_WRITE_ACCESS As UInteger = &h00000002
Public Const SCSI_IOCTL_DATA_OUT As Byte = 0
Public Const SCSI_IOCTL_DATA_IN As Byte = 1
Public Const SCSI_IOCTL_DATA_UNSPECIFIED As Byte = 2
Public Const METHOD_BUFFERED As UInteger = 0
Public Const METHOD_IN_DIRECT As UInteger = 1
Public Const METHOD_OUT_DIRECT As UInteger = 2
Public Const METHOD_NEITHER As UInteger = 3
Public Const FILE_DEVICE_CONTROLLER As UInteger= 4
Public Const FILE_DEVICE_FILE_SYSTEM As UInteger= 9
Public Const FILE_DEVICE_MASS_STORAGE As UInteger= 45
Public Shared ReadOnly IOCTL_SCSI_BASE As UInteger = FILE_DEVICE_CONTROLLER
Public Shared ReadOnly IOCTL_SCSI_PASS_THROUGH_DIRECT As UInteger = CTL_CODE(IOCTL_SCSI_BASE, &h0405, METHOD_BUFFERED, FILE_READ_ACCESS Or FILE_WRITE_ACCESS)
Public Shared ReadOnly FSCTL_BASE As UInteger = FILE_DEVICE_FILE_SYSTEM
Public Shared ReadOnly FSCTL_LOCK_VOLUME As UInteger = CTL_CODE(FSCTL_BASE, 6, METHOD_BUFFERED, FILE_ANY_ACCESS)
Public Shared ReadOnly FSCTL_DISMOUNT_VOLUME As UInteger = CTL_CODE(FSCTL_BASE, 8, METHOD_BUFFERED, FILE_ANY_ACCESS)
Public Shared ReadOnly IOCTL_STORAGE_BASE As UInteger = FILE_DEVICE_MASS_STORAGE
Public Shared ReadOnly IOCTL_STORAGE_CHECK_VERIFY As UInteger = CTL_CODE(IOCTL_STORAGE_BASE, &h0200, METHOD_BUFFERED, FILE_READ_ACCESS)
Public Shared ReadOnly IOCTL_STORAGE_MEDIA_REMOVAL As UInteger = CTL_CODE(IOCTL_STORAGE_BASE, &h0201, METHOD_BUFFERED, FILE_READ_ACCESS)
Public Shared ReadOnly IOCTL_STORAGE_EJECT_MEDIA As UInteger = CTL_CODE(IOCTL_STORAGE_BASE, &h0202, METHOD_BUFFERED, FILE_READ_ACCESS)
Public Shared ReadOnly IOCTL_STORAGE_LOAD_MEDIA As UInteger = CTL_CODE(IOCTL_STORAGE_BASE, &h0203, METHOD_BUFFERED, FILE_READ_ACCESS)
Private Shared Function CTL_CODE(ByVal t As UInteger, ByVal f As UInteger, ByVal m As UInteger, ByVal a As UInteger) As UInteger
Return CUInt(t << 16 Or a << 14 Or f << 2 Or m)
End Function
Public Shared Function IOCtl(ByVal hDevice As IntPtr, ByVal dwIoControlCode As UInteger) As Boolean
Dim bytesReturned As UInteger
Return DeviceIoControl(hDevice, dwIoControlCode, IntPtr.Zero, CUInt(0), IntPtr.Zero, CUInt(0), bytesReturned, IntPtr.Zero)
End Function
End Class
<StructLayout(LayoutKind.Sequential)> _
Private Structure SCSI_PASS_THROUGH_DIRECT
Public Length As UShort
Public ScsiStatus As Byte
Public PathId As Byte
Public TargetId As Byte
Public Lun As Byte
Public CdbLength As Byte
Public SenseInfoLength As Byte
Public DataIn As Byte
Public DataTransferLength As UInteger
Public TimeOutValue As UInteger
'Public DataBuffer As Byte()
Public DataBuffer As IntPtr
Public SenseInfoOffset As UInteger
<MarshalAs(UnmanagedType.ByValArray, SizeConst := 16)> Public Cdb As Byte()
End Structure
<StructLayout(LayoutKind.Sequential)> _
Private Structure PREVENT_MEDIA_REMOVAL
Public PreventMediaRemoval As Byte
End Structure
End Module