How to "safely" delete folder into Recycle Bin
Asked Answered
O

2

10

I'm looking for a way to put a folder (with subfolders) into a Recycle Bin with these conditions:

  1. It must be done silently -- without any Windows UI.

  2. The folder must never be permanently deleted. If it can't be put into Recycle Bin, I'd expect the API to fail.

  3. Get a callback routine for the process like CopyFileEx does.

So far I was able to come up with this:

SHFILEOPSTRUCT sfo = {0};
sfo.wFunc = FO_DELETE;
sfo.pFrom = L"K:\\test del from USB\0";     //Folder on a USB stick
sfo.fFlags = FOF_ALLOWUNDO |
     FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_NOCONFIRMMKDIR |
     FOF_WANTNUKEWARNING;

int res = SHFileOperation(&sfo);
BOOL bFullSuccess = res == 0 && !sfo.fAnyOperationsAborted;

Which horribly fails on a folder located on a USB flash drive, i.e. it is permanently deleted in despite of the FOF_ALLOWUNDO flag.

So whether I'm not doing something right, or SHFileOperation API is very wrong!

Any idea how to do what I outlined above?

EDIT: I implemented the IRecycleBinManager::WillRecycle method as was suggested by @Denis Anisimov, but there's evidently more to it. Here's my C++ version. First interface definition for the method I need:

#if defined(__cplusplus) && !defined(CINTERFACE)

    MIDL_INTERFACE("5869092D-8AF9-4A6C-AE84-1F03BE2246CC")
    IRecycleBinManager : public IUnknown
    {
    public:

    //function WillRecycle(const pszPath: LPCWSTR): HRESULT; stdcall;
        virtual HRESULT STDMETHODCALLTYPE WillRecycle( 
            /* [string][in] */ __RPC__in LPCWSTR pszFile) = 0;
    };

#endif

and then the call itself:

HRESULT hr;

CoInitializeEx(NULL, COINIT_DISABLE_OLE1DDE | COINIT_APARTMENTTHREADED);

// {4A04656D-52AA-49DE-8A09-CB178760E748}
const CLSID CLSID_RecycleBinManager = {0x4A04656D, 0x52AA, 0x49DE, {0x8A, 0x09, 0xCB, 0x17, 0x87, 0x60, 0xE7, 0x48}};

// {5869092D-8AF9-4A6C-AE84-1F03BE2246CC}
const IID IID_IRecycleBinManager = {0x5869092D, 0x8AF9, 0x4A6C, {0xAE, 0x84, 0x1F, 0x03, 0xBE, 0x22, 0x46, 0xCC}};

IRecycleBinManager* pIRBM = NULL;

hr = CoCreateInstance(CLSID_RecycleBinManager, NULL, CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER,
                      IID_IRecycleBinManager, (void**) &pIRBM);
//  hr = SHCoCreateInstance(NULL, &CLSID_RecycleBinManager, NULL, IID_IRecycleBinManager, (void **)&pIRBM);
if (SUCCEEDED(hr))
{

    hr = pIRBM->WillRecycle(L"C:\\test del");   //Crashes

    pIRBM->Release();
}

Unfortunately I'm getting this error on the line where I'm supposed to call WillRecycle method:

Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call. This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.

enter image description here

Oppilate answered 18/5, 2014 at 9:34 Comment(9)
Condition #2 (without further qualification) appears to exclude the use of the Recycle Bin. Since items in the recycle are eventually removed (silently). Is your desire that the folder not be nuked by your program, or that it not be nuked by any program (read: explorer.exe which maintains the recycle bin)Blackington
Casual Googling suggests the Recycle Bin is not created on removable and network media. (Couldn't locate a definitive MSDN page in the first 3 Google pages, sorry.)Turin
Looks like Windows feature: answers.microsoft.com/en-us/windows/forum/windows_7-performance/…Veronique
Probably SHQueryRecycleBin function can help to detect whether recycle bin is available for given drive.Veronique
It appears as though SHFileOperation will not do to implement your goal. You may have to roll your own solution. Helpful information is available under Handling Shell Data Transfer Scenarios - Dropping Files on the Recycle Bin.Stockinet
@Stockinet It does not work. Try to drag and drop file from USB without Recycle Bin on Recycle Bin icon on desktop and Windows will ask you: "Are you sure you want to permanently delete this file?"Bolometer
@AlexFarber: Thanks. So Alex, is there a way to know if a folder being deleted via the SHFileOperation API is placed into the Recycle Bin vs. permanently deleted?Oppilate
@IInspectable: I was trying to read the link you provided and it mostly talks about drag-and-dropping files into the Recycle Bin. Is there something that I missed -- relative to my question here?Oppilate
FOF_ALLOWUNDO: Preserve undo information, if possible. Operations can be undone only from the same process that performed the original operation. If, despite earlier warnings against doing so, pFrom does not contain fully-qualified path and file names, this flag is ignored.Derte
B
10

Every drive has its own Recycle Bin. And when you delete file from drive С: it should be moved to Recycle Bin on drive С:. When you delete file from USB drive it should be moved to Recycle Bin on USB drive. But when USB drive has no Recycle Bin then file is permanently deleted. This is default Windows behavior.

FOF_ALLOWUNDO flag is RECOMMENDATION only. MSDN says about FOF_ALLOWUNDO flag:

Preserve undo information, if possible.

So there is no any error when Windows permanently deleted files even when you use FOF_ALLOWUNDO flag.

The only way I see is to check presence of Recycle Bin on drive with SHQueryRecycleBin function (as pointed by Alex Farber in comment) before delete operation. But even if Recycle Bin presents it is not full guaranty that file will be deleted to Recycle Bin. Recycle Bin has maximal limit of size and it can be already full.

UPDATE

You can use hack. You can emulate removing of file into Recycle Bin with you own code which will create all necessary system records in C:\$Recycle.Bin\UserSID folder. I tested this method on Windows 7 and it works correctly. It allows to ignore limitation of max size of Recycle Bin. Also it allows to move files from USB into Recycle Bin on any drive.

UPDATE 2

For Vista+ you can use undocumented interface IRecycleBinManager (Russian description can be found on webpage http://rcrrad.com/2010/10/14/bitbucket-interfaces/):

const
  IID_IEnumRecycleItems: TGUID = '{6E325F88-D12F-49E5-895B-8EC98630C021}';
  IID_IRecycle: TGUID = '{0125E62F-8349-443A-854B-A55FB84CFA35}';
  IID_IRecycleBin: TGUID = '{F964AD97-96F4-48AB-B444-E8588BC7C7B3}';
  IID_IRecycleBinManager: TGUID = '{5869092D-8AF9-4A6C-AE84-1F03BE2246CC}';
  CLSID_RecycleBinManager: TGUID = '{4A04656D-52AA-49DE-8A09-CB178760E748}';

type
  { Тип Корзины }
  tagRECYCLEBIN_TYPE = (RBTYPE_VOLUME, RBTYPE_KNOWNFOLDER);
  TRecycleBinType = tagRECYCLEBIN_TYPE;

  { Данные об удаленном элементе }
  PDeletedItem = ^TDeletedItem;
  tagDELETEDITEM = packed record
    dwFileSizeLow: DWORD;
    dwFileSizeHigh: DWORD;
    ftDeletionTime: TFileTime;
    szOriginalPath: array[0..Pred(MAX_PATH)] of WideChar;
    szDisplacedPath: array[0..Pred(MAX_PATH)] of WideChar;
  end;
  TDeletedItem = tagDELETEDITEM;

  { Перечислитель элементов Корзины }
  IEnumRecycleItems = interface(IUnknown)
    ['{6E325F88-D12F-49E5-895B-8EC98630C021}']
    { celt может быть равен только единице }
    function Next(celt: ULONG; out rgelt: TDeletedItem;
      var pceltFetched: ULONG): HRESULT; stdcall;
    { Not Implemented }
    function Skip(celt: ULONG): HRESULT; stdcall;
    function Reset: HRESULT; stdcall;
    { Not Implemented }
    function Clone(out ppenum: IEnumRecycleItems): HRESULT; stdcall;
  end;

  { "Интерфейс-переходник" между IRecycleBin и IRecycleBinManager }
  IRecycle = interface(IUnknown)
    ['{0125E62F-8349-443A-854B-A55FB84CFA35}']
    function Compact(): HRESULT; stdcall;
    function GetFileData(const pszPath: LPCWSTR;
      out lpData: TDeletedItem): HRESULT; stdcall;
    function GetItemCount(out lpCount: TLargeInteger): HRESULT; stdcall;
    function GetUsedSpace(out lpUsedSpace: TLargeInteger): HRESULT; stdcall;
    function IsEmpty(): HRESULT; stdcall;
    function PurgeAll(pfo: IFileOperation): HRESULT; stdcall;
    function PurgeItems(const lpstrItems: LPCWSTR;
      pfo: IFileOperation): HRESULT; stdcall;
    function SuspendUpdating(fSuspend: BOOL): HRESULT; stdcall;
    function RecycleItem(const lpstrItem: LPCWSTR; const dwAttrs: DWORD;
      const iFileSize: TLargeInteger; out psi: IShellItem): HRESULT; stdcall;
    function RestoreItems(const lpstrItems: LPCWSTR;
      pfo: IFileOperation): HRESULT; stdcall;
    function IsRecycled(const pszPath: LPCWSTR;
      lpRecycled: PBOOL): HRESULT; stdcall;
    function EnumItems(dwFlags: DWORD;
      out EnumRecycleItems: IEnumRecycleItems): HRESULT; stdcall;
    function WillRecycle(const pszPath: LPCWSTR): HRESULT; stdcall;
  end;

  { Представляет определенную Корзину на конкретном диске }
  IRecycleBin = interface(IUnknown)
    ['{F964AD97-96F4-48AB-B444-E8588BC7C7B3}']
    function Compact(): HRESULT; stdcall;
    function GetFileData(const pszPath: LPCWSTR;
      out lpData: TDeletedItem): HRESULT; stdcall;
    function GetItemCount(out lpCount: TLargeInteger): HRESULT; stdcall;
    function GetUsedSpace(out lpUsedSpace: TLargeInteger): HRESULT; stdcall;
    function IsEmpty(): HRESULT; stdcall;
    function PurgeAll(pfo: IFileOperation): HRESULT; stdcall;
    function PurgeItems(const lpstrItems: LPCWSTR;
      pfo: IFileOperation): HRESULT; stdcall;
    function SuspendUpdating(fSuspend: BOOL): HRESULT; stdcall;
    function RecycleItem(const lpstrItem: LPCWSTR; const dwAttrs: DWORD;
      const iFileSize: TLargeInteger; out psi: IShellItem): HRESULT; stdcall;
    function RestoreItems(const lpstrItems: LPCWSTR;
      pfo: IFileOperation): HRESULT; stdcall;
    function IsRecycled(const pszPath: LPCWSTR;
      lpRecycled: PBOOL): HRESULT; stdcall;
    function EnumItems(dwFlags: DWORD;
      out EnumRecycleItems: IEnumRecycleItems): HRESULT; stdcall;
    function WillRecycle(const pszPath: LPCWSTR): HRESULT; stdcall;
    function Initialize(const rbType: TRecycleBinType;
      const pszID: LPCWSTR): HRESULT; stdcall;
    function GetTypeID(out rbType: TRecycleBinType;
      var pszID: LPWSTR): HRESULT; stdcall;
    function GetIDList(out ppidl: PItemIDList): HRESULT; stdcall;
    function GetLocation(pszPathBuffer: LPWSTR;
      cchMax: DWORD): HRESULT; stdcall;
    function GetMaxCapacityRange(out lpMin: TLargeInteger;
      out lpMax: TLargeInteger): HRESULT; stdcall;
    function GetMaxCapacity(out lpCapacity: TLargeInteger): HRESULT; stdcall;
    function SetMaxCapacity(const lpCapacity: TLargeInteger): HRESULT; stdcall;
    function GetPurgeOnDelete(out fNukeOnDelete: BOOL): HRESULT; stdcall;
    function SetPurgeOnDelete(const fNukeOnDelete: BOOL): HRESULT; stdcall;
  end;

  { Менеджер всех Корзин данной ОС }
  IRecycleBinManager = interface(IUnknown)
    ['{5869092D-8AF9-4A6C-AE84-1F03BE2246CC}']
    function Compact(): HRESULT; stdcall;
    function GetFileData(const pszPath: LPCWSTR;
      out lpData: TDeletedItem): HRESULT; stdcall;
    function GetItemCount(out lpCount: TLargeInteger): HRESULT; stdcall;
    function GetUsedSpace(out lpUsedSpace: TLargeInteger): HRESULT; stdcall;
    function IsEmpty(): HRESULT; stdcall;
    function PurgeAll(pfo: IFileOperation): HRESULT; stdcall;
    function PurgeItems(const lpstrItems: LPCWSTR;
      pfo: IFileOperation): HRESULT; stdcall;
    function SuspendUpdating(fSuspend: BOOL): HRESULT; stdcall;
    { Not Implemented }
    function RecycleItem(const lpstrItem: LPCWSTR; const dwAttrs: DWORD;
      const iFileSize: TLargeInteger; out psi: IShellItem): HRESULT; stdcall;
    function RestoreItems(const lpstrItems: LPCWSTR;
      pfo: IFileOperation): HRESULT; stdcall;
    function IsRecycled(const pszPath: LPCWSTR;
      lpRecycled: PBOOL): HRESULT; stdcall;
    function EnumItems(dwFlags: DWORD;
      out EnumRecycleItems: IEnumRecycleItems): HRESULT; stdcall;
    function WillRecycle(const pszPath: LPCWSTR): HRESULT; stdcall;
    function DelayCompaction(const fDelay: BOOL): HRESULT; stdcall;
    function GetRecycleBinCount(out iCount: Integer): HRESULT; stdcall;
    function GetRecycleBinAt(const index: Integer; const iid: TGUID;
      out ppv): HRESULT; stdcall;
    function GetRecycleBin(const pszPath: LPCWSTR; const iid: TGUID;
      out ppv): HRESULT; stdcall;
    function Refresh(): HRESULT; stdcall;
  end;

You can check the possibility of deletion of the file into Recycle Bin with the following code:

function CanFileBeDeletedToRecycleBin(const AFileName: UnicodeString): Boolean;
var
  RecycleBinManager: IRecycleBinManager;
begin
  OleCheck(CoCreateInstance(CLSID_RecycleBinManager, nil, CLSCTX_INPROC_SERVER or CLSCTX_LOCAL_SERVER, IRecycleBinManager, RecycleBinManager));
  try
    Result := RecycleBinManager.WillRecycle(PWideChar(AFileName)) = S_OK;
  finally
    RecycleBinManager := nil;
  end;
end;

UPDATE 3

Also you can try the following code for delete oject into Recycle Bin:

function GetObjectSize(const AFileName: UnicodeString): Int64;
var
  FindHandle: THandle;
  FindData: TWin32FindDataW;
  S: Int64;
begin
  Result := 0;
  FindHandle := FindFirstFileW(PWideChar(AFileName), FindData);
  if FindHandle = INVALID_HANDLE_VALUE then
    RaiseLastOSError;
  try
    repeat
      if (FindData.cFileName <> UnicodeString('.')) and (FindData.cFileName <> '..') then
        begin
          Int64Rec(S).Lo := FindData.nFileSizeLow;
          Int64Rec(S).Hi := FindData.nFileSizeHigh;
          Result := Result + S;
          if FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY <> 0 then
            Result := Result + GetObjectSize(AFileName + '\*.*');
        end;
    until not FindNextFileW(FindHandle, FindData);
  finally
    FindClose(FindHandle);
  end;
end;

procedure DeleteToRecycleBin(const AFileName: UnicodeString);
var
  Attr: DWORD;
  Size: Int64;
  RecycleBinManager: IRecycleBinManager;
  RecycleBin: IRecycleBin;
  ShellItem: IShellItem;
begin
  OleCheck(CoCreateInstance(CLSID_RecycleBinManager, nil, CLSCTX_INPROC_SERVER or CLSCTX_LOCAL_SERVER, IRecycleBinManager, RecycleBinManager));
  try
    OleCheck(RecycleBinManager.GetRecycleBin(PWideChar(AFileName), IRecycleBin, RecycleBin));
    try
      Attr := GetFileAttributes(PWideChar(AFileName));
      Size := GetObjectSize(AFileName);
      OleCheck(RecycleBin.RecycleItem(PWideChar(AFileName), Attr, Size, ShellItem));
      ShellItem := nil;
    finally
      RecycleBin := nil;
    end;
  finally
    RecycleBinManager := nil;
  end;
end;
Bolometer answered 18/5, 2014 at 10:46 Comment(22)
Thanks. Yes, I know that about Recycle Bin. What I don't understand is how unpredictable that API is. So far, true to say, I can call SHQueryRecycleBin before calling SHFileOperation but in case of a USB drive, all I get is E_FAIL which is not very descriptive and I'm sure can be returned in a host of other situations. So there's seriously no way of knowing whether a file or a folder will be permanently deleted or placed in the Recycle Bin, is there?Oppilate
And another issue with your suggested approach is the situation when a folder/file being deleted is too large for the Recycle Bin. In that case it is also permanently deleted. So how will I know that that will be done by the SHFileOperation API? It sounds like I need a new question...Oppilate
Why do you want to delete folders into Recycle Bin? Maybe it will be better if you create your own storage for deleted files?Bolometer
I don't. It's a client's request.Oppilate
Which the target OS is?Bolometer
I tried your suggested WillRecycle method but it crashes the app. I posted my C++ code in the original question. Can you take a look? I'm clearly missing something.Oppilate
Sorry but I am not C++ guru. I cannot convert Delphi code I use to C++. It is better if you ask somebody why knows both languages.Bolometer
I understand. Can you try to compile your Delphi code and see if RecycleBinManager.WillRecycle works on your end -- just like you showed above? Because when I call it like you did, it crashes.Oppilate
Hmm. Interesting. Why is mine crashing then... Just curious, when you define the IRecycleBinManager interface, do you need to provide all methods or just the ones I used -- WillRecycle in my case?Oppilate
Yes, you must provide all methods.Bolometer
Thanks. Let me try to redo it. Hopefully I'll convert it to C. Also, what is PWideChar? Is it a pointer to a null-terminated string consisting of 16-bit chars? Or is it some special COM string?Oppilate
Yes, it is a pointer to null-terminated string consisting of 16-bit chars (PWideChar = LPCWSTR)Bolometer
Sorry for asking too many questions, I'm just trying to wade through this Delphi thicket. How can I tell a pointer from a plain variable? For instance, const iid: TGUID; in the GetRecycleBinAt function definition, is it a pointer to GUID or just a GUID variable.Oppilate
Delphi "const iid: TGUID" is equivalent to C++ "[in] TGUID riid"Bolometer
@DenisAnisimov: Not quite. In Delphi, passing a record by const passes it by reference, not by value. So the equivalent of const iid: TGUID in C++ is actually const TGUID& iid, or const REFIID iid in COM syntax.Vote
@DenisAnisimov: I just happened to build a project with your code and no matter what path I pass into RecycleBinManager.WillRecycle it returns S_OK or a plain 0. Am I missing something?Oppilate
@Oppilate Cannot confirm. What are the filenames do you pass in WillRecycle?Bolometer
@DenisAnisimov: One nice person converted your project to C++ for me and I'm testing it there: 1drv.ms/SZxHCJ True to say, that's the first time I'm dealing with C++ Builder. So I put in a path that is definitely too big for recycle bin into CanFileBeDeletedToRecycleBin and it still returned S_OK. I then put an invalid path and it still gave me S_OK. Can you take a look?Oppilate
@c00000fd: You are right. It looks WillRecycle check only drive letter of filename. Did you try code from Update3 of my answer? Also there is still a possibility to you hack (first update of my answer). I described structure of RB files in the answer on you other question.Bolometer
Thanks. Without your IRecycleBinManager.WillRecycle method I'm back to basics. Which means trying to guess if files can be placed into the recycle bin, which is kinda dangerous. I gave details over here: forums.codeguru.com/…Oppilate
Btw, on an unrelated subject. One word of advice about your GetObjectSize method in update 3. Make sure to skip symbolic links, FILE_ATTRIBUTE_REPARSE_POINT attribute. Otherwise you may end up with an infinite recursive loop.Oppilate
The above link to IRecycleBinManager has gone. I, too am very interested in deciphering the file handling in shell32, please add anything that comes to light. :)Siddra
O
1

I was able to come up with a solution to all 3 of my original points/requests.

In a nutshell, one needs to use the IFileOperation interface and implement IFileOperationProgressSink in it.

Here's full code sample and explanation for that.

EDIT: Ok, there's more to it. The method I posted above doesn't cover all bases :(

Oppilate answered 20/5, 2014 at 21:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.