[SHLEXTDBG] Added IQueryAssociations, SHGetFileInfo and ShellExecute actions (#6030)

Added multiple new actions, most of them will use the system default implementation but can be forced to use a specific CLSID or IShellFolder implementation.

-  /shgfi (SHGetFileInfo)
-  /assocq (IQueryAssociations)
-  /shellexec (ShellExecuteEx on pidl)
-  /dumpmenu (Dumps the HMENU of a IContextMenu with its menu ids and verbs)

Added /explorerinstance as a new wait mode and made it the default. All the other wait modes are hacks that just works around bugs in ROS.
This commit is contained in:
Whindmar Saksit 2024-03-24 21:27:04 +01:00 committed by GitHub
parent 7dd8a30cf9
commit f6cf6954eb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 576 additions and 54 deletions

View File

@ -3,5 +3,5 @@ add_executable(shlextdbg shlextdbg.cpp shlextdbg.rc)
set_module_type(shlextdbg win32cui UNICODE)
target_link_libraries(shlextdbg uuid cpprt atl_classes)
add_importlibs(shlextdbg ole32 comctl32 shell32 user32 msvcrt kernel32)
add_importlibs(shlextdbg ole32 comctl32 shell32 shlwapi advapi32 user32 msvcrt kernel32)
add_cd_file(TARGET shlextdbg DESTINATION reactos/system32 FOR all)

View File

@ -7,12 +7,380 @@
#include <windows.h>
#include <shlobj.h>
#include <shlwapi.h>
#include <atlbase.h> // thanks gcc
#include <atlcom.h> // thanks gcc
#include <atlstr.h>
#include <atlsimpcoll.h>
#include <conio.h>
#include <shellutils.h>
#include <shlwapi_undoc.h>
static void PrintHelp(PCWSTR ExtraLine = NULL)
{
if (ExtraLine)
wprintf(L"%s\n\n", ExtraLine);
wprintf(L"shlextdbg /clsid={clsid} [/dll=dllname] /IShellExtInit=filename |shlextype| |waitoptions|\n");
wprintf(L" {clsid}: The CLSID or ProgID of the object to create\n");
wprintf(L" dll: Optional dllname to create the object from, instead of CoCreateInstance\n");
wprintf(L" filename: The filename to pass to IShellExtInit->Initialze\n");
wprintf(L" shlextype: The type of shell extention to run:\n");
wprintf(L" /IShellPropSheetExt to create a property sheet\n");
wprintf(L" /IContextMenu=verb to activate the specified verb\n");
wprintf(L" waitoptions: Specify how to wait:\n");
wprintf(L" /explorerinstance: Wait for SHGetInstanceExplorer (Default)\n");
wprintf(L" /infinite: Keep on waiting infinitely\n");
wprintf(L" /openwindows: Wait for all windows from the current application to close\n");
wprintf(L" /input: Wait for input\n");
wprintf(L" /nowait\n");
wprintf(L"\n");
wprintf(L"shlextdbg /shgfi=path\n");
wprintf(L" Call SHGetFileInfo. Prefix path with $ to parse as a pidl.\n");
wprintf(L"\n");
wprintf(L"shlextdbg /assocq <[{bhid}]path> <string|data|key> <type> <initflags> <queryflags> <initstring> [extra] [maxsize]\n");
wprintf(L" Uses the default implementation from AssocCreate if path is empty.\n");
wprintf(L"\n");
wprintf(L"shlextdbg /shellexec=path [/see] [verb] [class]\n");
wprintf(L"\n");
wprintf(L"shlextdbg /dumpmenu=[{clsid}]path [/cmf]\n");
}
/*
Examples:
/clsid={513D916F-2A8E-4F51-AEAB-0CBC76FB1AF8} /IShellExtInit=C:\RosBE\Uninstall.exe /IShellPropSheetExt
/clsid=CompressedFolder /IShellExtInit=e:\test.zip /IContextMenu=extract /openwindows
/clsid=CompressedFolder /IShellExtInit=e:\test.zip /IContextMenu=extract /openwindows /dll=R:\build\dev\devenv\dll\shellext\zipfldr\Debug\zipfldr.dll
/shgfi=c:\freeldr.ini
/assocq "" string 1 0 0 .txt
/assocq "" string friendlytypename 0x400 0 .txt "" 10
/openwindows /shellexec=c: /invoke properties
/dumpmenu=%windir%\explorer.exe /extended
/dumpmenu {D969A300-E7FF-11d0-A93B-00A0C90F2719}c:
*/
static LONG StrToNum(PCWSTR in)
{
PWCHAR end;
LONG v = wcstol(in, &end, 0);
return (end > in) ? v : 0;
}
static int ErrMsg(int Error)
{
WCHAR buf[400];
for (UINT e = Error, cch; ;)
{
lstrcpynW(buf, L"?", _countof(buf));
cch = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, e, 0, buf, _countof(buf), NULL);
while (cch && buf[cch - 1] <= ' ')
buf[--cch] = UNICODE_NULL; // Remove trailing newlines
if (cch || HIWORD(e) != HIWORD(HRESULT_FROM_WIN32(1)))
break;
e = HRESULT_CODE(e); // "WIN32_FROM_HRESULT"
}
wprintf(Error < 0 ? L"Error 0x%.8X %s\n" : L"Error %d %s\n", Error, buf);
return Error;
}
template<class T>
static bool CLSIDPrefix(T& String, CLSID& Clsid)
{
WCHAR buf[38 + 1];
if (String[0] == '{')
{
lstrcpynW(buf, String, _countof(buf));
if (SUCCEEDED(CLSIDFromString(buf, &Clsid)))
{
String = String + 38;
return true;
}
}
return false;
}
static HRESULT GetUIObjectOfAbsolute(LPCITEMIDLIST pidl, REFIID riid, void** ppv)
{
CComPtr<IShellFolder> shellFolder;
PCUITEMID_CHILD child;
HRESULT hr = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &shellFolder), &child);
if (SUCCEEDED(hr))
hr = shellFolder->GetUIObjectOf(NULL, 1, &child, riid, NULL, ppv);
return hr;
}
static HRESULT CreateShellItemFromParse(PCWSTR Path, IShellItem** ppSI)
{
PIDLIST_ABSOLUTE pidl = NULL;
HRESULT hr = SHParseDisplayName(Path, NULL, &pidl, 0, NULL);
if (SUCCEEDED(hr))
{
hr = SHCreateShellItem(NULL, NULL, pidl, ppSI);
SHFree(pidl);
}
return hr;
}
static void GetAssocClass(LPCWSTR Path, LPCITEMIDLIST pidl, HKEY& hKey)
{
hKey = NULL;
IQueryAssociations* pQA;
if (SUCCEEDED(GetUIObjectOfAbsolute(pidl, IID_PPV_ARG(IQueryAssociations, &pQA))))
{
pQA->GetKey(0, ASSOCKEY_CLASS, NULL, &hKey); // Not implemented in ROS
pQA->Release();
}
if (!hKey)
{
DWORD cb;
WCHAR buf[MAX_PATH];
PWSTR ext = PathFindExtensionW(Path);
SHFILEINFOW info;
info.dwAttributes = 0;
SHGetFileInfoW((LPWSTR)pidl, 0, &info, sizeof(info), SHGFI_PIDL | SHGFI_ATTRIBUTES);
if (info.dwAttributes & SFGAO_FOLDER)
{
ext = const_cast<LPWSTR>(L"Directory");
}
else if (info.dwAttributes & SFGAO_BROWSABLE)
{
ext = const_cast<LPWSTR>(L"Folder"); // Best guess
}
else
{
cb = sizeof(buf);
if (!SHGetValueW(HKEY_CLASSES_ROOT, ext, NULL, NULL, buf, &cb))
{
RegOpenKeyExW(HKEY_CLASSES_ROOT, buf, 0, KEY_READ, &hKey);
}
}
if (!hKey)
{
RegOpenKeyExW(HKEY_CLASSES_ROOT, ext, 0, KEY_READ, &hKey);
}
}
}
static void DumpBytes(const void *Data, SIZE_T cb)
{
for (SIZE_T i = 0; i < cb; ++i)
{
wprintf(L"%s%.2X", i ? L" " : L"", ((LPCBYTE)Data)[i]);
}
wprintf(L"\n");
}
static HRESULT GetCommandString(IContextMenu& CM, UINT Id, UINT Type, LPWSTR buf, UINT cchMax)
{
if (cchMax < 1) return E_INVALIDARG;
*buf = UNICODE_NULL;
// First try to retrieve the UNICODE string directly
HRESULT hr = CM.GetCommandString(Id, Type | GCS_UNICODE, 0, (char*)buf, cchMax);
if (FAILED(hr))
{
// It failed, try to retrieve an ANSI string instead then convert it to UNICODE
STRRET sr;
sr.uType = STRRET_CSTR;
hr = CM.GetCommandString(Id, Type & ~GCS_UNICODE, 0, sr.cStr, _countof(sr.cStr));
if (SUCCEEDED(hr))
hr = StrRetToBufW(&sr, NULL, buf, cchMax);
}
return hr;
}
static void DumpMenu(HMENU hMenu, UINT IdOffset, IContextMenu* pCM, BOOL FakeInit, UINT Indent)
{
bool recurse = Indent != UINT(-1);
WCHAR buf[MAX_PATH];
MENUITEMINFOW mii;
mii.cbSize = FIELD_OFFSET(MENUITEMINFOW, hbmpItem);
for (UINT i = 0, defid = GetMenuDefaultItem(hMenu, FALSE, 0); ; ++i)
{
mii.fMask = MIIM_STRING;
mii.dwTypeData = buf;
mii.cch = _countof(buf);
*buf = UNICODE_NULL;
if (!GetMenuItemInfo(hMenu, i, TRUE, &mii))
lstrcpynW(buf, L"?", _countof(buf)); // Tolerate string failure
mii.fMask = MIIM_ID | MIIM_SUBMENU | MIIM_FTYPE;
mii.hSubMenu = NULL;
mii.dwTypeData = NULL;
mii.cch = 0;
if (!GetMenuItemInfo(hMenu, i, TRUE, &mii))
break;
BOOL sep = mii.fType & MFT_SEPARATOR;
wprintf(L"%-4d", (sep || mii.wID == UINT(-1)) ? mii.wID : (mii.wID - IdOffset));
for (UINT j = 0; j < Indent && recurse; ++j)
wprintf(L" ");
wprintf(L"%s%s", mii.hSubMenu ? L">" : L"|", sep ? L"----------" : buf);
if (!sep && pCM && SUCCEEDED(GetCommandString(*pCM, mii.wID - IdOffset,
GCS_VERB, buf, _countof(buf))))
{
wprintf(L" [%s]", buf);
}
wprintf(L"%s\n", (defid == mii.wID && defid != UINT(-1)) ? L" (Default)" : L"");
if (mii.hSubMenu && recurse)
{
if (FakeInit)
SHForwardContextMenuMsg(pCM, WM_INITMENUPOPUP, (WPARAM)mii.hSubMenu, LOWORD(i), NULL, TRUE);
DumpMenu(mii.hSubMenu, IdOffset, pCM, FakeInit, Indent + 1);
}
}
}
static int SHGFI(PCWSTR Path)
{
PIDLIST_ABSOLUTE pidl = NULL;
UINT flags = 0, ret = 0;
if (*Path == L'$')
{
HRESULT hr = SHParseDisplayName(++Path, NULL, &pidl, 0, NULL);
if (FAILED(hr))
return ErrMsg(hr);
flags |= SHGFI_PIDL;
Path = (LPCWSTR)pidl;
}
else if (GetFileAttributes(Path) == INVALID_FILE_ATTRIBUTES)
{
flags |= SHGFI_USEFILEATTRIBUTES;
}
SHFILEINFOW info;
if (!SHGetFileInfoW(Path, 0, &info, sizeof(info), flags |
SHGFI_DISPLAYNAME | SHGFI_ATTRIBUTES | SHGFI_TYPENAME))
{
info.szDisplayName[0] = info.szTypeName[0] = UNICODE_NULL;
info.dwAttributes = 0;
ret = ERROR_FILE_NOT_FOUND;
}
wprintf(L"Display: %s\n", info.szDisplayName);
wprintf(L"Attributes: 0x%x\n", info.dwAttributes);
wprintf(L"Type: %s\n", info.szTypeName);
if (!SHGetFileInfoW(Path, 0, &info, sizeof(info), flags | SHGFI_ICONLOCATION))
{
info.szDisplayName[0] = UNICODE_NULL;
info.iIcon = -1;
}
wprintf(L"Icon: %s,%d\n", info.szDisplayName, info.iIcon);
if (!SHGetFileInfoW(Path, 0, &info, sizeof(info), flags | SHGFI_SYSICONINDEX))
{
info.iIcon = -1;
}
wprintf(L"Index: %d\n", info.iIcon);
SHFree(pidl);
return ret;
}
static HRESULT AssocQ(int argc, WCHAR **argv)
{
UINT qtype = StrToNum(argv[2]);
ASSOCF iflags = StrToNum(argv[3]);
ASSOCF qflags = StrToNum(argv[4]);
PCWSTR extra = (argc > 6 && *argv[6]) ? argv[6] : NULL;
WCHAR buf[MAX_PATH * 2];
DWORD maxSize = (argc > 7 && *argv[7]) ? StrToNum(argv[7]) : sizeof(buf);
HRESULT hr;
CComPtr<IQueryAssociations> qa;
PWSTR path = argv[0];
if (*path)
{
CLSID clsid, *pclsid = NULL;
if (CLSIDPrefix(path, clsid))
pclsid = &clsid;
CComPtr<IShellItem> si;
if (SUCCEEDED(hr = CreateShellItemFromParse(path, &si)))
{
hr = si->BindToHandler(NULL, pclsid ? *pclsid : BHID_AssociationArray, IID_PPV_ARG(IQueryAssociations, &qa));
if (FAILED(hr) && !pclsid)
hr = si->BindToHandler(NULL, BHID_SFUIObject, IID_PPV_ARG(IQueryAssociations, &qa));
}
}
else
{
hr = AssocCreate(CLSID_QueryAssociations, IID_PPV_ARG(IQueryAssociations, &qa));
}
if (FAILED(hr))
return ErrMsg(hr);
hr = qa->Init(iflags, argv[5], NULL, NULL);
if (FAILED(hr))
return ErrMsg(hr);
DWORD size = maxSize;
if (!wcsicmp(argv[1], L"string"))
{
if (!wcsicmp(argv[2], L"COMMAND"))
qtype = ASSOCSTR_COMMAND;
if (!wcsicmp(argv[2], L"EXECUTABLE"))
qtype = ASSOCSTR_EXECUTABLE;
if (!wcsicmp(argv[2], L"FRIENDLYDOCNAME") || !wcsicmp(argv[2], L"FriendlyTypeName"))
qtype = ASSOCSTR_FRIENDLYDOCNAME;
if (!wcsicmp(argv[2], L"DEFAULTICON"))
qtype = ASSOCSTR_DEFAULTICON;
buf[0] = UNICODE_NULL;
size /= sizeof(buf[0]); // Convert to number of characters
hr = qa->GetString(qflags, (ASSOCSTR)qtype, extra, buf, &size);
size *= sizeof(buf[0]); // Convert back to bytes
if (SUCCEEDED(hr) ||
hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER))
{
wprintf(L"0x%.8X: %s\n", hr, buf);
}
else
{
wprintf(size != maxSize ? L"%u " : L"", size);
ErrMsg(hr);
}
}
else if (!wcsicmp(argv[1], L"data"))
{
if (!wcsicmp(argv[2], L"EDITFLAGS"))
qtype = ASSOCDATA_EDITFLAGS;
if (!wcsicmp(argv[2], L"VALUE"))
qtype = ASSOCDATA_VALUE;
hr = qa->GetData(qflags, (ASSOCDATA)qtype, extra, buf, &size);
if (SUCCEEDED(hr))
{
wprintf(L"0x%.8X: %u byte(s) ", hr, size);
DumpBytes(buf, min(size, maxSize));
}
else
{
wprintf(size != maxSize ? L"%u " : L"", size);
ErrMsg(hr);
}
}
else if (!wcsicmp(argv[1], L"key"))
{
HKEY hKey = NULL;
hr = qa->GetKey(qflags, (ASSOCKEY)qtype, extra, &hKey);
if (SUCCEEDED(hr))
{
wprintf(L"0x%.8X: hKey %p\n", hr, hKey);
RegQueryValueExW(hKey, L"shlextdbg", 0, NULL, NULL, NULL); // Filter by this in Process Monitor
RegCloseKey(hKey);
}
else
{
ErrMsg(hr);
}
}
else
{
PrintHelp(L"Unknown query");
return ErrMsg(ERROR_INVALID_PARAMETER);
}
return hr;
}
enum WaitType
{
@ -20,6 +388,7 @@ enum WaitType
Wait_Infinite,
Wait_OpenWindows,
Wait_Input,
Wait_ExplorerInstance,
};
CLSID g_CLSID = { 0 };
@ -27,7 +396,7 @@ CStringW g_DLL;
CStringW g_ShellExtInit;
bool g_bIShellPropSheetExt = false;
CStringA g_ContextMenu;
WaitType g_Wait = Wait_None;
WaitType g_Wait = Wait_ExplorerInstance;
HRESULT CreateIDataObject(CComHeapPtr<ITEMIDLIST>& pidl, CComPtr<IDataObject>& dataObject, PCWSTR FileName)
{
@ -105,7 +474,11 @@ HRESULT LoadAndInitialize(REFIID riid, LPVOID* ppv)
if (!SUCCEEDED(hr))
return hr;
hr = spShellExtInit->Initialize(pidl, spDataObject, NULL);
HKEY hKey = NULL;
GetAssocClass(g_ShellExtInit.GetString(), pidl, hKey);
hr = spShellExtInit->Initialize(pidl, spDataObject, hKey);
if (hKey)
RegCloseKey(hKey);
if (!SUCCEEDED(hr))
{
wprintf(L"IShellExtInit->Initialize failed: 0x%x\n", hr);
@ -154,7 +527,73 @@ void WaitWindows()
wprintf(L"All windows closed (ignoring console window)\n");
}
struct ExplorerInstance : public IUnknown
{
HWND m_hWnd;
volatile LONG m_rc;
ExplorerInstance() : m_hWnd(NULL), m_rc(1) {}
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppv)
{
static const QITAB rgqit[] = { { 0 } };
return QISearch(this, rgqit, riid, ppv);
}
virtual ULONG STDMETHODCALLTYPE AddRef()
{
if (g_Wait == Wait_ExplorerInstance)
wprintf(L"INFO: SHGetInstanceExplorer\n");
return InterlockedIncrement(&m_rc);
}
virtual ULONG STDMETHODCALLTYPE Release()
{
if (g_Wait == Wait_ExplorerInstance)
wprintf(L"INFO: Release ExplorerInstance\n");
ULONG r = InterlockedDecrement(&m_rc);
if (!r)
PostMessage(m_hWnd, WM_CLOSE, 0, 0);
return r;
}
void Wait()
{
SHSetInstanceExplorer(NULL);
m_hWnd = CreateWindowEx(WS_EX_TOOLWINDOW, TEXT("STATIC"), NULL, WS_POPUP,
0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
BOOL loop = InterlockedDecrement(&m_rc) != 0;
MSG msg;
while (loop && (int)GetMessage(&msg, NULL, 0, 0) > 0)
{
if (msg.hwnd == m_hWnd && msg.message == WM_CLOSE)
PostMessage(m_hWnd, WM_QUIT, 0, 0);
DispatchMessage(&msg);
}
}
} g_EI;
static void Wait()
{
LPCWSTR nag = L"(Please use SHGetInstanceExplorer in your code instead)";
switch (g_Wait)
{
case Wait_None:
break;
case Wait_Infinite:
_putws(nag);
while (true)
Sleep(1000);
break;
case Wait_OpenWindows:
_putws(nag);
WaitWindows();
break;
case Wait_Input:
wprintf(L"Press any key to continue... %s\n", nag);
_getch();
break;
case Wait_ExplorerInstance:
g_EI.Wait();
break;
}
}
CSimpleArray<HPROPSHEETPAGE> g_Pages;
static BOOL CALLBACK cb_AddPage(HPROPSHEETPAGE page, LPARAM lParam)
@ -196,36 +635,14 @@ static bool isCmd(int argc, WCHAR** argv, int n, PCWSTR check)
return !wcsicmp(argv[n] + 1, check);
}
static void PrintHelp(PCWSTR ExtraLine)
{
if (ExtraLine)
wprintf(L"%s\n", ExtraLine);
wprintf(L"shlextdbg /clsid={clsid} [/dll=dllname] /IShellExtInit=filename |shlextype| |waitoptions|\n");
wprintf(L" {clsid}: The CLSID or ProgID of the object to create\n");
wprintf(L" dll: Optional dllname to create the object from, instead of CoCreateInstance\n");
wprintf(L" filename: The filename to pass to IShellExtInit->Initialze\n");
wprintf(L" shlextype: The type of shell extention to run:\n");
wprintf(L" /IShellPropSheetExt to create a property sheet\n");
wprintf(L" /IContextMenu=verb to activate the specified verb\n");
wprintf(L" waitoptions: Specify how to wait:\n");
wprintf(L" /infinite: Keep on waiting infinitely\n");
wprintf(L" /openwindows: Wait for all windows from the current application to close\n");
wprintf(L" /input: Wait for input\n");
wprintf(L"\n");
}
/*
Examples:
/clsid={513D916F-2A8E-4F51-AEAB-0CBC76FB1AF8} /IShellExtInit=C:\RosBE\Uninstall.exe /IShellPropSheetExt
/clsid=CompressedFolder /IShellExtInit=e:\test.zip /IContextMenu=extract /openwindows
/clsid=CompressedFolder /IShellExtInit=e:\test.zip /IContextMenu=extract /openwindows /dll=R:\build\dev\devenv\dll\shellext\zipfldr\Debug\zipfldr.dll
*/
extern "C" // and another hack for gcc
int wmain(int argc, WCHAR **argv)
{
INITCOMMONCONTROLSEX icc = { sizeof(icc), ICC_LINK_CLASS | ICC_STANDARD_CLASSES };
InitCommonControlsEx(&icc);
CoInitialize(NULL);
SHSetInstanceExplorer(static_cast<IUnknown*>(&g_EI));
bool failArgs = false;
for (int n = 1; n < argc; ++n)
{
@ -233,7 +650,124 @@ int wmain(int argc, WCHAR **argv)
if (cmd[0] == '-' || cmd[0] == '/')
{
PCWSTR arg;
if (isCmdWithArg(argc, argv, n, L"clsid", arg))
if (isCmdWithArg(argc, argv, n, L"shgfi", arg))
{
failArgs = true;
if (*arg)
return SHGFI(arg);
}
else if (isCmd(argc, argv, n, L"assocq"))
{
failArgs = true;
if (argc - (n + 1) >= 6 && argc - (n + 1) <= 8)
return AssocQ(argc - (n + 1), &argv[(n + 1)]);
}
else if (isCmdWithArg(argc, argv, n, L"shellexec", arg))
{
PIDLIST_ABSOLUTE pidl = NULL;
HRESULT hr = SHParseDisplayName(arg, NULL, &pidl, 0, NULL);
if (FAILED(hr))
return ErrMsg(hr);
SHELLEXECUTEINFOW sei = { sizeof(sei), SEE_MASK_IDLIST | SEE_MASK_UNICODE };
sei.lpIDList = pidl;
sei.nShow = SW_SHOW;
while (++n < argc)
{
if (argv[n][0] != '-' && argv[n][0] != '/')
break;
else if (isCmd(argc, argv, n, L"INVOKE"))
sei.fMask |= SEE_MASK_INVOKEIDLIST;
else if (isCmd(argc, argv, n, L"NOUI"))
sei.fMask |= SEE_MASK_FLAG_NO_UI;
else if (isCmd(argc, argv, n, L"ASYNCOK"))
sei.fMask |= SEE_MASK_ASYNCOK ;
else if (isCmd(argc, argv, n, L"NOASYNC"))
sei.fMask |= SEE_MASK_NOASYNC;
else
wprintf(L"WARN: Ignoring switch %s\n", argv[n]);
}
if (n < argc && *argv[n++])
{
sei.lpVerb = argv[n - 1];
}
if (n < argc && *argv[n++])
{
sei.lpClass = argv[n - 1];
sei.fMask |= SEE_MASK_CLASSNAME;
}
UINT succ = ShellExecuteExW(&sei), gle = GetLastError();
SHFree(pidl);
if (!succ)
return ErrMsg(gle);
Wait();
return 0;
}
else if (isCmdWithArg(argc, argv, n, L"dumpmenu", arg))
{
HRESULT hr;
CComPtr<IContextMenu> cm;
if (CLSIDPrefix(arg, g_CLSID))
{
g_ShellExtInit = arg;
hr = LoadAndInitialize(IID_PPV_ARG(IContextMenu, &cm));
}
else
{
CComPtr<IShellItem> si;
hr = CreateShellItemFromParse(arg, &si);
if (SUCCEEDED(hr))
hr = si->BindToHandler(NULL, BHID_SFUIObject, IID_PPV_ARG(IContextMenu, &cm));
}
if (SUCCEEDED(hr))
{
UINT first = 10, last = 9000;
UINT cmf = 0, nosub = 0, fakeinit = 0;
while (++n < argc)
{
if (argv[n][0] != '-' && argv[n][0] != '/')
break;
else if (isCmd(argc, argv, n, L"DEFAULTONLY"))
cmf |= CMF_DEFAULTONLY;
else if (isCmd(argc, argv, n, L"NODEFAULT"))
cmf |= CMF_NODEFAULT;
else if (isCmd(argc, argv, n, L"DONOTPICKDEFAULT"))
cmf |= CMF_DONOTPICKDEFAULT;
else if (isCmd(argc, argv, n, L"EXTENDED") || isCmd(argc, argv, n, L"EXTENDEDVERBS"))
cmf |= CMF_EXTENDEDVERBS;
else if (isCmd(argc, argv, n, L"SYNCCASCADEMENU"))
cmf |= CMF_SYNCCASCADEMENU;
else if (isCmd(argc, argv, n, L"EXPLORE"))
cmf |= CMF_EXPLORE;
else if (isCmd(argc, argv, n, L"VERBSONLY"))
cmf |= CMF_VERBSONLY;
else if (isCmd(argc, argv, n, L"NOVERBS"))
cmf |= CMF_NOVERBS;
else if (isCmd(argc, argv, n, L"DISABLEDVERBS"))
cmf |= CMF_DISABLEDVERBS;
else if (isCmd(argc, argv, n, L"OPTIMIZEFORINVOKE"))
cmf |= CMF_OPTIMIZEFORINVOKE;
else if (isCmd(argc, argv, n, L"CANRENAME"))
cmf |= CMF_CANRENAME;
else if (isCmd(argc, argv, n, L"NOSUBMENU"))
nosub++;
else if (isCmd(argc, argv, n, L"INITMENUPOPUP"))
fakeinit++; // Tickle async submenus
else
wprintf(L"WARN: Ignoring switch %s\n", argv[n]);
}
HMENU hMenu = CreatePopupMenu();
hr = cm->QueryContextMenu(hMenu, 0, first, last, cmf);
if (SUCCEEDED(hr))
{
DumpMenu(hMenu, first, cm, fakeinit, nosub ? -1 : 0);
}
DestroyMenu(hMenu);
}
if (FAILED(hr))
return ErrMsg(hr);
return 0;
}
else if (isCmdWithArg(argc, argv, n, L"clsid", arg))
{
HRESULT hr = CLSIDFromString(arg, &g_CLSID);
if (!SUCCEEDED(hr))
@ -270,6 +804,14 @@ int wmain(int argc, WCHAR **argv)
{
g_Wait = Wait_Input;
}
else if (isCmd(argc, argv, n, L"explorerinstance"))
{
g_Wait = Wait_ExplorerInstance;
}
else if (isCmd(argc, argv, n, L"nowait"))
{
g_Wait = Wait_None;
}
else
{
wprintf(L"Unknown argument: %s\n", cmd);
@ -284,7 +826,6 @@ int wmain(int argc, WCHAR **argv)
return E_INVALIDARG;
}
CLSID EmptyCLSID = { 0 };
if (EmptyCLSID == g_CLSID)
{
@ -298,10 +839,6 @@ int wmain(int argc, WCHAR **argv)
return E_INVALIDARG;
}
INITCOMMONCONTROLSEX icc = { sizeof(icc), ICC_LINK_CLASS | ICC_STANDARD_CLASSES };
InitCommonControlsEx(&icc);
CoInitialize(NULL);
HRESULT hr;
if (g_bIShellPropSheetExt)
{
@ -337,6 +874,8 @@ int wmain(int argc, WCHAR **argv)
if (!SUCCEEDED(hr))
return hr;
// FIXME: Must call QueryContextMenu before InvokeCommand?
CMINVOKECOMMANDINFO cm = { sizeof(cm), 0 };
cm.lpVerb = g_ContextMenu.GetString();
cm.nShow = SW_SHOW;
@ -350,23 +889,6 @@ int wmain(int argc, WCHAR **argv)
wprintf(L"IContextMenu->InvokeCommand returned: 0x%x\n", hr);
}
switch (g_Wait)
{
case Wait_None:
break;
case Wait_Infinite:
while (true) {
Sleep(1000);
}
break;
case Wait_OpenWindows:
WaitWindows();
break;
case Wait_Input:
wprintf(L"Press any key to continue...\n");
_getch();
break;
}
Wait();
return 0;
}