Subversion Repositories recyclebinunit

Rev

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