Home > Blockchain >  Delete a directory and all of its files using Windows-Shell
Delete a directory and all of its files using Windows-Shell

Time:11-06

I'm writing a MFC program that can occasionally remove a directory with all of its content. These directories will be located in %LOCALAPPDATA%\MyApp so there will be no permission issue (at least I hope).

My first guess was to use CFileFind recursively along with DeleteFile and DeleteDirectory Win32 functions, but a fast look at RemoveDirectory documentation pointed me to SHFileOperation and finally to IFileOperation which, if I have understood, should be the way how a pure Windows Desktop Application has to interact with the file system, so I would exclude the std::filesystem::remove_all solution if possible.

Now, this is surely a problem of mine because I hate COM, but I found this documentation not very clear if not cryptic, so I decided to have a look at the developer's guide where I got to the File Operations Samples which surely helps to understand, but not completely because in my case I have to find the files to delete, so I finally got to this guide which should contains all the informations I'm looking for.

Now, if I got the situation I have to:

  • access the desktop, which is the root of FS;
  • get the path to my directory from the desktop;
  • perform my operations;

In plain ol' C :

void CWindowsUtilities::RemoveDirectory(HWND parent, CString& directory)
{
    IShellFolder* desktop;
    HRESULT res = SHGetDesktopFolder(&desktop);
    if (SUCCEEDED(res))
    {
        ULONG eaten = 0UL;
        LPITEMIDLIST itemIdList;
        res = desktop->ParseDisplayName(parent, nullptr, directory.GetBuffer(), &eaten, &itemIdList, nullptr);
        if (SUCCEEDED(res))
        {
            IShellFolder* directoryToDelete;
            res = desktop->BindToObject(itemIdList, nullptr, IID_IShellFolder, reinterpret_cast<LPVOID*>(&directoryToDelete));
            if (SUCCEEDED(res))
            {
                IEnumIDList* elementsToDelete;
                res = directoryToDelete->EnumObjects(parent, SHCONTF_INCLUDEHIDDEN | SHCONTF_CHECKING_FOR_CHILDREN | SHCONTF_FOLDERS | SHCONTF_NAVIGATION_ENUM, &elementsToDelete);
                if (res == S_OK)
                {
                    IFileOperation* fileOp;
                    res = CoCreateInstance(__uuidof(FileOperation), nullptr, CLSCTX_ALL, IID_PPV_ARGS(&fileOp));
                    if (SUCCEEDED(res))
                    {
                        res = fileOp->SetOperationFlags(FOFX_ADDUNDORECORD | FOFX_RECYCLEONDELETE);
                        if (SUCCEEDED(res))
                        {
                           res = fileOp->QueryInterface(IID_PPV_ARGS(&fileOp));
                           if (SUCCEEDED(res))
                           {
                             res = fileOp->DeleteItems(elementsToDelete);
                             if (SUCCEEDED(res))
                             {
                                res = fileOp->PerformOperations();
                             }
                           }
                        }
                    }
                    fileOp->Release();
                }
                directoryToDelete->Release();
            }
            CoTaskMemFree(itemIdList);
        }
        desktop->Release();
    }
}

Letting run the code it goes without exceptions, but on DeleteItems I got:

E_NOINTERFACE No such interface supported.

Trying to understand where my problem can be, I've tried to enumerate the files in the directory using:

void CWindowsUtilities::Navigate(IShellFolder* accesso)
{
    IEnumIDListPtr file;
    HRESULT res = accesso->EnumObjects(nullptr, SHCONTF_FOLDERS, &file);
    if (res == S_OK)
    {
        LPITEMIDLIST itemID;
        ULONG fetched;
        while (file->Next(1UL, &itemID, &fetched) == S_OK)
        {
            STRRET nome = { 0 };
            accesso->GetDisplayNameOf(itemID, SHGDN_NORMAL, &nome);
            CoTaskMemFree(itemID);
        }
        file->Release();
     }
}

and so I can see that I can navigate the desktop but not the directory I want to delete. I also tried using SHParseDisplayName instead of desktop->ParseDisplayName but with no success, even if I got a different itemIdList.

What am I doing wrong?

CodePudding user response:

You can use SHFileOperation It has been replaced in Windows vista but still works great.

SHFILEOPSTRUCT SHFileOp, SHDirOp;
ZeroMemory(&SHDirOp, sizeof(SHFILEOPSTRUCT));
SHDirOp.hwnd = NULL;
SHDirOp.wFunc = FO_DELETE;
SHDirOp.pFrom = _T("path to folder");
SHDirOp.pTo = NULL;
SHDirOp.fFlags =
    FOF_MULTIDESTFILES /* | FOF_NOCONFIRMMKDIR | FOF_NOCONFIRMATION */;

//The Copying Function
SHFileOperation(&SHDirOp);

If you really want to use the IFileOperator here is how you could use it:

BOOL deleteFileOrFolder(LPCWSTR fileOrFolderPath) {
    HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
    if (FAILED(hr)) {
        //Couldn't initialize COM library - clean up and return
        MessageBox(NULL, L"Couldn't initialize COM library", L"Whoops", MB_OK | MB_ICONERROR);
        CoUninitialize();
        return FALSE;
    }
    //Initialize the file operation
    IFileOperation* fileOperation;
    hr = CoCreateInstance(CLSID_FileOperation, NULL, CLSCTX_ALL, IID_PPV_ARGS(&fileOperation));
    if (FAILED(hr)) {
        //Couldn't CoCreateInstance - clean up and return
        MessageBox(NULL, L"Couldn't CoCreateInstance", L"Whoops", MB_OK | MB_ICONERROR);
        CoUninitialize();
        return FALSE;
    }
    hr = fileOperation->SetOperationFlags(FOF_NOCONFIRMATION | FOF_SILENT | FOF_NOERRORUI);
    if (FAILED(hr)) {
        //Couldn't add flags - clean up and return
        MessageBox(NULL, L"Couldn't add flags", L"Whoops", MB_OK | MB_ICONERROR);
        fileOperation->Release();
        CoUninitialize();
        return FALSE;
    }
    IShellItem* fileOrFolderItem = NULL;
    hr = SHCreateItemFromParsingName(fileOrFolderPath, NULL, IID_PPV_ARGS(&fileOrFolderItem));
    if (FAILED(hr)) {
        //Couldn't get file into an item - clean up and return (maybe the file doesn't exist?)
        MessageBox(NULL, L"Couldn't get file into an item", L"Whoops", MB_OK | MB_ICONERROR);
        fileOrFolderItem->Release();
        fileOperation->Release();
        CoUninitialize();
        return FALSE;
    }
    hr = fileOperation->DeleteItem(fileOrFolderItem, NULL); //The second parameter is if you want to keep track of progress
    fileOrFolderItem->Release();
    if (FAILED(hr)) {
        //Failed to mark file/folder item for deletion - clean up and return
        MessageBox(NULL, L"Failed to mark file/folder item for deletion", L"Whoops", MB_OK | MB_ICONERROR);
        fileOperation->Release();
        CoUninitialize();
        return FALSE;
    }
    hr = fileOperation->PerformOperations();
    fileOperation->Release();
    CoUninitialize();
    if (FAILED(hr)) {
        //failed to carry out delete - return
        MessageBox(NULL, L"failed to carry out delete", L"Whoops", MB_OK | MB_ICONERROR);
        return FALSE;
    }
    return TRUE;
}

Both methods had already been resolved in SO in the following links respectively:

https://stackoverflow.com/a/22226730/1856251

https://stackoverflow.com/a/68736092/1856251

Looking at your code I can see that one of the things you are doing wrong is that as per IFileOperation::DeleteItems documentation it takes a pointer of one of the following: IShellItemArray, IDataObject, IEnumShellItems or IPersistIDList. But you are passing a IEnumIDList pointer.

  • Related