Subversion Repositories recyclebinunit

Rev

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