Subversion Repositories recyclebinunit

Rev

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