Subversion Repositories recyclebinunit

Rev

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