VB 5/6-Tipp 0718: USB Medium abmelden (sicheres Entfernen)
von Frank Schüler
Beschreibung
Dieses Beispiel zeigt wie ein USB-Medium (USB-Sticks, USB-Festplatten und andere USB-Massenspeicher) sicher vom System abgemeldet und ausgeworfen werden kann, wie dies in Windows von der Funktion "Hardware sicher entfernen" bekannt ist.
Schwierigkeitsgrad: | Verwendete API-Aufrufe: CM_Get_DevNode_Status, CM_Get_Parent, CM_Locate_DevNodeA, CM_Request_Device_EjectA, CloseHandle, CreateFileA (CreateFile), DeviceIoControl, GetDriveTypeA (GetDriveType), GetLogicalDrives, IIDFromString, QueryDosDeviceA (QueryDosDevice), SetupDiDestroyDeviceInfoList, SetupDiEnumDeviceInterfaces, SetupDiGetClassDevsA (SetupDiGetClassDevs), SetupDiGetDeviceInterfaceDetailA (SetupDiGetDeviceInterfaceDetail) | Download: |
'Dieser Quellcode stammt von http://www.activevb.de 'und kann frei verwendet werden. Für eventuelle Schäden 'wird nicht gehaftet. 'Um Fehler oder Fragen zu klären, nutzen Sie bitte unser Forum. 'Ansonsten viel Spaß und Erfolg mit diesem Source! '----------- Anfang Projektdatei SaveUsbEject.vbp ----------- '------- Anfang Formular "frmMain" alias frmMain.frm ------- ' Steuerelement: Schaltfläche "cmdEject" ' Steuerelement: Kombinationsliste "cbDrive" Option Explicit ' ----==== KERNEL32 Deklarationen ====---- Private Declare Function GetLogicalDrives Lib "kernel32" () As Long Private Sub cmdEject_Click() Dim strDriveLetter As String ' sind Einträge in der ComboBox vorhanden If cbDrive.ListCount > 0 Then ' Laufwerksbuchstaben auslesen strDriveLetter = Chr$(65 + cbDrive.ItemData(cbDrive.ListIndex)) ' Laufwerk auswerfen If EjectUsbDrive(strDriveLetter, True) Then MsgBox "Laufwerk '" & strDriveLetter & ":\' wurde " & _ "erfolgreich enfernt.", vbOKOnly Or vbInformation ' alle auswerfbare Laufwerke ermitteln Call ListRemovableDrives Else MsgBox "Laufwerk '" & strDriveLetter & ":\' konnte nicht " & _ "enfernt werden.", vbOKOnly Or vbExclamation End If End If End Sub Private Sub ListRemovableDrives() Dim lngDriveNum As Long Dim lngRet As Long Dim strDriveName As String ' Button deaktivieren cmdEject.Enabled = False ' alle Items in der ListBox löschen cbDrive.Clear ' alle verfügbare Laufwerke ermitteln lngRet = GetLogicalDrives ' alle Laufwerksnummern durchlaufen For lngDriveNum = 0 To 25 ' ist das entsprechende Bit in lngRet <> 0 If (lngRet And 2 ^ lngDriveNum) <> 0 Then ' Laufwerksbuchstabe strDriveName = Chr$(65 + lngDriveNum) ' Laufwerkstyp ermitteln (USB-Laufwerke) If Len(GetDevInstStr(strDriveName)) > 0 Then ' Daten in der ComboBox ausgeben und speichern cbDrive.AddItem strDriveName & ":\" cbDrive.ItemData(cbDrive.NewIndex) = lngDriveNum End If End If ' nächste Laufwerksnummer Next lngDriveNum ' sind Einträge in der ComboBox vorhanden If cbDrive.ListCount > 0 Then ' erster Eintrag in der ComboBox cbDrive.ListIndex = 0 ' Button aktivieren cmdEject.Enabled = True End If End Sub Private Sub Form_Load() ' alle auswerfbare Laufwerke ermitteln Call ListRemovableDrives End Sub '-------- Ende Formular "frmMain" alias frmMain.frm -------- '-------- Anfang Modul "modEject" alias modEject.bas -------- Option Explicit ' ----==== Const ====---- Private Const CR_SUCCESS As Long = &H0 Private Const DN_REMOVABLE As Long = &H4000 Private Const DRIVE_CDROM As Long = 5 Private Const DRIVE_FIXED As Long = 3 Private Const DRIVE_REMOVABLE As Long = 2 Private Const DIGCF_DEVICEINTERFACE As Long = &H10 Private Const DIGCF_PRESENT As Long = &H2 Private Const ERROR_INSUFFICIENT_BUFFER As Long = 122 Private Const ERROR_NO_MORE_ITEMS As Long = 259& Private Const FILE_SHARE_READ As Long = &H1 Private Const FILE_SHARE_WRITE As Long = &H2 Private Const INVALID_HANDLE_VALUE As Long = -1 Private Const IOCTL_STORAGE_GET_DEVICE_NUMBER As Long = &H2D1080 Private Const MAX_PATH As Long = 260 Private Const OPEN_EXISTING As Long = 3 Private Const GUID_DEVINTERFACE_DISK As String = _ "{53F56307-B6BF-11D0-94F2-00A0C91EFB8B}" Private Const GUID_DEVINTERFACE_FLOPPY As String = _ "{53F56311-B6BF-11D0-94F2-00A0C91EFB8B}" Private Const GUID_DEVINTERFACE_CDROM As String = _ "{53F56308-B6BF-11D0-94F2-00A0C91EFB8B}" ' ----==== Type ====---- Private Type GUID Data1 As Long Data2 As Integer Data3 As Integer Data4(7) As Byte End Type Private Type SP_DEVICE_INTERFACE_DATA cbSize As Long InterfaceClassGuid As GUID flags As Long Reserved As Long End Type Private Type STORAGE_DEVICE_NUMBER DeviceType As Long DeviceNumber As Long PartitionNumber As Long End Type ' ----==== KERNEL32 Deklarationen ====---- Private Declare Function CloseHandle Lib "kernel32" ( _ ByVal hObject As Long) As Long Private Declare Function CreateFile Lib "kernel32" _ Alias "CreateFileA" ( _ ByVal lpFileName As String, _ ByVal dwDesiredAccess As Long, _ ByVal dwShareMode As Long, _ ByRef lpSecurityAttributes As Any, _ ByVal dwCreationDisposition As Long, _ ByVal dwFlagsAndAttributes As Long, _ ByVal hTemplateFile As Long) As Long Private Declare Function DeviceIoControl Lib "kernel32" ( _ ByVal hDevice As Long, _ ByVal dwIoControlCode As Long, _ ByRef lpInBuffer As Any, _ ByVal nInBufferSize As Long, _ ByRef lpOutBuffer As Any, _ ByVal nOutBufferSize As Long, _ ByRef lpBytesReturned As Long, _ ByRef lpOverlapped As Any) As Long Private Declare Function GetDriveType Lib "kernel32" _ Alias "GetDriveTypeA" ( _ ByVal nDrive As String) As Long Private Declare Function QueryDosDevice Lib "kernel32.dll" _ Alias "QueryDosDeviceA" ( _ ByVal lpDeviceName As String, _ ByVal lpTargetPath As String, _ ByVal ucchMax As Long) As Long ' ----==== OLE32 Deklarationen ====---- Private Declare Function IIDFromString Lib "ole32" ( _ ByVal lpsz As Long, _ ByRef lpiid As GUID) As Long ' ----==== SETUPAPI Deklarationen ====---- Private Declare Function CM_Get_DevNode_Status Lib "setupapi.dll" ( _ ByRef pulStatus As Long, _ ByRef pulProblemNumber As Long, _ ByVal dnDevInst As Long, _ ByVal ulFlags As Long) As Long Private Declare Function CM_Get_Parent Lib "setupapi.dll" ( _ ByRef pdnDevInst As Long, _ ByVal dnDevInst As Long, _ ByVal ulFlags As Long) As Long Private Declare Function CM_Locate_DevNodeA Lib "setupapi.dll" ( _ ByRef pdnDevInst As Long, _ ByVal pDeviceID As Long, _ ByVal ulFlags As Long) As Long Private Declare Function CM_Request_Device_EjectA Lib "setupapi.dll" ( _ ByVal dnDevInst As Long, _ ByRef pVetoType As Long, _ ByVal pszVetoName As Long, _ ByVal ulNameLength As Long, _ ByVal ulFlags As Long) As Long Private Declare Function SetupDiDestroyDeviceInfoList Lib "setupapi.dll" ( _ ByVal DeviceInfoSet As Long) As Long Private Declare Function SetupDiEnumDeviceInterfaces Lib "setupapi.dll" ( _ ByVal DeviceInfoSet As Long, _ ByRef DeviceInfoData As Any, _ ByRef InterfaceClassGuid As GUID, _ ByVal MemberIndex As Long, _ ByRef DeviceInterfaceData As SP_DEVICE_INTERFACE_DATA) As Long Private Declare Function SetupDiGetDeviceInterfaceDetail Lib "setupapi.dll" _ Alias "SetupDiGetDeviceInterfaceDetailA" ( _ ByVal DeviceInfoSet As Long, _ ByRef DeviceInterfaceData As SP_DEVICE_INTERFACE_DATA, _ ByRef DeviceInterfaceDetailData As Any, _ ByVal DeviceInterfaceDetailDataSize As Long, _ ByRef RequiredSize As Long, _ ByRef DeviceInfoData As Any) As Long Private Declare Function SetupDiGetClassDevs Lib "setupapi.dll" _ Alias "SetupDiGetClassDevsA" ( _ ByRef ClassGuid As GUID, _ ByVal Enumerator As String, _ ByVal hwndParent As Long, _ ByVal flags As Long) As Long ' ------------------------------------------------------ ' Funktion : EjectUsbDrive ' Beschreibung : USB-Laufwerk sicher entfernen/auswerfen ' Übergabewert : DriveLetter = Laufwerksbuchstabe ' ShowStdMessage = Standardnachricht anzeigen oder nicht ' Rückgabewert : True/False ' ------------------------------------------------------ ' USB Gerät abmelden. (Sicheres Entfernen) ' Als Hilfestellung diente der Anno 2003 in c't veröffentlichter C++ Code ' DevEject von Matthias Withopf. ' ------------------------------------------------------ Public Function EjectUsbDrive(ByVal DriveLetter As String, Optional _ ShowStdMessage As Boolean = True) As Boolean Dim strDevInst As String Dim hDevInst As Long Dim lngStatus As Long Dim lngProblem As Long Dim lngVetoType As Long Dim strVetoName As String ' Storagename aus der Registry auslesen strDevInst = GetDevInstStr(DriveLetter) ' ist ein Storagename vorhanden If Len(strDevInst) <> 0 Then ' Handle auf den Node holen If CM_Locate_DevNodeA(hDevInst, StrPtr(StrConv(strDevInst, _ vbFromUnicode)), 0&) = CR_SUCCESS Then ' wenn dieser Node nicht auswerfbar ist, dann solange bis zum ' Parentnode raufgehen bis dieser auswerfbar ist If CM_Get_DevNode_Status(lngStatus, lngProblem, hDevInst, 0&) = _ CR_SUCCESS Then ' solange die Schleife durchlaufen bis (lngStatus And ' DN_REMOVABLE) = DN_REMOVABLE Do While Not (lngStatus And DN_REMOVABLE) = DN_REMOVABLE ' wenn kein Parentnode mehr vorhanden, dann Schleife ' verlassen If Not CM_Get_Parent(hDevInst, hDevInst, 0&) = _ CR_SUCCESS Then Exit Do ' wenn CM_Get_DevNode_Status nicht = CR_SUCCESS ist, ' dann Schleife verlassen If Not CM_Get_DevNode_Status(lngStatus, lngProblem, _ hDevInst, 0&) = CR_SUCCESS Then Exit Do Loop ' ist dieser Node auswerbar If (lngStatus And DN_REMOVABLE) = DN_REMOVABLE Then ' wenn nicht die Standardnachricht angezeigt werden soll If Not ShowStdMessage Then ' strVetoName mit vbNullChar auffüllen strVetoName = String$(MAX_PATH, vbNullChar) End If ' wenn das auswerfen erfolgreich war If CM_Request_Device_EjectA(hDevInst, lngVetoType, _ StrPtr(strVetoName), Len(strVetoName), 0&) = _ CR_SUCCESS Then ' wenn nicht ' nicht die Standardnachricht anzeigt werden soll If Not ShowStdMessage Then MsgBox "Das Gerät 'USB-Massenspeichert' [" & _ DriveLetter & ":\] kann jetzt entfernt " & _ "werden.", vbOKOnly Or vbInformation End If ' entfernen war erfolgreich EjectUsbDrive = True Else ' wenn nicht ' nicht die Standardnachricht anzeigt werden soll If Not ShowStdMessage Then ' ist lngVetoType <> 0 If lngVetoType <> 0 Then ' strVetoName konvertieren strVetoName = StrConv(strVetoName, vbUnicode) ' strVetoName bis zum ersten vbNullChar strVetoName = Left$(strVetoName, InStr(1, _ strVetoName, vbNullChar) - 1) MsgBox "Das Gerät '" & strVetoName & "' " & _ "[" & DriveLetter & ":\] kann zur " & _ "Zeit nicht deaktiviert werden. " & _ "Wiederholen Sie den Vorgang " & _ "später.", vbOKOnly Or vbExclamation End If End If ' entfernen war nicht erfolgreich EjectUsbDrive = False End If End If End If End If End If End Function ' ------------------------------------------------------ ' Funktion : GetDevInstStr ' Beschreibung : Gerätenamen zu einem Laufwerk ermitteln ' Übergabewert : DriveLetter = Laufwerksbuchstabe ' Rückgabewert : Gerätename ' ------------------------------------------------------ ' Der Original C++ Code zu dieser Funktion ist auf der Seite ' http://www.codeproject.com/KB/system/RemoveDriveByLetter.aspx ' zu finden. Einge Teile zu dieser Funktion stammen auch von ' http://www.vbarchiv.net/workshop/workshop78s2.html ' ------------------------------------------------------ Public Function GetDevInstStr(ByVal DriveLetter As String) As String Dim hVolume As Long Dim hDevInfo As Long Dim bolIsFloppy As Boolean Dim bytBuff() As Byte Dim lngRet As Long Dim lngBuffLen As Long Dim lngDevNumber As Long Dim lngIndex As Long Dim strRet As String Dim strBuff As String * MAX_PATH Dim strDosDevName As String Dim tGUID As GUID Dim tSDN As STORAGE_DEVICE_NUMBER Dim tSPDID As SP_DEVICE_INTERFACE_DATA ' Handle auf das Laufwerk holen hVolume = CreateFile("\\.\" & DriveLetter & ":", 0&, FILE_SHARE_READ Or _ FILE_SHARE_WRITE, ByVal 0&, OPEN_EXISTING, 0&, ByVal 0&) ' ist ein gültiges Handle vorhanden If hVolume <> INVALID_HANDLE_VALUE Then ' Laufwerksnummer ermitteln If DeviceIoControl(hVolume, IOCTL_STORAGE_GET_DEVICE_NUMBER, ByVal _ 0&, 0&, tSDN, Len(tSDN), lngRet, ByVal 0&) Then ' Laufwerksnummer speichern lngDevNumber = tSDN.DeviceNumber End If ' Handle schließen Call CloseHandle(hVolume) End If ' DOS-Gerätenamen vom Laufwerk ermitteln If QueryDosDevice(DriveLetter & ":", strBuff, MAX_PATH) Then ' DOS-Gerätename strDosDevName = Left$(strBuff, InStr(1, strBuff, vbNullChar) - 1) ' ist das Laufwerk ein Diskettenlauferk If InStr(1, strDosDevName, "\Floppy") Then ' Laufwerk ist ein Diskettenlaufwerk bolIsFloppy = True End If End If ' Laufwerkstyp ermitteln Select Case GetDriveType(DriveLetter & ":\") ' auswerfbare Laufwerke Case DRIVE_REMOVABLE ' Diskettenlaufwerke If bolIsFloppy Then ' String zu GUID konvertieren Call IIDFromString(StrPtr(GUID_DEVINTERFACE_FLOPPY), tGUID) Else ' andere auswerfbare Laufwerke ' String zu GUID konvertieren Call IIDFromString(StrPtr(GUID_DEVINTERFACE_DISK), tGUID) End If ' HD-Laufwerke Case DRIVE_FIXED ' String zu GUID konvertieren Call IIDFromString(StrPtr(GUID_DEVINTERFACE_DISK), tGUID) ' CDROM-Laufwerke Case DRIVE_CDROM ' String zu GUID konvertieren Call IIDFromString(StrPtr(GUID_DEVINTERFACE_CDROM), tGUID) End Select ' Handle auf die Geräteklasse holen hDevInfo = SetupDiGetClassDevs(tGUID, vbNullString, 0&, DIGCF_PRESENT Or _ DIGCF_DEVICEINTERFACE) ' ist ein gültiges Handle vorhanden If hDevInfo <> INVALID_HANDLE_VALUE Then tSPDID.cbSize = Len(tSPDID) ' alle Geräte in dieser Geräteklasse durchlaufen Do ' Geräte in dieser Geräteklasse auflisten If SetupDiEnumDeviceInterfaces(hDevInfo, ByVal 0&, tGUID, _ lngIndex, tSPDID) = 0 Then ' wenn keine Geräte mehr in dieser Geräteklasse vorhanden sind If Err.LastDllError = ERROR_NO_MORE_ITEMS Then ' Schleife verlassen Exit Do Else ' Funktion verlassen Exit Function End If End If ' hier ermitteln wir die erforderliche Buffergröße zur ' Aufnahme des Gerätenamens If SetupDiGetDeviceInterfaceDetail(hDevInfo, tSPDID, ByVal 0&, _ 0&, lngBuffLen, ByVal 0&) = 0 Then ' wenn der Buffer zu klein ist If Err.LastDllError = ERROR_INSUFFICIENT_BUFFER Then 'Type SP_DEVICE_INTERFACE_DETAIL_DATA ' cbSize As Long ' DevicePath(0) As Byte 'End Type ' anstelle von SP_DEVICE_INTERFACE_DETAIL_DATA ' verwenden wir hier ein BytaArray ' Buffer dimensionieren ReDim bytBuff(lngBuffLen - 1) ' Len(SP_DEVICE_INTERFACE_DETAIL_DATA) bytBuff(0) = 5 ' Gerätenamen ermitteln If SetupDiGetDeviceInterfaceDetail(hDevInfo, tSPDID, _ bytBuff(0), lngBuffLen, lngBuffLen, ByVal 0&) <> 0 _ Then ' Handle auf das Gerät holen hVolume = CreateFile(Mid$(StrConv(bytBuff, _ vbUnicode), 5, lngBuffLen - 5), 0&, _ FILE_SHARE_READ Or FILE_SHARE_WRITE, ByVal 0&, _ OPEN_EXISTING, 0&, ByVal 0&) ' ist ein gültiges Handle vorhanden If hVolume <> INVALID_HANDLE_VALUE Then ' Gerätenummer ermitteln If DeviceIoControl(hVolume, _ IOCTL_STORAGE_GET_DEVICE_NUMBER, ByVal 0&, _ 0&, tSDN, Len(tSDN), lngRet, ByVal 0&) Then ' ist die Laufwerksnummer gleich der ' Gerätenummer If lngDevNumber = tSDN.DeviceNumber Then ' Handle auf das Gerät schließen Call CloseHandle(hVolume) ' Handle auf die Geräteklasse schließen Call SetupDiDestroyDeviceInfoList( _ hDevInfo) ' Gerätename strRet = Mid$(StrConv(bytBuff, _ vbUnicode), 5, lngBuffLen - 5) ' # durch \ ersetzen strRet = Replace(strRet, "#", "\") ' die ersten 4 Zeichen und die Geräte-GUID ' wegschneiden strRet = Mid$(strRet, 5, InStr(1, _ strRet, "{") - 6) ' für EjectUsbDrive benötigen wir ' nur auswerfbare USB-Laufwerke If UCase$(Left$(strRet, 7)) = _ "USBSTOR" Then ' Gerätenamen zurückgeben GetDevInstStr = strRet End If End If End If ' Handle auf das Gerät schließen Call CloseHandle(hVolume) End If End If End If End If ' nächste Geräteindexnummer lngIndex = lngIndex + 1 ' nächstes Geräte in dieser Geräteklasse Loop ' Handle auf die Geräteklasse schließen Call SetupDiDestroyDeviceInfoList(hDevInfo) End If End Function '--------- Ende Modul "modEject" alias modEject.bas --------- '------------ Ende Projektdatei SaveUsbEject.vbp ------------
Tipp-Kompatibilität:
Windows/VB-Version | Win32s | Win95 | Win98 | WinME | WinNT4 | Win2000 | WinXP |
VB4 | |||||||
VB5 | |||||||
VB6 |
Ihre Meinung
Falls Sie Fragen zu diesem Artikel haben oder Ihre Erfahrung mit anderen Nutzern austauschen möchten, dann teilen Sie uns diese bitte in einem der unten vorhandenen Themen oder über einen neuen Beitrag mit. Hierzu können sie einfach einen Beitrag in einem zum Thema passenden Forum anlegen, welcher automatisch mit dieser Seite verknüpft wird.
Archivierte Nutzerkommentare
Klicken Sie diesen Text an, wenn Sie die 4 archivierten Kommentare ansehen möchten.
Diese stammen noch von der Zeit, als es noch keine direkte Forenunterstützung für Fragen und Kommentare zu einzelnen Artikeln gab.
Aus Gründen der Vollständigkeit können Sie sich die ausgeblendeten Kommentare zu diesem Artikel aber gerne weiterhin ansehen.
Kommentar von Frank Schüler am 21.06.2011 um 22:17
Hi
Ah, ein interner Card-Reader. Japp, da hilft nur ein Reboot. ;-)
Mfg Frank
Kommentar von piper am 21.06.2011 um 21:59
Hallo Frank
Hmmm - von wegen rein/rausstöpseln: Dazu benötigte ich Hammer, Axt, Trennscheibe, Sprengstoff oder zumindestens einen Schraubenzieher... das Ding ist nämlich hart eingebaut. Im übrigen ist Dein Code täglich im Einsatz; funktioniert mit normalen USB-Sticks bestens. Eine wertvolle Automationshilfe.
Hinweis: Der korrekte Dismount eines Card-Reader-Drives kann mit dem Context-Menu <Eject> erfolgen. Also, next Release: If Is CardReaderDrive then Eject... :-)
Thanks und Gruss
Alfred
Kommentar von Frank Schüler am 21.06.2011 um 20:28
Hi piper
Ups, sehe erst jetzt das Du da eine Message hinterlassen hast. Lieber spät als nie eine Antwort. ;-) Bei Multi-Card-Reader wird immer der komplette Reader ausgeworfen. Einzelne Laufwerke eines Multi-Card-Readers lassen sich nicht auswerfen. Das gleiche Verhalten zeigt auch W2K und XP. k.A wie es unter VISTA und W7 ausschaut. Vermute aber das dort ebenfalls der komplette Reader ausgeworfen wird. Card-Reader abziehen und wieder anstöpseln und schon sind alle Laufwerke des Card-Readers wieder da.
Kommentar von piper am 25.12.2010 um 19:58
Jahrelang gesucht - et voilà! Perfekte Lösung. Lediglich mit dem Multi-Card-Reader kommt sie nicht zurecht. Doch da zeigt auch XPProf-nativ ein ziemlich nicht-determiniertes Verhalten (Irrtümlicher Rootdevice-Unload oder sowas --> Cardreader verschwunden. --> Reboot --> vielleicht kommt er dann wieder...) :-(
Dem Autor vielen Dank für den Code; tolle Leistung!