{*******************************************************} { } { Tocsg.USB } { } { Copyright (C) 2022 kku } { } {*******************************************************} unit Tocsg.USB; interface uses Tocsg.Obj, System.Classes, System.SysUtils, Winapi.Windows, System.Generics.Collections, Winapi.Messages; const GUID_DEVINTERFACE_USB_DEVICE: TGUID = '{A5DCBF10-6530-11D2-901F-00C04FB951ED}'; DBT_DEVICEARRIVAL = $8000; // system detected a new device DBT_DEVICEREMOVECOMPLETE = $8004; // device is gone DBT_DEVICEQUERYREMOVE = $8001; DBT_DEVNODES_CHANGED = $0007; DBTF_MEDIA = $0001; DBT_DEVTYP_VOLUME = $0002; DBT_DEVTYP_PORT = $0003; DBT_DEVTYP_NET = $0004; DBT_DEVTYP_DEVICEINTERFACE = $0005; // device interface class DBT_DEVTYP_HANDLE = $0006; type PDevBroadcastHdr = ^DEV_BROADCAST_HDR; DEV_BROADCAST_HDR = {$IFNDEF WIN64} packed {$ENDIF} record dwSize : DWORD; dwDevicetype : DWORD; dwReserved : DWORD; end; PDevBroadcastDeviceInterface = ^DEV_BROADCAST_DEVICEINTERFACE; DEV_BROADCAST_DEVICEINTERFACE = record dwSize : DWORD; dwDevicetype : DWORD; dwReserved : DWORD; ClassGUID : TGUID; nName : Short; end; PDevBroadcastVolume = ^TDevBroadcastVolume; TDevBroadcastVolume = {$IFNDEF WIN64} packed {$ENDIF} record dwSize: DWORD; dwDevicetype: DWORD; dwReserved: DWORD; dwUnitmask: DWORD; wFlags: Word; end; PDevBroadcastHandle = ^TDevBroadcastHandle; DEV_BROADCAST_HANDLE = record dbch_size: DWORD; dbch_devicetype: DWORD; dbch_reserved: DWORD; dbch_handle: THandle; { file handle used in call to RegisterDeviceNotification } dbch_hdevnotify: HDEVNOTIFY; { HDEVNOTIFY returned from RegisterDeviceNotification } { The following 3 fields are only valid if wParam is DBT_CUSTOMEVENT. } dbch_eventguid: TGUID; dbch_nameoffset: LongInt; { offset (bytes) of variable-length string buffer (-1 if none)} dbch_data: array[0..0] of BYTE; { variable-sized buffer, potentially containing binary and/or text data } end; TDevBroadcastHandle = DEV_BROADCAST_HANDLE; TUSBChangeEvent = procedure(Sender: TObject; pInfo: PDevBroadcastVolume) of object; TDevChangeEvent = procedure(Sender: TObject; pInfo: PDevBroadcastDeviceInterface) of object; TUSBChangeQueryEvent = procedure(Sender: TObject; sDrive: String; var bAccept: Boolean) of object; TTgUSBEventNotify = class(TTgObject) private hWindowHandle_ : HWND; evUSBArrival_, evUSBRemove_ : TUSBChangeEvent; evDevArrival_, evDevRemove_ : TDevChangeEvent; evUSBQueryRemove_ : TUSBChangeQueryEvent; DcQueryRemoveNotify_: TDictionary; // 특정 환경에서 USB 연결/해제 시 DBT_DEVICEARRIVAL, DBT_DEVICEREMOVECOMPLETE 값을 // 제대로 받아오지 못하는 경우가 있다. 이 경우 기존 드라이브를 비교해서 연결/해제를 판단함. dwLogicalDrvs_ : DWORD; bDeviceChanging_: Boolean; procedure ProcessWindowMessage(var msg: TMessage); function RegisterDeviceChange(sPath: String): Boolean; overload; function GetQueryRemovePath(hDev: HDEVNOTIFY): String; procedure SetEventQueryRemove(evUSBChangeQueryEvent: TUSBChangeQueryEvent); protected procedure process_WM_DEVICECHANGE(var msg: TMessage); public constructor Create; destructor Destroy; override; function RegisterDeviceChange: Boolean; overload;// not use published property OnUSBArrival: TUSBChangeEvent write evUSBArrival_; property OnUSBQueryRemove: TUSBChangeQueryEvent write SetEventQueryRemove; property OnUSBRemove: TUSBChangeEvent write evUSBRemove_; property OnDevArrival: TDevChangeEvent write evDevArrival_; property OnDevRemove: TDevChangeEvent write evDevRemove_; end; const REG_ENUM_USB = 'SYSTEM\CurrentControlSet\Enum\USB\'; REG_ENUM_USBSTOR = 'SYSTEM\CurrentControlSet\Enum\USBSTOR\'; REG_ENUM_USE_DISK_NUM = 'SYSTEM\CurrentControlSet\Services\disk\Enum\'; // 현재 인식된 물리디스크 넘버를 확인할수 있다. REG_MOUNTED_DEVICES = 'SYSTEM\MountedDevices\'; REG_USB_LASTWRITE_CONTROL1 = 'SYSTEM\CurrentControlSet\Control\DeviceClasses\{53f56307-b6bf-11d0-94f2-00a0c91efb8b}\##?#USBSTOR#%s#%s#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}\Control'; REG_USB_LASTWRITE_CONTROL2 = 'SYSTEM\CurrentControlSet\Control\DeviceClasses\{53f5630d-b6bf-11d0-94f2-00a0c91efb8b}\##?#STORAGE#RemovableMedia#%s&RM#{53f5630d-b6bf-11d0-94f2-00a0c91efb8b}\Control'; REG_USB_LASTWRITE_CONTROL_VISTA = 'SYSTEM\CurrentControlSet\Control\DeviceClasses\{53f56307-b6bf-11d0-94f2-00a0c91efb8b}\##?#USBSTOR#%s#%s#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}\Control'; type PUSBRec = ^TUSBRec; TUSBRec = record sDeviceName, sDescription, sDriveLetter, sFriendlyName, sSerial, sParentIdPrefix, sVID, sPID, sUsbstor, sSerial2 : AnsiString; dtCreate, dtLastWrite: TDateTime; nDiskNum : Integer; // 연결되어 있다면 값이, 없다면 -1 end; TTgUSBStorInfo = class(TObject) private bIsVista_: Boolean; protected USBStorList_: TList; procedure OnUSBStorNotify(Sender: TObject; const Item: PUSBRec; Action: TCollectionNotification); function GetCount: Integer; function GetUSBInfoStep_1: Boolean; function GetUSBInfoStep_2: Boolean; function GetUSBInfoStep_3: Boolean; function GetUSBInfoStep_4: Boolean; function GetUSBInfoStep_5: Boolean; public Constructor Create; Destructor Destroy; override; function GetUSBInfoByLetter(cDrive: AnsiChar): PUSBRec; function GetInfo(nIndex: Integer): PUSBRec; procedure PutInfo(nIndex: Integer; const pData: PUSBRec); function UpdateUSBStorInfo: Boolean; // function RemoveUSBInfo(pData: PUSBRec) : Boolean; property USBRecs[nIndex: Integer]: PUSBRec read GetInfo; default; property Count: Integer read GetCount; end; implementation uses Tocsg.Disk, System.Win.Registry, Tocsg.Safe, Tocsg.DateTime, Tocsg.Exception, EM.WinOSVersion, Tocsg.Registry; { TTgUSBEventNotify } // 이거 스레드에서 생성하면 오동작 할 가능성이 농후함. // 내부적으로 다열로그를 만들어서 메세지를 받기 때문.. Constructor TTgUSBEventNotify.Create; begin {$IFDEF TRACE1} _Trace('Create()'); {$ENDIF} inherited Create; DcQueryRemoveNotify_ := TDictionary.Create; dwLogicalDrvs_ := GetLogicalDrives; bDeviceChanging_ := false; hWindowHandle_ := AllocateHWnd(ProcessWindowMessage); end; Destructor TTgUSBEventNotify.Destroy; begin DeallocateHWnd(hWindowHandle_); FreeAndNil(DcQueryRemoveNotify_); inherited; {$IFDEF TRACE1} _Trace('Destroy()'); {$ENDIF} end; procedure TTgUSBEventNotify.SetEventQueryRemove(evUSBChangeQueryEvent: TUSBChangeQueryEvent); procedure register_notify; var sDrive: String; nDrive: Integer; begin for nDrive := 2 to 31 do begin sDrive := Format('%s:\', [Char(Integer('A')+nDrive)]); case GetDriveType(PChar(sDrive)) of DRIVE_FIXED, DRIVE_REMOVABLE : if GetDriveSize(sDrive) <> 0 then RegisterDeviceChange(sDrive); end; end; end; begin DcQueryRemoveNotify_.Clear; evUSBQueryRemove_ := evUSBChangeQueryEvent; if Assigned(evUSBQueryRemove_) then register_notify; end; function TTgUSBEventNotify.GetQueryRemovePath(hDev: HDEVNOTIFY): String; begin if DcQueryRemoveNotify_.ContainsKey(hDev) then Result := DcQueryRemoveNotify_[hDev] else Result := ''; end; procedure TTgUSBEventNotify.ProcessWindowMessage(var msg: TMessage); begin case Msg.Msg of WM_DEVICECHANGE : process_WM_DEVICECHANGE(msg); end; Msg.Result := DefWindowProc(hWindowHandle_, msg.Msg, msg.wParam, msg.lParam); end; procedure TTgUSBEventNotify.process_WM_DEVICECHANGE(var msg : TMessage); var // nTime: Integer; dwAddDrv, dwDelDrv, dwNewDrvs : DWORD; pInfo: PDevBroadcastVolume; bAccept: Boolean; sDrive: String; procedure compare_drives; begin dwAddDrv := dwLogicalDrvs_ xor dwNewDrvs; dwDelDrv := dwAddDrv; dwAddDrv := dwNewDrvs and dwAddDrv; dwDelDrv := dwLogicalDrvs_ and dwDelDrv; end; procedure ProcessChangeDevice(dwDrvs: DWORD; bAdd: Boolean); var i: Integer; m: DWORD; begin if dwDrvs = 0 then exit; for i := 0 to 31 do begin m := dwDrvs and (1 shl i); if m <> 0 then begin // 일단 이것만 쓰니깐 이것만 채워준다 pInfo.dwUnitmask := m; if bAdd and Assigned(evUSBArrival_) then begin evUSBArrival_(self, pInfo); // 추가 22_0504 13:49:21 kku if Assigned(evUSBQueryRemove_) then begin var sDrive: String := GetDriveFromMask(pInfo.dwUnitmask); if GetDriveExtent(sDrive).liExtentLength.QuadPart <> 0 then begin if GetDriveSize(sDrive) <> 0 then case Integer(GetDriveType(PChar(sDrive))) of DRIVE_REMOVABLE, DRIVE_FIXED : RegisterDeviceChange(sDrive); end; end; end; end else if not bAdd and Assigned(evUSBRemove_) then evUSBRemove_(self, pInfo); end; end; end; begin // DBT_DEVICEQUERYREMOVE // DBT_DEVICEQUERYREMOVEFAILED // DBT_DEVICEREMOVECOMPLETE // flags & DBTF_MEDIA = 1 -> 씨디룸 // flags & DBTF_MEDIA = 2 -> 네트워크 드라이브 // 반응속도가 참 느리네.. 비스타에서! case msg.WParam of {$IF true} DBT_DEVICEARRIVAL : begin case PDevBroadcastHdr(msg.LParam).dwDevicetype of DBT_DEVTYP_VOLUME : begin if Assigned(evUSBArrival_) then begin evUSBArrival_(self, PDevBroadcastVolume(msg.LParam)); // 추가 22_0504 13:49:21 kku if Assigned(evUSBQueryRemove_) then begin sDrive := GetDriveFromMask(PDevBroadcastVolume(msg.LParam).dwUnitmask); if GetDriveExtent(sDrive).liExtentLength.QuadPart <> 0 then begin if GetDriveSize(sDrive) <> 0 then case Integer(GetDriveType(PChar(sDrive))) of DRIVE_REMOVABLE, DRIVE_FIXED : RegisterDeviceChange(sDrive); end; end; end; end; sDrive := GetDriveFromMask(PDevBroadcastVolume(msg.LParam).dwUnitmask); if Assigned(evUSBQueryRemove_) then RegisterDeviceChange(sDrive); // begin // if register_deviceChange(sDrive) then // OutputDebugString('register_deviceChange Succss~~~~~~~~~~~~~~~~~') // else // OutputDebugString('register_deviceChange Fail!!!!!!!!!!!!!!!!!!!!!'); // end; end; DBT_DEVTYP_DEVICEINTERFACE : begin if Assigned(evDevArrival_) then begin evDevArrival_(Self, PDevBroadcastDeviceInterface(msg.LParam)); _Trace('DevArrival = %s', [GUIDToString(PDevBroadcastDeviceInterface(msg.LParam).ClassGUID)]); end; end; end; end; DBT_DEVICEQUERYREMOVE : begin if PDevBroadcastHdr(msg.LParam).dwDevicetype = DBT_DEVTYP_HANDLE then if Assigned(evUSBQueryRemove_) then begin sDrive := GetQueryRemovePath(PDevBroadcastHandle(msg.LParam).dbch_hdevnotify); if sDrive <> '' then begin bAccept := true; evUSBQueryRemove_(self, sDrive, bAccept); if not bAccept then // 거부임? 그럼 거부 msg.Result := BROADCAST_QUERY_DENY; end; end; end; DBT_DEVICEREMOVECOMPLETE : begin case PDevBroadcastHdr(msg.LParam).dwDevicetype of DBT_DEVTYP_VOLUME : begin if Assigned(evUSBRemove_) then evUSBRemove_(self, PDevBroadcastVolume(msg.LParam)); end; DBT_DEVTYP_HANDLE : begin try DcQueryRemoveNotify_.Remove(PDevBroadcastHandle(msg.LParam).dbch_hdevnotify); except // 건덕지 없을듯 end; end; DBT_DEVTYP_DEVICEINTERFACE : begin if Assigned(evDevRemove_) then begin evDevRemove_(Self, PDevBroadcastDeviceInterface(msg.LParam)); _Trace('DevRemove = %s', [GUIDToString(PDevBroadcastDeviceInterface(msg.LParam).ClassGUID)]); end; end; end; end; {$ELSE} // 이건 특정한 상황(?)에서 메세지가 이거 빼곤 다른게 안받아져서 구현한 거임.. // 급해서 이렇게 만들어서 썼는데 이거보단 위에껄 추천함 DBT_DEVNODES_CHANGED : begin if bDeviceChanging_ then exit; bDeviceChanging_ := true; dwNewDrvs := GetLogicalDrives; nTime := 0; while nTime < 5000 do begin if dwLogicalDrvs_ <> dwNewDrvs then begin New(pInfo); ZeroMemory(pInfo, SizeOf(TDevBroadcastVolume)); try compare_drives; dwLogicalDrvs_ := dwNewDrvs; ProcessChangeDevice(dwAddDrv, true); ProcessChangeDevice(dwDelDrv, false); finally Dispose(pInfo); bDeviceChanging_ := false; end; break; end; dwNewDrvs := GetLogicalDrives; WaitForTimer(10); Inc(nTime); end; end; {$IFEND} end; end; // not use function TTgUSBEventNotify.RegisterDeviceChange: Boolean; var dbi : DEV_BROADCAST_DEVICEINTERFACE; nSize : Integer; hDev : HDEVNOTIFY; begin Result := False; nSize := SizeOf(DEV_BROADCAST_DEVICEINTERFACE); ZeroMemory(@dbi, nSize); dbi.dwSize := nSize; dbi.dwDevicetype := DBT_DEVTYP_DEVICEINTERFACE; dbi.ClassGUID := GUID_DEVINTERFACE_USB_DEVICE; hDev := RegisterDeviceNotification(hWindowHandle_, @dbi, DEVICE_NOTIFY_WINDOW_HANDLE); if hDev <> nil then Result := True; end; function TTgUSBEventNotify.RegisterDeviceChange(sPath: String): Boolean; var hDev : HDEVNOTIFY; hPath : THandle; DevHandle : TDevBroadcastHandle; begin Result := false; try // 윈도우 xp에서 빈디스크 에러 메시지 걸러내기 if GetDriveExtent(sPath).liExtentLength.QuadPart = 0 then exit; if not DirectoryExists(sPath) then exit; hPath := CreateFile(PChar(sPath), GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL, 0); if hPath = INVALID_HANDLE_VALUE then exit; try ZeroMemory(@DevHandle, SizeOf(TDevBroadcastHandle)); DevHandle.dbch_size := SizeOf(TDevBroadcastHandle); DevHandle.dbch_devicetype := DBT_DEVTYP_HANDLE; DevHandle.dbch_handle := hPath; hDev := RegisterDeviceNotification(hWindowHandle_, @DevHandle, DEVICE_NOTIFY_WINDOW_HANDLE); if hDev <> nil then begin DcQueryRemoveNotify_.Add(hDev, sPath); Result := true; end; finally CloseHandle(hPath); end; except on E: Exception do ETgException.TraceException(Self, E); end; end; procedure Get_VID_PID(sInfo: AnsiString; var sVid: AnsiString; var sPid: AnsiString); var nPos1, nPos2, nLen: Integer; begin sInfo := LowerCase(sInfo); nPos1 := Pos('vid_', sInfo); nPos2 := Pos('pid_', sInfo); if (nPos1 = 0) or (nPos2 = 0) then exit; nLen := Length(sInfo); sVid := Copy(sInfo, nPos1+4, nLen-nPos2-3); sPid := Copy(sInfo, nPos2+4, nLen-nPos2-3); end; { TTgUSBStorInfo } Constructor TTgUSBStorInfo.Create; var ver: TWinVerInfo; begin Inherited Create; bIsVista_ := false; ver := GetWinVersion; // 비스타나 체크한다 if ver.Version.Major = 6 then bIsVista_ := true; USBStorList_ := TList.Create; USBStorList_.OnNotify := OnUSBStorNotify; end; Destructor TTgUSBStorInfo.Destroy; begin FreeAndNil(USBStorList_); Inherited; end; procedure TTgUSBStorInfo.OnUSBStorNotify(Sender: TObject; const Item: PUSBRec; Action: TCollectionNotification); begin case Action of cnAdded: ; cnRemoved: Dispose(Item); cnExtracted: ; end; end; function TTgUSBStorInfo.GetUSBInfoByLetter(cDrive: AnsiChar): PUSBRec; var pData: PUSBRec; i: Integer; begin Result := nil; for i := 0 to USBStorList_.Count - 1 do begin pData := USBStorList_[i]; if (pData.sDriveLetter <> '') and (pData.sDriveLetter[1] = cDrive) then begin Result := pData; exit; end; end; end; function TTgUSBStorInfo.GetCount: Integer; begin Result := USBStorList_.Count; end; function TTgUSBStorInfo.GetInfo(nIndex: Integer): PUSBRec; begin if (nIndex >= 0) and (nIndex < Count) then Result := USBStorList_[nIndex] else Result := nil; end; procedure TTgUSBStorInfo.PutInfo(nIndex: Integer; const pData: PUSBRec); begin if (nIndex >= 0) and (nIndex < Count) then USBStorList_[nIndex] := pData; end; // USB 목록에서 Service가 USBSTOR인것을 추출하여 // 장치이름, 설명, 생성일자, 시리얼 넘버, vid, pid를 얻어온다. function TTgUSBStorInfo.GetUSBInfoStep_1: Boolean; var pData : PUSBRec; reg, regSub : TRegistry; lstKey, lstSubKey, lstSubSubKey : TStringList; i, j, n : Integer; sKey : String; dtCreate, dtCompCreate : TDateTime; regInfo : TRegKeyInfo; begin Result := false; Guard(reg, TRegistry.Create); reg.RootKey := HKEY_LOCAL_MACHINE; if not reg.OpenKeyReadOnly(REG_ENUM_USB) then begin // ASSERT(false); exit; end; Guard(regSub, TRegistry.Create); regSub.RootKey := HKEY_LOCAL_MACHINE; Guard(lstKey, TStringList.Create); Guard(lstSubKey, TStringList.Create); Guard(lstSubSubKey, TStringList.Create); reg.GetKeyNames(lstKey); for i := 0 to lstKey.Count - 1 do begin sKey := REG_ENUM_USB + lstKey[i] + '\'; lstSubKey.Clear; reg.CloseKey; if not reg.OpenKeyReadOnly(sKey) then begin // ASSERT(false); exit; end; reg.GetKeyNames(lstSubKey); for j := 0 to lstSubKey.Count - 1 do begin reg.CloseKey; if not reg.OpenKeyReadOnly(sKey + lstSubKey[j]) then begin // ASSERT(false); exit; end; // 이 시간이.. 하위 키값때문인지 바뀔수도 있다.. // 하위 키값이 더 오래된거면 그걸로 맞춘다. if reg.GetKeyInfo(regInfo) then begin dtCreate := ConvFileTimeToDateTime_Local(regInfo.FileTime); reg.GetKeyNames(lstSubSubKey); for n := 0 to lstSubSubKey.Count - 1 do if regSub.OpenKeyReadOnly(sKey + lstSubKey[j] + '\' + lstSubSubKey[n]) then begin if regSub.GetKeyInfo(regInfo) then begin dtCompCreate := ConvFileTimeToDateTime_Local(regInfo.FileTime); if dtCompCreate < dtCreate then dtCreate := dtCompCreate; end; regSub.CloseKey; end; end else dtCreate := 0; if LowerCase(reg.ReadString('Service')) = 'usbstor' then begin New(pData); ZeroMemory(pData, SizeOf(TUSBRec)); pData.sDeviceName := reg.ReadString('LocationInformation'); pData.sDescription := reg.ReadString('DeviceDesc'); // 외장 USB하드는 ParentIdPrefix가 여기에 위치하는거 같다.. // 이건.. serial2로 넣도록하자 // USBSTOR에 또 ParentIdPrefix가 있는데 이건 볼륨명 찾을때 필요하다. // 둘이 무슨차이가 있는거지..? if reg.ValueExists('ParentIdPrefix') then begin pData.sSerial2 := reg.ReadString('ParentIdPrefix'); end else begin pData.sSerial2 := lstSubKey[j]; if Length(pData.sSerial2) > 30 then SetLength(pData.sSerial2, 30); end; // n := Pos('&', lstSubKey[j]); // if n = 0 then pData.sSerial := lstSubKey[j]; pData.sSerial := lstSubKey[j]; if Length(pData.sSerial) > 30 then SetLength(pData.sSerial, 30); // pData.sSerial2 := lstSubKey[j]; Get_VID_PID(Ansistring(lstKey[i]), pData.sVid, pData.sPid); pData.dtCreate := dtCreate; pData.nDiskNum := -1; USBStorList_.Add(pData); end; end; end; Result := true; end; // 드라이브 볼륨명을 얻기위해 ParentIdPrefix 값을 구하고 // USB의 FriendlyName을 구한다. function TTgUSBStorInfo.GetUSBInfoStep_2: Boolean; var pData : PUSBRec; reg : TRegistry; lstKey, lstSubKey : TStringList; sKey : String; i, j, c : Integer; begin Result := false; Guard(reg, TRegistry.Create); reg.RootKey := HKEY_LOCAL_MACHINE; if not reg.KeyExists(REG_ENUM_USBSTOR) then begin Result := True; Exit; end; if not reg.OpenKeyReadOnly(REG_ENUM_USBSTOR) then begin // ASSERT(false); exit; end; Guard(lstKey, TStringList.Create); Guard(lstSubKey, TStringList.Create); reg.GetKeyNames(lstKey); for i := 0 to lstKey.Count - 1 do begin sKey := REG_ENUM_USBSTOR + lstKey[i] + '\'; lstSubKey.Clear; reg.CloseKey; if not reg.OpenKeyReadOnly(sKey) then begin // ASSERT(false); exit; end; reg.GetKeyNames(lstSubKey); for j := 0 to lstSubKey.Count - 1 do begin reg.CloseKey; if not reg.OpenKeyReadOnly(sKey + lstSubKey[j]) then begin // ASSERT(false); exit; end; for c := 0 to USBStorList_.Count - 1 do begin pData := USBStorList_[c]; if Pos(pData.sSerial, lstSubKey[j]) > 0 then begin if reg.ValueExists('ParentIdPrefix') then pData.sParentIdPrefix := reg.ReadString('ParentIdPrefix'); if pData.sFriendlyName = '' then pData.sFriendlyName := reg.ReadString('FriendlyName'); // vista의 경우 추가 정보획득.. // if bIsVista_ then begin pData.sUsbstor := lstKey[i]; pData.sSerial2 := lstSubKey[j]; end; end else if lstSubKey[j] = pData.sSerial2 then begin // 외장 USB하드의 경우를 위해 이렇게 처리 // 마지막 사용일자를 구하는 위치가 다른데.. 그 위치를 구하기 위한 준비 if reg.ValueExists('ParentIdPrefix') then pData.sParentIdPrefix := reg.ReadString('ParentIdPrefix'); pData.sUsbstor := lstKey[i]; // pData.sSerial2 := lstSubKey[j]; end; end; end; end; Result := true; end; // 물리디스크 번호를 얻어온다. 0이상이면 연결된 상태이고 -1이면 없는상태이다 function TTgUSBStorInfo.GetUSBInfoStep_3: Boolean; var pData : PUSBRec; reg : TRegistry; // lstValue : TStringList; i, c, n, nCnt : Integer; sVal : String; begin Result := false; Guard(reg, TRegistry.Create); reg.RootKey := HKEY_LOCAL_MACHINE; if not reg.OpenKeyReadOnly(REG_ENUM_USE_DISK_NUM) then begin // ASSERT(false); exit; end; if not reg.ValueExists('Count') then exit; nCnt := reg.ReadInteger('Count'); for i := 0 to nCnt - 1 do begin sVal := IntToStr(i); if not reg.ValueExists(sVal) then break; sVal := reg.ReadString(sVal); for c := 0 to USBStorList_.Count - 1 do begin pData := USBStorList_[c]; // vista의 경우 serial로 검사~ // if bIsVista_ then // begin // n := Pos(pData.sSerial2, sVal); // end else // n := Pos(pData.sParentIdPrefix, sVal); n := Pos(pData.sSerial2, sVal); // 이건 xp나 비스타나 똑같네 if n <> 0 then pData.nDiskNum := i; end; end; Result := true; end; // 마운트 정보를 확인해서 드라이브 볼륨명을 구한다. function TTgUSBStorInfo.GetUSBInfoStep_4: Boolean; var reg : TRegistry; lstValue : TStringList; i, c : Integer; sLetter, sValue, sBuf : String; pData : PUSBRec; regInfo : TRegDataInfo; buf : array of WideChar; function ExtractDriveLetter(const str: String): String; var nPos: Integer; begin nPos := LastDelimiter('\', str); if nPos = -1 then begin Result := ''; exit; end; Result := UpperCase(Copy(str, nPos+1, Length(str)-nPos)); end; begin Result := false; Guard(reg, TRegistry.Create); reg.RootKey := HKEY_LOCAL_MACHINE; if not reg.OpenKeyReadOnly(REG_MOUNTED_DEVICES) then begin // ASSERT(false); exit; end; Guard(lstValue, TStringList.Create); reg.GetValueNames(lstValue); for i := 0 to lstValue.Count - 1 do begin sValue := LowerCase(lstValue[i]); if Pos('dosdevices', sValue) <> 0 then begin if not reg.GetDataInfo(sValue, regInfo) then begin // ASSERT(false); exit; end; // 20보다 작으면 아니라고 보는게 좋다.. // 외장 하드 디스크의 경우엔 이렇게 나타나는데 다른방식으로 구해주도록 하자 if regInfo.DataSize < 20 then begin sLetter := ExtractDriveLetter(sValue); for c := 0 to USBStorList_.Count - 1 do begin pData := USBStorList_[c]; if pData.nDiskNum < 0 then continue; if pData.nDiskNum = GetDriveExtent(sLetter).dwDiskNumber then begin pData.sDriveLetter := sLetter; break; end; end; continue; end; if regInfo.RegData = rdBinary then begin SetLength(buf, regInfo.DataSize+1); ZeroMemory(buf, regInfo.DataSize+1); reg.ReadBinaryData(sValue, buf[0], regInfo.DataSize); sBuf := WideCharToString(@buf[0]); for c := 0 to USBStorList_.Count - 1 do begin pData := USBStorList_[c]; if (Pos(pData.sParentIdPrefix, sBuf) <> 0) or (Pos(pData.sSerial2, sBuf) <> 0) then begin pData.sDriveLetter := ExtractDriveLetter(sValue); break; end; end; end; end; end; Result := true; end; // 가장 최근에 연결/해제한 날짜를 가져온다. function TTgUSBStorInfo.GetUSBInfoStep_5: Boolean; var reg : TRegistry; pData : PUSBRec; i : Integer; regInfo : TRegKeyInfo; sKey : String; begin Result := true; Guard(reg, TRegistry.Create); reg.RootKey := HKEY_LOCAL_MACHINE; for i := 0 to Count - 1 do begin pData := USBStorList_[i]; // if (bIsVista_ = false) and (pData.sParentIdPrefix = '') then continue; // vista와 2000, xp는 다르다!! if bIsVista_ then sKey := Format(REG_USB_LASTWRITE_CONTROL_VISTA, [pData.sUsbstor, pData.sSerial2]) else begin // 외장 USB하드는 serial이 정보에 포함되지 않기때문에 이렇게 구분 if (pData.sSerial = '') or (pData.sParentIdPrefix = '') then sKey := Format(REG_USB_LASTWRITE_CONTROL1, [pData.sUsbstor, pData.sSerial2]) else sKey := Format(REG_USB_LASTWRITE_CONTROL2, [pData.sParentIdPrefix]); end; reg.CloseKey; if reg.OpenKeyReadOnly(sKey) then begin if reg.GetKeyInfo(regInfo) then pData.dtLastWrite := ConvFileTimeToDateTime_Local(regInfo.FileTime); end; end; end; function TTgUSBStorInfo.UpdateUSBStorInfo: Boolean; begin USBStorList_.Clear; Result := GetUSBInfoStep_1; if not Result then exit; Result := GetUSBInfoStep_2; if not Result then exit; Result := GetUSBInfoStep_3; if not Result then exit; Result := GetUSBInfoStep_4; if not Result then exit; Result := GetUSBInfoStep_5; if not Result then exit; end; end.