diff --git a/modules/rosapps/applications/devutils/shlextdbg/CMakeLists.txt b/modules/rosapps/applications/devutils/shlextdbg/CMakeLists.txt index 41686b7813c..636e4185c58 100644 --- a/modules/rosapps/applications/devutils/shlextdbg/CMakeLists.txt +++ b/modules/rosapps/applications/devutils/shlextdbg/CMakeLists.txt @@ -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) diff --git a/modules/rosapps/applications/devutils/shlextdbg/shlextdbg.cpp b/modules/rosapps/applications/devutils/shlextdbg/shlextdbg.cpp index cbb7f9bc477..090c0b501bc 100644 --- a/modules/rosapps/applications/devutils/shlextdbg/shlextdbg.cpp +++ b/modules/rosapps/applications/devutils/shlextdbg/shlextdbg.cpp @@ -7,12 +7,380 @@ #include #include +#include #include // thanks gcc #include // thanks gcc #include #include #include #include +#include + +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> [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 +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 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(L"Directory"); + } + else if (info.dwAttributes & SFGAO_BROWSABLE) + { + ext = const_cast(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 qa; + PWSTR path = argv[0]; + if (*path) + { + CLSID clsid, *pclsid = NULL; + if (CLSIDPrefix(path, clsid)) + pclsid = &clsid; + CComPtr 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& pidl, CComPtr& 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 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(&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 cm; + if (CLSIDPrefix(arg, g_CLSID)) + { + g_ShellExtInit = arg; + hr = LoadAndInitialize(IID_PPV_ARG(IContextMenu, &cm)); + } + else + { + CComPtr 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; }