Subversion Repositories recyclebinunit

Rev

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