2009-01-12 31 views

回答

8

這是一個快速而髒的翻譯this sample code從support.microsoft.com刪除驅動器。但它只適用於在我的系統上擁有管理權限的用戶。

有關使用USB設備的更多信息,請參見this answerconcept03

function OpenVolume(ADrive: char): THandle; 
var 
    RootName, VolumeName: string; 
    AccessFlags: DWORD; 
begin 
    RootName := ADrive + ':\'; (* '\'' // keep SO syntax highlighting working *) 
    case GetDriveType(PChar(RootName)) of 
    DRIVE_REMOVABLE: 
     AccessFlags := GENERIC_READ or GENERIC_WRITE; 
    DRIVE_CDROM: 
     AccessFlags := GENERIC_READ; 
    else 
    Result := INVALID_HANDLE_VALUE; 
    exit; 
    end; 
    VolumeName := Format('\\.\%s:', [ADrive]); 
    Result := CreateFile(PChar(VolumeName), AccessFlags, 
    FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0); 
    if Result = INVALID_HANDLE_VALUE then 
    RaiseLastWin32Error; 
end; 

function LockVolume(AVolumeHandle: THandle): boolean; 
const 
    LOCK_TIMEOUT = 10 * 1000; // 10 Seconds 
    LOCK_RETRIES = 20; 
    LOCK_SLEEP = LOCK_TIMEOUT div LOCK_RETRIES; 

// #define FSCTL_LOCK_VOLUME CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 6, METHOD_BUFFERED, FILE_ANY_ACCESS) 
    FSCTL_LOCK_VOLUME = (9 shl 16) or (0 shl 14) or (6 shl 2) or 0; 
var 
    Retries: integer; 
    BytesReturned: Cardinal; 
begin 
    for Retries := 1 to LOCK_RETRIES do begin 
    Result := DeviceIoControl(AVolumeHandle, FSCTL_LOCK_VOLUME, nil, 0, 
     nil, 0, BytesReturned, nil); 
    if Result then 
     break; 
    Sleep(LOCK_SLEEP); 
    end; 
end; 

function DismountVolume(AVolumeHandle: THandle): boolean; 
const 
// #define FSCTL_DISMOUNT_VOLUME CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 8, METHOD_BUFFERED, FILE_ANY_ACCESS) 
    FSCTL_DISMOUNT_VOLUME = (9 shl 16) or (0 shl 14) or (8 shl 2) or 0; 
var 
    BytesReturned: Cardinal; 
begin 
    Result := DeviceIoControl(AVolumeHandle, FSCTL_DISMOUNT_VOLUME, nil, 0, 
    nil, 0, BytesReturned, nil); 
    if not Result then 
    RaiseLastWin32Error; 
end; 

function PreventRemovalOfVolume(AVolumeHandle: THandle; 
    APreventRemoval: boolean): boolean; 
const 
// #define IOCTL_STORAGE_MEDIA_REMOVAL CTL_CODE(IOCTL_STORAGE_BASE, 0x0201, METHOD_BUFFERED, FILE_READ_ACCESS) 
    IOCTL_STORAGE_MEDIA_REMOVAL = ($2d shl 16) or (1 shl 14) or ($201 shl 2) or 0; 
type 
    TPreventMediaRemoval = record 
    PreventMediaRemoval: BOOL; 
    end; 
var 
    BytesReturned: Cardinal; 
    PMRBuffer: TPreventMediaRemoval; 
begin 
    PMRBuffer.PreventMediaRemoval := APreventRemoval; 
    Result := DeviceIoControl(AVolumeHandle, IOCTL_STORAGE_MEDIA_REMOVAL, 
    @PMRBuffer, SizeOf(TPreventMediaRemoval), nil, 0, BytesReturned, nil); 
    if not Result then 
    RaiseLastWin32Error; 
end; 

function AutoEjectVolume(AVolumeHandle: THandle): boolean; 
const 
// #define IOCTL_STORAGE_EJECT_MEDIA CTL_CODE(IOCTL_STORAGE_BASE, 0x0202, METHOD_BUFFERED, FILE_READ_ACCESS) 
    IOCTL_STORAGE_EJECT_MEDIA = ($2d shl 16) or (1 shl 14) or ($202 shl 2) or 0; 
var 
    BytesReturned: Cardinal; 
begin 
    Result := DeviceIoControl(AVolumeHandle, IOCTL_STORAGE_EJECT_MEDIA, nil, 0, 
    nil, 0, BytesReturned, nil); 
    if not Result then 
    RaiseLastWin32Error; 
end; 

function EjectVolume(ADrive: char): boolean; 
var 
    VolumeHandle: THandle; 
begin 
    Result := FALSE; 
    // Open the volume 
    VolumeHandle := OpenVolume(ADrive); 
    if VolumeHandle = INVALID_HANDLE_VALUE then 
    exit; 
    try 
    // Lock and dismount the volume 
    if LockVolume(VolumeHandle) and DismountVolume(VolumeHandle) then begin 
     // Set prevent removal to false and eject the volume 
     if PreventRemovalOfVolume(VolumeHandle, FALSE) then 
     AutoEjectVolume(VolumeHandle); 
    end; 
    finally 
    // Close the volume so other processes can use the drive 
    CloseHandle(VolumeHandle); 
    end; 
end; 

procedure TForm1.Button1Click(Sender: TObject); 
begin 
    EjectVolume('E'); 
end; 
+0

這將是非常糟糕的,如果非用戶管理員cou ld刪除管理員正在使用的USB磁盤(或者甚至確定它是否在使用中)。所以限制似乎是合乎邏輯的 – MSalters 2009-01-12 11:55:12

+0

不是。即使我可以通過通知區域中的「安全刪除硬件」圖標移除U盤,此代碼對我來說也不起作用(非管理員,「超級用戶」的成員)。它適用於「運行爲...」和管理員帳戶,雖然... – mghie 2009-01-12 12:08:35

2

這不會彈出驅動器,但會沖洗驅動器緩衝區並使其可以安全地移除。它需要Vista和更高版本的管理權限(如果作爲有限權限的用戶運行,則爲XP)。它可能應該有一個嘗試..最終確保CloseHandle被調用;我把這個作爲一個練習留給讀者,因爲在沒有水平滾動的情況下,代碼格式是緊的。 :-)

unit USBDriveFlush; 

interface 

    uses Windows; 

type 
    // Taken from JEDI JwaWinIoctl 
    PSTORAGE_HOTPLUG_INFO = ^STORAGE_HOTPLUG_INFO; 
    {$EXTERNALSYM PSTORAGE_HOTPLUG_INFO} 
    _STORAGE_HOTPLUG_INFO = record 
    Size: DWORD; // version 
    MediaRemovable: BOOLEAN; // ie. zip, jaz, cdrom, mo, etc. vs hdd 
    MediaHotplug: BOOLEAN; // ie. does the device succeed a lock 
          // even though its not lockable media? 
    DeviceHotplug: BOOLEAN; // ie. 1394, USB, etc. 
    WriteCacheEnableOverride: BOOLEAN; // This field should not be 
          // relied upon because it is no longer used 
    end; 
    {$EXTERNALSYM _STORAGE_HOTPLUG_INFO} 
    STORAGE_HOTPLUG_INFO = _STORAGE_HOTPLUG_INFO; 
    {$EXTERNALSYM STORAGE_HOTPLUG_INFO} 
    TStorageHotplugInfo = STORAGE_HOTPLUG_INFO; 
    PStorageHotplugInfo = PSTORAGE_HOTPLUG_INFO;  

    function FlushUSBDrive(const Drive: string): Boolean; 

implementation 

function FlushUSBDrive(const Drive: string): Boolean; 
var 
    shpi : TStorageHotplugInfo; 
    retlen : DWORD; //unneeded, but deviceiocontrol expects it 
    h : THandle; 
begin 
    Result := False; 
    h := CreateFile(PChar('\\.\' + Drive), 
        0, 
        FILE_SHARE_READ or FILE_SHARE_WRITE, 
        nil, 
        OPEN_EXISTING, 
        0, 
        0); 
    if h <> INVALID_HANDLE_VALUE then 
    begin 
    shpi.Size := SizeOf(shpi); 

    if DeviceIoControl(h, 
         IOCTL_STORAGE_GET_HOTPLUG_INFO, 
         nil, 
         0, 
         @shpi, 
         SizeOf(shpi), 
         retlen, 
         nil) then 
    begin 
     //shpi now has the existing values, so you can check to 
     //see if the device is already hot-pluggable 
     if not shpi.DeviceHotplug then 
     begin 
     shpi.DeviceHotplug:= True; 

     //Need to use correct administrator security privilages here 
     //otherwise it'll just give 'access is denied' error 
     Result := DeviceIoControl(h, 
            IOCTL_STORAGE_SET_HOTPLUG_INFO, 
            @shpi, 
            SizeOf(shpi), 
            nil, 
            0, 
            retlen, 
            nil); 
     end; 
    end; 
    CloseHandle(h); 
    end; 
end; 

使用示例:

if FlushUSBDrive('G:') then 
    ShowMessage('Safe to remove USB drive G:') 
else 
    ShowMessage('Flush of drive G: failed!' + 
    SysErrorMessage(GetLastError())); 
4

用於移除USB驅動器是使用功能的關鍵,

檢查該樣本Delphi應用程序,基於本文How to Prepare a USB Drive for Safe Removal和上使用JEDI API Library & Security Code Library

{$APPTYPE CONSOLE} 

{$R *.res} 

uses 
    JwaWinIoctl, 
    Cfg, 
    CfgMgr32, 
    SetupApi, 
    Windows, 
    SysUtils; 


function GetDrivesDevInstByDeviceNumber(DeviceNumber : LONG; DriveType : UINT; szDosDeviceName: PChar) : DEVINST; 
var 
StorageGUID : TGUID; 
IsFloppy : Boolean; 
hDevInfo : SetupApi.HDEVINFO; 
dwIndex : DWORD; 
res  : BOOL; 
pspdidd : PSPDeviceInterfaceDetailData; 
spdid : SP_DEVICE_INTERFACE_DATA; 
spdd  : SP_DEVINFO_DATA; 
dwSize : DWORD; 
hDrive : THandle; 
sdn  : STORAGE_DEVICE_NUMBER; 
dwBytesReturned : DWORD; 
begin 
    Result:=0; 
    IsFloppy := pos('\\Floppy', szDosDeviceName)>0; // who knows a better way? 
    case DriveType of 
    DRIVE_REMOVABLE: 
       if (IsFloppy) then 
        StorageGUID := GUID_DEVINTERFACE_FLOPPY 
       else 
        StorageGUID := GUID_DEVINTERFACE_DISK; 

    DRIVE_FIXED: StorageGUID := GUID_DEVINTERFACE_DISK; 
    DRIVE_CDROM: StorageGUID := GUID_DEVINTERFACE_CDROM; 
    else 
     exit 
    end; 

    // Get device interface info set handle for all devices attached to system 
    hDevInfo := SetupDiGetClassDevs(@StorageGUID, nil, 0, DIGCF_PRESENT OR DIGCF_DEVICEINTERFACE); 
    if (NativeUInt(hDevInfo) <> INVALID_HANDLE_VALUE) then 
    try 
    // Retrieve a context structure for a device interface of a device information set 
    dwIndex := 0; 
    //PSP_DEVICE_INTERFACE_DETAIL_DATA pspdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)Buf; 
    spdid.cbSize := SizeOf(spdid); 

    while true do 
    begin 
     res := SetupDiEnumDeviceInterfaces(hDevInfo, nil, StorageGUID, dwIndex, spdid); 
     if not res then 
     break; 

     dwSize := 0; 
     SetupDiGetDeviceInterfaceDetail(hDevInfo, @spdid, nil, 0, dwSize, nil); // check the buffer size 

     if (dwSize<>0) then 
     begin 
     pspdidd := AllocMem(dwSize); 
     try 
     pspdidd.cbSize := SizeOf(TSPDeviceInterfaceDetailData); 
     ZeroMemory(@spdd, sizeof(spdd)); 
     spdd.cbSize := SizeOf(spdd); 
     res := SetupDiGetDeviceInterfaceDetail(hDevInfo, @spdid, pspdidd, dwSize, dwSize, @spdd); 
     if res then 
     begin 
      // open the disk or cdrom or floppy 
      hDrive := CreateFile(pspdidd.DevicePath, 0, FILE_SHARE_READ OR FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0); 
     if (hDrive <> INVALID_HANDLE_VALUE) then 
      try 
       // get its device number 
       dwBytesReturned := 0; 
       res := DeviceIoControl(hDrive, IOCTL_STORAGE_GET_DEVICE_NUMBER, nil, 0, @sdn, sizeof(sdn), dwBytesReturned, nil); 
       if res then 
       begin 
       if (DeviceNumber = sdn.DeviceNumber) then 
       begin // match the given device number with the one of the current device 
        Result:= spdd.DevInst; 
        exit; 
       end; 
       end; 
      finally 
      CloseHandle(hDrive); 
      end; 
     end; 
     finally 
     FreeMem(pspdidd); 
     end; 
     end; 
     Inc(dwIndex); 
    end; 
    finally 
    SetupDiDestroyDeviceInfoList(hDevInfo); 
    end; 
end; 

procedure EjectUSB(const DriveLetter:char); 
var 
    szRootPath, szDevicePath : PChar; 
    szVolumeAccessPath : PChar; 
    hVolume : THandle; 
    DeviceNumber : LONG; 
    sdn : STORAGE_DEVICE_NUMBER; 
    dwBytesReturned : DWORD; 
    res : BOOL; 
    resCM : Cardinal; 
    DriveType : UINT; 
    szDosDeviceName : array [0..MAX_PATH-1] of Char; 
    DevInst : CfgMgr32.DEVINST; 
    VetoType : PNP_VETO_TYPE; 
    VetoName : array [0..MAX_PATH-1] of WCHAR; 
    bSuccess : Boolean; 
    DevInstParent : CfgMgr32.DEVINST; 
    tries : Integer; 
begin 
    szRootPath := PChar(DriveLetter+':\'); 
    szDevicePath := PChar(DriveLetter+':'); 
    szVolumeAccessPath := PChar(Format('\\.\%s:',[DriveLetter])); 

    DeviceNumber:=-1; 
    // open the storage volume 
    hVolume := CreateFile(szVolumeAccessPath, 0, FILE_SHARE_READ OR FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0); 
    if (hVolume <> INVALID_HANDLE_VALUE) then 
    try 
    //get the volume's device number 
    dwBytesReturned := 0; 
    res := DeviceIoControl(hVolume, IOCTL_STORAGE_GET_DEVICE_NUMBER, nil, 0, @sdn, SizeOf(sdn), dwBytesReturned, nil); 
    if res then 
     DeviceNumber := sdn.DeviceNumber; 
    finally 
    CloseHandle(hVolume); 
    end; 
    if DeviceNumber=-1 then exit; 

    // get the drive type which is required to match the device numbers correctely 
    DriveType := GetDriveType(szRootPath); 

    // get the dos device name (like \device\floppy0) to decide if it's a floppy or not - who knows a better way? 
    QueryDosDevice(szDevicePath, szDosDeviceName, MAX_PATH); 

    // get the device instance handle of the storage volume by means of a SetupDi enum and matching the device number 
    DevInst := GetDrivesDevInstByDeviceNumber(DeviceNumber, DriveType, szDosDeviceName); 

    if (DevInst = 0) then 
    exit; 

    VetoType := PNP_VetoTypeUnknown; 
    bSuccess := false; 

    // get drives's parent, e.g. the USB bridge, the SATA port, an IDE channel with two drives! 
    DevInstParent := 0; 
    resCM := CM_Get_Parent(DevInstParent, DevInst, 0); 

    for tries:=0 to 3 do // sometimes we need some tries... 
    begin 
     FillChar(VetoName[0], SizeOf(VetoName), 0); 

     // CM_Query_And_Remove_SubTree doesn't work for restricted users 
     //resCM = CM_Query_And_Remove_SubTree(DevInstParent, &VetoType, VetoNameW, MAX_PATH, CM_REMOVE_NO_RESTART); // CM_Query_And_Remove_SubTreeA is not implemented under W2K! 
     //resCM = CM_Query_And_Remove_SubTree(DevInstParent, NULL, NULL, 0, CM_REMOVE_NO_RESTART); // with messagebox (W2K, Vista) or balloon (XP) 

     resCM := CM_Request_Device_Eject(DevInstParent, @VetoType, @VetoName[0], Length(VetoName), 0); 
     resCM := CM_Request_Device_Eject(DevInstParent,nil, nil, 0, 0); // optional -> shows messagebox (W2K, Vista) or balloon (XP) 

     bSuccess := (resCM=CR_SUCCESS) and (VetoType=PNP_VetoTypeUnknown); 
     if (bSuccess) then 
      break; 

     Sleep(500); // required to give the next tries a chance! 
    end; 

    if (bSuccess) then 
     Writeln('Success') 
    else 
     Writeln('Failed'); 
end; 

begin 
    try 
    LoadSetupApi; 
    LoadConfigManagerApi; 
    EjectUSB('F'); 
    except 
    on E: Exception do 
     Writeln(E.ClassName, ': ', E.Message); 
    end; 
    Readln; 

end. 
相關問題