none
VB code to eject Portable drive

    Question

  • For the last few days I have been searching for this code, tried several without success, getting error messages I dont understand & some do nothing, no error msg, no eject.

    Ideally I want universal code for both USB and portable drives, is there a difference in code requirements?

    If somebody can help it would be much appreciated

    Monday, February 11, 2019 4:47 AM

Answers

  • This is the test I did on my disks, but not sure it will work for you or maybe some changes will be needed... =>

    Option Strict On
    Imports System.Runtime.InteropServices
    Imports System.Text
    Public Class Form1
    
        <DllImport("Setupapi.dll", CharSet:=CharSet.Unicode, SetLastError:=True)>
        Public Shared Function SetupDiGetClassDevs(ByRef ClassGuid As Guid, ByVal Enumerator As IntPtr, ByVal hWndParent As IntPtr, ByVal Flags As Integer) As IntPtr
        End Function
    
        <DllImport("Setupapi.dll", CharSet:=CharSet.Unicode, SetLastError:=True)>
        Public Shared Function SetupDiEnumDeviceInterfaces(ByVal DeviceInfoSet As IntPtr, ByVal DeviceInfoData As IntPtr, ByRef InterfaceClassGuid As Guid, ByVal MemberIndex As Integer, ByRef DeviceInterfaceData As SP_DEVICE_INTERFACE_DATA) As Boolean
        End Function
    
        <DllImport("Setupapi.dll", CharSet:=CharSet.Unicode, SetLastError:=True)>
        Public Shared Function SetupDiGetDeviceInterfaceDetail(ByVal hDevInfo As IntPtr, ByRef deviceInterfaceData As SP_DEVICE_INTERFACE_DATA, ByVal deviceInterfaceDetailData As IntPtr, ByVal deviceInterfaceDetailDataSize As UInt32, <System.Runtime.InteropServices.Out()> ByRef requiredSize As UInt32, ByVal deviceInfoData As IntPtr) As Boolean
        End Function
    
        <DllImport("Setupapi.dll", CharSet:=CharSet.Unicode, SetLastError:=True)>
        Public Shared Function SetupDiGetDeviceInterfaceDetail(ByVal hDevInfo As IntPtr, ByRef deviceInterfaceData As SP_DEVICE_INTERFACE_DATA, ByRef deviceInterfaceDetailData As SP_DEVICE_INTERFACE_DETAIL_DATA, ByVal deviceInterfaceDetailDataSize As UInt32, <System.Runtime.InteropServices.Out()> ByRef requiredSize As UInt32, ByRef deviceInfoData As SP_DEVINFO_DATA) As Boolean
        End Function
    
        <DllImport("Setupapi.dll", CharSet:=CharSet.Unicode, SetLastError:=True)>
        Public Shared Function SetupDiDestroyDeviceInfoList(ByVal DeviceInfoSet As IntPtr) As Boolean
        End Function
    
        Public Const DIGCF_PRESENT As Integer = &H2
        Public Const DIGCF_DEVICEINTERFACE As Integer = &H10
        Public Const ERROR_INSUFFICIENT_BUFFER As Integer = 122
    
        <StructLayout(LayoutKind.Sequential)>
        Public Structure SP_DEVICE_INTERFACE_DATA
            Public cbSize As Integer
            Public InterfaceClassGuid As Guid
            Public Flags As Integer
            Public Reserved As IntPtr
        End Structure
    
        <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)>
        Public Structure SP_DEVINFO_DATA
            Public cbSize As Integer
            Public ClassGuid As Guid
            Public DevInst As Integer
            Public Reserved As IntPtr
        End Structure
    
        <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode, Pack:=1)>
        Public Structure SP_DEVICE_INTERFACE_DETAIL_DATA
            Public cbSize As Integer
            <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=260)>
            Public DevicePath As String
        End Structure
    
        <DllImport("Cfgmgr32.dll", CharSet:=CharSet.Unicode, SetLastError:=True)>
        Public Shared Function CM_Get_Parent(ByRef pdnDevInst As Integer, dnDevInst As Integer, ulFlags As Integer) As Integer
        End Function
    
        <DllImport("Cfgmgr32.dll", CharSet:=CharSet.Unicode, SetLastError:=True)>
        Public Shared Function CM_Get_DevNode_Registry_Property_Ex(dnDevInst As Integer, ulProperty As Integer, ByRef pulRegDataType As Integer, Buffer As IntPtr,
             ByRef pulLength As Integer, ulFlags As Integer, hMachine As IntPtr) As Integer
        End Function
    
        <DllImport("Cfgmgr32.dll", CharSet:=CharSet.Unicode, SetLastError:=True)>
        Public Shared Function CM_Get_Device_ID(dnDevInst As Integer, Buffer As StringBuilder, BufferLen As Integer, ulFlags As Integer) As Integer
        End Function
    
        Public Const CM_DRP_CAPABILITIES As Integer = &H10    'Capabilities REG_DWORD Property (R)
        Public Const CM_DRP_FRIENDLYNAME As Integer = &HD    'FriendlyName REG_SZ Property (RW)
    
        Public Const CM_DEVCAP_LOCKSUPPORTED = &H1
        Public Const CM_DEVCAP_EJECTSUPPORTED = &H2
        Public Const CM_DEVCAP_REMOVABLE = &H4
        Public Const CM_DEVCAP_DOCKDEVICE = &H8
        Public Const CM_DEVCAP_UNIQUEID = &H10
        Public Const CM_DEVCAP_SILENTINSTALL = &H20
        Public Const CM_DEVCAP_RAWDEVICEOK = &H40
        Public Const CM_DEVCAP_SURPRISEREMOVALOK = &H80
        Public Const CM_DEVCAP_HARDWAREDISABLED = &H100
        Public Const CM_DEVCAP_NONDYNAMIC = &H200
    
        <DllImport("Cfgmgr32.dll", CharSet:=CharSet.Unicode, SetLastError:=True)>
        Public Shared Function CM_Request_Device_Eject_Ex(dnDevInst As Integer, ByRef pVetoType As PNP_VETO_TYPE, pszVetoName As System.Text.StringBuilder, ulNameLength As Integer, ulFlags As Integer, hMachine As IntPtr) As Integer
        End Function
    
        Public Enum PNP_VETO_TYPE
            PNP_VetoTypeUnknown            ' Name Is unspecified
            PNP_VetoLegacyDevice            ' Name Is an Instance Path
            PNP_VetoPendingClose           ' Name Is an Instance Path
            PNP_VetoWindowsApp            ' Name Is a Module
            PNP_VetoWindowsService          'Name Is a Service
            PNP_VetoOutstandingOpen        ' Name Is an Instance Path
            PNP_VetoDevice                  ' Name Is an Instance Path
            PNP_VetoDriver                  ' Name Is a Driver Service Name
            PNP_VetoIllegalDeviceRequest    ' Name Is an Instance Path
            PNP_VetoInsufficientPower     ' Name Is unspecified
            PNP_VetoNonDisableable        ' Name Is an Instance Path
            PNP_VetoLegacyDriver           ' Name Is a Service
            PNP_VetoInsufficientRights      'Name Is unspecified
        End Enum
    
        Friend WithEvents ListView1 As ListView
        Friend WithEvents Button1 As Button
    
        Dim nDevInstRemovable As Integer = 0
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            Me.ClientSize = New System.Drawing.Size(800, 210)
            Me.CenterToScreen()
    
            ListView1 = New ListView()
            ListView1.Location = New System.Drawing.Point(10, 10)
            ListView1.Size = New System.Drawing.Size(780, 150)
            ListView1.Columns.Add("Friendly Name", 200, HorizontalAlignment.Left)
            ListView1.Columns.Add("Device ID", 560, HorizontalAlignment.Left)
            ListView1.Columns.Add("Device Inst", 0, HorizontalAlignment.Left)
            ListView1.View = View.Details
            ListView1.FullRowSelect = True
            Me.Controls.Add(ListView1)
    
            Button1 = New Button()
            Button1.Location = New System.Drawing.Point(10, 170)
            Button1.Name = "Button1"
            Button1.Size = New System.Drawing.Size(75, 23)
            Button1.TabIndex = 0
            Button1.Text = "Try to Eject"
            Button1.UseVisualStyleBackColor = True
            Controls.Add(Button1)
            Button1.Enabled = False
    
            PopulateListView()
        End Sub
    
        Private Sub PopulateListView()
            Dim nStatus As Integer = 0
            Dim b As Boolean = False
            Dim i As Integer = 0
            Dim GUID_DEVINTERFACE_DISK As New Guid("53f56307-b6bf-11d0-94f2-00a0c91efb8b")
            Dim hdevDisplayInfoSet As IntPtr = SetupDiGetClassDevs(GUID_DEVINTERFACE_DISK, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT Or DIGCF_DEVICEINTERFACE)
            If (hdevDisplayInfoSet <> IntPtr.Zero) Then
                Dim data As SP_DEVICE_INTERFACE_DATA = New SP_DEVICE_INTERFACE_DATA()
                While (nStatus = 0)
                    data.cbSize = Marshal.SizeOf(GetType(SP_DEVICE_INTERFACE_DATA))
                    b = SetupDiEnumDeviceInterfaces(hdevDisplayInfoSet, IntPtr.Zero, GUID_DEVINTERFACE_DISK, i, data)
                    If (Not b) Then
                        nStatus = Marshal.GetLastWin32Error()
                        Exit While
                    End If
    
                    Dim dwBytes As UInteger = 0
                    Dim bRet As Boolean = SetupDiGetDeviceInterfaceDetail(hdevDisplayInfoSet, data, IntPtr.Zero, 0, dwBytes, IntPtr.Zero)
                    nStatus = Marshal.GetLastWin32Error()
                    If (Not bRet And Marshal.GetLastWin32Error() = ERROR_INSUFFICIENT_BUFFER) Then
                        Dim didd As SP_DEVICE_INTERFACE_DETAIL_DATA = New SP_DEVICE_INTERFACE_DETAIL_DATA()
                        Dim nSize = 4 + If(IntPtr.Size = 4, 2, 4)
                        didd.cbSize = nSize
                        Dim da As SP_DEVINFO_DATA = New SP_DEVINFO_DATA()
                        da.cbSize = Marshal.SizeOf(da)
                        bRet = SetupDiGetDeviceInterfaceDetail(hdevDisplayInfoSet, data, didd, dwBytes, dwBytes, da)
                        If (bRet) Then
    
                            Dim sFriendlyName As String = Nothing
                            Dim pBuffer As IntPtr = IntPtr.Zero
                            Dim pulRegDataType As Integer = 0
                            Dim nBufferSize As Integer = (260 + 1) * Marshal.SystemDefaultCharSize
                            pBuffer = Marshal.AllocHGlobal(nBufferSize)
                            nStatus = CM_Get_DevNode_Registry_Property_Ex(da.DevInst, CM_DRP_FRIENDLYNAME, pulRegDataType, pBuffer, nBufferSize, 0, IntPtr.Zero)
                            If (nStatus = 0) Then
                                sFriendlyName = Marshal.PtrToStringAuto(pBuffer)
                            End If
                            Marshal.FreeHGlobal(pBuffer)
    
                            Dim sbDeviceID As StringBuilder = New StringBuilder(260)
                            nStatus = CM_Get_Device_ID(da.DevInst, sbDeviceID, sbDeviceID.Capacity, 0)
                            If (nStatus = 0) Then
                                Dim str(2) As String
                                Dim lvi As ListViewItem
                                str(0) = sFriendlyName
                                str(1) = sbDeviceID.ToString()
                                str(2) = CType(da.DevInst, String)
                                lvi = New ListViewItem(str)
                                ListView1.Items.Add(lvi)
                            End If
                            nStatus = 0
                        End If
                    End If
                    i += 1
                End While
                SetupDiDestroyDeviceInfoList(hdevDisplayInfoSet)
            End If
        End Sub
    
        Private Sub ListView1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ListView1.SelectedIndexChanged
            Dim i As Integer = 1
            Dim selitems As ListView.SelectedListViewItemCollection = ListView1.SelectedItems
            Dim item As ListViewItem
            Dim nDevInst As Integer = 0
            Dim bRemovable As Boolean = False
            nDevInstRemovable = 0
            For Each item In selitems
                nDevInst = Integer.Parse(item.SubItems(2).Text)
                Dim pBuffer As IntPtr = IntPtr.Zero
                Dim nBufferSize As Integer = Marshal.SizeOf(GetType(Integer))
                Dim pulRegDataType As Integer = 0
                pBuffer = Marshal.AllocHGlobal(nBufferSize)
                Dim nReturn As Integer = CM_Get_DevNode_Registry_Property_Ex(nDevInst, CM_DRP_CAPABILITIES, pulRegDataType, pBuffer, nBufferSize, 0, IntPtr.Zero)
                If (nReturn = 0) Then
                    Dim nCap As Integer = Marshal.ReadInt32(pBuffer, 0)
                    If CBool((CType(nCap, Integer) And CM_DEVCAP_REMOVABLE)) Then
                        bRemovable = True
                        nDevInstRemovable = nDevInst
                        Exit For
                    Else
                        Dim nDevInstParent As Integer = 0
                        nReturn = CM_Get_Parent(nDevInstParent, nDevInst, 0)
                        If (nReturn = 0) Then
                            Dim pBuffer2 As IntPtr = IntPtr.Zero
                            pBuffer2 = Marshal.AllocHGlobal(nBufferSize)
                            nReturn = CM_Get_DevNode_Registry_Property_Ex(nDevInstParent, CM_DRP_CAPABILITIES, pulRegDataType, pBuffer2, nBufferSize, 0, IntPtr.Zero)
                            If (nReturn = 0) Then
                                nCap = Marshal.ReadInt32(pBuffer2, 0)
                                If CBool((CType(nCap, Integer) And CM_DEVCAP_REMOVABLE)) Then
                                    bRemovable = True
                                    nDevInstRemovable = nDevInstParent
                                    Exit For
                                End If
                            End If
                            Marshal.FreeHGlobal(pBuffer2)
                        End If
                    End If
                End If
                Marshal.FreeHGlobal(pBuffer)
            Next
            If (bRemovable) Then
                Button1.Enabled = True
            Else
                Button1.Enabled = False
            End If
        End Sub
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            Dim sbOut As StringBuilder = New StringBuilder(260)
            Dim pVetoType As PNP_VETO_TYPE
            Dim nRet As Integer = CM_Request_Device_Eject_Ex(nDevInstRemovable, pVetoType, sbOut, 260, 0, IntPtr.Zero)
            If (nRet = 0) Then
                Button1.Enabled = False
                ListView1.Items.Clear()
                PopulateListView()
            End If
        End Sub
    End Class


    • Marked as answer by x38class 13 hours 30 minutes ago
    Friday, February 15, 2019 2:16 PM

All replies

  •  I have been searching for this code, tried several without success, getting error messages I dont understand & some do nothing, no error msg, no eject.

    Ideally I want universal code for both USB and portable drives, is there a difference in code requirements?

    Explain what you mean by a non-USB portable drive. In what way would it be
    connected to the computer?

    To be sure we're on the same page, describe what you mean by "eject Portable 
    drive". Are you referring to ejecting the media such as a CD or DVD? Or are
    you referring to the process required for safe removal of a drive itself - to
    ensure that all cached writes are flushed to the medium? Obviously there is 
    no programmatic way to physically spit a USB plug out of a USB jack as even
    the hardware isn't capable of that.

    - Wayne

    Monday, February 11, 2019 5:30 AM
  • A portable drive is a metal box with a socket on it with a disk inside that you plug into a usb socket, it is not a usb drive, I am not interested in CD or DVD

    So, I re-state I want to program code to eject/remove a usb or a portable drive.

    I use the word eject because that is what windows shows when you right click on the drive.

    Monday, February 11, 2019 5:36 AM
  • Hi,

    try the code:

    Imports System.Runtime.InteropServices
    
    Public Class Form1
        Const OPEN_EXISTING As Integer = 3
        Const GENERIC_READ As UInteger = &HFFFFFFFFUI
        Const GENERIC_WRITE As UInteger = &H40000000
        Const IOCTL_STORAGE_EJECT_MEDIA As UInteger = &H2D4808
        <DllImport("kernel32")>
        Private Shared Function CloseHandle(ByVal handle As IntPtr) As Integer
    
        End Function
        <DllImport("kernel32")>
        Private Shared Function DeviceIoControl(ByVal deviceHandle As IntPtr, ByVal ioControlCode As UInteger, 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 Integer
    
        End Function
        <DllImport("kernel32")>
        Private Shared Function CreateFile(ByVal filename As String, ByVal desiredAccess As UInteger, ByVal shareMode As UInteger, ByVal securityAttributes As IntPtr, ByVal creationDisposition As Integer, ByVal flagsAndAttributes As Integer, ByVal templateFile As IntPtr) As IntPtr
    
        End Function
    
        Private Shared Sub EjectDrive(ByVal driveLetter As Char)
            Dim path As String = "\\.\" & driveLetter & ":"
            Dim handle As IntPtr = CreateFile(path, GENERIC_READ Or GENERIC_WRITE, 0, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero)
    
            If CLng(handle) = -1 Then
                MessageBox.Show("Unable to open drive " & driveLetter)
                Return
            End If
    
            Dim dummy As Integer = 0
            DeviceIoControl(handle, IOCTL_STORAGE_EJECT_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, dummy, IntPtr.Zero)
            CloseHandle(handle)
            MessageBox.Show("OK to remove drive.")
        End Sub
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            EjectDrive("F")
        End Sub
    End Class

    Best Regards,

    Alex


    MSDN Community Support Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Monday, February 11, 2019 5:39 AM
    Moderator
  • Thanks Alex for that, will try it & advise
    Monday, February 11, 2019 5:42 AM
  • Tried it on both a usb & portable drive, error msg is "unable to open drive"
    Monday, February 11, 2019 5:55 AM
  • I am not interested in CD or DVD

    I use the word eject because that is what windows shows when you right click on the drive.

    So you are referring to the Safe Removal process I described. No physical
    "ejection" occurs when "Eject" is selected. Windows attempts to flush all
    outstanding requests (if any) for the drive, and remove it from the devices
    associated with this computer. If all goes well, it should then display a
    message that it is safe to remove (unplug) the drive.

    Often the drive is never cleared for removal. Many factors can cause this.
    including open programs having handles to files on the drive, etc.

    So when you say "no eject" what *exactly* were you expecting to see? Is it
    the message that it is safe to remove the drive? Or is it the disappearance 
    of the drive letter that you are looking for, and it never happens?

    Also, when you say you have tried many code examples without success you should
    post at least some of them or links to the code examples. There is no point in 
    anyone spending their time posting code that you have already tried.

    It may also help if you provide specifics about your environment. Which version 
    of Visual Studio or VB are you using? What version of Windows? etc.

    - Wayne

    Monday, February 11, 2019 6:09 AM
  • When I open explorer I expect no sight of the drive I want ejected, ie it was there, run code, it is not there.

    I am using visual studio 2010

    The portable drive was just inserted before running the code, so no files would be being accessed or open

    Monday, February 11, 2019 6:13 AM
  • for wayne. code item 1

    https://social.msdn.microsoft.com/Forums/vstudio/en-US/b604b554-e9bf-4fb7-84c4-1078287fb4cd/how-to-unplug-safely-eject-and-force-to-eject-the-usb-storage-device-by-vbnet?forum=vbgeneral

    Private Declare Function CreateFile Lib "kernel32" Alias "CreateFileA" (ByVal lpFileName As String, ByVal dwDesiredAccess As Integer, ByVal dwShareMode As Integer, ByVal lpSecurityAttributes As IntPtr, ByVal dwCreationDisposition As Integer, ByVal dwFlagsAndAttributes As Integer, ByVal hTemplateFile As IntPtr) As IntPtr
        Private Declare Function DeviceIoControl Lib "kernel32" (ByVal hDevice As IntPtr, ByVal dwIoControlCode As Integer, ByVal lpInBuffer As IntPtr, ByVal nInBufferSize As Integer, ByVal lpOutBuffer As IntPtr, ByVal nOutBufferSize As Integer, ByRef lpBytesReturned As Integer, ByVal lpOverlapped As IntPtr) As Integer
        Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As IntPtr) As Integer
        Private Const INVALID_HANDLE_VALUE As Integer = -1
        Private Const OPEN_EXISTING As Integer = 3
        Private Const GENERIC_READ As Integer = &H80000000
        Private Const GENERIC_WRITE As Integer = &H40000000
        Private Const IOCTL_STORAGE_EJECT_MEDIA As Integer = 2967560

        Private Sub EjectCDRom(ByVal driveletter As String)
            Dim path As String = "\\.\" + driveletter
            If Not path.EndsWith(":") Then path = path + ":"
            Dim hDrive As IntPtr = CreateFile(path, GENERIC_READ Or GENERIC_WRITE, 0, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero)
            If CInt(hDrive) = INVALID_HANDLE_VALUE Then Throw New IO.IOException("Could not open drive " + driveletter)
            Dim dummy As Integer
            DeviceIoControl(hDrive, IOCTL_STORAGE_EJECT_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, dummy, IntPtr.Zero)
            CloseHandle(hDrive)
        End Sub

        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            EjectCDRom("f")
        End Sub

        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

        End Sub

    Monday, February 11, 2019 6:14 AM
  • for wayne code item 2

    https://social.msdn.microsoft.com/Forums/vstudio/en-US/b604b554-e9bf-4fb7-84c4-1078287fb4cd/how-to-unplug-safely-eject-and-force-to-eject-the-usb-storage-device-by-vbnet?forum=vbgeneral

    Private Declare Function CreateFile Lib "kernel32" Alias "CreateFileA" (ByVal lpFileName As String, ByVal dwDesiredAccess As Integer, ByVal dwShareMode As Integer, ByVal lpSecurityAttributes As IntPtr, ByVal dwCreationDisposition As Integer, ByVal dwFlagsAndAttributes As Integer, ByVal hTemplateFile As IntPtr) As IntPtr
        Private Declare Function DeviceIoControl Lib "kernel32" (ByVal hDevice As IntPtr, ByVal dwIoControlCode As Integer, ByVal lpInBuffer As IntPtr, ByVal nInBufferSize As Integer, ByVal lpOutBuffer As IntPtr, ByVal nOutBufferSize As Integer, ByRef lpBytesReturned As Integer, ByVal lpOverlapped As IntPtr) As Integer
        Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As IntPtr) As Integer
        Private Const INVALID_HANDLE_VALUE As Integer = -1
        Private Const OPEN_EXISTING As Integer = 3
        Private Const GENERIC_READ As Integer = &H80000000
        Private Const GENERIC_WRITE As Integer = &H40000000
        Private Const IOCTL_STORAGE_EJECT_MEDIA As Integer = 2967560

        Private Sub EjectCDRom(ByVal driveletter As String)
            Dim path As String = "\\.\" + driveletter
            If Not path.EndsWith(":") Then path = path + ":"
            Dim hDrive As IntPtr = CreateFile(path, GENERIC_READ Or GENERIC_WRITE, 0, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero)
            If CInt(hDrive) = INVALID_HANDLE_VALUE Then Throw New IO.IOException("Could not open drive " + driveletter)
            Dim dummy As Integer
            DeviceIoControl(hDrive, IOCTL_STORAGE_EJECT_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, dummy, IntPtr.Zero)
            CloseHandle(hDrive)
        End Sub

        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            EjectCDRom("f")
        End Sub

        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

        End Sub

    Monday, February 11, 2019 6:15 AM
  • Thanks for replies so far, unfortunately I have to leave, back in 22 hours
    Monday, February 11, 2019 6:30 AM
  •  The below code works for me.  You can try it in a new Form project with 1 ComboBox and 1 Button added to the Form.  When you run it,  it will list just the removable drives in the combobox.  You can select the drive from the combobox that you want to safely eject,  and click Button1 to eject it.  It then refreshes the removable drives listed in the combobox.  The drive is also removed from the explorer window too.

     I did not add any code to detect when a drive is inserted though,  because your question was just about safely ejecting the drives.  So basically the SafelyEjectDrive sub and win32 api functions in the example below is the main focus of what you need.  The rest is just for making the example easy to test in a new project.

    Imports System.Runtime.InteropServices
    
    Public Class Form1
        Private Const GENERIC_READ As UInteger = &H80000000UI
        Private Const GENERIC_WRITE As UInteger = &H40000000
        Private Const FILE_SHARE_READ As UInteger = &H1
        Private Const FILE_SHARE_WRITE As UInteger = &H2
        Private Const OPEN_EXISTING As Integer = &H3
        Private Const IOCTL_STORAGE_EJECT_MEDIA As UInteger = &H2D4808
    
        <DllImport("kernel32")>
        Private Shared Function DeviceIoControl(ByVal deviceHandle As IntPtr, ByVal ioControlCode As UInteger, 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 Integer
        End Function
    
        <DllImport("kernel32.dll", EntryPoint:="CreateFileW")>
        Public Shared Function CreateFileW(<MarshalAs(UnmanagedType.LPWStr)> ByVal lpFileName As String, ByVal dwDesiredAccess As UInteger, ByVal dwShareMode As UInteger, ByVal lpSecuritys As IntPtr, ByVal dwCreationDisposition As UInteger, ByVal dwFlagsAnds As UInteger, ByVal hTemplateFile As IntPtr) As IntPtr
        End Function
    
        <DllImport("kernel32")> Private Shared Function CloseHandle(ByVal handle As IntPtr) As Integer
        End Function
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            ComboBox1.DropDownStyle = ComboBoxStyle.DropDownList
            RefreshRemovableDriveList()
        End Sub
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            SafelyEjectDrive(CType(ComboBox1.SelectedItem, IO.DriveInfo).Name)
        End Sub
    
        Private Sub RefreshRemovableDriveList()
            With ComboBox1
                .DataSource = Nothing
                .DataSource = IO.DriveInfo.GetDrives.Where(Function(x) x.DriveType = IO.DriveType.Removable AndAlso x.IsReady).ToArray
                .DisplayMember = "Name"
            End With
        End Sub
    
        'driveletter can be passed to this sub with or without the ending backslash, like C: or C:\
        Private Sub SafelyEjectDrive(driveLetter As String)
            Dim DriveDevicePath As String = "\\.\" & If(driveLetter.EndsWith("\"), driveLetter.Remove(driveLetter.IndexOf("\")), driveLetter)
            Dim handle As IntPtr = CreateFileW(DriveDevicePath, GENERIC_READ Or GENERIC_WRITE, FILE_SHARE_READ Or FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero)
            If handle.ToInt32 = -1 Then Throw New ComponentModel.Win32Exception(Marshal.GetLastWin32Error)
            Dim ByteCount As Integer = 0
            DeviceIoControl(handle, IOCTL_STORAGE_EJECT_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, ByteCount, IntPtr.Zero)
            CloseHandle(handle)
            RefreshRemovableDriveList()
        End Sub
    End Class
    
     

     You can see here that it removes the drive from the explorer window when I safely eject it with my example app,  code above.

     

     If it does not work as is on your end,  then try running it as an administrator to see if it helps.  I am logged in as an admin and did not feel like switching users to test it in a standard user account.  8)


    If you say it can`t be done then i`ll try it

    Monday, February 11, 2019 6:15 PM
  • Thanks IronRazerz for that, tried it & seems there is a difference between characteristics of drives.

    I can use the code to remove usb flash drives or sticks or whatever you call them,but not portable drives. I am working in debug mode as Administrator.

    Essentially I want to replicate the "safely remove hardware & eject media" icon that is in the bottom RHS of task bar, which works for any user.

    If you look at explorer and right click on USB drives you will get "eject" but not on portable drives (mine anyway)

    this code will do the same as yours:

    CreateObject("shell.application").namespace("M:\").self.invokeverb("Eject")

    So the question remains, how to eject both USB & portable drives

    Tuesday, February 12, 2019 4:21 AM
  • I can only spend 2 hours on web, so,back in 22 hours
    Tuesday, February 12, 2019 5:59 AM
  •  seems there is a difference between characteristics of drives.

    I can use the code to remove usb flash drives or sticks or whatever you call them,but not portable drives. I am working in debug mode as Administrator.

    If you look at explorer and right click on USB drives you will get "eject" but not on portable drives (mine anyway)


    FYI -

    Cannot eject safely an external USB drive 
    https://www.tenforums.com/drivers-hardware/78381-cannot-eject-safely-external-usb-drive.html

    Excerpt:

    >"I wanted to eject it, but right-click in the file explorer was not the 
    >option 'eject'...."

    "No, you can't eject hard drives from File Explorer. This is normal behaviour 
    because an external hard drive is not classed as 'removable media'. A USB 
    memory stick is, but a USB HDD isn't.

    The error message that you saw is caused by anything that may have the hard 
    drive open - even a command prompt is sufficient. Task Manager can also 
    prevent ejecting hard drives. It shows statistics for them on its 
    'Performance' tab same as it does for all other hard drives. Using Process 
    Monitor may well have the same effect."

    - Wayne

    Tuesday, February 12, 2019 5:45 PM
  • Thanks IronRazerz for that, tried it & seems there is a difference between characteristics of drives.

    I can use the code to remove usb flash drives or sticks or whatever you call them,but not portable drives. I am working in debug mode as Administrator.

    Essentially I want to replicate the "safely remove hardware & eject media" icon that is in the bottom RHS of task bar, which works for any user.

    If you look at explorer and right click on USB drives you will get "eject" but not on portable drives (mine anyway)

    this code will do the same as yours:

    CreateObject("shell.application").namespace("M:\").self.invokeverb("Eject")

    So the question remains, how to eject both USB & portable drives

     Ok,  I see.  Just to be clear,  are you saying that you tried passing the drive letter of the usb external hard drive to the SafelyEjectDrive sub in my example,  or are you saying it just did not list the usb external hard drive?

     Also,  does right clicking the drive and selecting 'eject' cause any errors to be presented to you?

     


    If you say it can`t be done then i`ll try it

    Tuesday, February 12, 2019 10:57 PM
  • Thanks IronRazerz and Wayne for info.

    IronRazerz, using your code or mine does not eject an external hard drive, but does usb

    Right clicking the drive in explorer does not show an eject option, but USB do. 

    Lets put it back to a simple excercise, regardless of drive type, regardless of user privelidges, I can eject a drive using the windows icon on bottom rhs of task bar.

    Essentially I want to replicate by code, the "safely remove hardware & eject media" icon that is in the bottom RHS of task bar, which works for any user.

    Wednesday, February 13, 2019 4:32 AM
  • Maybe you can try with CM_Request_Device_Eject_Ex that Explorer seems to use

    I have no Portable drive to test and could only test on an external HD

    But it only worked on the parent Device Instance handle; I used CM_Get_Parent to get it (it removed the drive and the icon in the tray)

      <DllImport("Cfgmgr32.dll", CharSet:=CharSet.Unicode, SetLastError:=True)>
        Public Shared Function CM_Locate_DevNode(ByRef pdnDevInst As Integer, pDeviceID As String, ulFlags As Integer) As Integer
        End Function
    
        <DllImport("Cfgmgr32.dll", CharSet:=CharSet.Unicode, SetLastError:=True)>
        Public Shared Function CM_Get_Parent(ByRef pdnDevInst As Integer, dnDevInst As Integer, ulFlags As Integer) As Integer
        End Function
    
        <DllImport("Cfgmgr32.dll", CharSet:=CharSet.Unicode, SetLastError:=True)>
        Public Shared Function CM_Get_DevNode_Registry_Property_Ex(dnDevInst As Integer, ulProperty As Integer, ByRef pulRegDataType As Integer, ByRef Buffer As Integer,
             ByRef pulLength As Integer, ulFlags As Integer, hMachine As IntPtr) As Integer
        End Function
    
        Public Const CM_DRP_CAPABILITIES As Integer = &H10    'Capabilities REG_DWORD Property (R)
    
        Public Const CM_DEVCAP_LOCKSUPPORTED = &H1
        Public Const CM_DEVCAP_EJECTSUPPORTED = &H2
        Public Const CM_DEVCAP_REMOVABLE = &H4
        Public Const CM_DEVCAP_DOCKDEVICE = &H8
        Public Const CM_DEVCAP_UNIQUEID = &H10
        Public Const CM_DEVCAP_SILENTINSTALL = &H20
        Public Const CM_DEVCAP_RAWDEVICEOK = &H40
        Public Const CM_DEVCAP_SURPRISEREMOVALOK = &H80
        Public Const CM_DEVCAP_HARDWAREDISABLED = &H100
        Public Const CM_DEVCAP_NONDYNAMIC = &H200
    
        Public Const CM_LOCATE_DEVNODE_PHANTOM As Integer = &H1

        <DllImport("Cfgmgr32.dll", CharSet:=CharSet.Unicode, SetLastError:=True)>
        Public Shared Function CM_Request_Device_Eject_Ex(dnDevInst As Integer, ByRef pVetoType As PNP_VETO_TYPE, pszVetoName As System.Text.StringBuilder, ulNameLength As Integer, ulFlags As Integer, hMachine As IntPtr) As Integer
        End Function
    
        Public Enum PNP_VETO_TYPE
            PNP_VetoTypeUnknown            ' Name Is unspecified
            PNP_VetoLegacyDevice            ' Name Is an Instance Path
            PNP_VetoPendingClose           ' Name Is an Instance Path
            PNP_VetoWindowsApp            ' Name Is a Module
            PNP_VetoWindowsService          'Name Is a Service
            PNP_VetoOutstandingOpen        ' Name Is an Instance Path
            PNP_VetoDevice                  ' Name Is an Instance Path
            PNP_VetoDriver                  ' Name Is a Driver Service Name
            PNP_VetoIllegalDeviceRequest    ' Name Is an Instance Path
            PNP_VetoInsufficientPower     ' Name Is unspecified
            PNP_VetoNonDisableable        ' Name Is an Instance Path
            PNP_VetoLegacyDriver           ' Name Is a Service
            PNP_VetoInsufficientRights      'Name Is unspecified
        End Enum

    Thursday, February 14, 2019 12:18 PM
  • Thanks Castorix31, having looked at the code you supplied it is not something I am familiar with, so I have no idea how the requested drive letter is passed to the function or how the code is used, can you please provide a sample of how the code is applied in my case.

    Thanks

    Friday, February 15, 2019 4:29 AM
  • This is the test I did on my disks, but not sure it will work for you or maybe some changes will be needed... =>

    Option Strict On
    Imports System.Runtime.InteropServices
    Imports System.Text
    Public Class Form1
    
        <DllImport("Setupapi.dll", CharSet:=CharSet.Unicode, SetLastError:=True)>
        Public Shared Function SetupDiGetClassDevs(ByRef ClassGuid As Guid, ByVal Enumerator As IntPtr, ByVal hWndParent As IntPtr, ByVal Flags As Integer) As IntPtr
        End Function
    
        <DllImport("Setupapi.dll", CharSet:=CharSet.Unicode, SetLastError:=True)>
        Public Shared Function SetupDiEnumDeviceInterfaces(ByVal DeviceInfoSet As IntPtr, ByVal DeviceInfoData As IntPtr, ByRef InterfaceClassGuid As Guid, ByVal MemberIndex As Integer, ByRef DeviceInterfaceData As SP_DEVICE_INTERFACE_DATA) As Boolean
        End Function
    
        <DllImport("Setupapi.dll", CharSet:=CharSet.Unicode, SetLastError:=True)>
        Public Shared Function SetupDiGetDeviceInterfaceDetail(ByVal hDevInfo As IntPtr, ByRef deviceInterfaceData As SP_DEVICE_INTERFACE_DATA, ByVal deviceInterfaceDetailData As IntPtr, ByVal deviceInterfaceDetailDataSize As UInt32, <System.Runtime.InteropServices.Out()> ByRef requiredSize As UInt32, ByVal deviceInfoData As IntPtr) As Boolean
        End Function
    
        <DllImport("Setupapi.dll", CharSet:=CharSet.Unicode, SetLastError:=True)>
        Public Shared Function SetupDiGetDeviceInterfaceDetail(ByVal hDevInfo As IntPtr, ByRef deviceInterfaceData As SP_DEVICE_INTERFACE_DATA, ByRef deviceInterfaceDetailData As SP_DEVICE_INTERFACE_DETAIL_DATA, ByVal deviceInterfaceDetailDataSize As UInt32, <System.Runtime.InteropServices.Out()> ByRef requiredSize As UInt32, ByRef deviceInfoData As SP_DEVINFO_DATA) As Boolean
        End Function
    
        <DllImport("Setupapi.dll", CharSet:=CharSet.Unicode, SetLastError:=True)>
        Public Shared Function SetupDiDestroyDeviceInfoList(ByVal DeviceInfoSet As IntPtr) As Boolean
        End Function
    
        Public Const DIGCF_PRESENT As Integer = &H2
        Public Const DIGCF_DEVICEINTERFACE As Integer = &H10
        Public Const ERROR_INSUFFICIENT_BUFFER As Integer = 122
    
        <StructLayout(LayoutKind.Sequential)>
        Public Structure SP_DEVICE_INTERFACE_DATA
            Public cbSize As Integer
            Public InterfaceClassGuid As Guid
            Public Flags As Integer
            Public Reserved As IntPtr
        End Structure
    
        <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)>
        Public Structure SP_DEVINFO_DATA
            Public cbSize As Integer
            Public ClassGuid As Guid
            Public DevInst As Integer
            Public Reserved As IntPtr
        End Structure
    
        <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode, Pack:=1)>
        Public Structure SP_DEVICE_INTERFACE_DETAIL_DATA
            Public cbSize As Integer
            <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=260)>
            Public DevicePath As String
        End Structure
    
        <DllImport("Cfgmgr32.dll", CharSet:=CharSet.Unicode, SetLastError:=True)>
        Public Shared Function CM_Get_Parent(ByRef pdnDevInst As Integer, dnDevInst As Integer, ulFlags As Integer) As Integer
        End Function
    
        <DllImport("Cfgmgr32.dll", CharSet:=CharSet.Unicode, SetLastError:=True)>
        Public Shared Function CM_Get_DevNode_Registry_Property_Ex(dnDevInst As Integer, ulProperty As Integer, ByRef pulRegDataType As Integer, Buffer As IntPtr,
             ByRef pulLength As Integer, ulFlags As Integer, hMachine As IntPtr) As Integer
        End Function
    
        <DllImport("Cfgmgr32.dll", CharSet:=CharSet.Unicode, SetLastError:=True)>
        Public Shared Function CM_Get_Device_ID(dnDevInst As Integer, Buffer As StringBuilder, BufferLen As Integer, ulFlags As Integer) As Integer
        End Function
    
        Public Const CM_DRP_CAPABILITIES As Integer = &H10    'Capabilities REG_DWORD Property (R)
        Public Const CM_DRP_FRIENDLYNAME As Integer = &HD    'FriendlyName REG_SZ Property (RW)
    
        Public Const CM_DEVCAP_LOCKSUPPORTED = &H1
        Public Const CM_DEVCAP_EJECTSUPPORTED = &H2
        Public Const CM_DEVCAP_REMOVABLE = &H4
        Public Const CM_DEVCAP_DOCKDEVICE = &H8
        Public Const CM_DEVCAP_UNIQUEID = &H10
        Public Const CM_DEVCAP_SILENTINSTALL = &H20
        Public Const CM_DEVCAP_RAWDEVICEOK = &H40
        Public Const CM_DEVCAP_SURPRISEREMOVALOK = &H80
        Public Const CM_DEVCAP_HARDWAREDISABLED = &H100
        Public Const CM_DEVCAP_NONDYNAMIC = &H200
    
        <DllImport("Cfgmgr32.dll", CharSet:=CharSet.Unicode, SetLastError:=True)>
        Public Shared Function CM_Request_Device_Eject_Ex(dnDevInst As Integer, ByRef pVetoType As PNP_VETO_TYPE, pszVetoName As System.Text.StringBuilder, ulNameLength As Integer, ulFlags As Integer, hMachine As IntPtr) As Integer
        End Function
    
        Public Enum PNP_VETO_TYPE
            PNP_VetoTypeUnknown            ' Name Is unspecified
            PNP_VetoLegacyDevice            ' Name Is an Instance Path
            PNP_VetoPendingClose           ' Name Is an Instance Path
            PNP_VetoWindowsApp            ' Name Is a Module
            PNP_VetoWindowsService          'Name Is a Service
            PNP_VetoOutstandingOpen        ' Name Is an Instance Path
            PNP_VetoDevice                  ' Name Is an Instance Path
            PNP_VetoDriver                  ' Name Is a Driver Service Name
            PNP_VetoIllegalDeviceRequest    ' Name Is an Instance Path
            PNP_VetoInsufficientPower     ' Name Is unspecified
            PNP_VetoNonDisableable        ' Name Is an Instance Path
            PNP_VetoLegacyDriver           ' Name Is a Service
            PNP_VetoInsufficientRights      'Name Is unspecified
        End Enum
    
        Friend WithEvents ListView1 As ListView
        Friend WithEvents Button1 As Button
    
        Dim nDevInstRemovable As Integer = 0
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            Me.ClientSize = New System.Drawing.Size(800, 210)
            Me.CenterToScreen()
    
            ListView1 = New ListView()
            ListView1.Location = New System.Drawing.Point(10, 10)
            ListView1.Size = New System.Drawing.Size(780, 150)
            ListView1.Columns.Add("Friendly Name", 200, HorizontalAlignment.Left)
            ListView1.Columns.Add("Device ID", 560, HorizontalAlignment.Left)
            ListView1.Columns.Add("Device Inst", 0, HorizontalAlignment.Left)
            ListView1.View = View.Details
            ListView1.FullRowSelect = True
            Me.Controls.Add(ListView1)
    
            Button1 = New Button()
            Button1.Location = New System.Drawing.Point(10, 170)
            Button1.Name = "Button1"
            Button1.Size = New System.Drawing.Size(75, 23)
            Button1.TabIndex = 0
            Button1.Text = "Try to Eject"
            Button1.UseVisualStyleBackColor = True
            Controls.Add(Button1)
            Button1.Enabled = False
    
            PopulateListView()
        End Sub
    
        Private Sub PopulateListView()
            Dim nStatus As Integer = 0
            Dim b As Boolean = False
            Dim i As Integer = 0
            Dim GUID_DEVINTERFACE_DISK As New Guid("53f56307-b6bf-11d0-94f2-00a0c91efb8b")
            Dim hdevDisplayInfoSet As IntPtr = SetupDiGetClassDevs(GUID_DEVINTERFACE_DISK, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT Or DIGCF_DEVICEINTERFACE)
            If (hdevDisplayInfoSet <> IntPtr.Zero) Then
                Dim data As SP_DEVICE_INTERFACE_DATA = New SP_DEVICE_INTERFACE_DATA()
                While (nStatus = 0)
                    data.cbSize = Marshal.SizeOf(GetType(SP_DEVICE_INTERFACE_DATA))
                    b = SetupDiEnumDeviceInterfaces(hdevDisplayInfoSet, IntPtr.Zero, GUID_DEVINTERFACE_DISK, i, data)
                    If (Not b) Then
                        nStatus = Marshal.GetLastWin32Error()
                        Exit While
                    End If
    
                    Dim dwBytes As UInteger = 0
                    Dim bRet As Boolean = SetupDiGetDeviceInterfaceDetail(hdevDisplayInfoSet, data, IntPtr.Zero, 0, dwBytes, IntPtr.Zero)
                    nStatus = Marshal.GetLastWin32Error()
                    If (Not bRet And Marshal.GetLastWin32Error() = ERROR_INSUFFICIENT_BUFFER) Then
                        Dim didd As SP_DEVICE_INTERFACE_DETAIL_DATA = New SP_DEVICE_INTERFACE_DETAIL_DATA()
                        Dim nSize = 4 + If(IntPtr.Size = 4, 2, 4)
                        didd.cbSize = nSize
                        Dim da As SP_DEVINFO_DATA = New SP_DEVINFO_DATA()
                        da.cbSize = Marshal.SizeOf(da)
                        bRet = SetupDiGetDeviceInterfaceDetail(hdevDisplayInfoSet, data, didd, dwBytes, dwBytes, da)
                        If (bRet) Then
    
                            Dim sFriendlyName As String = Nothing
                            Dim pBuffer As IntPtr = IntPtr.Zero
                            Dim pulRegDataType As Integer = 0
                            Dim nBufferSize As Integer = (260 + 1) * Marshal.SystemDefaultCharSize
                            pBuffer = Marshal.AllocHGlobal(nBufferSize)
                            nStatus = CM_Get_DevNode_Registry_Property_Ex(da.DevInst, CM_DRP_FRIENDLYNAME, pulRegDataType, pBuffer, nBufferSize, 0, IntPtr.Zero)
                            If (nStatus = 0) Then
                                sFriendlyName = Marshal.PtrToStringAuto(pBuffer)
                            End If
                            Marshal.FreeHGlobal(pBuffer)
    
                            Dim sbDeviceID As StringBuilder = New StringBuilder(260)
                            nStatus = CM_Get_Device_ID(da.DevInst, sbDeviceID, sbDeviceID.Capacity, 0)
                            If (nStatus = 0) Then
                                Dim str(2) As String
                                Dim lvi As ListViewItem
                                str(0) = sFriendlyName
                                str(1) = sbDeviceID.ToString()
                                str(2) = CType(da.DevInst, String)
                                lvi = New ListViewItem(str)
                                ListView1.Items.Add(lvi)
                            End If
                            nStatus = 0
                        End If
                    End If
                    i += 1
                End While
                SetupDiDestroyDeviceInfoList(hdevDisplayInfoSet)
            End If
        End Sub
    
        Private Sub ListView1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ListView1.SelectedIndexChanged
            Dim i As Integer = 1
            Dim selitems As ListView.SelectedListViewItemCollection = ListView1.SelectedItems
            Dim item As ListViewItem
            Dim nDevInst As Integer = 0
            Dim bRemovable As Boolean = False
            nDevInstRemovable = 0
            For Each item In selitems
                nDevInst = Integer.Parse(item.SubItems(2).Text)
                Dim pBuffer As IntPtr = IntPtr.Zero
                Dim nBufferSize As Integer = Marshal.SizeOf(GetType(Integer))
                Dim pulRegDataType As Integer = 0
                pBuffer = Marshal.AllocHGlobal(nBufferSize)
                Dim nReturn As Integer = CM_Get_DevNode_Registry_Property_Ex(nDevInst, CM_DRP_CAPABILITIES, pulRegDataType, pBuffer, nBufferSize, 0, IntPtr.Zero)
                If (nReturn = 0) Then
                    Dim nCap As Integer = Marshal.ReadInt32(pBuffer, 0)
                    If CBool((CType(nCap, Integer) And CM_DEVCAP_REMOVABLE)) Then
                        bRemovable = True
                        nDevInstRemovable = nDevInst
                        Exit For
                    Else
                        Dim nDevInstParent As Integer = 0
                        nReturn = CM_Get_Parent(nDevInstParent, nDevInst, 0)
                        If (nReturn = 0) Then
                            Dim pBuffer2 As IntPtr = IntPtr.Zero
                            pBuffer2 = Marshal.AllocHGlobal(nBufferSize)
                            nReturn = CM_Get_DevNode_Registry_Property_Ex(nDevInstParent, CM_DRP_CAPABILITIES, pulRegDataType, pBuffer2, nBufferSize, 0, IntPtr.Zero)
                            If (nReturn = 0) Then
                                nCap = Marshal.ReadInt32(pBuffer2, 0)
                                If CBool((CType(nCap, Integer) And CM_DEVCAP_REMOVABLE)) Then
                                    bRemovable = True
                                    nDevInstRemovable = nDevInstParent
                                    Exit For
                                End If
                            End If
                            Marshal.FreeHGlobal(pBuffer2)
                        End If
                    End If
                End If
                Marshal.FreeHGlobal(pBuffer)
            Next
            If (bRemovable) Then
                Button1.Enabled = True
            Else
                Button1.Enabled = False
            End If
        End Sub
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            Dim sbOut As StringBuilder = New StringBuilder(260)
            Dim pVetoType As PNP_VETO_TYPE
            Dim nRet As Integer = CM_Request_Device_Eject_Ex(nDevInstRemovable, pVetoType, sbOut, 260, 0, IntPtr.Zero)
            If (nRet = 0) Then
                Button1.Enabled = False
                ListView1.Items.Clear()
                PopulateListView()
            End If
        End Sub
    End Class


    • Marked as answer by x38class 13 hours 30 minutes ago
    Friday, February 15, 2019 2:16 PM
  • Thanks Castorix31

    Excellent, what more can I say!, works fine, much appreciated.

    Just one minor point, can you advise where I can add the drive letter, thanks

    One other issue that is not your concern, I have a drive not being used but Microsoft says it has open files, I don't have any open files (so your code will not work on this drive) I now have to work on how to get those files? closed whatever they are so your program will work on that drive.

    Again, thanks 

    13 hours 30 minutes ago

  •  I have a drive not being used but Microsoft says it has open files, I don't have any open files (so your code will not work on this drive) I now have to work on how to get those files? closed whatever they are 


    Have you tried any of the Sysinternals utilities? Examples:

    Handle v4.21
    https://docs.microsoft.com/en-us/sysinternals/downloads/handle

    "Handle is a utility that displays information about open handles for any 
    process in the system. You can use it to see the programs that have a file
    open, or to see the object types and names of all the handles of a program."

    "You can also get a GUI-based version of this program, Process Explorer, 
    here at Sysinternals."

    Process Explorer v16.22
    https://docs.microsoft.com/en-us/sysinternals/downloads/process-explorer

    "Ever wondered which program has a particular file or directory open? 
    Now you can find out. Process Explorer shows you information about which
    handles and DLLs processes have opened or loaded."

    Process Monitor v3.50
    https://docs.microsoft.com/en-us/sysinternals/downloads/procmon

    "Process Monitor is an advanced monitoring tool for Windows that shows 
    real-time file system, Registry and process/thread activity. It combines
    the features of two legacy Sysinternals utilities, Filemon and Regmon ..."

    - Wayne

    12 hours 53 minutes ago
  • Just one minor point, can you advise where I can add the drive letter, thanks

    You can get the drive letters with WMI.

    For example, from the Device Instance ID  =>

    Add at beginning :

    ' Add reference to System.Management.dll
    Imports System.Management

    Function :

    Private Function GetDriveLettersFromDevInstID(sDeviceInstanceID As String) As String
        Dim sDriveLetters As String = Nothing
        Dim mocDiskDrives As ManagementObjectCollection
        Dim mocLogicalDisks As ManagementObjectCollection
        Dim mocDiskPartitions As ManagementObjectCollection
        Dim sDeviceID As String
        Dim sPartitionDeviceID As String
        Dim sLogicalDeviceID As String
        Dim sQuery As String = "SELECT * FROM Win32_DiskDrive where PNPDeviceID = """ + sDeviceInstanceID + """"
        sQuery = sQuery.Replace("\", "\\")
        Using mos As ManagementObjectSearcher = New ManagementObjectSearcher("root\CIMV2", sQuery)
            mocDiskDrives = mos.Get()
            For Each mojDrive As ManagementObject In mocDiskDrives
                sDeviceID = mojDrive.GetPropertyValue("DeviceID").ToString
                sDeviceID = sDeviceID.Replace("\", "\\")
                mos.Query.QueryString = "ASSOCIATORS OF {Win32_DiskDrive.DeviceID=""" + sDeviceID + """} WHERE AssocClass = " + "Win32_DiskDriveToDiskPartition"
                mocDiskPartitions = mos.Get()
                For Each mojPartition As ManagementObject In mocDiskPartitions
                    sPartitionDeviceID = mojPartition.GetPropertyValue("DeviceID").ToString
                    mos.Query.QueryString = "ASSOCIATORS Of {Win32_DiskPartition.DeviceID=""" + sPartitionDeviceID + """} WHERE AssocClass = " + "Win32_LogicalDiskToPartition"
                    mocLogicalDisks = mos.Get
                    For Each mojLogicakDisk As ManagementObject In mocLogicalDisks
                        sLogicalDeviceID = mojLogicakDisk.GetPropertyValue("DeviceID").ToString
                        If sDriveLetters Is Nothing Then
                            sDriveLetters += sLogicalDeviceID
                        Else
                            sDriveLetters += ", "
                            sDriveLetters += sLogicalDeviceID
                        End If
                    Next
                Next
            Next
        End Using
        Return sDriveLetters
    End Function


    Test  :
    Dim sDriveLetters = GetDriveLettersFromDevInstID(sbDeviceID.ToString())


    2 hours 25 minutes ago