如何在 shell 环境(winapi)中获取扩展名的文件名?

How to get filename with extension in shell envirionment (winapi)?

我有一个程序可以获取有关 windows 回收站中文件的信息。我看到的存储桶访问问题的唯一非拐杖解决方案是使用 shell 环境。

我有一个使用 winapi(shell 环境)从回收站中删除单个文件(或文件夹)的代码,如下所示:

#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;
}

在上面的代码中,最上面有常量定义(SCID_RemovedFromSCID_RemovedName),用于通过函数[=14]获取被删除文件所在的路径和名称=].但是,生成的名称并不总是包含扩展名,只有当文件以扩展名显示在文件资源管理器中的方式在系统中注册时才会包含。在其他情况下,获取的名称没有扩展名。

我找不到常量,其中 GetDetailsEx 将 return 全名(带扩展名),而不管用户的设置如何。到目前为止,我只找到了一个拐杖:使用 pRecycleBin->GetDisplayNameOf(pidl, SHGDN_FORPARSING, &sret); 来获取扩展名,但是有一个问题:我无法确定如何解释名称 some.docx 和扩展名 $RXZH1I6.docx:是some.docx 全名(如果显示扩展名)或 some.docx.docx 是全名(如果隐藏扩展名)。原来这拐杖更危险

如何获得保证有扩展名的任何文件(“NONFOLDER”)的名称?

下面是一些示例代码(使用 ATL 实现智能指针),显示回收站(或您喜欢的任何其他文件夹)中所有项目的所有属性。

您可以选择您想要的属性。我已经转储了一个带有已删除 txt 文件的示例输出。 该项目的显示名称实际上取决于加工设置,但您可以看到还有许多其他有趣的属性,例如:

  • System.Recycle.DeletedFrom(相当于 SCID_RemovedFrom)
  • System.Recycle.DateDeleted(相当于 SCID_DateDeleted)
  • System.ItemNameDisplay(注意虚拟物品,你不会总是 有一个 System.FileName)
  • System.ItemType(注意虚拟物品, 你不会总是有 System.FileExtension)

示例输出:

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

示例代码:

#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;
}