Subversion Repositories recyclebinunit

Rev

Rev 76 | Go to most recent revision | Details | Last modification | View Log | RSS feed

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