none
VB code to eject Portable drive RRS feed

  • 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 Saturday, February 16, 2019 4:54 AM
    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 Saturday, February 16, 2019 4:54 AM
    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 

    Saturday, February 16, 2019 4:53 AM

  •  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

    Saturday, February 16, 2019 5:31 AM
  • 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())


    • Edited by Castorix31 Saturday, February 16, 2019 4:04 PM
    Saturday, February 16, 2019 3:59 PM
  • Thanks, Wayne for info re open files, will take a while for me to get round to checking that out, will advise back here if I get any answers.

    Again, thanks to Castorix31, will add that to my project

    Sunday, February 17, 2019 3:00 AM
  • At the time of my last post, I was in a hurry to complete my web tasks, now I have had time to evaluate your answer for getting drive letters I am having some difficulty in seeing how I would add that to you previous code to show the drive as part of the listview text for each drive. if you can assist I would be most grateful

    Thanks

    Monday, February 18, 2019 4:52 AM
  • If I add    
    Dim sDriveLetters As String = GetDriveLettersFromDevInstID(sbDeviceID.ToString())
    after
    nStatus = CM_Get_Device_ID(da.DevInst, sbDeviceID, sbDeviceID.Capacity, 0)
    If (nStatus = 0) Then
     I get a string = "C:, D:, E:" for my main HD and  "F:" for external HD (the string can be added to another column in the ListView for example)
     I used a string to test, but you can return a List, an array, etc...
    Monday, February 18, 2019 3:30 PM
  • Thanks for that, I do understand your process to get drive letters, however i am trying to see drive letters against your original code:

    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

     slightly modified similar to below adding new array 0 for drive letter, so all is related when shown in the listview, so now we show 4 items

    If (nStatus = 0) Then
                                Dim str(2) As String
                                Dim lvi As ListViewItem

    str(0) = Driveletter
                                str(1) = sFriendlyName
                                str(2) = sbDeviceID.ToString()
                                str(3) = CType(da.DevInst, String)
                                lvi = New ListViewItem(str)
                                ListView1.Items.Add(lvi)
                            End If

    Tuesday, February 19, 2019 2:27 AM
  • Thanks to those who have contributed, so, I gather as there is no reply to my suggestion above that adding the drive letter to the listview is a problem.

    Will wait another day or so until I try to find a solution, as knowing the drive letter is a key element.

    Friday, February 22, 2019 5:37 AM
  • Thanks to those who have contributed, so, I gather as there is no reply to my suggestion above that adding the drive letter to the listview is a problem.

    Will wait another day or so until I try to find a solution, as knowing the drive letter is a key element.

      I did not look at Castorix31's example real good but,  it would seem as though it should be able to be done with a little reworking.  You need to study the code and understand how it all works together first.

      However,  I would create a small class that can hold the info for each drive,  like it's DeviceId,  FriendlyName,  and DriveLetters, as Properties.  Keep those in a List(Of TheSmallClass) and use them to iterate through to get each drive's info as you are adding the ListViewItem's and their SubItems.  It would be better than using the ListViewItem's and SubItems as your data storage.  It would allow for easier expansion on the project latter.

     

     


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

    Friday, February 22, 2019 10:18 PM
  • thanks IronRazerz for your suggestion, I have been writing programs since the mid 1970's, starting in DOS basic, then VB5 & VB6, then even having to convert VB6 to visual studio, quite a task especially print functions. My expertise is in data manipulation & I have written some complex apps over the years.

    However this area is not familiar to me & rarely need system functions for my apps which is why I post some of my issues. Castorix31 has been most helpful and his code is much appreciated, if he is not able to assist any further I will have to try and get a solution in another forum.

    Saturday, February 23, 2019 5:29 AM
  • I don't understand your last problem.

    I just see you added str(3) but without increasing the array size (still str(2))

    If I add a column at end (to avoid shifting indexes), I get the drive letters in the new column :

    • Edited by Castorix31 Saturday, February 23, 2019 9:58 AM
    Saturday, February 23, 2019 9:49 AM
  • Thanks Castorix31 for returning to my query, yes I made a mistake not increasing the array to 3

    of all the code you supplied, the only part I understand is:

    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

    Although you have provided a screen shot which shows what I need & an explanation of what you did, I have no idea how you coded that, your explanation of adding a column and extracting the drive letter is beyond my expertise to alter the code. Evaluating system fundamentals & manipulating code to show details has been a mystery to me, that is why I posted in this forum.

    If you could post your amended code to show how the drive letter is added to the last column I would be most grateful, Again thanks for you help

    Sunday, February 24, 2019 3:15 AM
  • I just added, in bold:

    ListView1.Columns.Add("Device Inst", 0, HorizontalAlignment.Left)
    ListView1.Columns.Add("Drive Letters", 100, HorizontalAlignment.Left)

    and 

    If (nStatus = 0) Then
        Dim sDriveLetters As String = GetDriveLettersFromDevInstID(sbDeviceID.ToString())
        Dim str(3) As String
        Dim lvi As ListViewItem
        str(0) = sFriendlyName
        str(1) = sbDeviceID.ToString()
        str(2) = CType(da.DevInst, String)
        str(3) = sDriveLetters
        lvi = New ListViewItem(str)
        ListView1.Items.Add(lvi)
    End If

    Sunday, February 24, 2019 8:51 AM
  • Thanks, thats a great help, much appreciated

    seems I am missing function code for:

    GetDriveLettersFromDevInstID(sbDeviceID.ToString())

    • Edited by x38class Monday, February 25, 2019 12:25 AM
    Sunday, February 24, 2019 11:44 PM
  • My apologies for adding another request, but it seems I am missing the function code for:

    GetDriveLettersFromDevInstID(sbDeviceID.ToString())

    Thanks for your patience.

    Monday, February 25, 2019 8:14 PM
  • But it is just above...

    Post from "Saturday, February 16, 2019 3:59 PM"

    Monday, February 25, 2019 8:31 PM
  • My sincere apologies, the error I made was not adding your function you created for the drive letters to the original code, I overlooked the fact you had created the new function & i had not realized it needed to be added to the project.

    Now I have corrected that, I am extremely pleased with the result, again, thanks for your patience

    Tuesday, February 26, 2019 6:11 AM