Subversion Repositories recyclebinunit

Rev

Rev 102 | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. unit RecBinUnit2 platform;
  2.  
  3. ////////////////////////////////////////////////////////////////////////////////////
  4. // RECYCLE-BIN-UNIT V2 BY DANIEL MARSCHALL, VIATHINKSOFT                          //
  5. // E-MAIL: info@daniel-marschall.de                                               //
  6. // Web:    www.daniel-marschall.de & www.viathinksoft.de                          //
  7. ////////////////////////////////////////////////////////////////////////////////////
  8. // Revision: 04 JUN 2023                                                          //
  9. // This unit is freeware, but please link to my website if you are using it!      //
  10. ////////////////////////////////////////////////////////////////////////////////////
  11. // Successfully tested with:                                                      //
  12. // Windows 95b (without IE4 Shell Extensions)                                     //
  13. // Windows 95b (with IE4 Shell Extensions)                                        //
  14. // Windows 98 SE                                                                  //
  15. // Windows NT4 SP6                                                                //
  16. // Windows XP SP3                                                                 //
  17. // Windows 2000 SP4                                                               //
  18. // Windows 2003 Server EE SP1                                                     //
  19. // Windows Vista                                                                  //
  20. // Windows 7                                                                      //
  21. // Windows 10 (version 1 and version 2 format)                                    //
  22. // Windows 11                                                                     //
  23. ////////////////////////////////////////////////////////////////////////////////////
  24.  
  25. // Delphi 7 Compatibility:  (TODO: compiler switches)
  26. // - Remove "static"
  27. // - Remove "strict"
  28. // - Remove "$REGION"
  29.  
  30. // TODO: ReadBuffer überall try-except
  31. // TODO: Always check EOF before reading anything?
  32. // TODO: Don't crash when timestamp is invalid. Do something else instead.
  33. // TODO: Is it possible to identify a Vista-file that is not named $Ixxxxxx.ext?
  34. // TODO: RecyclerGetInfofiles() check additionally for removable device?
  35. //       RecyclerIsValid() is false.
  36. // TODO: Make it possible to empty the recycle bin of one specific drive!
  37. // TODO: Unknown! Do you know the answer?
  38. //       - How does Windows 9x/NT manage the daylight saving time (if it does)?
  39. //       - How does Windows Vista+ react to a RECYCLER\ folder on a NTFS device?
  40. //       - How does Windows Vista+ react to a RECYCLED\ folder on a FAT device? ==> Win7: is ignored!
  41. //       - How does Windows XP react to RECYCLED\ folder on a FAT device?
  42. // TODO: Translate all comments from German to English
  43. // TODO: Do we need this (maybe not all drives have A: till Z:?) http://stackoverflow.com/questions/17110543/how-to-retrieve-the-disk-signature-of-all-the-disks-in-windows-using-delphi-7
  44. // TODO: Add a lot of setters for system config stuff
  45.  
  46. // If enabled, the deletion timestamps will not be converted by the WinAPI.
  47. {.$DEFINE FILETIME_DELPHI_CODE}
  48.  
  49. // If a value is set in HKEY_LOCAL_MACHINE, it will be prefered, even if gpedit.msc shows "Not configured"!
  50. {$DEFINE GroupPolicyAcceptHKLMTrick}
  51.  
  52. interface
  53.  
  54. uses
  55.   Windows, SysUtils, Classes, ContNrs, ShellAPI, Registry, Messages, Math;
  56.  
  57. const
  58.   RECBINUNIT_VERSION = '2023-06-04';
  59.  
  60.   RECYCLER_CLSID: TGUID = '{645FF040-5081-101B-9F08-00AA002F954E}';
  61.   NULL_GUID:      TGUID = '{00000000-0000-0000-0000-000000000000}';
  62.  
  63. type
  64.   EAPICallError = class(Exception);
  65.   EEventCategoryNotDefined = class(Exception);
  66.   EInvalidDrive = class(Exception);
  67.  
  68.   PSHQueryRBInfo = ^TSHQueryRBInfo;
  69.   {$IFDEF WIN64}
  70.   // ATTENTION! MUST NOT BE PACKED! Alignment for 64 bit must be 8 and for 32 bit must be 4
  71.   TSHQueryRBInfo = record
  72.   {$ELSE}
  73.   TSHQueryRBInfo = packed record
  74.   {$ENDIF}
  75.     cbSize      : DWORD;
  76.     i64Size     : int64;
  77.     i64NumItems : int64;
  78.   end;
  79.  
  80.   TRbRecycleBinItem = class(TObject)
  81.   strict private
  82.     function GetSource: string;
  83.   strict protected
  84.     FSourceAnsi: AnsiString;
  85.     FSourceUnicode: WideString;
  86.     FID: string;
  87.     FSourceDrive: Char;
  88.     FDeletionTime: TDateTime;
  89.     FOriginalSize: int64;
  90.     FIndexFile: string;
  91.     FRemovedEntry: boolean;
  92.     procedure ReadFromStream(stream: TStream); virtual; abstract;
  93.     function GetPhysicalFile: string; virtual; abstract; // protected, because it will be read by "property"
  94.   public
  95.     property PhysicalFile: string read GetPhysicalFile;
  96.     property SourceAnsi: AnsiString read FSourceAnsi;
  97.     property SourceUnicode: WideString read FSourceUnicode;
  98.     property Source: string read GetSource; // will bei either ANSI or Unicode, depending on the Delphi version
  99.     property ID: string read FID;
  100.     property SourceDrive: Char read FSourceDrive;
  101.     property DeletionTime: TDateTime read FDeletionTime;
  102.     property OriginalSize: int64 read FOriginalSize;
  103.     property IndexFile: string read FIndexFile;
  104.     property RemovedEntry: boolean read FRemovedEntry; // the file is NOT in the recycle bin anymore!
  105.  
  106.     // Attention: There are no official API calls. The delete and recover
  107.     // functions might fail and/or damage the shell cache. Handle with care!
  108.     function DeleteFile: boolean; virtual; abstract;
  109.     function RecoverFile: boolean; virtual; abstract;
  110.     function OpenFile: boolean; virtual; abstract;
  111.   end;
  112.  
  113.   TRbInfoAItem = class(TRbRecycleBinItem)
  114.   strict protected
  115.     procedure ReadFromStream(stream: TStream); override;
  116.     function GetPhysicalFile: string; override;
  117.   public
  118.     constructor Create(fs: TStream; AIndexFile: string); reintroduce;
  119.     function DeleteFile: boolean; override;
  120.     // TODO: function RecoverFile: boolean; override;
  121.     // TODO: function OpenFile: boolean; override;
  122.   end;
  123.  
  124.   TRbInfoWItem = class(TRbRecycleBinItem)
  125.   strict protected
  126.     procedure ReadFromStream(stream: TStream); override;
  127.     function GetPhysicalFile: string; override;
  128.   public
  129.     constructor Create(fs: TStream; AIndexFile: string); reintroduce;
  130.     function DeleteFile: boolean; override;
  131.     // TODO: function RecoverFile: boolean; override;
  132.     // TODO: function OpenFile: boolean; override;
  133.   end;
  134.  
  135.   TRbVistaItem = class(TRbRecycleBinItem)
  136.   strict protected
  137.     procedure ReadFromStream(stream: TStream); override;
  138.     function GetPhysicalFile: string; override;
  139.   public
  140.     constructor Create(fs: TStream; AIndexFile, AID: string); reintroduce;
  141.     function DeleteFile: boolean; override;
  142.     // TODO: function RecoverFile: boolean; override;
  143.     // TODO: function OpenFile: boolean; override;
  144.   end;
  145.  
  146.   TRbRecycleBin = class(TObject)
  147.   strict private
  148.     FFileOrDirectory: string;
  149.     FSID: string;
  150.     FTolerantReading: boolean;
  151.   public
  152.     constructor Create(AFileOrDirectory: string; ASID: string='');
  153.  
  154.     function GetItem(id: string): TRbRecycleBinItem;
  155.     procedure ListItems(list: TObjectList{TRbRecycleBinItem});
  156.     function CheckIndexes(slErrors: TStrings): boolean;
  157.  
  158.     property FileOrDirectory: string read FFileOrDirectory;
  159.     property SID: string read FSID;
  160.  
  161.     // Allows an index file to be read, even if an incompatible multiboot combination
  162.     // corrupted it. Default: true.
  163.     property TolerantReading: boolean read FTolerantReading write FTolerantReading;
  164.   end;
  165.  
  166.   // TODO: Wie sieht es aus mit Laufwerken, die nur als Mount-Point eingebunden sind?
  167.   TRbDrive = class(TObject)
  168.   strict private
  169.     FDriveLetter: AnsiChar;
  170.  
  171.     function OldCapacityPercent(var res: integer): boolean; // in % (0-100)
  172.     function NewCapacityAbsolute(var res: integer): boolean; // in MB
  173.  
  174.     function DiskSize: integer; // in MB
  175.     function DriveNumber: integer;
  176.   strict protected
  177.     function IsFAT: boolean;
  178.     procedure CheckDriveExisting;
  179.  
  180.     // will return NULL_GUID in case of an error or if it is not supported
  181.     function GetVolumeGUID: TGUID;
  182.     function GetVolumeGUIDAvailable: boolean;
  183.  
  184.     // TODO: get drive serial
  185.   public
  186.     constructor Create(ADriveLetter: AnsiChar);
  187.  
  188.     // Wenn UserSID='', dann werden alle Recycler gefunden
  189.     procedure ListRecycleBins(list: TObjectList{TRbRecycleBin}; UserSID: string='');
  190.  
  191.     property DriveLetter: AnsiChar read FDriveLetter;
  192.     property VolumeGUID: TGUID read GetVolumeGUID;
  193.     property VolumeGUIDAvailable: boolean read GetVolumeGUIDAvailable;
  194.     function GetAPIInfo: TSHQueryRBInfo;
  195.     function GetSize: int64;
  196.     function GetNumItems: int64;
  197.     function IsEmpty: boolean;
  198.  
  199.     function GetMaxPercentUsage: Extended; // 0..1
  200.     function GetMaxAbsoluteUsage: integer; // in MB
  201.     function GetNukeOnDelete: boolean;
  202.   end;
  203.  
  204.   GPOLICYBOOL = (gpUndefined, gpEnabled, gpDisabled);
  205.  
  206.   TRecycleBinManager = class(TObject)
  207.   public
  208.     class procedure ListDrives(list: TObjectList{TRbDrive}); static;
  209.     class function RecycleBinPossible(Drive: AnsiChar): boolean; static;
  210.  
  211.     class function OwnRecyclersSize: int64; static;
  212.     class function OwnRecyclersNumItems: int64; static;
  213.     class function OwnRecyclersEmpty: boolean; static;
  214.  
  215.     class function EmptyOwnRecyclers(flags: cardinal): boolean; overload; static;
  216.     class function EmptyOwnRecyclers(sound, progress, confirmation: boolean): boolean; overload; static;
  217.  
  218.     class function RecyclerGetCurrentIconString: string; static;
  219.     class function RecyclerGetDefaultIconString: string; static;
  220.     class function RecyclerGetEmptyIconString: string; static;
  221.     class function RecyclerGetFullIconString: string; static;
  222.  
  223.     class function GetGlobalMaxPercentUsage: integer; static; // TODO: In Win Vista: absolute and not relative sizes
  224.     class function GetGlobalNukeOnDelete: boolean; static;
  225.     class function UsesGlobalSettings: boolean; static;
  226.  
  227.     class function RecyclerGetName: string; static;
  228.     class function RecyclerGetInfoTip: string; static;
  229.     class function RecyclerGetIntroText: string; static;
  230.  
  231.     class function RecyclerEmptyEventGetCurrentSound: string; static;
  232.     class function RecyclerEmptyEventGetDefaultSound: string; static;
  233.     class function RecyclerEmptyEventGetName: string; static;
  234.     class function RecyclerEmptyEventGetSound(ACategory: string): string; static;
  235.     class procedure RecyclerEmptyEventGetSoundCategories(AStringList: TStringList); static;
  236.  
  237.     // TODO: In future also detect for other users
  238.     // TODO: Also make a setter (incl. Message to Windows Explorer?)
  239.     class function RecyclerGroupPolicyConfirmFileDelete: GPOLICYBOOL; static;
  240.     class function RecyclerGroupPolicyNoRecycleFiles: GPOLICYBOOL; static;
  241.     class function RecyclerGroupPolicyRecycleBinSize: integer; static;
  242.  
  243.     class function RecyclerConfirmationDialogEnabled: boolean; static;
  244.     class procedure RecyclerConfirmationDialogSetEnabled(NewSetting: boolean); static;
  245.     class function RecyclerShellStateConfirmationDialogEnabled: boolean; static;
  246.  
  247.     // Diese Funktion ist false, wenn sie z.B. unter Windows 95 ohne Internet Explorer
  248.     // 4.0 Shell Extension ausgeführt wird. Wenn abwärtskompatibler Code geschrieben
  249.     // werden soll, sollte RecyclerQueryFunctionAvailable() verwendet werden, da
  250.     // unter Windows 95 folgende Funktionalitäten NICHT vorhanden sind:
  251.     // - RecyclerIsEmpty
  252.     // - RecyclerGetNumItems
  253.     // - RecyclerGetSize
  254.     // - RecyclerGetAPIInfo
  255.     class function RecyclerQueryFunctionAvailable: boolean; static;
  256.  
  257.     class function RecyclerAddFileOrFolder(FileOrFolder: string; confirmation: boolean=false): boolean; static;
  258.   end;
  259.  
  260. function GPBoolToString(value: GPOLICYBOOL): string;
  261.  
  262. implementation
  263.  
  264. uses
  265.   RecBinUnitLowLvl;
  266.  
  267. {$REGION 'WinAPI/RTL declarations'}
  268. (*
  269. const
  270.   {$IFDEF MSWINDOWS}
  271.     shell32  = 'shell32.dll';
  272.     advapi32 = 'advapi32.dll';
  273.     kernel32 = 'kernel32.dll';
  274.   {$ENDIF}
  275.   {$IFDEF LINUX}
  276.     shell32  = 'libshell32.borland.so';
  277.     advapi32 = 'libwine.borland.so';
  278.     kernel32 = 'libwine.borland.so';
  279.   {$ENDIF}
  280. *)
  281.  
  282. type
  283.   SHELLSTATE = record
  284.     Flags1: DWORD;
  285. (*
  286.     BOOL fShowAllObjects : 1;
  287.     BOOL fShowExtensions : 1;
  288.     BOOL fNoConfirmRecycle : 1;
  289.  
  290.     BOOL fShowSysFiles : 1;
  291.     BOOL fShowCompColor : 1;
  292.     BOOL fDoubleClickInWebView : 1;
  293.     BOOL fDesktopHTML : 1;
  294.     BOOL fWin95Classic : 1;
  295.     BOOL fDontPrettyPath : 1;
  296.     BOOL fShowAttribCol : 1; // No longer used, dead bit
  297.     BOOL fMapNetDrvBtn : 1;
  298.     BOOL fShowInfoTip : 1;
  299.     BOOL fHideIcons : 1;
  300.     BOOL fWebView : 1;
  301.     BOOL fFilter : 1;
  302.     BOOL fShowSuperHidden : 1;
  303.     BOOL fNoNetCrawling : 1;
  304. *)
  305.     dwWin95Unused: DWORD; // Win95 only - no longer supported pszHiddenFileExts
  306.     uWin95Unused: UINT; // Win95 only - no longer supported cbHiddenFileExts
  307.  
  308.     // Note: Not a typo!  This is a persisted structure so we cannot use LPARAM
  309.     lParamSort: Integer;
  310.     iSortDirection: Integer;
  311.  
  312.     version: UINT;
  313.  
  314.     // new for win2k. need notUsed var to calc the right size of ie4 struct
  315.     // FIELD_OFFSET does not work on bit fields
  316.     uNotUsed: UINT; // feel free to rename and use
  317.     Flags2: DWORD;
  318. (*
  319.     BOOL fSepProcess: 1;
  320.     // new for Whistler.
  321.     BOOL fStartPanelOn: 1;       //Indicates if the Whistler StartPanel mode is ON or OFF.
  322.     BOOL fShowStartPage: 1;      //Indicates if the Whistler StartPage on desktop is ON or OFF.
  323.     UINT fSpareFlags : 13;
  324. *)
  325.   end;
  326.   LPSHELLSTATE = ^SHELLSTATE;
  327.  
  328. const
  329.   // Masks for the SHELLSTATE
  330.   SSF_SHOWALLOBJECTS       = $00000001;
  331.   SSF_SHOWEXTENSIONS       = $00000002;
  332.   SSF_HIDDENFILEEXTS       = $00000004;
  333.   SSF_SERVERADMINUI        = $00000004;
  334.   SSF_SHOWCOMPCOLOR        = $00000008;
  335.   SSF_SORTCOLUMNS          = $00000010;
  336.   SSF_SHOWSYSFILES         = $00000020;
  337.   SSF_DOUBLECLICKINWEBVIEW = $00000080;
  338.   SSF_SHOWATTRIBCOL        = $00000100;
  339.   SSF_DESKTOPHTML          = $00000200;
  340.   SSF_WIN95CLASSIC         = $00000400;
  341.   SSF_DONTPRETTYPATH       = $00000800;
  342.   SSF_SHOWINFOTIP          = $00002000;
  343.   SSF_MAPNETDRVBUTTON      = $00001000;
  344.   SSF_NOCONFIRMRECYCLE     = $00008000;
  345.   SSF_HIDEICONS            = $00004000;
  346.   SSF_FILTER               = $00010000;
  347.   SSF_WEBVIEW              = $00020000;
  348.   SSF_SHOWSUPERHIDDEN      = $00040000;
  349.   SSF_SEPPROCESS           = $00080000;
  350.   SSF_NONETCRAWLING        = $00100000;
  351.   SSF_STARTPANELON         = $00200000;
  352.   SSF_SHOWSTARTPAGE        = $00400000;
  353. {$ENDREGION}
  354.  
  355. resourcestring
  356.   LNG_API_CALL_ERROR = 'Error while calling the API. Additional information: "%s".';
  357.   LNG_NOT_CALLABLE = '%s not callable';
  358.   LNG_ERROR_CODE = '%s (Arguments: %s) returns error code %s';
  359.   LNG_FILE_NOT_FOUND = 'File not found: %s';
  360.   LNG_INVALID_INFO_FORMAT = 'Unexpected record size: %s';
  361.   LNG_DRIVE_NOT_EXISTING = 'Drive %s does not exist.';
  362.  
  363. const
  364.   {$IFDEF UNICODE}
  365.   C_SHEmptyRecycleBin = 'SHEmptyRecycleBinW';
  366.   C_SHQueryRecycleBin = 'SHQueryRecycleBinW';
  367.   C_GetVolumeNameForVolumeMountPoint = 'GetVolumeNameForVolumeMountPointW';
  368.   {$ELSE}
  369.   C_SHEmptyRecycleBin = 'SHEmptyRecycleBinA';
  370.   C_SHQueryRecycleBin = 'SHQueryRecycleBinA';
  371.   C_GetVolumeNameForVolumeMountPoint = 'GetVolumeNameForVolumeMountPointA';
  372.   {$ENDIF}
  373.   C_SHGetSettings = 'SHGetSettings';
  374.   C_SHGetSetSettings = 'SHGetSetSettings';
  375.  
  376. type
  377.   TSHQueryRecycleBin = function(pszRootPath: LPCTSTR; var pSHQueryRBInfo: TSHQueryRBInfo): HRESULT; stdcall;
  378.   TGetVolumeNameForVolumeMountPoint = function(lpszVolumeMountPoint: LPCTSTR; lpszVolumeName: LPTSTR; cchBufferLength: DWORD): BOOL; stdcall;
  379.   TSHEmptyRecycleBin = function(Wnd: HWND; pszRootPath: LPCTSTR; dwFlags: DWORD): HRESULT; stdcall;
  380.   TSHGetSettings = procedure(var lpss: SHELLSTATE; dwMask: DWORD); stdcall;
  381.   TSHGetSetSettings = procedure(var lpss: SHELLSTATE; dwMask: DWORD; bSet: BOOL); stdcall;
  382.  
  383. procedure AnsiRemoveNulChars(var s: AnsiString);
  384. begin
  385.   while (Length(s) > 0) and (s[Length(s)] = #0) do
  386.     s := Copy(s, 1, Length(s)-1);
  387. end;
  388.  
  389. procedure UnicodeRemoveNulChars(var s: WideString);
  390. begin
  391.   while (Length(s) > 0) and (s[Length(s)] = #0) do
  392.     s := Copy(s, 1, Length(s)-1);
  393. end;
  394.  
  395. function GetDriveGUID(driveLetter: AnsiChar; var guid: TGUID): DWORD;
  396. var
  397.   Buffer: array[0..50] of Char;
  398.   x: string;
  399.   PGetVolumeNameForVolumeMountPoint: TGetVolumeNameForVolumeMountPoint;
  400.   RBHandle: THandle;
  401. begin
  402.   RBHandle := LoadLibrary(kernel32);
  403.   try
  404.     if RBHandle <> 0 then
  405.     begin
  406.       PGetVolumeNameForVolumeMountPoint := GetProcAddress(RBHandle, C_GetVolumeNameForVolumeMountPoint);
  407.       if not Assigned(@PGetVolumeNameForVolumeMountPoint) then
  408.       begin
  409.         result := GetLastError;
  410.         FreeLibrary(RBHandle);
  411.         RBHandle := 0;
  412.       end
  413.       else
  414.       begin
  415.         if PGetVolumeNameForVolumeMountPoint(PChar(driveLetter+':\'), Buffer, SizeOf(Buffer)) then
  416.         begin
  417.           x := string(buffer);
  418.           x := copy(x, 11, 38);
  419.           guid := StringToGUID(x);
  420.           result := ERROR_SUCCESS;
  421.         end
  422.         else
  423.           result := GetLastError;
  424.       end;
  425.     end
  426.     else result := GetLastError;
  427.   finally
  428.     if RBHandle <> 0 then FreeLibrary(RBHandle);
  429.   end;
  430. end;
  431.  
  432. function FileTimeToDateTime(FileTime: FILETIME): TDateTime;
  433. {$IFDEF FILETIME_DELPHI_CODE}
  434. var
  435.   SystemTime: TSystemTime;
  436.   nowUTC: TDateTime;
  437.   gmtDifference: int64;
  438. begin
  439.   GetSystemTime(SystemTime);
  440.   with SystemTime do
  441.   begin
  442.     // http://www.delphipraxis.net/post340194.html#34019
  443.     nowUTC := EncodeDate(wYear, wMonth, wDay) +
  444.               EncodeTime(wHour, wMinute, wSecond, wMilliseconds);
  445.   end;
  446.  
  447.   gmtDifference := datetimetounix(nowUTC) - datetimetounix(Now);
  448.  
  449.   // http://www.e-fense.com/helix/Docs/Recycler_Bin_Record_Reconstruction.pdf states:
  450.   // UnixTime = 0.0000001 * NTTime + 11644473600
  451.   // This is wrong! The correct formula is:
  452.   // UnixTime = 0.0000001 * NTTime - 11644473600 + c * 3600
  453.   // c = GMT-Difference (MEZ = 1) inclusive daylight saving time (+3600 seconds)
  454.   result := unixtodatetime(round(0.0000001 * int64(FileTime)) - 11644473600 - gmtDifference);
  455. {$ELSE}
  456. var
  457.   LocalTime: TFileTime;
  458.   DOSTime: Integer;
  459. begin
  460.   FileTimeToLocalFileTime(FileTime, LocalTime);
  461.   FileTimeToDosDateTime(LocalTime, LongRec(DOSTime).Hi, LongRec(DOSTime).Lo);
  462.   Result := FileDateToDateTime(DOSTime);
  463. {$ENDIF}
  464. end;
  465.  
  466. function DeleteDirectory(const Name: string): boolean;
  467. var
  468.   F: TSearchRec;
  469. begin
  470.   result := true;
  471.   if FindFirst(IncludeTrailingPathDelimiter(Name) + '*', faAnyFile, F) = 0 then
  472.   begin
  473.     try
  474.       repeat
  475.         if F.Attr and faDirectory <> 0 then
  476.         begin
  477.           if (F.Name <> '.') and (F.Name <> '..') then
  478.           begin
  479.             result := result and DeleteDirectory(IncludeTrailingPathDelimiter(Name) + F.Name);
  480.           end;
  481.         end
  482.         else
  483.         begin
  484.           if not DeleteFile(IncludeTrailingPathDelimiter(Name) + F.Name) then result := false;
  485.         end;
  486.       until FindNext(F) <> 0;
  487.     finally
  488.       FindClose(F);
  489.     end;
  490.     if not RemoveDir(Name) then result := false;
  491.   end;
  492. end;
  493.  
  494. function DriveLetterToDriveNumber(driveLetter: AnsiChar): integer;
  495. var
  496.   tmp: string;
  497. begin
  498.   tmp := LowerCase(string(driveLetter));
  499.   result := Ord(tmp[1])-Ord('a');
  500. end;
  501.  
  502. function GetStringFromDLL(filename: string; num: integer): string;
  503. const
  504.   // Source: http://www.eggheadcafe.com/forumarchives/vcmfc/sep2005/post23917443.asp
  505.   MAX_BUF = 4097; // OK?
  506. var
  507.   hLib: THandle;
  508.   buf: array[0..MAX_BUF] of char;
  509. begin
  510.   hLib := LoadLibrary(PChar(filename));
  511.   try
  512.     LoadString(hLib, num, buf, sizeof(buf));
  513.     result := buf;
  514.   finally
  515.     FreeLibrary(hLib);
  516.   end;
  517. end;
  518.  
  519. function ExpandEnvStr(const szInput: string): string;
  520. const
  521.   MAXSIZE = 32768; // laut PSDK sind 32k das Maximum
  522. begin
  523.   // Source: http://www.delphi-library.de/topic_Umgebungsvariable+in+einem+String+aufloesen_20516,0.html
  524.   SetLength(Result,MAXSIZE);
  525.   SetLength(Result,ExpandEnvironmentStrings(pchar(szInput),
  526.     @Result[1],length(Result))-1); //-1 um abschließendes #0 zu verwerfen
  527. end;
  528.  
  529. function DecodeReferenceString(s: string): string;
  530. var
  531.   dll, id, lang, cache: string;
  532.   sl, sl2: tstringlist;
  533. begin
  534.   // Beispiele
  535.   // Papierkorb                                                 -- Windows 95
  536.   // @C:\WINNT\system32\shell32.dll,-8964@1031,Papierkorb       -- Windows 2000
  537.  
  538.   if Copy(s, 1, 1) = '@' then
  539.   begin
  540.     // Referenz auf eine DLL
  541.     // @<dll>,-<id>[@<lang>][,<cache>]
  542.  
  543.     sl := TStringList.Create;
  544.     try
  545.       // '@' am Anfang entfernen
  546.       s := Copy(s, 2, length(s)-1);
  547.  
  548.       // Nach ',' auftrennen
  549.       // sl[0] --> dll
  550.       // sl[1] --> -id@lang
  551.       // sl[2] --> cache
  552.       sl.CommaText := s;
  553.  
  554.       if sl.Count > 2 then
  555.       begin
  556.         // Das Ergebnis ist bereits im Klartext vorhanden und muss nicht extrahiert werden
  557.         // Ist bei Windows 2000 der Fall
  558.         cache := sl[2];
  559.         result := cache;
  560.         exit;
  561.       end;
  562.  
  563.       if sl.Count > 1 then
  564.       begin
  565.         dll := sl[0];
  566.  
  567.         sl2 := TStringList.Create;
  568.         try
  569.           // Nach '@' auftrennen
  570.           // sl2[0] --> id
  571.           // sl2[1] --> lang
  572.           sl2.CommaText := StringReplace(sl[1], '@', ',', [rfReplaceAll]);
  573.  
  574.           id := sl2[0];
  575.  
  576.           if sl2.Count > 1 then
  577.           begin
  578.             // ToDo: In Zukunft beachten, sofern möglich
  579.             lang := sl2[1];
  580.           end;
  581.  
  582.           // Umgebungsvariablen erkennen und Minuszeichen entfernen
  583.           result := GetStringFromDLL(ExpandEnvStr(dll), -StrToInt(id));
  584.         finally
  585.           sl2.Free;
  586.         end;
  587.       end
  588.       else
  589.       begin
  590.         // Zu wenige Informationen!
  591.  
  592.         result := '';
  593.       end;
  594.     finally
  595.       sl.Free;
  596.     end;
  597.   end
  598.   else
  599.   begin
  600.     // Kein Hinweis auf eine Referenz
  601.     result := s;
  602.   end;
  603. end;
  604.  
  605. function GPBoolToString(value: GPOLICYBOOL): string;
  606. begin
  607.   case value of
  608.     gpUndefined: result := 'Not configured';
  609.     gpEnabled: result := 'Enabled';
  610.     gpDisabled: result := 'Disabled';
  611.   end;
  612. end;
  613.  
  614. { TRbRecycleBin }
  615.  
  616. constructor TRbRecycleBin.Create(AFileOrDirectory: string; ASID: string='');
  617. begin
  618.   inherited Create;
  619.  
  620.   FFileOrDirectory := AFileOrDirectory;
  621.   FSID := ASID;
  622.   TolerantReading := true;
  623. end;
  624.  
  625. // TODO: also a function that tests if the data files are still existing
  626. function TRbRecycleBin.CheckIndexes(slErrors: TStrings): boolean;
  627.  
  628.   procedure _Assert(assertion: boolean; msg: string; args: array of const);
  629.   begin
  630.     if not assertion then
  631.     begin
  632.       slErrors.Add(Format(msg, args));
  633.       result := false;
  634.     end;
  635.   end;
  636.  
  637.   procedure _HandleIndexFile(AFile: string);
  638.   var
  639.     fs: TFileStream;
  640.     infoHdr: TRbInfoHeader;
  641.   resourcestring
  642.     LNG_IDXERR_VISTA_FILESIZE = '%s: Vista index file has wrong size';
  643.     LNG_IDXERR_INFO_RECSIZE_UNEXPECTED = '%s: record size unexpected';
  644.     LNG_IDXERR_INFO_UNEXPECTED_EOF = '%s: file size wrong';
  645.   begin
  646.     fs := TFileStream.Create(AFile, fmOpenRead);
  647.     try
  648.       fs.Seek(0, soFromBeginning);
  649.  
  650.       if SameText(copy(ExtractFileName(AFile), 1, 2), '$I') then
  651.       begin
  652.         _Assert(fs.Size = SizeOf(TRbVistaItem), LNG_IDXERR_VISTA_FILESIZE, [AFile]);
  653.       end
  654.       else if SameText(ExtractFileName(AFile), 'INFO') or
  655.               SameText(ExtractFileName(AFile), 'INFO2') then
  656.       begin
  657.         fs.ReadBuffer(infoHdr, SizeOf(infoHdr));
  658.         _Assert((infoHdr.recordLength = SizeOf(TRbInfoRecordA)) or
  659.                 (infoHdr.recordLength = SizeOf(TRbInfoRecordW)), LNG_IDXERR_INFO_RECSIZE_UNEXPECTED, [AFile]);
  660.         _Assert((fs.Size-fs.Position) mod infoHdr.recordLength = 0, LNG_IDXERR_INFO_UNEXPECTED_EOF, [AFile]);
  661.         // TODO: we can also check infoHdr.totalSize or infoHdr.totalEntries
  662.       end
  663.       else Assert(false);
  664.  
  665.       // TODO: we could check each item for invalid stuff...?
  666.     finally
  667.       FreeAndNil(fs);
  668.     end;
  669.   end;
  670.  
  671.   procedure _HandleVistaDir(ADirectory: string);
  672.   var
  673.     SR: TSearchRec;
  674.   begin
  675.     ADirectory := IncludeTrailingPathDelimiter(ADirectory);
  676.  
  677.     if FindFirst(ADirectory + '$I*', faAnyFile, SR) = 0 then
  678.     begin
  679.       repeat
  680.         _HandleIndexFile(ADirectory+sr.Name);
  681.       until FindNext(SR) <> 0;
  682.     end;
  683.     FindClose(SR);
  684.   end;
  685.  
  686. begin
  687.   result := true;
  688.  
  689.   if DirectoryExists(FFileOrDirectory) then // Vista, as well as directories with INFO and INFO2
  690.   begin
  691.     _HandleVistaDir(FFileOrDirectory);
  692.  
  693.     if FileExists(IncludeTrailingPathDelimiter(FFileOrDirectory) + 'INFO2') then
  694.     begin
  695.       _HandleIndexFile(IncludeTrailingPathDelimiter(FFileOrDirectory) + 'INFO2');
  696.     end;
  697.  
  698.     if FileExists(IncludeTrailingPathDelimiter(FFileOrDirectory) + 'INFO') then
  699.     begin
  700.       _HandleIndexFile(IncludeTrailingPathDelimiter(FFileOrDirectory) + 'INFO');
  701.     end;
  702.   end
  703.   else if FileExists(FFileOrDirectory) then
  704.   begin
  705.     _HandleIndexFile(FFileOrDirectory);
  706.   end
  707.   else raise Exception.CreateFmt(LNG_FILE_NOT_FOUND, [FFileOrDirectory]);
  708. end;
  709.  
  710. function TRbRecycleBin.GetItem(id: string): TRbRecycleBinItem;
  711.  
  712.   procedure _HandleIndexFile(AFile: string);
  713.   var
  714.     fs: TFileStream;
  715.     infoHdr: TRbInfoHeader;
  716.     testItem: TRbRecycleBinItem;
  717.   begin
  718.     fs := TFileStream.Create(AFile, fmOpenRead);
  719.     try
  720.       fs.Seek(0, soFromBeginning);
  721.  
  722.       if SameText(ExtractFileName(AFile), '$I'+id) then
  723.       begin
  724.         result := TRbVistaItem.Create(fs, AFile, id);
  725.       end
  726.       else
  727.       begin
  728.         fs.ReadBuffer(infoHdr, SizeOf(infoHdr));
  729.         case infoHdr.recordLength of
  730.           SizeOf(TRbInfoRecordA):
  731.           begin
  732.             while fs.Position < fs.size do
  733.             begin
  734.               testItem := TRbInfoAItem.Create(fs, AFile);
  735.               if testItem.ID = id then
  736.               begin
  737.                 result := testItem;
  738.                 break;
  739.               end;
  740.             end;
  741.           end;
  742.           SizeOf(TRbInfoRecordW):
  743.           begin
  744.             while fs.Position < fs.size do
  745.             begin
  746.               testItem := TRbInfoWItem.Create(fs, AFile);
  747.               if testItem.ID = id then
  748.               begin
  749.                 result := testItem;
  750.                 break;
  751.               end;
  752.             end;
  753.           end
  754.           else
  755.           begin
  756.             raise Exception.CreateFmt(LNG_INVALID_INFO_FORMAT, [AFile]);
  757.           end;
  758.         end;
  759.       end;
  760.     finally
  761.       FreeAndNil(fs);
  762.     end;
  763.   end;
  764.  
  765.   procedure _HandleVistaDir(ADirectory: string);
  766.   var
  767.     SR: TSearchRec;
  768.     fs: TFileStream;
  769.     id: string;
  770.   begin
  771.     ADirectory := IncludeTrailingPathDelimiter(ADirectory);
  772.  
  773.     if FileExists(ADirectory + '$I' + id) then
  774.     begin
  775.       fs := TFileStream.Create(ADirectory+sr.Name, fmOpenRead);
  776.       try
  777.         fs.Seek(0, soFromBeginning);
  778.         result := TRbVistaItem.Create(fs, ADirectory+sr.Name, id);
  779.       finally
  780.         FreeAndNil(fs);
  781.       end;
  782.     end;
  783.   end;
  784.  
  785. begin
  786.   result := nil;
  787.  
  788.   if DirectoryExists(FFileOrDirectory) then // Vista, as well as directories with INFO and INFO2
  789.   begin
  790.     _HandleVistaDir(FFileOrDirectory);
  791.     if Assigned(result) then exit;
  792.  
  793.     if FileExists(IncludeTrailingPathDelimiter(FFileOrDirectory) + 'INFO2') then
  794.     begin
  795.       _HandleIndexFile(IncludeTrailingPathDelimiter(FFileOrDirectory) + 'INFO2');
  796.       if Assigned(result) then exit;
  797.     end;
  798.  
  799.     if FileExists(IncludeTrailingPathDelimiter(FFileOrDirectory) + 'INFO') then
  800.     begin
  801.       _HandleIndexFile(IncludeTrailingPathDelimiter(FFileOrDirectory) + 'INFO');
  802.       if Assigned(result) then exit;
  803.     end;
  804.   end
  805.   else if FileExists(FFileOrDirectory) then
  806.   begin
  807.     _HandleIndexFile(FFileOrDirectory);
  808.     if Assigned(result) then exit;
  809.   end
  810.   else raise Exception.CreateFmt(LNG_FILE_NOT_FOUND, [FFileOrDirectory]);
  811. end;
  812.  
  813. procedure TRbRecycleBin.ListItems(list: TObjectList{TRbRecycleBinItem});
  814.  
  815.   procedure _HandleIndexFile(AFile: string);
  816.   var
  817.     fs: TFileStream;
  818.     infoHdr: TRbInfoHeader;
  819.     vistaId: string;
  820.     wTest: TRbInfoWItem;
  821.     bakPosition: int64;
  822.     testVistaItem: TRbVistaItem;
  823.   begin
  824.     fs := TFileStream.Create(AFile, fmOpenRead);
  825.     try
  826.       fs.Seek(0, soFromBeginning);
  827.  
  828.       {$REGION 'First try if it is a Vista index file'}
  829.       testVistaItem := nil;
  830.       if SameText(copy(ExtractFileName(AFile), 1, 2), '$I') then
  831.       begin
  832.         vistaId := copy(AFile, 3, Length(AFile)-2);
  833.         testVistaItem := TRbVistaItem.Create(fs, AFile, vistaId);
  834.       end
  835.       else
  836.       begin
  837.         vistaId := ''; // manual file that was not named $I..., so we cannot get $R... ID and therefore no physical file!
  838.         try
  839.           testVistaItem := TRbVistaItem.Create(fs, AFile, vistaId);
  840.           if (Copy(testVistaItem.Source,2,2) <> ':\') and
  841.              (Copy(testVistaItem.Source,2,2) <> '\\') then
  842.             FreeAndNil(testVistaItem);
  843.         except
  844.           testVistaItem := nil;
  845.         end;
  846.       end;
  847.       {$ENDREGION}
  848.  
  849.       if Assigned(testVistaItem) then
  850.       begin
  851.         list.Add(testVistaItem);
  852.       end
  853.       else
  854.       begin
  855.         fs.Seek(0, soFromBeginning);
  856.         if TolerantReading then
  857.         begin
  858.           // This is a special treatment how to recover data from an INFO/INFO2 file
  859.           // which was corrupted by an incompatible multiboot configuration.
  860.           // Example:
  861.           // - Win95 without IE4 and WinNT4 both write into the INFO file. But Win95 appends the ANSI record and WinNT appends an Unicode record.
  862.           // - Win95 with IE4 and Windows 2000/2003/XP write into the INFO2 file. But Win9x appends the ANSI record and Win2k+ appends an Unicode record.
  863.           fs.ReadBuffer(infoHdr, SizeOf(infoHdr));
  864.           while fs.Position < fs.size do
  865.           begin
  866.             // Can we actually read a Unicode record?
  867.             if fs.Position + SizeOf(TRbInfoRecordW) <= fs.Size then
  868.             begin
  869.               // Try to read the Unicode record and check if it is valid
  870.               // In case it is no Unicode record, then the Unicode part will be the
  871.               // ANSI source name of the next record. In this case, we won't get
  872.               // a ':\' or '\\' at the Unicode string.
  873.               bakPosition := fs.Position;
  874.               wTest := TRbInfoWItem.Create(fs, AFile);
  875.               if (Copy(wTest.SourceUnicode, 2, 2) = ':\') or
  876.                  (Copy(wTest.SourceUnicode, 2, 2) = '\\') then
  877.               begin
  878.                 // Yes, it is a valid Unicode record.
  879.                 list.Add(wTest);
  880.               end
  881.               else
  882.               begin
  883.                 // No, it is not a valid Unicode record. Jump back, and we need
  884.                 // to assume that the following record will be a valid ANSI record.
  885.                 fs.Position := bakPosition;
  886.                 list.Add(TRbInfoAItem.Create(fs, AFile));
  887.               end;
  888.             end
  889.             else if fs.Position + SizeOf(TRbInfoRecordA) <= fs.Size then
  890.             begin
  891.               // No, there is not enough space left for an Unicode record.
  892.               // So we assume that the following record will be a valid ANSI record.
  893.               list.Add(TRbInfoAItem.Create(fs, AFile));
  894.             end
  895.             else
  896.             begin
  897.               // Not enough space to read a ANSI record!
  898.               // Ignore it
  899.             end;
  900.           end;
  901.         end
  902.         else
  903.         begin
  904.           // This is the code for non-tolerant reading of the records.
  905.           fs.ReadBuffer(infoHdr, SizeOf(infoHdr));
  906.           case infoHdr.recordLength of
  907.             SizeOf(TRbInfoRecordA):
  908.             begin
  909.               while fs.Position < fs.size do
  910.               begin
  911.                 list.Add(TRbInfoAItem.Create(fs, AFile));
  912.               end;
  913.             end;
  914.             SizeOf(TRbInfoRecordW):
  915.             begin
  916.               while fs.Position < fs.size do
  917.               begin
  918.                 list.Add(TRbInfoWItem.Create(fs, AFile));
  919.               end;
  920.             end
  921.             else
  922.             begin
  923.               raise Exception.CreateFmt(LNG_INVALID_INFO_FORMAT, [AFile]);
  924.             end;
  925.           end;
  926.         end;
  927.       end;
  928.     finally
  929.       FreeAndNil(fs);
  930.     end;
  931.   end;
  932.  
  933.   // TODO: Nice would be some progress callback function
  934.   procedure _HandleVistaDir(ADirectory: string);
  935.   var
  936.     SR: TSearchRec;
  937.     fs: TFileStream;
  938.     id: string;
  939.   begin
  940.     ADirectory := IncludeTrailingPathDelimiter(ADirectory);
  941.  
  942.     //if FindFirst(ADirectory + '$I*', faAnyFile, SR) = 0 then
  943.     if FindFirst(ADirectory + '*', faAnyFile, SR) = 0 then
  944.     begin
  945.       repeat
  946.         // FindFirst filter '$I*' finds the file '$RJK3R4P.~43~' and '$RJB99A3.~55~' (yes, $R !!!) Why?!
  947.         // ... so, we just filter for '*' and try to find the '$I*' files ourselves...
  948.         if UpperCase(Copy(sr.Name,1,2)) = '$I' then
  949.         begin
  950.           id := sr.Name;
  951.           { id := ChangeFileExt(id, ''); }  // Removed code: We keep the file extention as part of the ID, because we do not know if the ID is otherwise unique
  952.           id := Copy(id, 3, Length(id)-2);
  953.  
  954.           fs := TFileStream.Create(ADirectory+sr.Name, fmOpenRead);
  955.           try
  956.             fs.Seek(0, soFromBeginning);
  957.             list.Add(TRbVistaItem.Create(fs, ADirectory+sr.Name, id));
  958.           finally
  959.             FreeAndNil(fs);
  960.           end;
  961.         end;
  962.       until FindNext(SR) <> 0;
  963.     end;
  964.     FindClose(SR);
  965.   end;
  966.  
  967. begin
  968.   if DirectoryExists(FFileOrDirectory) then // Vista, as well as directories with INFO and INFO2
  969.   begin
  970.     _HandleVistaDir(FFileOrDirectory);
  971.  
  972.     if FileExists(IncludeTrailingPathDelimiter(FFileOrDirectory) + 'INFO2') then
  973.     begin
  974.       _HandleIndexFile(IncludeTrailingPathDelimiter(FFileOrDirectory) + 'INFO2');
  975.     end;
  976.  
  977.     if FileExists(IncludeTrailingPathDelimiter(FFileOrDirectory) + 'INFO') then
  978.     begin
  979.       _HandleIndexFile(IncludeTrailingPathDelimiter(FFileOrDirectory) + 'INFO');
  980.     end;
  981.   end
  982.   else if FileExists(FFileOrDirectory) then
  983.   begin
  984.     _HandleIndexFile(FFileOrDirectory); // Either INFO, or INFO2, or a single Vista index file
  985.   end
  986.   else raise Exception.CreateFmt(LNG_FILE_NOT_FOUND, [FFileOrDirectory]);
  987. end;
  988.  
  989. { TRbDrive }
  990.  
  991. procedure TRbDrive.CheckDriveExisting;
  992. begin
  993.   // Does the drive exist?
  994.   // see http://www.delphipraxis.net/post2933.html
  995.   if not GetLogicalDrives and (1 shl DriveNumber) <> 0 then
  996.   begin
  997.     raise EInvalidDrive.CreateFmt(LNG_DRIVE_NOT_EXISTING, [UpperCase(string(FDriveLetter))+':']);
  998.   end;
  999. end;
  1000.  
  1001. constructor TRbDrive.Create(ADriveLetter: AnsiChar);
  1002. begin
  1003.   inherited Create;
  1004.  
  1005.   FDriveLetter := ADriveLetter;
  1006.   CheckDriveExisting;
  1007. end;
  1008.  
  1009. function TRbDrive.DiskSize: integer;
  1010. begin
  1011.   result := SysUtils.DiskSize(DriveNumber+1 {0 is current, 1 is A}) div (1024*1024);
  1012. end;
  1013.  
  1014. function TRbDrive.DriveNumber: integer;
  1015. begin
  1016.   result := DriveLetterToDriveNumber(FDriveLetter);
  1017. end;
  1018.  
  1019. function TRbDrive.GetAPIInfo: TSHQueryRBInfo;
  1020. var
  1021.   PSHQueryRecycleBin: TSHQueryRecycleBin;
  1022.   RBHandle: THandle;
  1023.   res: HRESULT;
  1024.   Path: string;
  1025. begin
  1026.   Path := FDriveLetter + ':\';
  1027.  
  1028.   // Ref: http://www.delphipraxis.net/post1291.html
  1029.  
  1030.   RBHandle := LoadLibrary(shell32);
  1031.   try
  1032.     PSHQueryRecycleBin := nil;
  1033.     if RBHandle <> 0 then
  1034.     begin
  1035.       PSHQueryRecycleBin := GetProcAddress(RBHandle, C_SHQueryRecycleBin);
  1036.       if not Assigned(@PSHQueryRecycleBin) then
  1037.       begin
  1038.         FreeLibrary(RBHandle);
  1039.         RBHandle := 0;
  1040.       end;
  1041.     end;
  1042.  
  1043.     FillChar(result, SizeOf(TSHQueryRBInfo), 0);
  1044.     result.cbSize := SizeOf(TSHQueryRBInfo);
  1045.  
  1046.     if (RBHandle <> 0) and Assigned(PSHQueryRecycleBin) then
  1047.     begin
  1048.       res := PSHQueryRecycleBin(PChar(Path), result);
  1049.       // if Succeeded(res) then
  1050.       if res = S_OK then
  1051.       begin
  1052.         // Alles OK, unser result hat nun die gewünschten Daten.
  1053.       end
  1054.       else
  1055.       begin
  1056.         // Since Windows Vista, SHQueryRecycleBin will fail with E_FAIL (80004005)
  1057.         // if Path is a floppy or CD drive...
  1058.         raise EAPICallError.CreateFmt(LNG_API_CALL_ERROR, [Format(LNG_ERROR_CODE, [C_SHQueryRecycleBin, Path, '0x'+IntToHex(res, 2*SizeOf(HRESULT))])]);
  1059.       end;
  1060.     end
  1061.     else
  1062.       raise EAPICallError.CreateFmt(LNG_API_CALL_ERROR, [Format(LNG_NOT_CALLABLE, [C_SHQueryRecycleBin])]);
  1063.   finally
  1064.     if RBHandle <> 0 then FreeLibrary(RBHandle);
  1065.   end;
  1066. end;
  1067.  
  1068. function TRbDrive.GetMaxPercentUsage: Extended;
  1069. var
  1070.   abs: integer; // in MB
  1071.   rel: integer; // in % (0-100)
  1072.   gpSetting: integer;
  1073. const
  1074.   DEFAULT_PERCENT = 10; // Windows 95 default
  1075. begin
  1076.   gpSetting := TRecycleBinManager.RecyclerGroupPolicyRecycleBinSize;
  1077.   if gpSetting <> -1 then
  1078.     result := gpSetting / 100
  1079.   else if TRecycleBinManager.UsesGlobalSettings then
  1080.     result := TRecycleBinManager.GetGlobalMaxPercentUsage / 100
  1081.   else if OldCapacityPercent(rel) then
  1082.   begin
  1083.     result := rel / 100;
  1084.   end
  1085.   else if NewCapacityAbsolute(abs) then
  1086.   begin
  1087.     result := abs / DiskSize;
  1088.   end
  1089.   else
  1090.   begin
  1091.     result := DEFAULT_PERCENT / 100;
  1092.   end;
  1093. end;
  1094.  
  1095. function TRbDrive.GetMaxAbsoluteUsage: integer;
  1096. var
  1097.   abs: integer; // in MB
  1098.   rel: integer; // in % (0-100)
  1099.   gpSetting: integer;
  1100. const
  1101.   DEFAULT_PERCENT = 10; // Windows 95 default
  1102. begin
  1103.   gpSetting := TRecycleBinManager.RecyclerGroupPolicyRecycleBinSize;
  1104.   if gpSetting <> -1 then
  1105.     result := Ceil(gpSetting/100 * DiskSize)
  1106.   else if TRecycleBinManager.UsesGlobalSettings then
  1107.     result := Ceil(TRecycleBinManager.GetGlobalMaxPercentUsage/100 * DiskSize)
  1108.   else if NewCapacityAbsolute(abs) then
  1109.   begin
  1110.     result := abs;
  1111.   end
  1112.   else if OldCapacityPercent(rel) then
  1113.   begin
  1114.     result := Ceil(rel/100 * DiskSize);
  1115.   end
  1116.   else
  1117.   begin
  1118.     result := Ceil(DEFAULT_PERCENT/100 * DiskSize);
  1119.   end;
  1120. end;
  1121.  
  1122. function TRbDrive.OldCapacityPercent(var res: integer): boolean;
  1123. var
  1124.   reg: TRegistry;
  1125.   purgeInfo: TRbWin95PurgeInfo;
  1126. begin
  1127.   if Win32MajorVersion >= 6 then
  1128.   begin
  1129.     // Only available till Windows XP
  1130.     result := false;
  1131.     exit;
  1132.   end;
  1133.  
  1134.   result := false;
  1135.  
  1136.   reg := TRegistry.Create;
  1137.   try
  1138.     reg.RootKey := HKEY_LOCAL_MACHINE;
  1139.  
  1140.     // Im Auslieferungszustand von Windows 95 ist dieser Schlüssel nicht vorhanden.
  1141.     // Er wird bei der ersten Änderung der Papierkorb-Einstellungen erstellt.
  1142.     if reg.OpenKeyReadOnly('SOFTWARE\Microsoft\Windows\CurrentVersion\explorer\BitBucket') then
  1143.     begin
  1144.       if reg.OpenKeyReadOnly(string(FDriveLetter)) then
  1145.       begin
  1146.         if reg.ValueExists('Percent') then
  1147.         begin
  1148.           // Windows 2000 - Informationen liegen aufgeschlüsselt in der Registry
  1149.  
  1150.           res := reg.ReadInteger('Percent');
  1151.           result := true;
  1152.         end;
  1153.       end
  1154.       else
  1155.       begin
  1156.         if reg.ValueExists('PurgeInfo') then
  1157.         begin
  1158.           // Windows 95 - Kodierte Informationen liegen in PurgeInfo
  1159.  
  1160.           reg.ReadBinaryData('PurgeInfo', purgeInfo, SizeOf(purgeInfo));
  1161.  
  1162.           res := purgeInfo.percentDrive[FDriveLetter];
  1163.           result := true;
  1164.         end;
  1165.       end;
  1166.  
  1167.       reg.CloseKey;
  1168.     end;
  1169.   finally
  1170.     reg.Free;
  1171.   end;
  1172. end;
  1173.  
  1174. function TRbDrive.NewCapacityAbsolute(var res: integer): boolean;
  1175. var
  1176.   reg: TRegistry;
  1177. begin
  1178.   if Win32MajorVersion < 6 then
  1179.   begin
  1180.     // Only available since Windows Vista
  1181.     result := false;
  1182.     exit;
  1183.   end;
  1184.  
  1185.   result := false;
  1186.  
  1187.   reg := TRegistry.Create;
  1188.   try
  1189.     reg.RootKey := HKEY_CURRENT_USER;
  1190.  
  1191.     if reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Explorer\BitBucket\Volume') then
  1192.     begin
  1193.       // Windows Vista and upwards
  1194.       if reg.OpenKeyReadOnly(GUIDToString(VolumeGUID)) then
  1195.       begin
  1196.         res := reg.ReadInteger('MaxCapacity'); // in MB
  1197.         result := true;
  1198.       end;
  1199.       reg.CloseKey;
  1200.     end;
  1201.   finally
  1202.     reg.Free;
  1203.   end;
  1204. end;
  1205.  
  1206. function TRbDrive.GetNukeOnDelete: boolean;
  1207. var
  1208.   reg: TRegistry;
  1209.   purgeInfo: TRbWin95PurgeInfo;
  1210. const
  1211.   RES_DEFAULT = false; // Windows 95 default
  1212. begin
  1213.   if TRecycleBinManager.RecyclerGroupPolicyNoRecycleFiles = gpEnabled then
  1214.     result := true
  1215.   else if TRecycleBinManager.UsesGlobalSettings then
  1216.     result := TRecycleBinManager.GetGlobalNukeOnDelete
  1217.   else
  1218.   begin
  1219.     result := RES_DEFAULT;
  1220.  
  1221.     reg := TRegistry.Create;
  1222.     try
  1223.       reg.RootKey := HKEY_CURRENT_USER;
  1224.  
  1225.       if reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Explorer\BitBucket\Volume') then
  1226.       begin
  1227.         // Windows Vista and upwards
  1228.         if reg.OpenKeyReadOnly(GUIDToString(VolumeGUID)) then
  1229.         begin
  1230.           result := reg.ReadBool('NukeOnDelete');
  1231.         end;
  1232.         reg.CloseKey;
  1233.       end
  1234.       else
  1235.       begin
  1236.         reg.RootKey := HKEY_LOCAL_MACHINE;
  1237.  
  1238.         // Im Auslieferungszustand von Windows 95 ist dieser Schlüssel nicht vorhanden.
  1239.         // Er wird bei der ersten Änderung der Papierkorb-Einstellungen erstellt.
  1240.         if reg.OpenKeyReadOnly('SOFTWARE\Microsoft\Windows\CurrentVersion\explorer\BitBucket') then
  1241.         begin
  1242.           if reg.OpenKeyReadOnly(string(FDriveLetter)) then
  1243.           begin
  1244.             if reg.ValueExists('NukeOnDelete') then
  1245.             begin
  1246.               // Windows 2000 - Informationen liegen aufgeschlüsselt in der Registry
  1247.  
  1248.               result := reg.ReadBool('NukeOnDelete');
  1249.             end;
  1250.           end
  1251.           else
  1252.           begin
  1253.             if reg.ValueExists('PurgeInfo') then
  1254.             begin
  1255.               // Windows 95 - Kodierte Informationen liegen in PurgeInfo
  1256.  
  1257.               reg.ReadBinaryData('PurgeInfo', purgeInfo, SizeOf(purgeInfo));
  1258.  
  1259.               result := ((purgeInfo.NukeOnDeleteBits shr DriveNumber) and 1) = 1;
  1260.             end;
  1261.           end;
  1262.  
  1263.           reg.CloseKey;
  1264.         end;
  1265.       end;
  1266.     finally
  1267.       reg.Free;
  1268.     end;
  1269.   end;
  1270. end;
  1271.  
  1272. function TRbDrive.GetNumItems: int64;
  1273. begin
  1274.   result := GetAPIInfo.i64NumItems;
  1275. end;
  1276.  
  1277. function TRbDrive.GetSize: int64;
  1278. begin
  1279.   result := GetAPIInfo.i64Size;
  1280. end;
  1281.  
  1282. function TRbDrive.GetVolumeGUID: TGUID;
  1283. begin
  1284.   if GetDriveGUID(FDriveLetter, result) <> ERROR_SUCCESS then
  1285.   begin
  1286.     result := NULL_GUID;
  1287.   end;
  1288. end;
  1289.  
  1290. function TRbDrive.GetVolumeGUIDAvailable: boolean;
  1291. begin
  1292.   result := not IsEqualGUID(VolumeGUID, NULL_GUID);
  1293. end;
  1294.  
  1295. function TRbDrive.IsEmpty: boolean;
  1296. begin
  1297.   result := GetNumItems = 0;
  1298. end;
  1299.  
  1300. function TRbDrive.IsFAT: boolean;
  1301. var
  1302.   Dummy2: DWORD;
  1303.   Dummy3: DWORD;
  1304.   FileSystem: array[0..MAX_PATH-1] of char;
  1305.   VolumeName: array[0..MAX_PATH-1] of char;
  1306.   s: string;
  1307. begin
  1308.   s := FDriveLetter + DriveDelim + PathDelim; // ohne die Auslagerung in einen String kommt es zu einer AV in ntdll
  1309.   GetVolumeInformation(PChar(s), VolumeName,
  1310.     SizeOf(VolumeName), nil, Dummy2, Dummy3, FileSystem, SizeOf(FileSystem));
  1311.   result := uppercase(copy(FileSystem, 0, 3)) = 'FAT';
  1312. end;
  1313.  
  1314. procedure TRbDrive.ListRecycleBins(list: TObjectList{TRbRecycleBin}; UserSID: string='');
  1315.  
  1316.   procedure _AddSIDFolders(dir: string; wholeFolder: boolean);
  1317.   var
  1318.     SR: TSearchRec;
  1319.   begin
  1320.     dir := IncludeTrailingPathDelimiter(dir);
  1321.     if FindFirst(dir+'S-*', faAnyFile, SR) = 0 then
  1322.     begin
  1323.       try
  1324.         repeat
  1325.           if (SR.Name = '.') or (SR.Name = '..') or not DirectoryExists(dir + SR.Name) then continue;
  1326.  
  1327.           if wholeFolder then
  1328.           begin
  1329.             // Vista
  1330.             list.Add(TRbRecycleBin.Create(dir+SR.Name, SR.Name));
  1331.           end
  1332.           else
  1333.           begin
  1334.             // Win95 .. WinXP
  1335.             if FileExists(IncludeTrailingPathDelimiter(dir+SR.Name) + 'INFO2') then
  1336.               list.Add(TRbRecycleBin.Create(IncludeTrailingPathDelimiter(dir+SR.Name) + 'INFO2', SR.Name));
  1337.             if FileExists(IncludeTrailingPathDelimiter(dir+SR.Name) + 'INFO') then
  1338.               list.Add(TRbRecycleBin.Create(IncludeTrailingPathDelimiter(dir+SR.Name) + 'INFO', SR.Name));
  1339.           end;
  1340.         until FindNext(SR) <> 0;
  1341.       finally
  1342.         FindClose(SR);
  1343.       end;
  1344.     end;
  1345.   end;
  1346.  
  1347. var
  1348.   dir: string;
  1349. begin
  1350.   // Find recyclers from Windows Vista or higher
  1351.  
  1352.   if IsFAT then
  1353.   begin
  1354.     dir := FDriveLetter + DriveDelim + PathDelim + '$recycle.bin' + PathDelim;
  1355.     if DirectoryExists(dir) then
  1356.     begin
  1357.       list.Add(TRbRecycleBin.Create(dir));
  1358.     end;
  1359.   end
  1360.   else
  1361.   begin
  1362.     if UserSID <> '' then
  1363.     begin
  1364.       dir := FDriveLetter + DriveDelim + PathDelim + '$recycle.bin' + PathDelim + UserSID + PathDelim;
  1365.       if DirectoryExists(dir) then
  1366.       begin
  1367.         list.Add(TRbRecycleBin.Create(dir, UserSID));
  1368.       end;
  1369.     end
  1370.     else
  1371.     begin
  1372.       _AddSIDFolders(FDriveLetter + DriveDelim + PathDelim + '$recycle.bin', true);
  1373.     end;
  1374.   end;
  1375.  
  1376.   // Find recyclers from Windows before Vista
  1377.  
  1378.   if IsFAT then
  1379.   begin
  1380.     dir := FDriveLetter + DriveDelim + PathDelim + 'Recycled' + PathDelim;
  1381.  
  1382.     // Both "recycle bins" are possible if you have multiboot (but do overwrite themselfes if you empty them)
  1383.     if FileExists(dir + 'INFO2') then
  1384.       list.Add(TRbRecycleBin.Create(dir + 'INFO2')); // Windows 95 with Internet Explorer 4 Extension or higher Windows 9x versions
  1385.     if FileExists(dir + 'INFO') then
  1386.       list.Add(TRbRecycleBin.Create(dir + 'INFO')); // Windows 95 native
  1387.   end
  1388.   else
  1389.   begin
  1390.     if UserSID <> '' then
  1391.     begin
  1392.       dir := FDriveLetter + DriveDelim + PathDelim + 'Recycler' + PathDelim + UserSID + PathDelim;
  1393.  
  1394.       if FileExists(dir + 'INFO2') then
  1395.         list.Add(TRbRecycleBin.Create(dir + 'INFO2', UserSID)); // Windows 2000+
  1396.       if FileExists(dir + 'INFO') then
  1397.         list.Add(TRbRecycleBin.Create(dir + 'INFO', UserSID)); // Windows NT 4
  1398.     end
  1399.     else
  1400.     begin
  1401.       _AddSIDFolders(FDriveLetter + DriveDelim + PathDelim + 'Recycler', false);
  1402.     end;
  1403.   end;
  1404. end;
  1405.  
  1406. { TRbInfoAItem }
  1407.  
  1408. procedure TRbInfoAItem.ReadFromStream(stream: TStream);
  1409. var
  1410.   r: TRbInfoRecordA;
  1411.   i: Integer;
  1412. begin
  1413.   stream.ReadBuffer(r, SizeOf(r));
  1414.  
  1415.   if r.sourceDrive = 26 then
  1416.     FSourceDrive := '@' // @ is the "Network home drive" of the Win95 time
  1417.   else
  1418.     FSourceDrive := Chr(Ord('A') + r.sourceDrive);
  1419.  
  1420.   // Win95 with IE4 and Win2000+:
  1421.   // Wenn ein Eintrag aus der INFO/INFO2 gelöscht wird, dann wird das erste Byte
  1422.   // von sourceAnsi auf Null gesetzt, damit die ganze INFO/INFO2 Datei nicht
  1423.   // ständig neu geschrieben werden muss (so wie es bei Win95 und WinNT4 der Fall war).
  1424.   // Wir lesen den Eintrag trotzdem, da unsere Software ja auch zu forensischen
  1425.   // Zwecken eingesetzt werden soll.
  1426.   if r.sourceAnsi[0] = #0 then
  1427.   begin
  1428.     FRemovedEntry := true;
  1429.     if (r.sourceDrive = 26) and (r.sourceAnsi[1] = '\') then
  1430.       r.sourceAnsi[0] := '\'
  1431.     else
  1432.       r.sourceAnsi[0] := AnsiChar(FSourceDrive);
  1433.   end;
  1434.  
  1435.   FSourceAnsi := r.sourceAnsi;
  1436.  
  1437.   // Unicode does not exist in INFO(1) structure
  1438.   (* FSourceUnicode := AnsiCharArrayToWideString(r.sourceAnsi); *)
  1439.   SetLength(FSourceUnicode, Length(r.sourceAnsi));
  1440.   for i := 0 to Length(r.sourceAnsi)-1 do
  1441.     FSourceUnicode[i+1] := WideChar(r.sourceAnsi[i]);
  1442.  
  1443.   FID := IntToStr(r.recordNumber);
  1444.   FDeletionTime := FileTimeToDateTime(r.deletionTime);
  1445.   FOriginalSize := r.originalSize;
  1446.  
  1447.   // Remove #0 at the end. There are some bugs where #0 is added to ANSI/Unicode read paths?! (probably in the ReadVista stuff)
  1448.   // TODO: Instead of using this workaround, fix "SourceUnicode" and "SourceAnsi" in the first place!
  1449.   AnsiRemoveNulChars(FSourceAnsi);
  1450.   UnicodeRemoveNulChars(FSourceUnicode);
  1451. end;
  1452.  
  1453. function TRbInfoAItem.DeleteFile: boolean;
  1454. var
  1455.   r: string;
  1456. begin
  1457.   r := GetPhysicalFile;
  1458.   if DirectoryExists(r) then
  1459.     result := DeleteDirectory(r) // Usually, the old recycle bin does not allow folders. Just to be sure, we include the code.
  1460.   else
  1461.     result := SysUtils.DeleteFile(r); // TODO: geht das oder gibt es zugriffsverletzung? --> Win95: Funktioniert
  1462.  
  1463.   // TODO: nun auch den eintrag aus der INFO-Datei rausschmeißen (Datei neu schreiben)
  1464. end;
  1465.  
  1466. function TRbInfoAItem.GetPhysicalFile: string;
  1467. begin
  1468.   if FRemovedEntry then
  1469.   begin
  1470.     result := '';
  1471.     Exit;
  1472.   end;
  1473.  
  1474.   // e.g. C:\...\DC0.doc
  1475.   result := IncludeTrailingPathDelimiter(ExtractFilePath(IndexFile)) +
  1476.             'D' + (* SourceDrive *) Source[1] + ID + ExtractFileExt(Source);
  1477. end;
  1478.  
  1479. constructor TRbInfoAItem.Create(fs: TStream; AIndexFile: string);
  1480. begin
  1481.   inherited Create;
  1482.   ReadFromStream(fs);
  1483.   FIndexFile := AIndexFile;
  1484. end;
  1485.  
  1486. { TRbInfoWItem }
  1487.  
  1488. procedure TRbInfoWItem.ReadFromStream(stream: TStream);
  1489. var
  1490.   r: TRbInfoRecordW;
  1491. begin
  1492.   stream.ReadBuffer(r, SizeOf(r));
  1493.  
  1494.   // Win95 with IE4 and Win2000+:
  1495.   // Wenn ein Eintrag aus der INFO/INFO2 gelöscht wird, dann wird das erste Byte
  1496.   // von sourceAnsi auf Null gesetzt, damit die ganze INFO/INFO2 Datei nicht
  1497.   // ständig neu geschrieben werden muss (so wie es bei Win95 und WinNT4 der Fall war).
  1498.   // Wir lesen den Eintrag trotzdem, da unsere Software ja auch zu forensischen
  1499.   // Zwecken eingesetzt werden soll.
  1500.   if r.sourceAnsi[0] = #0 then
  1501.   begin
  1502.     FRemovedEntry := true;
  1503.     r.sourceAnsi[0] := AnsiChar(r.sourceUnicode[0]);
  1504.     (*
  1505.     if (r.sourceDrive = 26) and (r.sourceAnsi[1] = '\') then
  1506.       r.sourceAnsi[0] := '\'
  1507.     else
  1508.       r.sourceAnsi[0] := AnsiChar(FSourceDrive);
  1509.     *)
  1510.   end;
  1511.  
  1512.   FSourceAnsi := r.sourceAnsi;
  1513.   FSourceUnicode := r.sourceUnicode;
  1514.   FID := IntToStr(r.recordNumber);
  1515.  
  1516.   if r.sourceDrive = 26 then
  1517.     FSourceDrive := '@' // @ is the "Network home drive" of the Win95 time
  1518.   else
  1519.     FSourceDrive := Chr(Ord('A') + r.sourceDrive);
  1520.  
  1521.   FDeletionTime := FileTimeToDateTime(r.deletionTime);
  1522.   FOriginalSize := r.originalSize;
  1523.  
  1524.   // Remove #0 at the end. There are some bugs where #0 is added to ANSI/Unicode read paths?! (probably in the ReadVista stuff)
  1525.   // TODO: Instead of using this workaround, fix "SourceUnicode" and "SourceAnsi" in the first place!
  1526.   AnsiRemoveNulChars(FSourceAnsi);
  1527.   UnicodeRemoveNulChars(FSourceUnicode);
  1528. end;
  1529.  
  1530. function TRbInfoWItem.DeleteFile: boolean;
  1531. var
  1532.   r: string;
  1533. begin
  1534.   r := GetPhysicalFile;
  1535.   if DirectoryExists(r) then
  1536.     result := DeleteDirectory(r)
  1537.   else
  1538.     result := SysUtils.DeleteFile(r); // TODO: geht das oder gibt es zugriffsverletzung?
  1539.  
  1540.   // TODO: nun auch den eintrag aus der INFO-Datei rausschmeißen (Erstes Byte auf 0 setzen)
  1541. end;
  1542.  
  1543. function TRbInfoWItem.GetPhysicalFile: string;
  1544. begin
  1545.   if FRemovedEntry then
  1546.   begin
  1547.     result := '';
  1548.     Exit;
  1549.   end;
  1550.  
  1551.   (*
  1552.   This is actually a bit tricky...
  1553.   Win95 will choose the first letter of the AnsiSource name.
  1554.   WinNT will choose the first letter of the UnicodeSource name.
  1555.   WinXP will choose the driveNumber member.
  1556.  
  1557.   Windows XP is kinda buggy when it comes to changing a drive letter.
  1558.   For example, the drive E: was changed to K:
  1559.   The drive letter is 04 (E), the Source name begins with E:\ and the physical file is De0.txt .
  1560.   After the recycle bin is opened the first time:
  1561.   - The recycle bin will show the file origin as K:\ and not as E:\
  1562.   - The file was renamed from De0.txt to Dk0.txt
  1563.   - The file can be recovered at this time
  1564.   When the recycle bin is closed, the INFO2 file will not be corrected (which is a bug).
  1565.   So, if you open the recycle bin again, the record will be marked
  1566.   as deleted in the INFO file (the first byte will be set to 0),
  1567.   because Windows searches for De0.txt and doesn't find it.
  1568.  
  1569.   (This comment also applies to TRbInfoAItem.GetPhysicalFile)
  1570.   *)
  1571.  
  1572.   // e.g. C:\...\DC0.doc
  1573.   result := IncludeTrailingPathDelimiter(ExtractFilePath(IndexFile)) +
  1574.             'D' + SourceDrive (* SourceUnicode[1] *) + ID + ExtractFileExt(SourceUnicode);
  1575. end;
  1576.  
  1577. constructor TRbInfoWItem.Create(fs: TStream; AIndexFile: string);
  1578. begin
  1579.   inherited Create;
  1580.   ReadFromStream(fs);
  1581.   FIndexFile := AIndexFile;
  1582. end;
  1583.  
  1584. { TRbVistaItem }
  1585.  
  1586. procedure TRbVistaItem.ReadFromStream(stream: TStream);
  1587. var
  1588.   r1: TRbVistaRecord1;
  1589.   r2: TRbVistaRecord2Head;
  1590.   r2SourceUnicode: array of WideChar;
  1591.   ver: int64;
  1592.   i: Integer;
  1593. resourcestring
  1594.   LNG_VISTA_WRONG_FORMAT = 'Invalid Vista index format version %d at file %s';
  1595. begin
  1596.   stream.ReadBuffer(ver, SizeOf(ver));
  1597.  
  1598.   if ver = 1 then
  1599.   begin
  1600.     stream.Seek(0, soBeginning);
  1601.     stream.ReadBuffer(r1, SizeOf(r1));
  1602.  
  1603.     (* FSourceAnsi := AnsiString(WideCharArrayToWideString(r1.sourceUnicode)); *)
  1604.     SetLength(FSourceAnsi, Length(r1.sourceUnicode));
  1605.     for i := 0 to Length(r1.sourceUnicode)-1 do
  1606.       FSourceAnsi[i+1] := AnsiChar(r1.sourceUnicode[i]); // Note: Invalid chars are automatically converted into '?'
  1607.  
  1608.     (* FSourceUnicode := WideCharArrayToWideString(r1.sourceUnicode); *)
  1609.     SetLength(FSourceUnicode, Length(r1.sourceUnicode));
  1610.     for i := 0 to Length(r1.sourceUnicode)-1 do
  1611.       FSourceUnicode[i+1] := r1.sourceUnicode[i];
  1612.  
  1613.     FID := ''; // will be added manually (at the constructor)
  1614.     FSourceDrive := Char(r1.sourceUnicode[1]);
  1615.     FDeletionTime := FileTimeToDateTime(r1.deletionTime);
  1616.     FOriginalSize := r1.originalSize;
  1617.   end
  1618.   else if ver = 2 then
  1619.   begin
  1620.     stream.Seek(0, soBeginning);
  1621.     stream.ReadBuffer(r2, SizeOf(r2));
  1622.  
  1623.     SetLength(r2SourceUnicode, SizeOf(WideChar)*(r2.SourceCountChars-1));
  1624.     stream.Read(r2SourceUnicode[0], SizeOf(WideChar)*(r2.sourceCountChars-1));
  1625.  
  1626.     // Invalid chars are automatically converted into '?'
  1627.     (* FSourceAnsi := AnsiString(WideCharArrayToWideString(r2sourceUnicode)); *)
  1628.     SetLength(FSourceAnsi, Length(r2sourceUnicode));
  1629.     for i := 0 to Length(r2sourceUnicode)-1 do
  1630.       FSourceAnsi[i+1] := AnsiChar(r2sourceUnicode[i]);
  1631.  
  1632.     (* FSourceUnicode := WideCharArrayToWideString(r2sourceUnicode); *)
  1633.     SetLength(FSourceUnicode, Length(r2sourceUnicode));
  1634.     for i := 0 to Length(r2sourceUnicode)-1 do
  1635.       FSourceUnicode[i+1] := WideChar(r2sourceUnicode[i]);
  1636.  
  1637.     FID := ''; // will be added manually (at the constructor)
  1638.     FSourceDrive := Char(r2sourceUnicode[1]);
  1639.     FDeletionTime := FileTimeToDateTime(r2.deletionTime);
  1640.     FOriginalSize := r2.originalSize;
  1641.   end
  1642.   else
  1643.   begin
  1644.     raise Exception.CreateFmt(LNG_VISTA_WRONG_FORMAT, [ver, FID]);
  1645.   end;
  1646.  
  1647.   // Remove #0 at the end. There are some bugs where #0 is added to ANSI/Unicode read paths?! (probably in the ReadVista stuff)
  1648.   // TODO: Instead of using this workaround, fix "SourceUnicode" and "SourceAnsi" in the first place!
  1649.   AnsiRemoveNulChars(FSourceAnsi);
  1650.   UnicodeRemoveNulChars(FSourceUnicode);
  1651. end;
  1652.  
  1653. function TRbVistaItem.DeleteFile: boolean;
  1654. var
  1655.   r: string;
  1656. begin
  1657.   r := GetPhysicalFile;
  1658.   if DirectoryExists(r) then
  1659.     result := DeleteDirectory(r)
  1660.   else
  1661.     result := SysUtils.DeleteFile(r);
  1662.  
  1663.   SysUtils.DeleteFile(FIndexFile);
  1664. end;
  1665.  
  1666. function TRbVistaItem.GetPhysicalFile: string;
  1667. begin
  1668.   result := FIndexFile;
  1669.   if Pos('$I', Result) = 0 then
  1670.     result := ''
  1671.   else
  1672.     result := StringReplace(Result, '$I', '$R', [rfIgnoreCase]);
  1673. end;
  1674.  
  1675. constructor TRbVistaItem.Create(fs: TStream; AIndexFile, AID: string);
  1676. begin
  1677.   inherited Create;
  1678.   FIndexFile := AIndexFile;
  1679.   FID := AID;
  1680.   ReadFromStream(fs);
  1681. end;
  1682.  
  1683. { TRecycleBinManager }
  1684.  
  1685. class function TRecycleBinManager.EmptyOwnRecyclers(flags: cardinal): boolean;
  1686. var
  1687.   PSHEmptyRecycleBin: TSHEmptyRecycleBin;
  1688.   LibHandle: THandle;
  1689. begin
  1690.   // Source: http://www.dsdt.info/tipps/?id=176
  1691.   result := true;
  1692.   LibHandle := LoadLibrary(shell32);
  1693.   try
  1694.     if LibHandle <> 0 then
  1695.     begin
  1696.       @PSHEmptyRecycleBin := GetProcAddress(LibHandle, C_SHEmptyRecycleBin);
  1697.       if @PSHEmptyRecycleBin <> nil then
  1698.       begin
  1699.         PSHEmptyRecycleBin(hInstance, nil, flags);
  1700.       end
  1701.       else
  1702.         result := false;
  1703.     end
  1704.     else
  1705.       result := false;
  1706.   finally
  1707.     @PSHEmptyRecycleBin := nil;
  1708.     if LibHandle <> 0 then FreeLibrary(LibHandle);
  1709.   end;
  1710. end;
  1711.  
  1712. class function TRecycleBinManager.EmptyOwnRecyclers(sound, progress, confirmation: boolean): boolean;
  1713. const
  1714.   SHERB_NOCONFIRMATION = $00000001;
  1715.   SHERB_NOPROGRESSUI   = $00000002;
  1716.   SHERB_NOSOUND        = $00000004;
  1717. var
  1718.   flags: cardinal;
  1719. begin
  1720.   flags := 0;
  1721.  
  1722.   if not progress then
  1723.     flags := flags or SHERB_NOPROGRESSUI;
  1724.   if not confirmation then
  1725.     flags := flags or SHERB_NOCONFIRMATION;
  1726.   if not sound then
  1727.     flags := flags or SHERB_NOSOUND;
  1728.  
  1729.   result := EmptyOwnRecyclers(flags);
  1730. end;
  1731.  
  1732. class function TRecycleBinManager.GetGlobalMaxPercentUsage: integer;
  1733. var
  1734.   reg: TRegistry;
  1735.   purgeInfo: TRbWin95PurgeInfo;
  1736. const
  1737.   RES_DEFAULT = 10; // Windows 95 - Standardwert
  1738. begin
  1739.   if Win32MajorVersion >= 6 then
  1740.   begin
  1741.     // Only available till Windows XP
  1742.     result := -1;
  1743.     exit;
  1744.   end;
  1745.  
  1746.   result := RES_DEFAULT;
  1747.  
  1748.   reg := TRegistry.Create;
  1749.   try
  1750.     reg.RootKey := HKEY_LOCAL_MACHINE;
  1751.  
  1752.     // Im Auslieferungszustand von Windows 95 ist dieser Schlüssel nicht vorhanden.
  1753.     // Er wird bei der ersten Änderung der Papierkorb-Einstellungen erstellt.
  1754.     if reg.OpenKeyReadOnly('SOFTWARE\Microsoft\Windows\CurrentVersion\explorer\BitBucket') then
  1755.     begin
  1756.       if reg.ValueExists('Percent') then
  1757.       begin
  1758.         // Windows 2000 - Informationen liegen aufgeschlüsselt in der Registry
  1759.  
  1760.         result := reg.ReadInteger('Percent');
  1761.       end
  1762.       else if reg.ValueExists('PurgeInfo') then
  1763.       begin
  1764.         // Windows 95 - Kodierte Informationen liegen in PurgeInfo
  1765.  
  1766.         reg.ReadBinaryData('PurgeInfo', purgeInfo, SizeOf(purgeInfo));
  1767.         result := purgeInfo.percentGlobal;
  1768.       end;
  1769.  
  1770.       reg.CloseKey;
  1771.     end;
  1772.   finally
  1773.     reg.Free;
  1774.   end;
  1775. end;
  1776.  
  1777. class function TRecycleBinManager.GetGlobalNukeOnDelete: boolean;
  1778. var
  1779.   reg: TRegistry;
  1780.   purgeInfo: TRbWin95PurgeInfo;
  1781. const
  1782.   RES_DEFAULT = false; // Windows 95 - Standardwert
  1783. begin
  1784.   if Win32MajorVersion >= 6 then
  1785.   begin
  1786.     // Only available till Windows XP
  1787.     result := false;
  1788.     exit;
  1789.   end;
  1790.  
  1791.   result := RES_DEFAULT;
  1792.  
  1793.   reg := TRegistry.Create;
  1794.   try
  1795.     reg.RootKey := HKEY_LOCAL_MACHINE;
  1796.  
  1797.     // Im Auslieferungszustand von Windows 95 ist dieser Schlüssel nicht vorhanden.
  1798.     // Er wird bei der ersten Änderung der Papierkorb-Einstellungen erstellt.
  1799.     if reg.OpenKeyReadOnly('SOFTWARE\Microsoft\Windows\CurrentVersion\explorer\BitBucket') then
  1800.     begin
  1801.       if reg.ValueExists('NukeOnDelete') then
  1802.       begin
  1803.         // Windows 2000 - Informationen liegen aufgeschlüsselt in der Registry
  1804.  
  1805.         result := reg.ReadBool('NukeOnDelete');
  1806.       end
  1807.       else if reg.ValueExists('PurgeInfo') then
  1808.       begin
  1809.         // Windows 95 - Kodierte Informationen liegen in PurgeInfo
  1810.  
  1811.         reg.ReadBinaryData('PurgeInfo', purgeInfo, SizeOf(purgeInfo));
  1812.         result := (purgeInfo.NukeOnDeleteBits and $8000000) = $8000000; // bit 27
  1813.       end;
  1814.  
  1815.       reg.CloseKey;
  1816.     end;
  1817.   finally
  1818.     reg.Free;
  1819.   end;
  1820. end;
  1821.  
  1822. (* TODO:
  1823. There are more registry values (found in WinXP):
  1824.  
  1825. BitBucket\<driveletter>
  1826.   VolumeSerialNumber
  1827.   IsUnicode
  1828.  
  1829. *)
  1830.  
  1831. class function TRecycleBinManager.UsesGlobalSettings: boolean;
  1832. var
  1833.   reg: TRegistry;
  1834.   purgeInfo: TRbWin95PurgeInfo;
  1835. const
  1836.   RES_DEFAULT = true; // Windows 95 - Standardwert
  1837. begin
  1838.   if Win32MajorVersion >= 6 then
  1839.   begin
  1840.     // Only available till Windows XP
  1841.     result := false;
  1842.     exit;
  1843.   end;
  1844.  
  1845.   result := RES_DEFAULT;
  1846.  
  1847.   reg := TRegistry.Create;
  1848.   try
  1849.     reg.RootKey := HKEY_LOCAL_MACHINE;
  1850.  
  1851.     // Im Auslieferungszustand von Windows 95 ist dieser Schlüssel nicht vorhanden.
  1852.     // Er wird bei der ersten Änderung der Papierkorb-Einstellungen erstellt.
  1853.     if reg.OpenKeyReadOnly('SOFTWARE\Microsoft\Windows\CurrentVersion\explorer\BitBucket') then
  1854.     begin
  1855.       if reg.ValueExists('UseGlobalSettings') then
  1856.       begin
  1857.         // Windows 2000 - Informationen liegen aufgeschlüsselt in der Registry
  1858.  
  1859.         result := reg.ReadBool('UseGlobalSettings');
  1860.       end
  1861.       else if reg.ValueExists('PurgeInfo') then
  1862.       begin
  1863.         // Windows 95 - Kodierte Informationen liegen in PurgeInfo
  1864.  
  1865.         reg.ReadBinaryData('PurgeInfo', purgeInfo, SizeOf(purgeInfo));
  1866.         result := purgeInfo.bGlobalSettings;
  1867.       end;
  1868.  
  1869.       reg.CloseKey;
  1870.     end;
  1871.   finally
  1872.     reg.Free;
  1873.   end;
  1874. end;
  1875.  
  1876. class procedure TRecycleBinManager.ListDrives(list: TObjectList{TRbDrive});
  1877. var
  1878.   drive: AnsiChar;
  1879. begin
  1880.   for drive := 'A' to 'Z' do
  1881.     if RecycleBinPossible(drive) then
  1882.       list.Add(TRbDrive.Create(drive));
  1883. end;
  1884.  
  1885. class function TRecycleBinManager.OwnRecyclersEmpty: boolean;
  1886. var
  1887.   drives: TObjectList;
  1888.   i: integer;
  1889. begin
  1890.   result := true;
  1891.  
  1892.   drives := TObjectList.Create(true);
  1893.   try
  1894.     ListDrives(drives);
  1895.     for i := 0 to drives.Count - 1 do
  1896.     begin
  1897.       result := result and TRbDrive(drives.Items[i]).IsEmpty;
  1898.       if not result then break;
  1899.     end;
  1900.   finally
  1901.     drives.Free;
  1902.   end;
  1903. end;
  1904.  
  1905. class function TRecycleBinManager.OwnRecyclersNumItems: int64;
  1906. var
  1907.   drives: TObjectList;
  1908.   i: integer;
  1909. begin
  1910.   result := 0;
  1911.  
  1912.   drives := TObjectList.Create(true);
  1913.   try
  1914.     ListDrives(drives);
  1915.     for i := 0 to drives.Count - 1 do
  1916.     begin
  1917.       result := result + TRbDrive(drives.Items[i]).GetNumItems;
  1918.     end;
  1919.   finally
  1920.     drives.Free;
  1921.   end;
  1922. end;
  1923.  
  1924. class function TRecycleBinManager.OwnRecyclersSize: int64;
  1925. var
  1926.   drives: TObjectList;
  1927.   i: integer;
  1928. begin
  1929.   result := 0;
  1930.  
  1931.   drives := TObjectList.Create(true);
  1932.   try
  1933.     ListDrives(drives);
  1934.     for i := 0 to drives.Count - 1 do
  1935.     begin
  1936.       result := result + TRbDrive(drives.Items[i]).GetSize;
  1937.     end;
  1938.   finally
  1939.     drives.Free;
  1940.   end;
  1941. end;
  1942.  
  1943. class function TRecycleBinManager.RecycleBinPossible(Drive: AnsiChar): boolean;
  1944. var
  1945.   typ: Integer;
  1946. begin
  1947.   // Does the drive exist?
  1948.   // see http://www.delphipraxis.net/post2933.html
  1949.   result := GetLogicalDrives and (1 shl DriveLetterToDriveNumber(Drive)) <> 0;
  1950.   if not result then exit;
  1951.  
  1952.   // Is it a fixed drive? (Only they can have recycle bins)
  1953.   // TODO: is that correct, or can also have other drive types have recyclers?
  1954.   typ := GetDriveType(PChar(Drive + ':\'));
  1955.   result := typ = DRIVE_FIXED;
  1956. end;
  1957.  
  1958. class function TRecycleBinManager.RecyclerGetCurrentIconString: string;
  1959. begin
  1960.   if OwnRecyclersEmpty then
  1961.     result := RecyclerGetEmptyIconString
  1962.   else
  1963.     result := RecyclerGetFullIconString;
  1964. end;
  1965.  
  1966. class function TRecycleBinManager.RecyclerGetDefaultIconString: string;
  1967. var
  1968.   reg: TRegistry;
  1969. begin
  1970.   // Please note: The "default" icon is not always the icon of the
  1971.   // current recycle bin in its current state (full, empty)
  1972.   // At Windows 95b, the registry value actually did change every time the
  1973.   // recycle bin state did change, but at Windows 2000 I could not see any
  1974.   // update, even after reboot. So, the registry value is possible fixed as
  1975.   // default = empty on newer OS versions.
  1976.  
  1977.   reg := TRegistry.Create;
  1978.   try
  1979.     reg.RootKey := HKEY_CLASSES_ROOT;
  1980.     if reg.OpenKeyReadOnly('CLSID\'+GUIDToString(RECYCLER_CLSID)+'\DefaultIcon') then
  1981.     begin
  1982.       result := reg.ReadString('');
  1983.       reg.CloseKey;
  1984.     end;
  1985.   finally
  1986.     reg.Free;
  1987.   end;
  1988. end;
  1989.  
  1990. class function TRecycleBinManager.RecyclerGetEmptyIconString: string;
  1991. var
  1992.   reg: TRegistry;
  1993. begin
  1994.   reg := TRegistry.Create;
  1995.   try
  1996.     reg.RootKey := HKEY_CLASSES_ROOT;
  1997.     if reg.OpenKeyReadOnly('CLSID\'+GUIDToString(RECYCLER_CLSID)+'\DefaultIcon') then
  1998.     begin
  1999.       result := reg.ReadString('Empty');
  2000.       reg.CloseKey;
  2001.     end;
  2002.   finally
  2003.     reg.Free;
  2004.   end;
  2005. end;
  2006.  
  2007. class function TRecycleBinManager.RecyclerGetFullIconString: string;
  2008. var
  2009.   reg: TRegistry;
  2010. begin
  2011.   reg := TRegistry.Create;
  2012.   try
  2013.     reg.RootKey := HKEY_CLASSES_ROOT;
  2014.     if reg.OpenKeyReadOnly('CLSID\'+GUIDToString(RECYCLER_CLSID)+'\DefaultIcon') then
  2015.     begin
  2016.       result := reg.ReadString('Full');
  2017.       reg.CloseKey;
  2018.     end;
  2019.   finally
  2020.     reg.Free;
  2021.   end;
  2022. end;
  2023.  
  2024. class function TRecycleBinManager.RecyclerGetInfoTip: string;
  2025. var
  2026.   reg: TRegistry;
  2027. begin
  2028.   // Not available in some older versions of Windows
  2029.  
  2030.   reg := TRegistry.Create;
  2031.   try
  2032.     reg.RootKey := HKEY_CLASSES_ROOT;
  2033.     if reg.OpenKeyReadOnly('CLSID\'+GUIDToString(RECYCLER_CLSID)) then
  2034.     begin
  2035.       result := reg.ReadString('InfoTip');
  2036.       result := DecodeReferenceString(result);
  2037.  
  2038.       reg.CloseKey;
  2039.     end;
  2040.   finally
  2041.     reg.Free;
  2042.   end;
  2043. end;
  2044.  
  2045. class function TRecycleBinManager.RecyclerGetIntroText: string;
  2046. var
  2047.   reg: TRegistry;
  2048. begin
  2049.   // Not available in some older versions of Windows
  2050.  
  2051.   reg := TRegistry.Create;
  2052.   try
  2053.     reg.RootKey := HKEY_CLASSES_ROOT;
  2054.     if reg.OpenKeyReadOnly('CLSID\'+GUIDToString(RECYCLER_CLSID)) then
  2055.     begin
  2056.       result := reg.ReadString('IntroText');
  2057.       result := DecodeReferenceString(result);
  2058.  
  2059.       reg.CloseKey;
  2060.     end;
  2061.   finally
  2062.     reg.Free;
  2063.   end;
  2064. end;
  2065.  
  2066. class function TRecycleBinManager.RecyclerGetName: string;
  2067. var
  2068.   reg: TRegistry;
  2069. begin
  2070.   // Windows 95b:
  2071.   // Change of CLSID\{645FF040-5081-101B-9F08-00AA002F954E} will change the desktop name of the recycle bin.
  2072.  
  2073.   // Windows 2000: If LocalizedString is available, the 3rd argument will be parsed
  2074.   // (if the third argument will removed, it will be read out from the DLL resource string automatically)
  2075.  
  2076.   reg := TRegistry.Create;
  2077.   try
  2078.     reg.RootKey := HKEY_CLASSES_ROOT;
  2079.     if reg.OpenKeyReadOnly('CLSID\'+GUIDToString(RECYCLER_CLSID)) then
  2080.     begin
  2081.       if reg.ValueExists('LocalizedString') then
  2082.       begin
  2083.         result := reg.ReadString('LocalizedString');
  2084.         result := DecodeReferenceString(result);
  2085.       end
  2086.       else
  2087.       begin
  2088.         result := reg.ReadString('');
  2089.       end;
  2090.  
  2091.       reg.CloseKey;
  2092.     end;
  2093.   finally
  2094.     reg.Free;
  2095.   end;
  2096. end;
  2097.  
  2098. class function TRecycleBinManager.RecyclerEmptyEventGetName: string;
  2099. var
  2100.   reg: TRegistry;
  2101. begin
  2102.   reg := TRegistry.Create;
  2103.   try
  2104.     reg.RootKey := HKEY_CURRENT_USER;
  2105.     if reg.OpenKeyReadOnly('AppEvents\EventLabels\EmptyRecycleBin') then
  2106.     begin
  2107.       result := reg.ReadString('');
  2108.       reg.CloseKey;
  2109.     end;
  2110.   finally
  2111.     reg.Free;
  2112.   end;
  2113. end;
  2114.  
  2115. class function TRecycleBinManager.RecyclerEmptyEventGetCurrentSound: string;
  2116. begin
  2117.   result := RecyclerEmptyEventGetSound('.Current');
  2118. end;
  2119.  
  2120. class function TRecycleBinManager.RecyclerEmptyEventGetDefaultSound: string;
  2121. begin
  2122.   result := RecyclerEmptyEventGetSound('.Default');
  2123. end;
  2124.  
  2125. class procedure TRecycleBinManager.RecyclerEmptyEventGetSoundCategories(AStringList: TStringList);
  2126. var
  2127.   reg: TRegistry;
  2128. begin
  2129.   reg := TRegistry.Create;
  2130.   try
  2131.     reg.RootKey := HKEY_CURRENT_USER;
  2132.     if reg.OpenKeyReadOnly('AppEvents\Schemes\Apps\Explorer\EmptyRecycleBin') then
  2133.     begin
  2134.       reg.GetKeyNames(AStringList);
  2135.       reg.CloseKey;
  2136.     end;
  2137.   finally
  2138.     reg.Free;
  2139.   end;
  2140. end;
  2141.  
  2142. class function TRecycleBinManager.RecyclerEmptyEventGetSound(ACategory: string): string;
  2143. var
  2144.   reg: TRegistry;
  2145. resourcestring
  2146.   LNG_SND_EVENT_CAT_ERROR = 'The category "%s" is not available for the notification event "%s".';
  2147. begin
  2148.   // Outputs an filename or empty string for no sound defined.
  2149.  
  2150.   reg := TRegistry.Create;
  2151.   try
  2152.     reg.RootKey := HKEY_CURRENT_USER;
  2153.     if reg.OpenKeyReadOnly('AppEvents\Schemes\Apps\Explorer\EmptyRecycleBin') then
  2154.     begin
  2155.       if reg.OpenKeyReadOnly(ACategory) then
  2156.       begin
  2157.         result := reg.ReadString('');
  2158.         reg.CloseKey;
  2159.       end
  2160.       else
  2161.         raise EEventCategoryNotDefined.CreateFmt(LNG_SND_EVENT_CAT_ERROR, [ACategory, 'EmptyRecycleBin']);
  2162.       reg.CloseKey;
  2163.     end;
  2164.   finally
  2165.     reg.Free;
  2166.   end;
  2167. end;
  2168.  
  2169. class function TRecycleBinManager.RecyclerQueryFunctionAvailable: boolean;
  2170. var
  2171.   RBHandle: THandle;
  2172.   SHQueryRecycleBin: TSHQueryRecycleBin;
  2173. begin
  2174.   // Windows 95 without Internet Explorer 4 has no SHQueryRecycleBinA.
  2175.   RBHandle := LoadLibrary(shell32);
  2176.   try
  2177.     if RBHandle <> 0 then
  2178.     begin
  2179.       SHQueryRecycleBin := GetProcAddress(RBHandle, C_SHQueryRecycleBin);
  2180.       if not Assigned(@SHQueryRecycleBin) then
  2181.       begin
  2182.         FreeLibrary(RBHandle);
  2183.         RBHandle := 0;
  2184.       end;
  2185.     end;
  2186.  
  2187.     result := RBHandle <> 0;
  2188.   finally
  2189.     if RBHandle <> 0 then FreeLibrary(RBHandle);
  2190.   end;
  2191. end;
  2192.  
  2193. class function TRecycleBinManager.RecyclerAddFileOrFolder(FileOrFolder: string; confirmation: boolean=false): boolean;
  2194. var
  2195.   Operation: TSHFileOpStruct;
  2196. begin
  2197.   // Template: http://www.dsdt.info/tipps/?id=116
  2198.   with Operation do
  2199.   begin
  2200.     Wnd := hInstance; // OK?
  2201.     wFunc := FO_DELETE;
  2202.     pFrom := PChar(FileOrFolder + #0);
  2203.     pTo := nil;
  2204.     fFlags := FOF_ALLOWUNDO;
  2205.     if not confirmation then fFlags := fFlags or FOF_NOCONFIRMATION;
  2206.   end;
  2207.   Result := SHFileOperation(Operation) = 0;
  2208. end;
  2209.  
  2210. class function TRecycleBinManager.RecyclerGroupPolicyNoRecycleFiles: GPOLICYBOOL;
  2211. var
  2212.   reg: TRegistry;
  2213. begin
  2214.   result := gpUndefined;
  2215.  
  2216.   reg := TRegistry.Create;
  2217.   try
  2218.     // If a value is set in HKEY_LOCAL_MACHINE, it will be prefered,
  2219.     // even if gpedit.msc shows "Not configured"!
  2220.     {$IFDEF GroupPolicyAcceptHKLMTrick}
  2221.     reg.RootKey := HKEY_LOCAL_MACHINE;
  2222.     if reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Policies\Explorer') then
  2223.     begin
  2224.       if reg.ValueExists('NoRecycleFiles') then
  2225.       begin
  2226.         if reg.ReadBool('NoRecycleFiles') then
  2227.           result := gpEnabled
  2228.         else
  2229.           result := gpDisabled;
  2230.         Exit;
  2231.       end;
  2232.       reg.CloseKey;
  2233.     end;
  2234.     {$ENDIF}
  2235.  
  2236.     reg.RootKey := HKEY_CURRENT_USER;
  2237.     if reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Policies\Explorer') then
  2238.     begin
  2239.       if reg.ValueExists('NoRecycleFiles') then
  2240.       begin
  2241.         if reg.ReadBool('NoRecycleFiles') then
  2242.           result := gpEnabled
  2243.         else
  2244.           result := gpDisabled;
  2245.       end;
  2246.       reg.CloseKey;
  2247.     end;
  2248.   finally
  2249.     reg.Free;
  2250.   end;
  2251. end;
  2252.  
  2253. class function TRecycleBinManager.RecyclerGroupPolicyConfirmFileDelete: GPOLICYBOOL;
  2254. var
  2255.   reg: TRegistry;
  2256. begin
  2257.   result := gpUndefined;
  2258.   reg := TRegistry.Create;
  2259.   try
  2260.     // If a value is set in HKEY_LOCAL_MACHINE, it will be prefered,
  2261.     // even if gpedit.msc shows "Not configured"!
  2262.     {$IFDEF GroupPolicyAcceptHKLMTrick}
  2263.     reg.RootKey := HKEY_LOCAL_MACHINE;
  2264.     if reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Policies\Explorer') then
  2265.     begin
  2266.       if reg.ValueExists('ConfirmFileDelete') then
  2267.       begin
  2268.         if reg.ReadBool('ConfirmFileDelete') then
  2269.           result := gpEnabled
  2270.         else
  2271.           result := gpDisabled;
  2272.         Exit;
  2273.       end;
  2274.       reg.CloseKey;
  2275.     end;
  2276.     {$ENDIF}
  2277.  
  2278.     reg.RootKey := HKEY_CURRENT_USER;
  2279.     if reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Policies\Explorer') then
  2280.     begin
  2281.       if reg.ValueExists('ConfirmFileDelete') then
  2282.       begin
  2283.         if reg.ReadBool('ConfirmFileDelete') then
  2284.           result := gpEnabled
  2285.         else
  2286.           result := gpDisabled;
  2287.       end;
  2288.       reg.CloseKey;
  2289.     end;
  2290.   finally
  2291.     reg.Free;
  2292.   end;
  2293. end;
  2294.  
  2295. class function TRecycleBinManager.RecyclerGroupPolicyRecycleBinSize: integer;
  2296. var
  2297.   reg: TRegistry;
  2298. begin
  2299.   result := -1;
  2300.   reg := TRegistry.Create;
  2301.   try
  2302.     // If a value is set in HKEY_LOCAL_MACHINE, it will be prefered,
  2303.     // even if gpedit.msc shows "Not configured"!
  2304.     {$IFDEF GroupPolicyAcceptHKLMTrick}
  2305.     reg.RootKey := HKEY_LOCAL_MACHINE;
  2306.     if reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Policies\Explorer') then
  2307.     begin
  2308.       if reg.ValueExists('RecycleBinSize') then
  2309.       begin
  2310.         result := reg.ReadInteger('RecycleBinSize');
  2311.         Exit;
  2312.       end;
  2313.       reg.CloseKey;
  2314.     end;
  2315.     {$ENDIF}
  2316.  
  2317.     reg.RootKey := HKEY_CURRENT_USER;
  2318.     if reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Policies\Explorer') then
  2319.     begin
  2320.       if reg.ValueExists('RecycleBinSize') then
  2321.       begin
  2322.         result := reg.ReadInteger('RecycleBinSize');
  2323.       end;
  2324.       reg.CloseKey;
  2325.     end;
  2326.   finally
  2327.     reg.Free;
  2328.   end;
  2329. end;
  2330.  
  2331. class function TRecycleBinManager.RecyclerConfirmationDialogEnabled: boolean;
  2332. var
  2333.   gp: GPOLICYBOOL;
  2334. begin
  2335.   gp := RecyclerGroupPolicyConfirmFileDelete;
  2336.   if gp <> gpUndefined then
  2337.   begin
  2338.     result := gp = gpEnabled;
  2339.   end
  2340.   else
  2341.   begin
  2342.     result := RecyclerShellStateConfirmationDialogEnabled;
  2343.   end;
  2344. end;
  2345.  
  2346. class function TRecycleBinManager.RecyclerShellStateConfirmationDialogEnabled: boolean;
  2347. var
  2348.   lpss: SHELLSTATE;
  2349.   bNoConfirmRecycle: boolean;
  2350.  
  2351.   PSHGetSettings: TSHGetSettings;
  2352.   RBHandle: THandle;
  2353.  
  2354.   reg: TRegistry;
  2355.   rbuf: array[0..255] of byte;
  2356. begin
  2357.   PSHGetSettings := nil;
  2358.   result := false; // Avoid warning message
  2359.  
  2360.   RBHandle := LoadLibrary(shell32);
  2361.   try
  2362.     if RBHandle <> 0 then
  2363.     begin
  2364.       PSHGetSettings := GetProcAddress(RBHandle, C_SHGetSettings);
  2365.       if not Assigned(@PSHGetSettings) then
  2366.       begin
  2367.         FreeLibrary(RBHandle);
  2368.         RBHandle := 0;
  2369.       end;
  2370.     end;
  2371.  
  2372.     if (RBHandle <> 0) and Assigned(PSHGetSettings) then
  2373.     begin
  2374.       ZeroMemory(@lpss, SizeOf(lpss));
  2375.       PSHGetSettings(lpss, SSF_NOCONFIRMRECYCLE);
  2376.       bNoConfirmRecycle := (lpss.Flags1 and 4) = 4; // fNoConfirmRecycle
  2377.  
  2378.       result := not bNoConfirmRecycle;
  2379.     end
  2380.     else
  2381.     begin
  2382.       reg := TRegistry.Create;
  2383.       try
  2384.         // API function call failed. Probably because Windows is too old.
  2385.         // Try to read out from registry.
  2386.         // The 3rd bit of the 5th byte of "ShellState" is the value
  2387.         // of "fNoConfirmRecycle".
  2388.  
  2389.         reg.RootKey := HKEY_CURRENT_USER;
  2390.         if reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Explorer') then
  2391.         begin
  2392.           ZeroMemory(@rbuf, SizeOf(rbuf));
  2393.           reg.ReadBinaryData('ShellState', rbuf, SizeOf(rbuf));
  2394.  
  2395.           // Lese 3tes Bit vom 5ten Byte
  2396.           bNoConfirmRecycle := ((rbuf[4] and 4) = 4);
  2397.           result := not bNoConfirmRecycle;
  2398.  
  2399.           reg.CloseKey;
  2400.         end
  2401.         else
  2402.         begin
  2403.           raise EAPICallError.CreateFmt(LNG_API_CALL_ERROR, [Format(LNG_NOT_CALLABLE, [C_SHGetSettings])]);
  2404.         end;
  2405.       finally
  2406.         reg.Free;
  2407.       end;
  2408.     end;
  2409.   finally
  2410.     if RBHandle <> 0 then FreeLibrary(RBHandle);
  2411.   end;
  2412. end;
  2413.  
  2414. class procedure TRecycleBinManager.RecyclerConfirmationDialogSetEnabled(NewSetting: boolean);
  2415. var
  2416.   lpss: SHELLSTATE;
  2417.  
  2418.   PSHGetSetSettings: TSHGetSetSettings;
  2419.   RBHandle: THandle;
  2420.  
  2421.   reg: TRegistry;
  2422.   rbuf: array[0..255] of byte;
  2423.  
  2424.   //dwResult: DWORD;
  2425.   lpdwResult: PDWORD_PTR;
  2426. begin
  2427.   PSHGetSetSettings := nil;
  2428.   lpdwResult := nil;
  2429.  
  2430.   RBHandle := LoadLibrary(shell32);
  2431.   try
  2432.     if RBHandle <> 0 then
  2433.     begin
  2434.       PSHGetSetSettings := GetProcAddress(RBHandle, C_SHGetSetSettings);
  2435.       if not Assigned(@PSHGetSetSettings) then
  2436.       begin
  2437.         FreeLibrary(RBHandle);
  2438.         RBHandle := 0;
  2439.       end;
  2440.     end;
  2441.  
  2442.     if (RBHandle <> 0) and Assigned(PSHGetSetSettings) then
  2443.     begin
  2444.       ZeroMemory(@lpss, SizeOf(lpss));
  2445.  
  2446.       PSHGetSetSettings(lpss, SSF_NOCONFIRMRECYCLE, false); // Get
  2447.  
  2448.       // Set 3rd bit equal to NewSetting
  2449.       if NewSetting then
  2450.         lpss.Flags1 := lpss.Flags1 or  $00000004
  2451.       else
  2452.         lpss.Flags1 := lpss.Flags1 and $FFFFFFFB;
  2453.  
  2454.       PSHGetSetSettings(lpss, SSF_NOCONFIRMRECYCLE, true); // Set
  2455.  
  2456.       SendMessageTimeout (
  2457.         HWND_BROADCAST, WM_SETTINGCHANGE,
  2458.         0, lParam (pChar ('ShellState')),
  2459.         SMTO_ABORTIFHUNG, 5000, lpdwResult(*dwResult*)
  2460.       );
  2461.     end
  2462.     else
  2463.     begin
  2464.       reg := TRegistry.Create;
  2465.       try
  2466.         // API function call failed. Probably because Windows is too old.
  2467.         // Try to read out from registry.
  2468.         // The 3rd bit of the 5th byte of "ShellState" is the value
  2469.         // of "fNoConfirmRecycle".
  2470.  
  2471.         reg.RootKey := HKEY_CURRENT_USER;
  2472.         if reg.OpenKey('Software\Microsoft\Windows\CurrentVersion\Explorer', false) then
  2473.         begin
  2474.           ZeroMemory(@rbuf, SizeOf(rbuf));
  2475.  
  2476.           reg.ReadBinaryData('ShellState', rbuf, SizeOf(rbuf)); // Get
  2477.  
  2478.           // Set 3rd bit equal to NewSetting
  2479.           if NewSetting then
  2480.             rbuf[4] := rbuf[4] or  $04
  2481.           else
  2482.             rbuf[4] := rbuf[4] and $FB;
  2483.  
  2484.           reg.WriteBinaryData('ShellState', rbuf, SizeOf(rbuf)); // Set
  2485.  
  2486.           SendMessageTimeout (
  2487.             HWND_BROADCAST, WM_SETTINGCHANGE,
  2488.             0, lParam (pChar ('ShellState')),
  2489.             SMTO_ABORTIFHUNG, 5000, lpdwResult(*dwResult*)
  2490.           );
  2491.  
  2492.           reg.CloseKey;
  2493.         end
  2494.         else
  2495.         begin
  2496.           raise EAPICallError.CreateFmt(LNG_API_CALL_ERROR, [Format(LNG_NOT_CALLABLE, [C_SHGetSetSettings])]);
  2497.         end;
  2498.       finally
  2499.         reg.Free;
  2500.       end;
  2501.     end;
  2502.   finally
  2503.     if RBHandle <> 0 then FreeLibrary(RBHandle);
  2504.   end;
  2505. end;
  2506.  
  2507. { TRbRecycleBinItem }
  2508.  
  2509. function TRbRecycleBinItem.GetSource: string;
  2510. begin
  2511.   {$IFDEF UNICODE}
  2512.   result := SourceUnicode;
  2513.   {$ELSE}
  2514.   result := SourceAnsi;
  2515.   {$ENDIF}
  2516. end;
  2517.  
  2518. end.
  2519.