[SHLEXTDBG] Added IQueryAssociations, SHGetFileInfo and ShellExecute actions (#6030)
authorWhindmar Saksit <whindsaks@proton.me>
Sun, 24 Mar 2024 20:27:04 +0000 (21:27 +0100)
committerGitHub <noreply@github.com>
Sun, 24 Mar 2024 20:27:04 +0000 (21:27 +0100)
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.

modules/rosapps/applications/devutils/shlextdbg/CMakeLists.txt
modules/rosapps/applications/devutils/shlextdbg/shlextdbg.cpp

index 41686b7..636e418 100644 (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)
index cbb7f9b..090c0b5 100644 (file)
 
 #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;
 }