.NETのDriveInfoクラスにはCD-ROMドライブの開閉やリムーバブルメディアの取り外しを行うためのメソッドが無いので、APIを使用して実現する。 具体的にはDeviceIoControlを使用してデバイスに直接制御信号を送る。 送信する命令を変えることでドライブのトレイ開閉や、メディアがセットされているかどうかを調べることもできる。

実装

以下のコードでは、次の四つのメソッドを用意して、DriveInfoクラスの拡張メソッドとして呼び出せるようにしている。

Eject
メディアの取り外す、またはドライブのトレイを開く
Load
メディアをセットする、またはドライブのトレイを閉じる
IsTrayOpened
ドライブのトレイが開いているかどうかを返す
HasMedia
ドライブにメディアがセットされているかどうかを返す (DriveInfo.IsReadyと同等)

なお、このコードはNT系のWindowsのみを対象としていて、9x系では動作しない。

using System;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

class Sample {
  static void Main()
  {
    foreach (var d in DriveInfo.GetDrives()) {
      Console.WriteLine("{0,-10} {1}", d.Name, d.DriveType);
    }

    Console.Write("テストするドライブを指定してください: ");

    var drive = new DriveInfo(Console.ReadLine().TrimEnd());

    if (drive.IsTrayOpened()) {
      Console.WriteLine("トレイを閉じます");

      drive.Load();

      Console.WriteLine("トレイを閉じました");
    }
    else {
      Console.WriteLine("トレイは閉じています");
    }

    Console.WriteLine("メディアをチェックします");

    if (drive.HasMedia()) {
      Console.WriteLine("メディアがセットされています");
    }
    else {
      Console.WriteLine("メディアはセットされていません");
    }

    Console.WriteLine("トレイを開きます");

    drive.Eject();

    Console.WriteLine("トレイを開きました");
  }
}

/// <summary>DriveInfoクラスにトレイの開閉を行う拡張メソッドを追加するクラス</summary>
static class DriveInfoEjectLoadExtensions {
  /// <summary>ドライブにメディアがセットされているかどうかを返す</summary>
  public static bool HasMedia(this DriveInfo drive)
  {
    using (var volume = Open(drive, false)) {
      return IOControl.IOCtl(volume.DangerousGetHandle(), IOControl.IOCTL_STORAGE_CHECK_VERIFY);
    }
  }

  /// <summary>ドライブのトレイが開いているかどうかを返す</summary>
  public unsafe static bool IsTrayOpened(this DriveInfo drive)
  {
    using (var volume = Open(drive, true)) {
      /*
       * http://www.eggheadcafe.com/conversation.aspx?messageid=33820121&threadid=33794406
       * http://forum.sources.ru/index.php?showtopic=225102
       */

      const int dataLength = 8;
      byte* data = stackalloc byte[dataLength];
      SCSI_PASS_THROUGH_DIRECT* sptd = stackalloc SCSI_PASS_THROUGH_DIRECT[1];

      var size = Marshal.SizeOf(typeof(SCSI_PASS_THROUGH_DIRECT));

      sptd[0].Length = (ushort)size;
      sptd[0].PathId = 0;
      sptd[0].TargetId = 0;
      sptd[0].CdbLength = 12;
      sptd[0].DataIn = IOControl.SCSI_IOCTL_DATA_IN;
      sptd[0].DataTransferLength = dataLength;
      sptd[0].TimeOutValue = 5;
      sptd[0].DataBuffer = (void*)data;
      sptd[0].Cdb[0] = 0xbd; // mechanism status
      sptd[0].Cdb[9] = 8; // timeout value

      uint bytesReturned;

      if (!IOControl.DeviceIoControl(volume.DangerousGetHandle(), IOControl.IOCTL_SCSI_PASS_THROUGH_DIRECT, (void*)sptd, (uint)size, (void*)sptd, (uint)size, out bytesReturned, IntPtr.Zero))
        throw new Win32Exception(Marshal.GetLastWin32Error());

      return ((data[1] & 0x10) == 0x10);
    }
  }

  /// <summary>メディアをセットする、またはドライブのトレイを閉じる</summary>
  public static void Load(this DriveInfo drive)
  {
    using (var volume = Open(drive, false)) {
      if (!IOControl.IOCtl(volume.DangerousGetHandle(), IOControl.IOCTL_STORAGE_LOAD_MEDIA))
        throw new Win32Exception(Marshal.GetLastWin32Error());
    }
  }

  /// <summary>メディアの取り外す、またはドライブのトレイを開く</summary>
  public static void Eject(this DriveInfo drive)
  {
    using (var volume = Open(drive, false)) {
      /*
       * http://support.microsoft.com/kb/165721/
       */

      const int maxRetry = 20; // デバイスロックの最大試行回数
      var locked = false;

      for (var t = 0; t < maxRetry; t++) {
        if (IOControl.IOCtl(volume.DangerousGetHandle(), IOControl.FSCTL_LOCK_VOLUME)) {
          locked = true;
          break;
        }
        else {
          System.Threading.Thread.Sleep(1000); // ロックできなければ1秒後に再試行
        }
      }

      if (!locked)
        throw new IOException("デバイスがビジー状態です");

      if (!IOControl.IOCtl(volume.DangerousGetHandle(), IOControl.FSCTL_DISMOUNT_VOLUME))
        throw new Win32Exception(Marshal.GetLastWin32Error());

      unsafe {
        uint bytesReturned;
        PREVENT_MEDIA_REMOVAL* inBuffer = stackalloc PREVENT_MEDIA_REMOVAL[1];

        inBuffer[0].PreventMediaRemoval = 0; // FALSE

        if (!IOControl.DeviceIoControl(volume.DangerousGetHandle(), IOControl.IOCTL_STORAGE_MEDIA_REMOVAL, (void*)inBuffer, (uint)Marshal.SizeOf(typeof(PREVENT_MEDIA_REMOVAL)), (void*)IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero))
          throw new Win32Exception(Marshal.GetLastWin32Error());
      }

      if (!IOControl.IOCtl(volume.DangerousGetHandle(), IOControl.IOCTL_STORAGE_EJECT_MEDIA))
        throw new Win32Exception(Marshal.GetLastWin32Error());
    }
  }

  /// <summary>ドライブを開いてSafeFileHandleを取得する</summary>
  private static SafeFileHandle Open(DriveInfo drive, bool accessWrite)
  {
    uint accessFlags;

    if (drive.DriveType == DriveType.CDRom)
      accessFlags = GENERIC_READ;
    else if (drive.DriveType == DriveType.Removable)
      accessFlags = GENERIC_READ | GENERIC_WRITE;
    else
      throw new NotSupportedException("リムーバブルドライブではありません");

    if (accessWrite)
      accessFlags |= GENERIC_WRITE;

    var handle = CreateFile(string.Format(@"\\.\{0}:", drive.Name[0]),
                            accessFlags,
                            FILE_SHARE_READ | FILE_SHARE_WRITE,
                            IntPtr.Zero,
                            OPEN_EXISTING,
                            0,
                            IntPtr.Zero);

    if (handle == INVALID_HANDLE_VALUE)
      throw new Win32Exception(Marshal.GetLastWin32Error());

    return new SafeFileHandle(handle, true);
  }

  [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Auto)]
  private static extern IntPtr CreateFile(string lpFIleName,
                                          uint dwDesiredAccess,
                                          uint dwShareMode,
                                          IntPtr lpSecurityAttributes,
                                          uint dwCreationDisposition,
                                          uint dwFlagsAndAttributes,
                                          IntPtr hTemplateFile);

  private const uint GENERIC_READ = 0x80000000;
  private const uint GENERIC_WRITE = 0x40000000;
  private const uint FILE_SHARE_READ = 0x00000001;
  private const uint FILE_SHARE_WRITE = 0x00000002;
  private const uint OPEN_EXISTING = 3;

  private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);

  // DeviceIoControl呼び出し関連のクラス
  private static class IOControl {
    [DllImport("kernel32", SetLastError = true)]
    public static unsafe extern bool DeviceIoControl(IntPtr hDevice,
                                                     uint dwIoControlCode,
                                                     void* lpInBuffer,
                                                     uint nInBufferSize,
                                                     void* lpOutBuffer,
                                                     uint nOutBufferSize,
                                                     out uint lpBytesReturned,
                                                     IntPtr lpOverlapped);

    public const uint FILE_ANY_ACCESS      = 0x00000000;
    public const uint FILE_SPECIAL_ACCESS  = FILE_ANY_ACCESS;
    public const uint FILE_READ_ACCESS     = 0x00000001;
    public const uint FILE_WRITE_ACCESS    = 0x00000002;

    public const byte SCSI_IOCTL_DATA_OUT         = 0;
    public const byte SCSI_IOCTL_DATA_IN          = 1;
    public const byte SCSI_IOCTL_DATA_UNSPECIFIED = 2;

    public const uint METHOD_BUFFERED    = 0;
    public const uint METHOD_IN_DIRECT   = 1;
    public const uint METHOD_OUT_DIRECT  = 2;
    public const uint METHOD_NEITHER     = 3;

    public const uint FILE_DEVICE_CONTROLLER    = 4;
    public const uint FILE_DEVICE_FILE_SYSTEM   = 9;
    public const uint FILE_DEVICE_MASS_STORAGE  = 45;

    public static readonly uint IOCTL_SCSI_BASE                   = FILE_DEVICE_CONTROLLER;
    public static readonly uint IOCTL_SCSI_PASS_THROUGH_DIRECT    = CTL_CODE(IOCTL_SCSI_BASE, 0x0405, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS);

    public static readonly uint FSCTL_BASE                        = FILE_DEVICE_FILE_SYSTEM;
    public static readonly uint FSCTL_LOCK_VOLUME                 = CTL_CODE(FSCTL_BASE, 6, METHOD_BUFFERED, FILE_ANY_ACCESS);
    public static readonly uint FSCTL_DISMOUNT_VOLUME             = CTL_CODE(FSCTL_BASE, 8, METHOD_BUFFERED, FILE_ANY_ACCESS);

    public static readonly uint IOCTL_STORAGE_BASE                = FILE_DEVICE_MASS_STORAGE;
    public static readonly uint IOCTL_STORAGE_CHECK_VERIFY        = CTL_CODE(IOCTL_STORAGE_BASE, 0x0200, METHOD_BUFFERED, FILE_READ_ACCESS);
    public static readonly uint IOCTL_STORAGE_MEDIA_REMOVAL       = CTL_CODE(IOCTL_STORAGE_BASE, 0x0201, METHOD_BUFFERED, FILE_READ_ACCESS);
    public static readonly uint IOCTL_STORAGE_EJECT_MEDIA         = CTL_CODE(IOCTL_STORAGE_BASE, 0x0202, METHOD_BUFFERED, FILE_READ_ACCESS);
    public static readonly uint IOCTL_STORAGE_LOAD_MEDIA          = CTL_CODE(IOCTL_STORAGE_BASE, 0x0203, METHOD_BUFFERED, FILE_READ_ACCESS);

    private static uint CTL_CODE(uint t, uint f, uint m, uint a)
    {
      return (uint)((t << 16) | (a << 14) | (f << 2) | m);
    }

    public unsafe static bool IOCtl(IntPtr hDevice, uint dwIoControlCode)
    {
      uint bytesReturned;

      return DeviceIoControl(hDevice, dwIoControlCode, (void*)IntPtr.Zero, 0, (void*)IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero);
    }
  }

  [StructLayout(LayoutKind.Sequential)]
  private unsafe struct SCSI_PASS_THROUGH_DIRECT {
    public ushort Length;
    public byte   ScsiStatus;
    public byte   PathId;
    public byte   TargetId;
    public byte   Lun;
    public byte   CdbLength;
    public byte   SenseInfoLength;
    public byte   DataIn;
    public uint   DataTransferLength;
    public uint   TimeOutValue;
    public void*  DataBuffer;
    public uint   SenseInfoOffset;
    public fixed byte Cdb[16];
  }

  [StructLayout(LayoutKind.Sequential)]
  private struct PREVENT_MEDIA_REMOVAL {
    public byte PreventMediaRemoval;
  }
}
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

動作例

Q:\に接続されている光学ディスクドライブでの例。 テストに使ったのはIODATA製ブルーレイドライブ、BRD-SH10B。

実行結果例
E:\temp>eject.exe
A:\        Removable
C:\        Fixed
D:\        Removable
E:\        Fixed
F:\        Fixed
Q:\        CDRom
R:\        CDRom
テストするドライブを指定してください: q:\
トレイを閉じます
トレイを閉じました
メディアをチェックします
メディアがセットされています
トレイを開きます
トレイを開きました

D:\に接続されているUSBメモリでの例。 テストに使ったのはSandisk製のもの。 Ejectメソッドを実行すると、「ハードウェアの安全な取り外し」を行った場合と同等の動作となる。

実行結果例
E:\temp>eject.exe
A:\        Removable
C:\        Fixed
D:\        Removable
E:\        Fixed
F:\        Fixed
Q:\        CDRom
R:\        CDRom
テストするドライブを指定してください: d:\
トレイは閉じています
メディアをチェックします
メディアがセットされています
トレイを開きます
トレイを開きました