unit IEDriveInfo; {================================================================== Component TDriveInfo / Version 2.6 / January 2000 ==================================================== Description: ============ Central drive management class. Provides information about all installed drives, for example wether drive is valid, disk is inserted displayname or volume size. Author: ======= (c) Ingo Eckel 1999 Sodener Weg 38 65812 Bad Soden Germany For detailed documentation and history see the documentation in TDriveInfo.htm. {==================================================================} {Required compiler options:} {$A+,B-,X+,H+,P+} interface uses Windows, Registry, SysUtils, Classes, ComCtrls, ShellApi, ShlObj, CommCtrl, Forms, BaseUtils; const {Flags used by TDriveInfo.ReadDriveStatus and TDriveView.RefreshRootNodes:} dsValid = 0; {checks only whether drive is still valid} dsImageIndex = 1; {Fetch imageindex, if not allready fetched} dsSize = 2; {Fetch disk size and serialnumber} dsDisplayName = 4; {Fetch drives displayname} dsAll = dsImageIndex or dsSize or dsDisplayName; FirstDrive = 'A'; FirstFixedDrive = 'C'; LastDrive = 'Z'; FirstSpecialFolder = CSIDL_DESKTOP; LastSpecialFolder = CSIDL_PRINTHOOD; type TDrive = Char; PDriveInfoRec = ^TDriveInfoRec; TDriveInfoRec = record PIDL : PItemIDList; {Fully qualyfied PIDL} Init : Boolean; {Drivestatus was updated once} Valid : Boolean; {Drivestatus is valid} DriveReady : Boolean; {Drive is ready} DriveType : Integer; {DRIVE_REMOVABLE, DRIVE_FIXED, DRIVE_CDROM, DRIVE_RAMDISK, DRIVE_REMOTE} DisplayName : string; {Windows displayname} PrettyName : string; {Prettyfied displayname} DriveSerial : DWORD; {Serial number of the drive} Size : Int64; {Drivesize} ImageIndex : Integer; {Drive imageIndex} end; TSpecialFolder = FirstSpecialFolder..LastSpecialFolder; PSpecialFolderRec = ^TSpecialFolderRec; TSpecialFolderRec = record Valid: Boolean; Location: string; DisplayName: string; ImageIndex: Integer; PIDL: PItemIDList; end; TDriveInfo = class(TObject) private FData: array[FirstDrive..LastDrive] of TDriveInfoRec; FNoDrives: DWORD; FDesktop: IShellFolder; FFolders: array[TSpecialFolder] of TSpecialFolderRec; FHonorDrivePolicy: Boolean; function GetData(Drive: TDrive): PDriveInfoRec; function GetFolder(Folder: TSpecialFolder): PSpecialFolderRec; procedure ReadDriveBasicStatus(Drive: TDrive); procedure ResetDrive(Drive: TDrive); procedure SetHonorDrivePolicy(Value: Boolean); public property Data[Drive: TDrive]: PDriveInfoRec read GetData; default; property SpecialFolder[Folder: TSpecialFolder]: PSpecialFolderRec read GetFolder; function GetImageIndex(Drive: TDrive): Integer; function GetDisplayName(Drive: TDrive): string; function GetPrettyName(Drive: TDrive): string; function ReadDriveStatus(Drive: TDrive; Flags: Integer): Boolean; property HonorDrivePolicy: Boolean read FHonorDrivePolicy write SetHonorDrivePolicy; constructor Create; destructor Destroy; override; procedure Load; end; function GetShellFileName(const Name: string): string; overload; function GetShellFileName(PIDL: PItemIDList): string; overLoad; function GetNetWorkName(Drive: Char): string; function GetNetWorkConnected(Drive: Char): Boolean; {Central drive information object instance of TDriveInfo} var DriveInfo : TDriveInfo; resourceString ErrorInvalidDrive = '%s is a invalid drive letter.'; implementation uses Math, PIDL, OperationWithTimeout; constructor TDriveInfo.Create; begin inherited; FHonorDrivePolicy := True; Load; end; {TDriveInfo.Create} destructor TDriveInfo.Destroy; var Drive: TDrive; begin for Drive := FirstDrive to LastDrive do with FData[Drive] do begin SetLength(DisplayName, 0); SetLength(PrettyName, 0); // This causes access violation // FreePIDL(PIDL); end; inherited; end; {TDriveInfo.Destroy} function TDriveInfo.GetFolder(Folder: TSpecialFolder): PSpecialFolderRec; var FileInfo: TShFileInfo; Path: PChar; Flags: Word; begin Assert((Folder >= Low(FFolders)) and (Folder <= High(FFolders))); with FFolders[Folder] do begin if not Valid then begin SpecialFolderLocation(Folder, Location, PIDL); if Assigned(PIDL) then begin Path := PChar(PIDL); Flags := SHGFI_PIDL; end else begin Path := PChar(Location); Flags := 0; end; SHGetFileInfo(Path, 0, FileInfo, SizeOf(FileInfo), SHGFI_DISPLAYNAME or SHGFI_SYSICONINDEX or SHGFI_SMALLICON or Flags); ImageIndex := FileInfo.iIcon; DisplayName := FileInfo.szDisplayName; Valid := True; end; end; Result := @FFolders[Folder]; end; procedure TDriveInfo.SetHonorDrivePolicy(Value: Boolean); var Drive: TDrive; begin if HonorDrivePolicy <> Value then begin FHonorDrivePolicy := Value; for Drive := FirstDrive to LastDrive do begin ReadDriveBasicStatus(Drive); end; end; end; procedure TDriveInfo.ReadDriveBasicStatus(Drive: TDrive); begin with FData[Drive] do begin DriveType := Windows.GetDriveType(PChar(Drive + ':\')); Valid := ((not FHonorDrivePolicy) or (not Bool((1 shl (Ord(Drive) - 65)) and FNoDrives))) and (DriveType in [DRIVE_REMOVABLE, DRIVE_FIXED, DRIVE_CDROM, DRIVE_RAMDISK, DRIVE_REMOTE]); end; end; procedure TDriveInfo.ResetDrive(Drive: TDrive); begin with FData[Drive] do begin DriveReady := False; DisplayName := ''; PrettyName := ''; DriveSerial := 0; Size := -1; ImageIndex := 0; end; end; procedure TDriveInfo.Load; var Drive: TDrive; Reg: TRegistry; Folder: TSpecialFolder; begin FNoDrives := 0; Reg := TRegistry.Create; try if Reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Policies\Explorer') Then Reg.ReadBinaryData('NoDrives', FNoDrives, SizeOf(FNoDrives)); except try FNoDrives := Reg.ReadInteger('NoDrives'); except end; end; Reg.Free; FDesktop := nil; for Drive := FirstDrive to LastDrive do begin with FData[Drive] do begin ReadDriveBasicStatus(Drive); Init := False; PIDL := nil; ResetDrive(Drive); end; end; for Folder := Low(FFolders) to High(FFolders) do FFolders[Folder].Valid := False; end; function TDriveInfo.GetImageIndex(Drive: TDrive): Integer; begin if (Drive < FirstDrive) or (Drive > LastDrive) then raise EConvertError.Create(Format(ErrorInvalidDrive, [Drive])); Result := 0; if FData[Drive].Valid then begin if FData[Drive].ImageIndex = 0 then ReadDriveStatus(Drive, dsImageIndex); Result := FData[Drive].ImageIndex; end; end; {TDriveInfo.GetImageIndex} function TDriveInfo.GetDisplayName(Drive: TDrive): string; begin if (Drive < FirstDrive) or (Drive > LastDrive) then raise EConvertError.Create(Format(ErrorInvalidDrive, [Drive])); Result := Drive + ':'; if FData[Drive].Valid then begin if Length(FData[Drive].DisplayName) = 0 then ReadDriveStatus(Drive, dsDisplayName); Result := FData[Drive].DisplayName; end; end; {TDriveInfo.GetDisplayname} function TDriveInfo.GetPrettyName(Drive: TDrive): string; begin if (Drive < FirstDrive) or (Drive > LastDrive) then raise EConvertError.Create(Format(ErrorInvalidDrive, [Drive])); Result := Drive + ':'; if FData[Drive].Valid then begin if Length(FData[Drive].PrettyName) = 0 then ReadDriveStatus(Drive, dsDisplayName); Result := FData[Drive].PrettyName; end; end; {TDriveInfo.GetPrettyName} function TDriveInfo.GetData(Drive: TDrive): PDriveInfoRec; begin if not CharInSet(Upcase(Drive), ['A'..'Z']) then raise EConvertError.Create(Format(ErrorInvalidDrive, [Drive])); Result := @FData[Upcase(Drive)]; end; {TDriveInfo.GetData} function TDriveInfo.ReadDriveStatus(Drive: TDrive; Flags: Integer): Boolean; var ErrorMode: Word; FileInfo: TShFileInfo; DriveID: string; CPos: Integer; Eaten: ULONG; ShAttr: ULONG; MaxFileNameLength: DWORD; FileSystemFlags: DWORD; begin if not Assigned(FDesktop) then SHGetDesktopFolder(FDesktop); Drive := Upcase(Drive); if (Drive < FirstDrive) or (Drive > LastDrive) then raise EConvertError.Create(Format(ErrorInvalidDrive, [Drive])); with FData[Drive] do begin Init := True; ReadDriveBasicStatus(Drive); if Valid then begin if (not Assigned(PIDL)) and (Drive >= FirstFixedDrive) then begin ShAttr := 0; if DriveType = DRIVE_REMOTE then begin ShellFolderParseDisplayNameWithTimeout( FDesktop, Application.Handle, nil, PChar(Drive + ':\'), Eaten, PIDL, ShAttr, 2 * MSecsPerSec); end else begin FDesktop.ParseDisplayName(Application.Handle, nil, PChar(Drive + ':\'), Eaten, PIDL, ShAttr); end; end; {Read driveStatus:} if (Flags and dsSize) <> 0 then begin { turn off critical errors } ErrorMode := SetErrorMode(SEM_FailCriticalErrors or SEM_NOOPENFILEERRORBOX); try { drive 1 = a, 2 = b, 3 = c, etc. } Size := DiskSize(Ord(Drive) - $40); DriveReady := (Size >= 0); if DriveReady then begin {Access the physical drive:} if GetVolumeInformation(PChar(Drive + ':\'), nil, 0, @DriveSerial, MaxFileNameLength, FileSystemFlags, nil, 0) then begin end else begin DriveSerial := 0; end; end else begin DriveSerial := 0; end; finally { restore old error mode } SetErrorMode(ErrorMode); end; end; {DisplayName:} if (Flags and dsDisplayName <> 0) then begin {Fetch drives displayname:} if Assigned(PIDL) then DisplayName := GetShellFileName(PIDL) else if Drive < FirstFixedDrive then DisplayName := GetShellFileName(Drive + ':\') // typical reason we do not have PIDL is that it took too long to // call ParseDisplayName, in what case calling SHGetFileInfo with // path (instead of PIDL) will take long too, avoiding that and using // fallback else DisplayName := '(' + Drive + ':)'; if DriveType <> DRIVE_REMOTE then begin PrettyName := Drive + ': ' + DisplayName; CPos := Pos(' (' + Drive + ':)', PrettyName); if CPos > 0 then Delete(PrettyName, CPos, 5); end else begin DriveID := GetNetWorkName(Drive); PrettyName := Format('%s: %s (%s)', [Drive, ExtractFileName(DriveID), ExtractFileDir(DriveID)]); end; end; {ImageIndex:} if ((Flags and dsImageIndex) <> 0) and (ImageIndex < 5) then begin if Assigned(PIDL) then begin SHGetFileInfo(PChar(PIDL), 0, FileInfo, SizeOf(FileInfo), SHGFI_SYSICONINDEX or SHGFI_SMALLICON or SHGFI_PIDL) end else begin SHGetFileInfo(PChar(Drive + ':\'), 0, FileInfo, SizeOf(FileInfo), SHGFI_SYSICONINDEX or SHGFI_SMALLICON); end; ImageIndex := FileInfo.iIcon; end; end else begin if Assigned(PIDL) then FreePIDL(PIDL); ResetDrive(Drive); end; Result := Valid and DriveReady; end; end; {TDriveInfo.ReadDriveStatus} function GetShellFileName(const Name: string): string; var SFI: TSHFileInfo; E: Integer; begin E := SetErrorMode(SEM_FAILCRITICALERRORS); try if SHGetFileInfo(PChar(Name), 0, SFI, SizeOf(TSHFileInfo), SHGFI_DISPLAYNAME) <> 0 then Result := SFI.szDisplayName; finally SetErrorMode(E); end; end; {GetShellFileName} function GetShellFileName(PIDL: PItemIDList): string; var SFI: TSHFileInfo; E: Integer; begin E := SetErrorMode(SEM_FAILCRITICALERRORS); try if SHGetFileInfo(PChar(PIDL), 0, SFI, SizeOf(TSHFileInfo), SHGFI_PIDL or SHGFI_DISPLAYNAME) <> 0 then Result := SFI.szDisplayName; finally SetErrorMode(E); end; end; {GetShellFileName} function GetNetWorkName(Drive: Char): string; var P: array[0..MAX_PATH] of Char; MaxLen : DWORD; begin MaxLen := MAX_PATH; if WNetGetConnection(PChar(string(Drive + ':')), P, MaxLen) = NO_ERROR then Result := P else Result := ''; end; {GetNetWorkName} type LPBYTE = ^BYTE; LMSTR = LPWSTR; NET_API_STATUS = DWORD; _USE_INFO_1 = record ui1_local: LMSTR; ui1_remote: LMSTR; ui1_password: LMSTR; ui1_status: DWORD; ui1_asg_type: DWORD; ui1_refcount: DWORD; ui1_usecount: DWORD; end; USE_INFO_1 = _USE_INFO_1; PUSE_INFO_1 = ^USE_INFO_1; LPVOID = Pointer; const USE_OK = 0; USE_PAUSED = 1; USE_SESSLOST = 2; USE_DISCONN = 2; USE_NETERR = 3; USE_CONN = 4; USE_RECONN = 5; function NetUseGetInfo(UncServerName: LMSTR; UseName: LMSTR; Level: DWORD; var BufPtr: LPBYTE): NET_API_STATUS; stdcall; external 'netapi32.dll'; function NetApiBufferFree(Buffer: Pointer): DWORD; stdcall; external 'netapi32.dll'; function GetNetWorkConnected(Drive: Char): Boolean; var BufPtr: LPBYTE; NetResult: Integer; begin NetResult := NetUseGetInfo(nil, PChar(Drive + ':'), 1, BufPtr); if NetResult = 0 then begin Result := (PUSE_INFO_1(BufPtr)^.ui1_status = USE_OK); NetApiBufferFree(LPVOID(BufPtr)); end else begin // NetUseGetInfo works for DFS shares only, hence when it fails // we suppose different share type and fallback to "connected" Result := True; end; end; initialization if not Assigned(DriveInfo) then DriveInfo := TDriveInfo.Create; finalization if Assigned(DriveInfo) then begin DriveInfo.Free; DriveInfo := nil; end; end.