Subversion Repositories recyclebinunit

Rev

Rev 77 | Rev 80 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
75 daniel-mar 1
unit RecBinUnit2 platform;
2
 
3
////////////////////////////////////////////////////////////////////////////////////
76 daniel-mar 4
// RECYCLE-BIN-UNIT V2 BY DANIEL MARSCHALL, VIATHINKSOFT                          //
75 daniel-mar 5
// E-MAIL: info@daniel-marschall.de                                               //
76 daniel-mar 6
// Web:    www.daniel-marschall.de & www.viathinksoft.de                          //
75 daniel-mar 7
////////////////////////////////////////////////////////////////////////////////////
78 daniel-mar 8
// Revision: 17 JUL 2016                                                          //
75 daniel-mar 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)                                        //
76 daniel-mar 14
// Windows 98 SE                                                                  //
75 daniel-mar 15
// Windows NT4 SP6                                                                //
76 daniel-mar 16
// Windows XP SP3                                                                 //
17
// Windows 2000 SP4                                                               //
75 daniel-mar 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?
76 daniel-mar 31
// TODO: Don't crash when timestamp is invalid. Do something else instead.
75 daniel-mar 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
78 daniel-mar 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
75 daniel-mar 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
78 daniel-mar 54
  Windows, SysUtils, Classes, ContNrs, ShellAPI, Registry, Messages, Math;
75 daniel-mar 55
 
56
const
78 daniel-mar 57
  RECBINUNIT_VERSION = '2016-07-17';
75 daniel-mar 58
 
59
  RECYCLER_CLSID: TGUID = '{645FF040-5081-101B-9F08-00AA002F954E}';
78 daniel-mar 60
  NULL_GUID:      TGUID = '{00000000-0000-0000-0000-000000000000}';
75 daniel-mar 61
 
62
type
63
  EAPICallError = class(Exception);
64
  EEventCategoryNotDefined = class(Exception);
65
  EInvalidDrive = class(Exception);
66
 
67
  PSHQueryRBInfo = ^TSHQueryRBInfo;
68
  TSHQueryRBInfo = packed record
76 daniel-mar 69
    cbSize      : DWORD;
75 daniel-mar 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
 
76 daniel-mar 107
  TRbInfoAItem = class(TRbRecycleBinItem)
75 daniel-mar 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
 
76 daniel-mar 118
  TRbInfoWItem = class(TRbRecycleBinItem)
75 daniel-mar 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;
76 daniel-mar 144
    FTolerantReading: boolean;
75 daniel-mar 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;
76 daniel-mar 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;
75 daniel-mar 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
 
78 daniel-mar 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;
75 daniel-mar 170
  strict protected
171
    function IsFAT: boolean;
172
    procedure CheckDriveExisting;
78 daniel-mar 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
75 daniel-mar 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;
77 daniel-mar 187
    property VolumeGUIDAvailable: boolean read GetVolumeGUIDAvailable;
75 daniel-mar 188
    function GetAPIInfo: TSHQueryRBInfo;
189
    function GetSize: int64;
190
    function GetNumItems: int64;
191
    function IsEmpty: boolean;
192
 
78 daniel-mar 193
    function GetMaxPercentUsage: Extended; // 0..1
194
    function GetMaxAbsoluteUsage: integer; // in MB
75 daniel-mar 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';
76 daniel-mar 354
  LNG_INVALID_INFO_FORMAT = 'Unexpected record size: %s';
75 daniel-mar 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;
76 daniel-mar 598
  TolerantReading := true;
75 daniel-mar 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;
76 daniel-mar 616
    infoHdr: TRbInfoHeader;
75 daniel-mar 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
76 daniel-mar 630
      else if SameText(ExtractFileName(AFile), 'INFO') or
631
              SameText(ExtractFileName(AFile), 'INFO2') then
75 daniel-mar 632
      begin
633
        fs.ReadBuffer(infoHdr, SizeOf(infoHdr));
76 daniel-mar 634
        _Assert((infoHdr.recordLength = SizeOf(TRbInfoRecordA)) or
635
                (infoHdr.recordLength = SizeOf(TRbInfoRecordW)), LNG_IDXERR_INFO_RECSIZE_UNEXPECTED, [AFile]);
75 daniel-mar 636
        _Assert((fs.Size-fs.Position) mod infoHdr.recordLength = 0, LNG_IDXERR_INFO_UNEXPECTED_EOF, [AFile]);
76 daniel-mar 637
        // TODO: we can also check infoHdr.totalSize or infoHdr.totalEntries
75 daniel-mar 638
      end
76 daniel-mar 639
      else Assert(false);
75 daniel-mar 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;
76 daniel-mar 691
    infoHdr: TRbInfoHeader;
75 daniel-mar 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
76 daniel-mar 709
          SizeOf(TRbInfoRecordA):
75 daniel-mar 710
          begin
711
            while fs.Position < fs.size do
712
            begin
76 daniel-mar 713
              testItem := TRbInfoAItem.Create(fs, AFile);
75 daniel-mar 714
              if testItem.ID = id then
715
              begin
716
                result := testItem;
717
                break;
718
              end;
719
            end;
720
          end;
76 daniel-mar 721
          SizeOf(TRbInfoRecordW):
75 daniel-mar 722
          begin
723
            while fs.Position < fs.size do
724
            begin
76 daniel-mar 725
              testItem := TRbInfoWItem.Create(fs, AFile);
75 daniel-mar 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;
76 daniel-mar 797
    infoHdr: TRbInfoHeader;
75 daniel-mar 798
    testID: string;
76 daniel-mar 799
    wTest: TRbInfoWItem;
800
    bakPosition: int64;
75 daniel-mar 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
76 daniel-mar 812
          testID := ''; // Just in case the user wants to read a single Vista file, but without the $I name
75 daniel-mar 813
 
814
        list.Add(TRbVistaItem.Create(fs, AFile, testID));
815
      end
816
      else
817
      begin
76 daniel-mar 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
75 daniel-mar 827
          begin
76 daniel-mar 828
            // Can we actually read a Unicode record?
829
            if fs.Position + SizeOf(TRbInfoRecordW) <= fs.Size then
75 daniel-mar 830
            begin
76 daniel-mar 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));
75 daniel-mar 855
            end;
856
          end;
76 daniel-mar 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):
75 daniel-mar 864
            begin
76 daniel-mar 865
              while fs.Position < fs.size do
866
              begin
867
                list.Add(TRbInfoAItem.Create(fs, AFile));
868
              end;
75 daniel-mar 869
            end;
76 daniel-mar 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;
75 daniel-mar 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
76 daniel-mar 933
    _HandleIndexFile(FFileOrDirectory); // Either INFO, or INFO2, or a single Vista index file
75 daniel-mar 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
78 daniel-mar 944
  if not GetLogicalDrives and (1 shl DriveNumber) <> 0 then
75 daniel-mar 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
 
78 daniel-mar 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
 
75 daniel-mar 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
 
78 daniel-mar 1017
function TRbDrive.GetMaxPercentUsage: Extended;
75 daniel-mar 1018
var
78 daniel-mar 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
75 daniel-mar 1073
  reg: TRegistry;
1074
  purgeInfo: TRbWin95PurgeInfo;
1075
begin
78 daniel-mar 1076
  if Win32MajorVersion >= 6 then
1077
  begin
1078
    // Only available till Windows XP
1079
    result := false;
1080
    exit;
1081
  end;
75 daniel-mar 1082
 
78 daniel-mar 1083
  result := false;
1084
 
75 daniel-mar 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
 
78 daniel-mar 1099
          res := reg.ReadInteger('Percent');
1100
          result := true;
75 daniel-mar 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
 
78 daniel-mar 1111
          res := purgeInfo.percentDrive[FDriveLetter];
1112
          result := true;
75 daniel-mar 1113
        end;
1114
      end;
1115
 
1116
      reg.CloseKey;
1117
    end;
1118
  finally
1119
    reg.Free;
1120
  end;
1121
end;
1122
 
78 daniel-mar 1123
function TRbDrive.NewCapacityAbsolute(var res: integer): boolean;
75 daniel-mar 1124
var
78 daniel-mar 1125
  reg: TRegistry;
75 daniel-mar 1126
begin
78 daniel-mar 1127
  if Win32MajorVersion < 6 then
1128
  begin
1129
    // Only available since Windows Vista
1130
    result := false;
1131
    exit;
1132
  end;
75 daniel-mar 1133
 
78 daniel-mar 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;
75 daniel-mar 1153
end;
1154
 
1155
function TRbDrive.GetNukeOnDelete: boolean;
1156
var
1157
  reg: TRegistry;
1158
  purgeInfo: TRbWin95PurgeInfo;
1159
const
78 daniel-mar 1160
  RES_DEFAULT = false; // Windows 95 default
75 daniel-mar 1161
begin
78 daniel-mar 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;
75 daniel-mar 1169
 
78 daniel-mar 1170
    reg := TRegistry.Create;
1171
    try
1172
      reg.RootKey := HKEY_CURRENT_USER;
75 daniel-mar 1173
 
78 daniel-mar 1174
      if reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Explorer\BitBucket\Volume') then
75 daniel-mar 1175
      begin
78 daniel-mar 1176
        // Windows Vista and upwards
1177
        if reg.OpenKeyReadOnly(GUIDToString(VolumeGUID)) then
75 daniel-mar 1178
        begin
1179
          result := reg.ReadBool('NukeOnDelete');
1180
        end;
78 daniel-mar 1181
        reg.CloseKey;
75 daniel-mar 1182
      end
1183
      else
1184
      begin
78 daniel-mar 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
75 daniel-mar 1190
        begin
78 daniel-mar 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
75 daniel-mar 1196
 
78 daniel-mar 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
75 daniel-mar 1205
 
78 daniel-mar 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;
75 daniel-mar 1213
        end;
1214
      end;
78 daniel-mar 1215
    finally
1216
      reg.Free;
75 daniel-mar 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
 
77 daniel-mar 1239
function TRbDrive.GetVolumeGUIDAvailable: boolean;
1240
begin
1241
  result := not IsEqualGUID(VolumeGUID, NULL_GUID);
1242
end;
1243
 
75 daniel-mar 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
76 daniel-mar 1333
      list.Add(TRbRecycleBin.Create(dir + 'INFO2')); // Windows 95 with Internet Explorer 4 Extension or higher Windows 9x versions
75 daniel-mar 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
76 daniel-mar 1344
        list.Add(TRbRecycleBin.Create(dir + 'INFO2', UserSID)); // Windows 2000+
75 daniel-mar 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
 
76 daniel-mar 1355
{ TRbInfoAItem }
75 daniel-mar 1356
 
76 daniel-mar 1357
procedure TRbInfoAItem.ReadFromStream(stream: TStream);
75 daniel-mar 1358
var
76 daniel-mar 1359
  r: TRbInfoRecordA;
75 daniel-mar 1360
begin
1361
  stream.ReadBuffer(r, SizeOf(r));
1362
 
1363
  FSourceDrive := Chr(Ord('A') + r.sourceDrive);
1364
 
76 daniel-mar 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.
75 daniel-mar 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
 
76 daniel-mar 1384
function TRbInfoAItem.DeleteFile: boolean;
75 daniel-mar 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
 
76 daniel-mar 1397
function TRbInfoAItem.GetPhysicalFile: string;
75 daniel-mar 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)) +
76 daniel-mar 1407
            'D' + (* SourceDrive *) SourceAnsi[1] + ID + ExtractFileExt(SourceAnsi);
75 daniel-mar 1408
end;
1409
 
76 daniel-mar 1410
constructor TRbInfoAItem.Create(fs: TStream; AIndexFile: string);
75 daniel-mar 1411
begin
1412
  inherited Create;
1413
  ReadFromStream(fs);
1414
  FIndexFile := AIndexFile;
1415
end;
1416
 
76 daniel-mar 1417
{ TRbInfoWItem }
75 daniel-mar 1418
 
76 daniel-mar 1419
procedure TRbInfoWItem.ReadFromStream(stream: TStream);
75 daniel-mar 1420
var
76 daniel-mar 1421
  r: TRbInfoRecordW;
75 daniel-mar 1422
begin
1423
  stream.ReadBuffer(r, SizeOf(r));
1424
 
76 daniel-mar 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).
75 daniel-mar 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
 
76 daniel-mar 1445
function TRbInfoWItem.DeleteFile: boolean;
75 daniel-mar 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
 
76 daniel-mar 1458
function TRbInfoWItem.GetPhysicalFile: string;
75 daniel-mar 1459
begin
1460
  if FRemovedEntry then
1461
  begin
1462
    result := '';
1463
    Exit;
1464
  end;
1465
 
76 daniel-mar 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
 
75 daniel-mar 1487
  // e.g. C:\...\DC0.doc
1488
  result := IncludeTrailingPathDelimiter(ExtractFilePath(IndexFile)) +
76 daniel-mar 1489
            'D' + SourceDrive (* SourceUnicode[1] *) + ID + ExtractFileExt(SourceUnicode);
75 daniel-mar 1490
end;
1491
 
76 daniel-mar 1492
constructor TRbInfoWItem.Create(fs: TStream; AIndexFile: string);
75 daniel-mar 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
78 daniel-mar 1598
  if Win32MajorVersion >= 6 then
1599
  begin
1600
    // Only available till Windows XP
1601
    result := -1;
1602
    exit;
1603
  end;
1604
 
75 daniel-mar 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
78 daniel-mar 1643
  if Win32MajorVersion >= 6 then
1644
  begin
1645
    // Only available till Windows XP
1646
    result := false;
1647
    exit;
1648
  end;
1649
 
75 daniel-mar 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
 
76 daniel-mar 1681
(* TODO:
1682
There are more registry values (found in WinXP):
1683
 
1684
BitBucket\<driveletter>
1685
  VolumeSerialNumber
1686
  IsUnicode
1687
 
1688
*)
1689
 
75 daniel-mar 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
78 daniel-mar 1697
  if Win32MajorVersion >= 6 then
1698
  begin
1699
    // Only available till Windows XP
1700
    result := false;
1701
    exit;
1702
  end;
1703
 
75 daniel-mar 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.