如何“安全地”将文件夹删除到回收站

10

我正在寻找一种方法将一个文件夹(包括子文件夹)放入回收站,并满足以下条件:

  1. 必须静默执行,没有任何Windows用户界面。

  2. 该文件夹绝不能被永久删除。如果无法将其放入回收站,则应该期望API失败。

  3. CopyFileEx一样获得有关进程的回调例程。

到目前为止,我能想到的只有这个:

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;

我需要翻译的内容是:在USB闪存驱动器上的文件夹上执行FOF_ALLOWUNDO标志后,该文件夹被永久删除。因此,无论我做什么,或者SHFileOperation API非常错误!

有没有办法实现我上面概述的内容?

编辑:如@Denis Anisimov所建议,我实现了IRecycleBinManager::WillRecycle方法,但显然还有更多要做。这是我的C++版本。首先是我需要的方法的接口定义:

#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

然后是调用本身:

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();
}

很不幸,在我应该调用WillRecycle方法的那一行出现了以下错误:
运行时检查失败#0-ESP的值在函数调用过程中未能正确保存。这通常是由于使用具有不同调用约定的函数指针声明调用一个声明为具有另一种调用约定的函数而导致的。

enter image description here


条件#2(没有进一步的限定)似乎排除了使用回收站。因为回收站中的项目最终会被删除(悄无声息地)。您是希望文件夹不被您的程序彻底删除,还是不被任何程序(即维护回收站的explorer.exe)删除? - enhzflep
随意搜索表明回收站不会在可移动和网络媒体上创建。(很抱歉,在前3页Google中找不到明确的MSDN页面。) - Jongware
看起来像是Windows的功能:http://answers.microsoft.com/en-us/windows/forum/windows_7-performance/has-windows-7-cancelled-the-recycle-feature-of-my/51810824-e331-4e1a-a3d8-8592916c6b75 - Alex F
3
可能SHQueryRecycleBin函数可以帮助检测给定驱动器是否可用于回收站。 - Alex F
1
@IInspectable 它不起作用。尝试从USB拖放文件到桌面上的回收站图标上,Windows会询问您:“您确定要永久删除此文件吗?” - Denis Anisimov
显示剩余4条评论
2个回答

10
每个驱动器都有自己的回收站。当您从C:驱动器删除文件时,它应该被移至C:驱动器上的回收站。当您从USB驱动器中删除文件时,它应该被移至USB驱动器上的回收站。但是,当USB驱动器没有回收站时,文件将被永久删除。这是默认的Windows行为。
FOF_ALLOWUNDO标志仅是建议性的。MSDN对FOF_ALLOWUNDO标志的说明如下:
保留撤消信息,如果可能的话
因此,即使使用FOF_ALLOWUNDO标志,Windows永久删除文件时也不会出现任何错误。
我唯一能想到的方法是在删除操作之前使用SHQueryRecycleBin函数(如评论中所指出的Alex Farber)检查驱动器上是否存在回收站。但是,即使回收站存在,也不能完全保证文件将被删除到回收站。回收站具有最大大小限制,它可能已经满了。
更新
您可以使用hack。您可以使用自己的代码模拟将文件移除到回收站,并创建C:\$Recycle.Bin\UserSID文件夹中所有必要的系统记录。我在Windows 7上测试过这种方法,它可以正常工作。它允许忽略回收站的最大大小限制。它还允许将文件从USB移动到任何驱动器上的回收站中。
更新2
对于Vista+,您可以使用未记录的接口IRecycleBinManager(俄语描述可以在网页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;

您可以使用以下代码检查将文件删除到回收站的可能性:

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;

更新 3

您也可以尝试以下代码将对象删除到回收站:

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;

谢谢。是的,我知道回收站的情况。但我不明白的是API的不可预测性。到目前为止,可以说,在调用SHFileOperation之前可以调用SHQueryRecycleBin,但在USB驱动器的情况下,我只得到了E_FAIL,这并不是很具描述性,我相信它也可能在其他情况下返回。所以,没有办法知道一个文件或文件夹是否会被永久删除或放入回收站,对吗? - c00000fd
你提出的方法还有一个问题,就是当要删除的文件夹/文件太大而无法放入回收站时,它将被永久删除。那么我怎么知道这个操作是否可以通过SHFileOperation API完成呢?看起来我需要提出一个新的问题... - c00000fd
1
为什么要将文件夹删除到回收站?也许如果您创建自己的垃圾桶来存储已删除的文件会更好? - Denis Anisimov
我不是这样的。这是客户的要求。 - c00000fd
目标操作系统是什么? - Denis Anisimov
显示剩余17条评论

1
我已经想出了解决我最初提出的三个问题/请求的方案。
简而言之,需要使用接口并在其中实现。 这是完整的代码示例和相关说明。
编辑:好吧,还有更多细节需要考虑。我之前发布的方法不够全面。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接