Page 1 sur 1

[R] Ejecter un disque dur EXTERNE (USB)  

Posté : ven. 08 févr. 2019 11:46
par franco
Bonjour.


Je cherchais avant tout un moyen « d'éjecter » tout périphérique USB.

Je suis tombé sur ce topic : https://www.autoitscript.com/forum/topi ... nt=1088380


Ca fonctionne pour les clés USB (var le type est "REMOVABLE")
Mais pas pour les disques dur externes USB...


En fait, ça fonctionne, si je met le type FIXED, comme le suggère le commentaire sur le script.
; USB external hard drives report as "Fixed", not "Removable", so use "All" to cover both kinds

Mais le disque INTERNE a aussi le type "FIXED", et je n'ai pas envie de le "dégommer" :mrgreen:

Je pensais - surement naïvement - faire une condition, en excluant les lecteurs dont le type est "Fixed"
Mais peine perdu :mrgreen:


Voici mon script :
#include <Process.au3>

$DriveLabel = "usbdrive" ; your drive label here

$aDrives = DriveGetDrive("ALL")
 ; USB external hard drives report as "Fixed", not "Removable", so use "All" to cover both kinds

If Not @error Then
    For $i = 1 To $aDrives[0]

;~ if not DriveGetType($aDrives[$i]) = "Fixed" then MsgBox(0,"",$aDrives[$i])
;~ if DriveGetType($aDrives[$i]) <> "Fixed" then MsgBox(0,"",$aDrives[$i])
MsgBox(0,"",DriveGetType($aDrives[$i]))
;~             MsgBox(0,"",DriveGetType($aDrives[$i]))

;~             EjectDrive($aDrives[$i] & "")



    Next
EndIf



Func EjectDrive($dLetter) ; From VB script, Authors XoLoX and ChrisL
    Local Const $SSF_DRIVES = 17
    Local $oShell, $oNameSpace, $oDrive, $strDrive
    $strDrive = $dLetter
    $strName = DriveGetLabel($strDrive) & "(" & $strDrive & ")"
    $oShell = ObjCreate("Shell.Application")
    $oNameSpace = $oShell.NameSpace($SSF_DRIVES)
    $oDrive = $oNameSpace.ParseName($strDrive)
    $oDrive.InvokeVerb("Eject")
    MsgBox(0, "2", "We're here in the function")
    TrayTip("USB Drive " & $strName & " ejected", "You can now remove the device safely.", 5, 1)
    MsgBox(0, "", $strName & " drive ejected. You can now remove the device safely.", 5)
EndFunc   ;==>EjectDrive

De là, j'ai une idée (mais surement pas fameuse)
- Faire un $aDrives = DriveGetDrive("ALL")
- exclure le lecteur si = à @homedrive...


Je vous l'avais bien dit, l'idée est foireuse...


Avez-vous une piste à me conseiller ?

PS : sans passer par un outil tiers, comme DevCon par exemple.


EDIT :
J'aurais pas le temps d'étudier cette piste, qui m'a l'air excellente : viewtopic.php?p=87533#p87533

je verrais surement ça demain, boulot oblige


++

Re: [..] Ejecter un disque dur EXTERNE (USB)

Posté : dim. 10 févr. 2019 00:04
par walkson
Bonjour,
Pas facile votre question. J'ai testé plein de codes sauf résultats sauf celui-ci

Code : Tout sélectionner

#include <WinApi.au3>
;Prototypes
;BOOL EjectVolume(TCHAR cDriveLetter);
;HANDLE OpenVolume(TCHAR cDriveLetter);
;BOOL LockVolume(HANDLE hVolume);
;BOOL DismountVolume(HANDLE hVolume);
;BOOL PreventRemovalOfVolume(HANDLE hVolume, BOOL fPrevent);
;BOOL AutoEjectVolume(HANDLE hVolume);
;BOOL CloseVolume(HANDLE hVolume);

;StringFormat Output
$szVolumeFormat = "\\\\.\\%s"
$szRootFormat = "%s\\"
$szErrorFormat = "Error %d: %s\n"
;------------------------------------------
;Arbitrary variables
;Global Const $INVALID_HANDLE_VALUE = 0
;------------------------------------------
;DeviceIoControl Contants
;Global Const $FSCTL_LOCK_VOLUME = int(0x090018)
;Global Const $FSCTL_DISMOUNT_VOLUME = int(0x00090020)
;Global Const $IOCTL_STORAGE_EJECT_MEDIA  = int(0x002D4808)
;Global Const $IOCTL_STORAGE_MEDIA_REMOVAL = int(0x002D4804)

;------------------------------------------
;Retry Constants
Global Const $LOCK_TIMEOUT = 10000       ; 10 Seconds
Global Const $LOCK_RETRIES = 20
$OpenVolume = InputBox("Ejecting...", "Enter the drive to eject", "G:")
ConsoleWrite("Trying to Eject the drive " & EjectVolume($OpenVolume) & @crlf)

Func ReportError($szMsg)
    ConsoleWrite(StringFormat($szErrorFormat, _WinAPI_GetLastErrorMessage (), $szMsg) & @CRLF)
    Exit
EndFunc   ;==>ReportError
Func OpenVolume($cDriveLetter)
    ;HANDLE hVolume
    ;UINT uDriveType
    ;TCHAR szVolumeName[8]
    ;TCHAR szRootName[5]
    ;DWORD dwAccessFlags
    $szRootName = StringFormat($szRootFormat, $cDriveLetter)
    $uDriveType = DriveGetType($szRootName);
    ConsoleWrite($szRootName & @tab & $uDriveType & @crlf)
    Switch $uDriveType
        Case "Fixed"
            $dwAccessFlags = 6
		Case "Removable"
            $dwAccessFlags = 6
        Case "CDROM"
            $dwAccessFlags = 2
        Case Else
            ConsoleWrite("Cannot eject.  Drive type is incorrect." & @CRLF)
            Return $INVALID_HANDLE_VALUE
    EndSwitch
    $szVolumeName = StringFormat($szVolumeFormat, $cDriveLetter)
    ;$szVolumeName = $szVolumeFormat & $cDriveLetter
    ConsoleWrite($szVolumeName & @crlf )
    $hVolume = _WinAPI_CreateFile ($szVolumeName, 2,$dwAccessFlags, 6)

    #cs
        hVolume = CreateFile(   szVolumeName,
        dwAccessFlags,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL,
        OPEN_EXISTING,
        0,
        NULL );
    #ce
    If ($hVolume == $INVALID_HANDLE_VALUE) Then ReportError("CreateFile");
    Return $hVolume;
EndFunc   ;==>OpenVolume
Func CloseVolume($hVolume)
    Return _WinAPI_CloseHandle ($hVolume);
EndFunc   ;==>CloseVolume
Func LockVolume($hVolume)
    Local $dwBytesReturned
    Local $dwSleepAmount
    Local $nTryCount
    local $iRead
    $dwSleepAmount = $LOCK_TIMEOUT / $LOCK_RETRIES;
    ; Do this in a loop until a timeout period has expired
    For $nTryCount = 0 To $nTryCount < $LOCK_RETRIES
        If _Device_Control($hVolume, $FSCTL_LOCK_VOLUME, $iRead) Then
            Return True
        Else
            Sleep($dwSleepAmount);
        EndIf
    Next
    Return False;
EndFunc   ;==>LockVolume
Func DismountVolume($hVolume)
    ConsoleWrite("Dismount " & $hVolume & @crlf)
    Local $dwBytesReturned, $iRead
    local $aResult = _Device_Control($hVolume, $FSCTL_DISMOUNT_VOLUME, $iRead)
    msgbox(0,"",$aResult)
    Return $aResult
    ;Return $dwBytesReturned
EndFunc   ;==>DismountVolume
Func PreventRemovalOfVolume($hVolume, $fPreventRemoval)
    Local $dwBytesReturned
    Local $aResult
    Local $lpInbuffer,$nInBufferSize,$lpOutBuffer,$nOutBufferSize,$lpOverlapped

    $PMRBUFFER = DllStructCreate("bool PreventMediaRemoval")

    DllStructSetData($PMRBUFFER,"PreventMediaRemoval",$fPreventRemoval)

    $lpBytesReturned    = DllStructCreate("int Read")
    $pRead   = DllStructGetPtr($lpBytesReturned, "Read")
    $aResult = Dllcall("kernel32.dll","int","DeviceIoControl","hwnd",$hVolume,"uint",$IOCTL_STORAGE_MEDIA_REMOVAL,"ptr",DllStructGetPtr($PMRBUFFER),"uint",DllStructGetSize($PMRBUFFER), _
    "ptr",$lpOutBuffer,"uint",$nOutBufferSize,"ptr",$pRead,"ptr",$lpOverlapped)
    if $aResult = 0 then msgbox(0,"",_WinAPI_GetLastErrorMessage())
    Return $aResult <> 0

     ;& PMRBuffer, sizeof (PREVENT_MEDIA_REMOVAL),
    ;NULL, 0,
    ; & dwBytesReturned,
    ;NULL);
EndFunc   ;==>PreventRemovalOfVolume
Func AutoEjectVolume($hVolume)
    Local $aResult, $iRead;
    $aResult = _Device_Control($hVolume, $IOCTL_STORAGE_EJECT_MEDIA, $iRead)
    Return $aResult
EndFunc   ;==>AutoEjectVolume
Func EjectVolume($cDriveLetter)
    Local $hVolume;
    Local $fRemoveSafely = False;
    Local $fAutoEject = False;
    ; Open the volume.
    $hVolume = OpenVolume($cDriveLetter);
    If $hVolume == $INVALID_HANDLE_VALUE Then Return False
    ; Lock and dismount the volume.
    If LockVolume($hVolume) And DismountVolume($hVolume) Then
        $fRemoveSafely = True;
        ConsoleWrite("Volume Locked and Dismounted, trying to eject " & @crlf)
        ; Set prevent removal to false and eject the volume.
        If PreventRemovalOfVolume($hVolume, False) And AutoEjectVolume($hVolume) Then
            $fAutoEject = True;
        EndIf
    Else
        ConsoleWrite("Volume can't be locked or dismounted, please close possible opened files" & @crlf)
    EndIf

    ; Close the volume so other processes can use the drive.
    If CloseVolume($hVolume) = False Then
        Return False;
    EndIf

    If $fAutoEject Then
        ConsoleWrite(StringFormat("Media in Drive %s has been ejected safely.\n", $cDriveLetter))
    Else
        If $fRemoveSafely Then
            ConsoleWrite(StringFormat("Media in Drive %s can be safely removed.\n", $cDriveLetter))
        EndIf
    EndIf

    Return True;
EndFunc   ;==>EjectVolume
Func _Device_Control($hDevice, $dwIoControlCode, ByRef $iRead)
    Local $aResult
    Local $lpInbuffer,$nInBufferSize,$lpOutBuffer,$nOutBufferSize,$lpOverlapped
    $tRead   = DllStructCreate("int Data")

    $aResult = Dllcall("kernel32.dll","int","DeviceIoControl","hwnd",$hDevice,"uint",$dwIoControlCode,"ptr",$lpInBuffer,"uint",0, _
    "ptr",$lpOutBuffer,"uint",0,"ptr",DllStructGetPtr($tRead),"ptr",$lpOverlapped)

    $iRead   = DllStructGetData($tRead, "Data")

    ConsoleWrite("Device Control " & $iRead & @CRLF)
    Return $aResult<>0
EndFunc   ;==>_Device_Control
et encore j'ai du le modifier en rajoutant
Case "Fixed"
$dwAccessFlags = 6
Le résultat n'est pas terrible car le disque est désactivé mais pas éjecté (reste visible mais inaccessible)
Dans mes recherches, plusieurs fois a été cité devcon.exe
https://docs.microsoft.com/fr-fr/window ... est/devcon
https://forum.pcastuces.com/commande_de ... s11219.htm
Je n'ai pas poussé plus loin mes recherches avec ce programme. Par contre, USB Disk Ejector semble plus simple d'emploi, programme portable et commandline https://quickandeasysoftware.net/softwa ... sk-ejector
The following command line options are available:

  * /?
    Displays a dialog that shows all command line options.

  * /NOSAVE
    Stops the program from saving any setting to a file and ignores any
    existing settings file.

  * /SILENT
    Stops balloon messages from appearing when the program is run in GUI
    mode.

  * /SHOWEJECT
    Shows the standard ''It is now safe to now safe to remove'' message
    when a drive is ejected. This message is disabled by default.

  * /REMOVETHIS
    Ejects the drive that the program is running from. Eg if the program
    is run from a usb stick on drive G then drive G would be ejected.

  * /REMOVELETTER
    Ejects the specified drive letter. Eg /REMOVELETTER G

  * /REMOVENAME
    Ejects the drive with the specified name. Eg /REMOVEDRIVE "Sandisk
    U3 Titanium"

    Partial name matching is possible if a wildcard (*) is used. Eg
    /REMOVENAME "*SANDISK" would eject a drive that had Sandisk in its name.

  * /CLOSEAPPS
    If there are any applications running from the drive, asks them to
    close. If any applications refuse to close then the eject will fail.
    This switch is considered relatively safe and should mean that no
    unsaved data is lost.

  * /CLOSEAPPSFORCE
    If there are any applications running from the drive, force them to
    close. This will mean that any unsaved data in those programs will
    be lost. This has the same effect as ending a task/process.
Fonctionne sur clef ou disque USB.
Je devine que vous préféreriez ne pas passer par un programme intermédiaire, désolé :P
Je vous joints un bout de code, la première partie liste toutes les connections USB (clef, disque, souris...) et la deuxième partie surveille les connections et déconnections de clefs ou de disque USB

Code : Tout sélectionner

$strComputer = "."

$objWMIService = ObjGet("winmgmts:\\" & $strComputer & "\root\cimv2")
$colDevices = $objWMIService.ExecQuery ("Select * From Win32_USBControllerDevice")

For $objDevice in $colDevices
    $strDeviceName = $objDevice.Dependent
    $strQuotes = Chr(34)
    $strDeviceName = StringReplace($strDeviceName, $strQuotes, "")
    $arrDeviceNames = StringSplit($strDeviceName, "=")
    $strDeviceName = $arrDeviceNames[2]
    $colUSBDevices = $objWMIService.ExecQuery ("Select * From Win32_PnPEntity Where DeviceID = '" & $strDeviceName & "'")
    For $objUSBDevice in $colUSBDevices
        MsgBox(0,"USB Description",$objUSBDevice.Description)
    Next
    MsgBox(0,"USB Devices",$strDeviceName)
Next
;===========================================================================================================================================================
$colEvents = $objWMIService.ExecNotificationQuery("Select * From __InstanceOperationEvent Within 5 Where " & "TargetInstance isa 'Win32_LogicalDisk'")

While 1
$objEvent = $colEvents.NextEvent
If $objEvent.TargetInstance.DriveType = 3 Or $objEvent.TargetInstance.DriveType = 2  Then ;2 / Removable Disk, 3 / Local Disk, 4 / Network Drive
Select
Case $objEvent.Path_.Class()="__InstanceCreationEvent"
Consolewrite("Drive " & $objEvent.TargetInstance.DeviceId & "has been added." & @CR)
Case $objEvent.Path_.Class()="__InstanceDeletionEvent"
Consolewrite("Drive " & $objEvent.TargetInstance.DeviceId & "has been removed."& @CR)
EndSelect
EndIf
WEnd
A noter que j'ai essayé Diskpart sans résultat et "RunDll32.exe shell32.dll,Control_RunDLL hotplug.dll" voie sans issue

Re: [..] Ejecter un disque dur EXTERNE (USB)

Posté : lun. 11 févr. 2019 23:10
par Tlem
Bonsoir.
Même si cela ne permet pas d'automatiser à 100% l'éjection, il y a cette solution : RunDll32.exe shell32.dll,Control_RunDLL hotplug.dll
Au moins, là il n'y a que les périphériques "éjectables".

Re: [..] Ejecter un disque dur EXTERNE (USB)

Posté : mer. 13 févr. 2019 15:07
par franco
Salut.

Désolé de répondre que maintenant, je suis en plein dans un état grippal lol...

Du coup, je réfléchis à 2 à l'heure.



@walkson : j'avoue que je voulais, au départ, me passer d'outil tiers. Mais plus j'ai testé usb disk ejector, moins j'ai envie de m'en passer. :mrgreen:

Le seul hic, c'est quand j'ai branché mon disque dur interne via boitier externe : plusieurs lettres...
Et selon la lettre, erreur avec usb disk ejector.

Du coup, j'ai cherché un autre outil en ligne de commandes, car ce concept m'a plu :mrgreen:
et j'ai trouvé RemoveDrive : https://www.uwe-sieber.de/drivetools_e.html

Suffit de faire RemoveDrive f: (par exemple) pour que cela fonctionne.


Du coup, j'ai pu finaliser un script à la vitesse d'une tortue (^^)


Voici le code :
#RequireAdmin
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>

$RmDr = @TempDir & "\RemoveDrive.exe"
FileInstall("RemoveDrive.exe",$RmDr)


$USBStor = "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\USBSTOR"

Global $Form1 = GUICreate("Form1", 615, 437, 192, 124)

$act_usb = GUICtrlCreateButton("Acctiver port USB", 5, 5, 120, 20)
$desact_usb = GUICtrlCreateButton("Désactiver port USB", 130, 5, 120, 20)
$eject_usb = GUICtrlCreateButton("Ejecter tout périphérique USB", 255, 5, 150, 20)

GUISetState(@SW_SHOW)
#EndRegion ### END Koda GUI section ###

While 1
   $nMsg = GUIGetMsg()
   Switch $nMsg
      Case $GUI_EVENT_CLOSE
         FileDelete($RmDr)
         Exit

      Case $desact_usb
         _ejectUSB()
         RegWrite($USBStor, "Start", "REG_DWORD", 4)

      Case $act_usb
         RegWrite($USBStor, "Start", "REG_DWORD", 3)

      Case $eject_usb
         _ejectUSB()


   EndSwitch
WEnd

Func _FindExtHdd()
   Local $iPid, $sStdout, $iTotalSize, $sGetSpace, $addspace, $externaldrives
   $iPid = Run(@ComSpec & ' /c wmic diskdrive get PNPDeviceID, size /format:csv | find /v "USBSTOR"', '', @SW_HIDE, 2)
   While ProcessExists($iPid)
      $sStdout &= StdoutRead($iPid)
   WEnd
   $sStdout = StringSplit($sStdout, ",")
   $iTotalSize = $sStdout[$sStdout[0]] / 1024 / 1024 / 1024 ; GB
   Do
      $iTotalSize = StringTrimRight($iTotalSize, 1)
   Until Not StringInStr($iTotalSize, ".")
   Local $aDriveList = DriveGetDrive("FIXED")
   For $w = 1 To UBound($aDriveList) - 1
      $sGetSpace = DriveSpaceTotal($aDriveList[$w]) / 1024
      Do
         $sGetSpace = StringTrimRight($sGetSpace, 1)
      Until Not StringInStr($sGetSpace, ".")
      $addspace += $sGetSpace
      If $addspace > $iTotalSize Then $externaldrives &= $aDriveList[$w] & @CRLF
   Next
   If $externaldrives Then Return $externaldrives
EndFunc   ;==>_FindExtHdd

Func _ejectUSB()
   Local $letters
   If Not _FindExtHdd() = 0 Then $letters &= _FindExtHdd() & @CRLF
   Local $aArray = DriveGetDrive("REMOVABLE")
   If Not @error Then
      For $i = 1 To $aArray[0]
         $letters &= $aArray[$i] & @CRLF
      Next
   EndIf
   If $letters <> '' Then
      For $a = 1 To StringSplit($letters, @LF)[0]
         $letter = StringSplit($letters, @LF)[$a]
         If $letter <> '' Then
            $command = '"' & $RmDr & '" ' & $letter
            RunWait(@ComSpec & " /c " & $command, "", @SW_HIDE)
         EndIf
      Next
   EndIf
EndFunc   ;==>_ejectUSB
A-t-il besoin d'être modifié ?
Il est fonctionnel pour ma part.


++ et merci de m'avoir aidé.

Re: [R] Ejecter un disque dur EXTERNE (USB)

Posté : lun. 18 févr. 2019 22:24
par franco
Bonsoir.

J'ai passé le topic en Résolu ( car je dois en poster un autre, radicalement différent ^^ )


A toute ! ^^