Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to "safely" delete folder into Recycle Bin

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

like image 662
c00000fd Avatar asked May 18 '14 09:05

c00000fd


People also ask

How do I delete something from the Recycle Bin without it going to the store?

Hold down the SHIFT key while pressing the DELETE key.

Does putting a file in the Recycle Bin permanently delete it?

The direct answer is no. You can treat the Recycle Bin as the last frontier for the data you wish to delete. Whether you accidentally or intentionally deleted any critical data, you can still easily retrieve them here before you permanently remove them from your system.

Do deleted folders go to Recycle Bin?

Generally, a file or folder would be stored in the Recycle Bin after they are deleted. Only clicking the key combination "Shift + Delete" could delete them directly. However, during the use, clicking delete button would directly delete files if the Recycle Bin's settings changed.

Does deleting a folder delete everything in it?

Remember, when you delete a folder, you're also deleting everything inside of it. Note: If you think you might need that folder (and its contents) later, back it up to a data file before you delete it. Right-click the folder you want to delete and click Delete Folder.


2 Answers

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;
like image 61
Denis Anisimov Avatar answered Sep 22 '22 10:09

Denis Anisimov


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 :(

like image 33
c00000fd Avatar answered Sep 19 '22 10:09

c00000fd