Subversion Repositories recyclebinunit

Rev

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