1

I have a program that gets information about files in the windows recycle bin. The only non-crutch solution to the bucket access problem I see is using the shell environment.

I have a code for removing single file (or folder) from recycle bin with winapi (shell envirionment) like the next:

#include <iostream>

#include <shobjidl_core.h>
#include <Shlobj.h>
#include <shlwapi.h>
#include <ntquery.h>

const SHCOLUMNID SCID_RemovedFrom = { PSGUID_DISPLACED, PID_DISPLACED_FROM };
const SHCOLUMNID SCID_RemovedName = { PSGUID_STORAGE, PID_STG_NAME };

const SHCOLUMNID SCID_DateDeleted = { PSGUID_DISPLACED, PID_DISPLACED_DATE };
const SHCOLUMNID SCID_DateCreated = { PSGUID_STORAGE, PID_STG_CREATETIME };
const SHCOLUMNID SCID_DateModifed = { PSGUID_STORAGE, PID_STG_WRITETIME };
const SHCOLUMNID SCID_DateOpened = { PSGUID_STORAGE, PID_STG_ACCESSTIME };

int main() {
    CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);

    LPSHELLFOLDER pDesktop = NULL;
    SHGetDesktopFolder(&pDesktop);

    LPITEMIDLIST pidlRecycleBin = NULL;
    SHGetSpecialFolderLocation(NULL, CSIDL_BITBUCKET, &pidlRecycleBin);

    IShellFolder2 *pRecycleBin;
    pDesktop->BindToObject(pidlRecycleBin, NULL, IID_IShellFolder2, (LPVOID*)&pRecycleBin);

    pDesktop->Release();
    CoTaskMemFree(pidlRecycleBin);

    IEnumIDList* penumFiles;
    pRecycleBin->EnumObjects(NULL, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &penumFiles);
    
    STRRET sret;

    IFileOperation *pfo;
    CoCreateInstance(CLSID_FileOperation, NULL, CLSCTX_ALL, IID_PPV_ARGS(&pfo));
    pfo->SetOperationFlags(FOF_NO_UI);

    LPITEMIDLIST pidl = NULL;

    BSTR bstr = NULL;
    VARIANT vt;
    SYSTEMTIME syst;

    while (penumFiles->Next(1, &pidl, NULL) != S_FALSE) {
        
        pRecycleBin->GetDisplayNameOf(pidl, SHGDN_NORMAL, &sret); // not a FULL name!!!

        StrRetToBSTR(&sret, pidl, &bstr);
        std::wcout << bstr << L' ';
        SysFreeString(bstr);

        pRecycleBin->GetDetailsEx(pidl, &SCID_DateDeleted, &vt);
        VariantTimeToSystemTime(vt.date, &syst);
        std::wcout << "\t" << syst.wHour << ":" << syst.wMinute << " " << syst.wDay << "." << syst.wMonth << "." << syst.wYear << std::endl;

        /*MARK ITEMS TO DELETE*/
        SHCreateItemWithParent(NULL, pRecycleBin, pidl, IID_IShellItem, (void**)&shi);
        pfo->DeleteItem(shi, NULL);
        shi->Release();

        CoTaskMemFree(pidl);
    }
    penumFiles->Release();
    pRecycleBin->Release();

    /*DELETE MARKED ITEMS*/
    pfo->PerformOperations();
    pfo->Release();

    //Update recycle bin icon [undocumented function]
    (void (*)())GetProcAddress(GetModuleHandle(L"shell32.dll"), "SHUpdateRecycleBinIcon")();

    CoUninitialize();
    return 0;
}

In the code above, at the top, there are constant definitions (SCID_RemovedFrom, SCID_RemovedName) for getting the path where the deleted file was located and the name via the function GetDetailsEx. However, the resulting name does not always contain the extension, but only when the file is registered in the system in such a way that the extension is displayed in File Explorer. In other cases, the name is obtained without an extension.

I couldn't find a constant where GetDetailsEx will return the full name (with the extension) regardless of the user's settings. So far, I've only found a crutch: use pRecycleBin->GetDisplayNameOf(pidl, SHGDN_FORPARSING, &sret); to get extensions, BUT there a problem: I can't determine how to interpret the name some.docx and extension $RXZH1I6.docx: is some.docx a full name (if extensions are shown) or some.docx.docx is a full name (if extensions are hidden). It turns out that this crutch is even dangerous.

How can I get the name of any file ("NONFOLDER") guaranteed to have an extension?

Alex A.
  • 422
  • 3
  • 12
  • 1
    Have you read [How can I get information about the items in the Recycle Bin?](https://devblogs.microsoft.com/oldnewthing/20110830-00/?p=9773) yet? – Remy Lebeau Feb 11 '21 at 19:55
  • @RemyLebeau I read it several times. It was from here that I picked up `PSGUID_DISPLACED`, which is not mentioned on msdn. However, here the notorious `GetDisplayNameOf` is used, which gives extensions depending on the user's settings. I need to get the extension guaranteed. – Alex A. Feb 12 '21 at 15:30
  • This is stated in the question: neither `GetDisplayNameOf` nor `pRecycleBin->GetDetailsEx(pidl, &SCID_RemovedName, &vt)` can give me a name with the extension always. Sometimes the extension is there (for *cpp*, for example), sometimes it is not (for *flac*, for example), although both are files. – Alex A. Feb 12 '21 at 15:35
  • Have you read [Recycle Bin Forensics](https://atalaysblog.wordpress.com/2019/03/19/recycle-bin-forensics/) yet? Worst case, you could just parse the bin's metadata files directly to get what you need. The information is there, since the bin needs it for restorations. – Remy Lebeau Feb 12 '21 at 16:35
  • 1
    I can't reproduce your problem, everything I recycle ends up with the full name. Otherwise, to get an item's "extension", use PKEY_ItemType (for all Shell Items) and/or PKEY_FileExtension for physical file/folders Shell Items – Simon Mourier Feb 12 '21 at 17:14
  • @SimonMourier The point is that the display of extensions depends on the user's settings, which is unacceptable in my case. If your system user settings do not affect the result, then... strange... Anyway, thanks for the tip! I don't remember ever touching the *Windows Property System*, but as it turned out, it has a solution. For me, the solution was `pRecycleBin->GetDetailsEx(pidl, &PKEY_FileName, &vt);`. The `PKEY_FileName` property returns the name of the file with the extension always. I just need to get the path to the old file location and concatenate them. – Alex A. Feb 15 '21 at 22:05
  • @SimonMourier Perhaps there is a way to get the path to the deletion location (to the place where the file was deleted from) with the full file name (containing the extension immediately)? This would be good because it would avoid the need for concatenation in my program. But in any case, using `propkey.h` is the answer, so you could post answer. – Alex A. Feb 15 '21 at 22:11
  • @RemyLebeau I haven't read this article before, thank you. In fact, this is also a solution, although I still wanted to stay in the shell infrastructure, and not parse [metafiles](https://df-stream.com/2016/04/fun-with-recycle-bin-i-files-windows-10/) (link from Recycle Bin Forensics). In addition, in this case, I would have to worry about the windows version, because the format is different in win10 and its predecessors. – Alex A. Feb 15 '21 at 22:18

1 Answers1

0

Here is some sample code (using ATL for smart pointers) that displays all properties for all items in the recycle bin (or any other folder you'd like).

You can pick the properties you want. I've dumped a sample output with a deleted txt file. The item's display name actually depends on the maching settings, but you can see there are lots of other interesting properties, for example:

  • System.Recycle.DeletedFrom (eq to SCID_RemovedFrom)
  • System.Recycle.DateDeleted (eq to SCID_DateDeleted)
  • System.ItemNameDisplay (note for virtual items, you won't always have a System.FileName)
  • System.ItemType (note for virtual items, you won't always have a System.FileExtension)

Sample output:

D:\temp\New Text Document.txt // this is the normal display name (depends on settings)
 System.ItemFolderNameDisplay = Recycle Bin
 System.ItemTypeText = Text Document
 System.ItemNameDisplay = New Text Document.txt
 System.Size = 0
 System.FileAttributes = 33
 System.DateModified = 2019/12/04:17:52:05.132
 System.DateCreated = 2019/12/04:17:52:06.000
 System.DateAccessed = 2019/12/04:17:52:06.000
 System.ItemNameDisplayWithoutExtension = $RWLK87M
 System.ContentType = text/plain
 System.Document.DateCreated = 2019/12/04:17:52:05.132
 System.Document.DateSaved = 2019/12/04:17:52:05.132
 System.Recycle.DeletedFrom = D:\temp
 System.Recycle.DateDeleted = 2021/02/16:08:18:46.000
 System.FileOwner = KILLROY\WasHere
 System.NetworkLocation = 
 System.ComputerName = KILLROY
 System.ItemPathDisplayNarrow = $RWLK87M (D:\$RECYCLE.BIN\Recycle Bin)
 System.PerceivedType = 1
 System.ItemType = .txt
 System.ParsingName = D:\$RECYCLE.BIN\S-1-5-21-2804694453-2728412037-1988799983-1001\$RWLK87M.txt
 System.SFGAOFlags = 1077936503
 System.ParsingPath = D:\$RECYCLE.BIN\S-1-5-21-2804694453-2728412037-1988799983-1001\$RWLK87M.txt
 System.FileExtension = .txt
 System.ItemDate = 2019/12/04:17:52:05.132
 System.KindText = Document
 System.FileAttributesDisplay = Read-only
 System.IsShared = 0
 System.SharedWith = 
 System.SharingStatus = 2
 System.ShareScope = 
 System.Security.EncryptionOwnersDisplay = 
 System.ItemName = $RWLK87M.txt
 System.Shell.SFGAOFlagsStrings = filesys; stream
 System.Link.TargetSFGAOFlagsStrings = 
 System.OfflineAvailability = 
 System.ZoneIdentifier = 0
 System.LastWriterPackageFamilyName = 
 System.AppZoneIdentifier = 
 System.Kind = document
 System.Security.EncryptionOwners = 
 System.ItemFolderPathDisplayNarrow = Recycle Bin (D:\$RECYCLE.BIN)
 System.FileName = New Text Document.txt
 System.Security.AllowedEnterpriseDataProtectionIdentities = 
 System.ThumbnailCacheId = 11524143073100486861
 System.VolumeId = {184CD9C9-570E-483F-9AE1-68D57270A239}
 System.Link.TargetParsingPath = 
 System.Link.TargetSFGAOFlags = 
 System.ItemFolderPathDisplay = D:\$RECYCLE.BIN\Recycle Bin
 System.ItemPathDisplay = D:\$RECYCLE.BIN\Recycle Bin\$RWLK87M.txt
 {9E5E05AC-1936-4A75-94F7-4704B8B01923} 0 = New Text Document.txt
 System.AppUserModel.ID = 
 System.AppUserModel.ParentID = 
 System.Link.TargetExtension = 
 System.OfflineStatus = 
 System.IsFolder = 0
 System.NotUserContent = 0
 System.StorageProviderAggregatedCustomStates = 
 System.SyncTransferStatusFlags = 
 System.DateImported = 2019/12/04:17:52:05.132
 System.ExpandoProperties = 
 System.FilePlaceholderStatus = 6

Sample code:

#include <iostream>
#include <shobjidl_core.h>
#include <shlobj.h>
#include <propvarutil.h>
#include <atlbase.h>
#include <atlcom.h>

#pragma comment(lib, "propsys")

int main() {
    HRESULT hr = CoInitialize(nullptr);
    {
        // get recycle bin folder
        CComPtr<IShellItem> bin;
        hr = SHCreateItemInKnownFolder(FOLDERID_RecycleBinFolder, 0, nullptr, IID_PPV_ARGS(&bin));
        if (SUCCEEDED(hr))
        {
            // enumerate items
            CComPtr<IEnumShellItems> items;
            hr = bin->BindToHandler(nullptr, BHID_EnumItems, IID_PPV_ARGS(&items));
            if (SUCCEEDED(hr))
            {
                do
                {
                    // get item
                    CComPtr<IShellItem> item;
                    hr = items->Next(1, &item, nullptr);
                    if (hr != 0)
                        break;

                    // get the display name (depends on settings)
                    CComHeapPtr<wchar_t> path;
                    item->GetDisplayName(SIGDN_NORMALDISPLAY, &path);
                    std::wcout << path.m_pData << std::endl;

                    // get item's property store
                    CComPtr<IPropertyStore> store;
                    hr = CComQIPtr<IShellItem2>(item)->GetPropertyStore(GPS_DEFAULT, IID_PPV_ARGS(&store));
                    if (SUCCEEDED(hr))
                    {
                        DWORD count = 0;
                        store->GetCount(&count);
                        for (DWORD i = 0; i < count; i++)
                        {
                            // print this property (name, value)
                            PROPERTYKEY pk;
                            hr = store->GetAt(i, &pk);
                            if (SUCCEEDED(hr))
                            {
                                // get property canonical name
                                CComHeapPtr<wchar_t> name;
                                PSGetNameFromPropertyKey(pk, &name);

                                // get property value
                                PROPVARIANT pv;
                                PropVariantInit(&pv);
                                hr = store->GetValue(pk, &pv);
                                if (SUCCEEDED(hr))
                                {
                                    CComHeapPtr<wchar_t> value;
                                    hr = PropVariantToStringAlloc(pv, &value); // propvarutil.h
                                    if (SUCCEEDED(hr))
                                    {
                                        if (!name) // unknown name
                                        {
                                            CComHeapPtr<wchar_t> fmtid;
                                            StringFromCLSID(pk.fmtid, &fmtid);
                                            std::wcout << L" " << fmtid.m_pData << L" " << pk.pid << L" = " << value.m_pData << std::endl;
                                        }
                                        else
                                        {
                                            std::wcout << L" " << name.m_pData << L" = " << value.m_pData << std::endl;
                                        }
                                    }
                                    else
                                    {
                                        // can't convert to string, print something useful
                                    }
                                }
                                PropVariantClear(&pv);
                            }
                        }
                    }
                } while (true);
            }
        }
    }
    CoUninitialize();
    return 0;
}
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298