[SHLEXTDBG] Dump IShellFolder column names and properties (#9228)

This commit is contained in:
Whindmar Saksit
2026-06-30 14:50:52 +02:00
committed by GitHub
parent cd609f468a
commit e21afa5b21
@@ -44,6 +44,8 @@ static void PrintHelp(PCWSTR ExtraLine = NULL)
wprintf(L"shlextdbg /shellexec=path [/see] [verb] [class]\n");
wprintf(L"\n");
wprintf(L"shlextdbg /dumpmenu=[{clsid}]path [/cmf]\n");
wprintf(L"shlextdbg /dumpfoldercolumns=folder\n");
wprintf(L"shlextdbg /dumppidl=path\n");
}
/*
@@ -58,6 +60,8 @@ Examples:
/openwindows /shellexec=c: /invoke properties
/dumpmenu=%windir%\explorer.exe /extended
/dumpmenu {D969A300-E7FF-11d0-A93B-00A0C90F2719}c:
/dumpfoldercolumns=c:\
/dumppidl=c:\
*/
@@ -143,14 +147,17 @@ static HRESULT GetUIObjectOfAbsolute(LPCITEMIDLIST pidl, REFIID riid, void** ppv
return hr;
}
static HRESULT CreateShellItemFromParse(PCWSTR Path, IShellItem** ppSI)
static HRESULT CreateShellItemFromParse(PCWSTR Path, IShellItem** ppSI, PIDLIST_ABSOLUTE *ppidl = NULL)
{
PIDLIST_ABSOLUTE pidl = NULL;
HRESULT hr = SHParseDisplayName(Path, NULL, &pidl, 0, NULL);
if (SUCCEEDED(hr))
{
hr = SHCreateShellItem(NULL, NULL, pidl, ppSI);
SHFree(pidl);
if (ppidl)
*ppidl = pidl;
else
SHFree(pidl);
}
return hr;
}
@@ -204,6 +211,25 @@ static void DumpBytes(const void *Data, SIZE_T cb)
wprintf(L"\n");
}
static inline CHAR SafeDumpChar(UINT Ch)
{
return Ch <= 255 && isprint(static_cast<BYTE>(Ch)) ? (BYTE)Ch : '.';
}
static void DumpHexBytes(LPCVOID Buffer, SIZE_T cb)
{
UINT cbPerLine = 16, cbLine;
for (SIZE_T i = 0, j; i < cb; i += cbLine)
{
cbLine = min(cbPerLine, cb - i);
for (j = 0; j < cbLine; ++j) wprintf(L" %.2X", ((BYTE*)Buffer)[i + j]);
for (j = 0; j < cbPerLine - cbLine; ++j) wprintf(L" ");
for (j = 0; j < cbLine; ++j) wprintf(L"%s%c", !j ? L" " : L"", SafeDumpChar(((BYTE*)Buffer)[i + j]));
for (j = 0; j < cbPerLine - cbLine; ++j) wprintf(L" ");
wprintf(L"\n");
}
}
static HRESULT GetCommandString(IContextMenu& CM, UINT Id, UINT Type, LPWSTR buf, UINT cchMax)
{
if (cchMax < 1) return E_INVALIDARG;
@@ -310,6 +336,127 @@ static int SHGFI(PCWSTR Path)
return ret;
}
static HRESULT DumpPidl(PCWSTR Path)
{
PIDLIST_ABSOLUTE pidl;
CComPtr<IShellItem> si;
HRESULT hr = CreateShellItemFromParse(Path, &si, &pidl);
if (FAILED(hr))
{
wprintf(L"Can't parse %s\n", Path);
return hr;
}
UINT lvl = 0;
for (PUITEMID_CHILD p = pidl; p; ++lvl)
{
if (!p->mkid.cb && p > pidl)
break;
PUITEMID_CHILD pNext = ILGetNext(p);
const WORD save = pNext ? pNext->mkid.cb : 0;
if (pNext)
pNext->mkid.cb = 0;
wprintf(L"#%u %ub", lvl, p->mkid.cb);
WCHAR buf[100];
VARIANT v;
V_VT(&v) = VT_EMPTY;
static const SHCOLUMNID pkey_descriptionid = { FMTID_ShellDetails, PID_DESCRIPTIONID };
CComPtr<IShellFolder2> pParentFolder;
PCUITEMID_CHILD pidlChildSelf;
hr = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder2, &pParentFolder), &pidlChildSelf);
if (SUCCEEDED(hr) && SUCCEEDED(hr = pParentFolder->GetDetailsEx(pidlChildSelf, &pkey_descriptionid, &v)))
{
if (V_VT(&v) == (VT_UI1 | VT_ARRAY))
{
const SHDESCRIPTIONID &shdid = *(SHDESCRIPTIONID*)V_ARRAY(&v)->pvData;
StringFromGUID2(shdid.clsid, buf, _countof(buf));
PCWSTR did = L"?";
switch (shdid.dwDescriptionId)
{
case SHDID_ROOT_REGITEM: did = L"ROOT_REGITEM"; break;
case SHDID_FS_FILE: did = L"FS_FILE"; break;
case SHDID_FS_DIRECTORY: did = L"FS_DIRECTORY"; break;
case SHDID_FS_OTHER: did = L"FS_OTHER"; break;
case SHDID_COMPUTER_FIXED: did = L"COMPUTER_FIXED"; break;
}
wprintf(L" %s %s(%#.2x)", buf, did, shdid.dwDescriptionId);
}
PropVariantClear((PROPVARIANT*)&v); // No need to drag in oleaut32, ole32 knows how to handle this
}
wprintf(L"\n");
DumpHexBytes(p->mkid.abID, p->mkid.cb > 2 ? p->mkid.cb - 2 : 0);
if (pNext)
pNext->mkid.cb = save;
p = pNext;
}
SHFree(pidl);
return S_OK;
}
static HRESULT DumpFolderColumns(PCWSTR Path)
{
CComPtr<IShellItem> si;
HRESULT hr = CreateShellItemFromParse(Path, &si);
if (FAILED(hr))
{
wprintf(L"Can't parse %s\n", Path);
return hr;
}
CComPtr<IShellFolder2> sf;
if (FAILED(hr = si->BindToHandler(NULL, BHID_SFObject, IID_PPV_ARG(IShellFolder2, &sf))))
{
wprintf(L"Could not bind to IShellFolder2\n");
return hr;
}
ULONG columns, defSort, defDisp;
for (columns = 0; ; ++columns)
{
SHELLDETAILS sd = {};
if (FAILED(sf->GetDetailsOf(NULL, columns, &sd)))
break;
PWSTR str;
if (FAILED(StrRetToStrW(&sd.str, NULL, &str)))
str = NULL;
SHCOLSTATEF flags;
if (FAILED(sf->GetDefaultColumnState(columns, &flags)))
flags = 0;
wprintf(L"%lu: %s 0x%.4x %d 0x%.8x\n", columns, str ? str : L"", (UINT)sd.fmt, sd.cxChar, flags);
SHFree(str);
}
if (SUCCEEDED(sf->GetDefaultColumn(0, &defSort, &defDisp)))
wprintf(L"%lu is the default sort column and %lu is the default display column.\n", defSort, defDisp);
HRESULT (WINAPI* PSGNFPK)(SHCOLUMNID*, PWSTR*) = NULL;
if (HMODULE hPS = LoadLibraryW(L"propsys.dll"))
{
// HACKFIX: PSGetNameFromPropertyKey is a stub in ROS, we will crash if we call it
HRESULT (WINAPI* PSGPD)(SHCOLUMNID*, REFIID, void**);
(FARPROC&)PSGPD = GetProcAddress(hPS, "PSGetPropertyDescription");
CComPtr<IShellFolder2> dummy;
SHCOLUMNID scid = {};
if (PSGPD && PSGPD(&scid, IID_PPV_ARG(IShellFolder2, &dummy)) != E_NOTIMPL)
(FARPROC&)PSGNFPK = GetProcAddress(hPS, "PSGetNameFromPropertyKey");
}
for (ULONG i = 0; i < max(columns, 999); ++i)
{
SHCOLUMNID scid = {};
if (FAILED(sf->MapColumnToSCID(i, &scid)))
continue;
WCHAR Buffer[100];
StringFromGUID2(scid.fmtid, Buffer, _countof(Buffer));
wprintf(L"%lu: %s,%-3lu", i, Buffer, scid.pid);
CComHeapPtr<WCHAR> str;
if (PSGNFPK && SUCCEEDED(PSGNFPK(&scid, &str)) && str)
wprintf(L" %s", (PWSTR)str);
wprintf(L"\n");
}
return columns ? S_OK : S_FALSE;
}
static HRESULT AssocQ(int argc, WCHAR **argv)
{
UINT qtype = StrToNum(argv[2]);
@@ -819,6 +966,14 @@ int wmain(int argc, WCHAR **argv)
return ErrMsg(hr);
return 0;
}
else if (isCmdWithArg(argc, argv, n, L"dumpfoldercolumns", arg))
{
return DumpFolderColumns(arg);
}
else if (isCmdWithArg(argc, argv, n, L"dumppidl", arg))
{
return DumpPidl(arg);
}
else if (isCmdWithArg(argc, argv, n, L"clsid", arg))
{
HRESULT hr = CLSIDFromString(arg, &g_CLSID);