1 /*
2 * PROJECT: shlextdbg
3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4 * PURPOSE: Shell extension debug utility
5 * COPYRIGHT: Copyright 2017 Mark Jansen (mark.jansen@reactos.org)
6 */
7
8 #include <windows.h>
9 #include <shlobj.h>
10 #include <shlwapi.h>
11 #include <atlbase.h> // thanks gcc
12 #include <atlcom.h> // thanks gcc
13 #include <atlstr.h>
14 #include <atlsimpcoll.h>
15 #include <conio.h>
16 #include <shellutils.h>
17 #include <shlwapi_undoc.h>
18
PrintHelp(PCWSTR ExtraLine=NULL)19 static void PrintHelp(PCWSTR ExtraLine = NULL)
20 {
21 if (ExtraLine)
22 wprintf(L"%s\n\n", ExtraLine);
23
24 wprintf(L"shlextdbg /clsid={clsid} [/dll=dllname] /IShellExtInit=filename |shlextype| |waitoptions|\n");
25 wprintf(L" {clsid}: The CLSID or ProgID of the object to create\n");
26 wprintf(L" dll: Optional dllname to create the object from, instead of CoCreateInstance\n");
27 wprintf(L" filename: The filename to pass to IShellExtInit->Initialze\n");
28 wprintf(L" shlextype: The type of shell extention to run:\n");
29 wprintf(L" /IShellPropSheetExt to create a property sheet\n");
30 wprintf(L" /IContextMenu=verb to activate the specified verb\n");
31 wprintf(L" waitoptions: Specify how to wait:\n");
32 wprintf(L" /explorerinstance: Wait for SHGetInstanceExplorer (Default)\n");
33 wprintf(L" /infinite: Keep on waiting infinitely\n");
34 wprintf(L" /openwindows: Wait for all windows from the current application to close\n");
35 wprintf(L" /input: Wait for input\n");
36 wprintf(L" /nowait\n");
37 wprintf(L"\n");
38 wprintf(L"shlextdbg /shgfi=path\n");
39 wprintf(L" Call SHGetFileInfo. Prefix path with $ to parse as a pidl.\n");
40 wprintf(L"\n");
41 wprintf(L"shlextdbg /assocq <[{bhid}]path> <string|data|key> <type> <initflags> <queryflags> <initstring> [extra] [maxsize]\n");
42 wprintf(L" Uses the default implementation from AssocCreate if path is empty.\n");
43 wprintf(L"\n");
44 wprintf(L"shlextdbg /shellexec=path [/see] [verb] [class]\n");
45 wprintf(L"\n");
46 wprintf(L"shlextdbg /dumpmenu=[{clsid}]path [/cmf]\n");
47 }
48
49 /*
50 Examples:
51
52 /clsid={513D916F-2A8E-4F51-AEAB-0CBC76FB1AF8} /IShellExtInit=C:\RosBE\Uninstall.exe /IShellPropSheetExt
53 /clsid=CompressedFolder /IShellExtInit=e:\test.zip /IContextMenu=extract /openwindows
54 /clsid=CompressedFolder /IShellExtInit=e:\test.zip /IContextMenu=extract /openwindows /dll=R:\build\dev\devenv\dll\shellext\zipfldr\Debug\zipfldr.dll
55 /shgfi=c:\freeldr.ini
56 /assocq "" string 1 0 0 .txt
57 /assocq "" string friendlytypename 0x400 0 .txt "" 10
58 /openwindows /shellexec=c: /invoke properties
59 /dumpmenu=%windir%\explorer.exe /extended
60 /dumpmenu {D969A300-E7FF-11d0-A93B-00A0C90F2719}c:
61
62 */
63
StrToNum(PCWSTR in)64 static LONG StrToNum(PCWSTR in)
65 {
66 PWCHAR end;
67 LONG v = wcstol(in, &end, 0);
68 return (end > in) ? v : 0;
69 }
70
ErrMsg(int Error)71 static int ErrMsg(int Error)
72 {
73 WCHAR buf[400];
74 for (UINT e = Error, cch; ;)
75 {
76 lstrcpynW(buf, L"?", _countof(buf));
77 cch = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, e, 0, buf, _countof(buf), NULL);
78 while (cch && buf[cch - 1] <= ' ')
79 buf[--cch] = UNICODE_NULL; // Remove trailing newlines
80 if (cch || HIWORD(e) != HIWORD(HRESULT_FROM_WIN32(1)))
81 break;
82 e = HRESULT_CODE(e); // "WIN32_FROM_HRESULT"
83 }
84 wprintf(Error < 0 ? L"Error 0x%.8X %s\n" : L"Error %d %s\n", Error, buf);
85 return Error;
86 }
87
88 template<class T>
CLSIDPrefix(T & String,CLSID & Clsid)89 static bool CLSIDPrefix(T& String, CLSID& Clsid)
90 {
91 WCHAR buf[38 + 1];
92 if (String[0] == '{')
93 {
94 lstrcpynW(buf, String, _countof(buf));
95 if (SUCCEEDED(CLSIDFromString(buf, &Clsid)))
96 {
97 String = String + 38;
98 return true;
99 }
100 }
101 return false;
102 }
103
GetUIObjectOfAbsolute(LPCITEMIDLIST pidl,REFIID riid,void ** ppv)104 static HRESULT GetUIObjectOfAbsolute(LPCITEMIDLIST pidl, REFIID riid, void** ppv)
105 {
106 CComPtr<IShellFolder> shellFolder;
107 PCUITEMID_CHILD child;
108 HRESULT hr = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &shellFolder), &child);
109 if (SUCCEEDED(hr))
110 hr = shellFolder->GetUIObjectOf(NULL, 1, &child, riid, NULL, ppv);
111 return hr;
112 }
113
CreateShellItemFromParse(PCWSTR Path,IShellItem ** ppSI)114 static HRESULT CreateShellItemFromParse(PCWSTR Path, IShellItem** ppSI)
115 {
116 PIDLIST_ABSOLUTE pidl = NULL;
117 HRESULT hr = SHParseDisplayName(Path, NULL, &pidl, 0, NULL);
118 if (SUCCEEDED(hr))
119 {
120 hr = SHCreateShellItem(NULL, NULL, pidl, ppSI);
121 SHFree(pidl);
122 }
123 return hr;
124 }
125
GetAssocClass(LPCWSTR Path,LPCITEMIDLIST pidl,HKEY & hKey)126 static void GetAssocClass(LPCWSTR Path, LPCITEMIDLIST pidl, HKEY& hKey)
127 {
128 hKey = NULL;
129 IQueryAssociations* pQA;
130 if (SUCCEEDED(GetUIObjectOfAbsolute(pidl, IID_PPV_ARG(IQueryAssociations, &pQA))))
131 {
132 pQA->GetKey(0, ASSOCKEY_CLASS, NULL, &hKey); // Not implemented in ROS
133 pQA->Release();
134 }
135 if (!hKey)
136 {
137 DWORD cb;
138 WCHAR buf[MAX_PATH];
139 PWSTR ext = PathFindExtensionW(Path);
140 SHFILEINFOW info;
141 info.dwAttributes = 0;
142 SHGetFileInfoW((LPWSTR)pidl, 0, &info, sizeof(info), SHGFI_PIDL | SHGFI_ATTRIBUTES);
143 if (info.dwAttributes & SFGAO_FOLDER)
144 {
145 ext = const_cast<LPWSTR>(L"Directory");
146 }
147 else if (info.dwAttributes & SFGAO_BROWSABLE)
148 {
149 ext = const_cast<LPWSTR>(L"Folder"); // Best guess
150 }
151 else
152 {
153 cb = sizeof(buf);
154 if (!SHGetValueW(HKEY_CLASSES_ROOT, ext, NULL, NULL, buf, &cb))
155 {
156 RegOpenKeyExW(HKEY_CLASSES_ROOT, buf, 0, KEY_READ, &hKey);
157 }
158 }
159 if (!hKey)
160 {
161 RegOpenKeyExW(HKEY_CLASSES_ROOT, ext, 0, KEY_READ, &hKey);
162 }
163 }
164 }
165
DumpBytes(const void * Data,SIZE_T cb)166 static void DumpBytes(const void *Data, SIZE_T cb)
167 {
168 for (SIZE_T i = 0; i < cb; ++i)
169 {
170 wprintf(L"%s%.2X", i ? L" " : L"", ((LPCBYTE)Data)[i]);
171 }
172 wprintf(L"\n");
173 }
174
GetCommandString(IContextMenu & CM,UINT Id,UINT Type,LPWSTR buf,UINT cchMax)175 static HRESULT GetCommandString(IContextMenu& CM, UINT Id, UINT Type, LPWSTR buf, UINT cchMax)
176 {
177 if (cchMax < 1) return E_INVALIDARG;
178 *buf = UNICODE_NULL;
179
180 // First try to retrieve the UNICODE string directly
181 HRESULT hr = CM.GetCommandString(Id, Type | GCS_UNICODE, 0, (char*)buf, cchMax);
182 if (FAILED(hr))
183 {
184 // It failed, try to retrieve an ANSI string instead then convert it to UNICODE
185 STRRET sr;
186 sr.uType = STRRET_CSTR;
187 hr = CM.GetCommandString(Id, Type & ~GCS_UNICODE, 0, sr.cStr, _countof(sr.cStr));
188 if (SUCCEEDED(hr))
189 hr = StrRetToBufW(&sr, NULL, buf, cchMax);
190 }
191 return hr;
192 }
193
DumpMenu(HMENU hMenu,UINT IdOffset,IContextMenu * pCM,BOOL FakeInit,UINT Indent)194 static void DumpMenu(HMENU hMenu, UINT IdOffset, IContextMenu* pCM, BOOL FakeInit, UINT Indent)
195 {
196 bool recurse = Indent != UINT(-1);
197 WCHAR buf[MAX_PATH];
198 MENUITEMINFOW mii;
199 mii.cbSize = FIELD_OFFSET(MENUITEMINFOW, hbmpItem);
200
201 for (UINT i = 0, defid = GetMenuDefaultItem(hMenu, FALSE, 0); ; ++i)
202 {
203 mii.fMask = MIIM_STRING;
204 mii.dwTypeData = buf;
205 mii.cch = _countof(buf);
206 *buf = UNICODE_NULL;
207 if (!GetMenuItemInfo(hMenu, i, TRUE, &mii))
208 lstrcpynW(buf, L"?", _countof(buf)); // Tolerate string failure
209 mii.fMask = MIIM_ID | MIIM_SUBMENU | MIIM_FTYPE;
210 mii.hSubMenu = NULL;
211 mii.dwTypeData = NULL;
212 mii.cch = 0;
213 if (!GetMenuItemInfo(hMenu, i, TRUE, &mii))
214 break;
215
216 BOOL sep = mii.fType & MFT_SEPARATOR;
217 wprintf(L"%-4d", (sep || mii.wID == UINT(-1)) ? mii.wID : (mii.wID - IdOffset));
218 for (UINT j = 0; j < Indent && recurse; ++j)
219 wprintf(L" ");
220 wprintf(L"%s%s", mii.hSubMenu ? L">" : L"|", sep ? L"----------" : buf);
221 if (!sep && pCM && SUCCEEDED(GetCommandString(*pCM, mii.wID - IdOffset,
222 GCS_VERB, buf, _countof(buf))))
223 {
224 wprintf(L" [%s]", buf);
225 }
226 wprintf(L"%s\n", (defid == mii.wID && defid != UINT(-1)) ? L" (Default)" : L"");
227 if (mii.hSubMenu && recurse)
228 {
229 if (FakeInit)
230 SHForwardContextMenuMsg(pCM, WM_INITMENUPOPUP, (WPARAM)mii.hSubMenu, LOWORD(i), NULL, TRUE);
231 DumpMenu(mii.hSubMenu, IdOffset, pCM, FakeInit, Indent + 1);
232 }
233 }
234 }
235
SHGFI(PCWSTR Path)236 static int SHGFI(PCWSTR Path)
237 {
238 PIDLIST_ABSOLUTE pidl = NULL;
239 UINT flags = 0, ret = 0;
240
241 if (*Path == L'$')
242 {
243 HRESULT hr = SHParseDisplayName(++Path, NULL, &pidl, 0, NULL);
244 if (FAILED(hr))
245 return ErrMsg(hr);
246 flags |= SHGFI_PIDL;
247 Path = (LPCWSTR)pidl;
248 }
249 else if (GetFileAttributes(Path) == INVALID_FILE_ATTRIBUTES)
250 {
251 flags |= SHGFI_USEFILEATTRIBUTES;
252 }
253 SHFILEINFOW info;
254 if (!SHGetFileInfoW(Path, 0, &info, sizeof(info), flags |
255 SHGFI_DISPLAYNAME | SHGFI_ATTRIBUTES | SHGFI_TYPENAME))
256 {
257 info.szDisplayName[0] = info.szTypeName[0] = UNICODE_NULL;
258 info.dwAttributes = 0;
259 ret = ERROR_FILE_NOT_FOUND;
260 }
261 wprintf(L"Display: %s\n", info.szDisplayName);
262 wprintf(L"Attributes: 0x%x\n", info.dwAttributes);
263 wprintf(L"Type: %s\n", info.szTypeName);
264
265 if (!SHGetFileInfoW(Path, 0, &info, sizeof(info), flags | SHGFI_ICONLOCATION))
266 {
267 info.szDisplayName[0] = UNICODE_NULL;
268 info.iIcon = -1;
269 }
270 wprintf(L"Icon: %s,%d\n", info.szDisplayName, info.iIcon);
271
272 if (!SHGetFileInfoW(Path, 0, &info, sizeof(info), flags | SHGFI_SYSICONINDEX))
273 {
274 info.iIcon = -1;
275 }
276 wprintf(L"Index: %d\n", info.iIcon);
277 SHFree(pidl);
278 return ret;
279 }
280
AssocQ(int argc,WCHAR ** argv)281 static HRESULT AssocQ(int argc, WCHAR **argv)
282 {
283 UINT qtype = StrToNum(argv[2]);
284 ASSOCF iflags = StrToNum(argv[3]);
285 ASSOCF qflags = StrToNum(argv[4]);
286 PCWSTR extra = (argc > 6 && *argv[6]) ? argv[6] : NULL;
287 WCHAR buf[MAX_PATH * 2];
288 DWORD maxSize = (argc > 7 && *argv[7]) ? StrToNum(argv[7]) : sizeof(buf);
289
290 HRESULT hr;
291 CComPtr<IQueryAssociations> qa;
292 PWSTR path = argv[0];
293 if (*path)
294 {
295 CLSID clsid, *pclsid = NULL;
296 if (CLSIDPrefix(path, clsid))
297 pclsid = &clsid;
298 CComPtr<IShellItem> si;
299 if (SUCCEEDED(hr = CreateShellItemFromParse(path, &si)))
300 {
301 hr = si->BindToHandler(NULL, pclsid ? *pclsid : BHID_AssociationArray, IID_PPV_ARG(IQueryAssociations, &qa));
302 if (FAILED(hr) && !pclsid)
303 hr = si->BindToHandler(NULL, BHID_SFUIObject, IID_PPV_ARG(IQueryAssociations, &qa));
304 }
305 }
306 else
307 {
308 hr = AssocCreate(CLSID_QueryAssociations, IID_PPV_ARG(IQueryAssociations, &qa));
309 }
310 if (FAILED(hr))
311 return ErrMsg(hr);
312 hr = qa->Init(iflags, argv[5], NULL, NULL);
313 if (FAILED(hr))
314 return ErrMsg(hr);
315
316 DWORD size = maxSize;
317 if (!_wcsicmp(argv[1], L"string"))
318 {
319 if (!_wcsicmp(argv[2], L"COMMAND"))
320 qtype = ASSOCSTR_COMMAND;
321 if (!_wcsicmp(argv[2], L"EXECUTABLE"))
322 qtype = ASSOCSTR_EXECUTABLE;
323 if (!_wcsicmp(argv[2], L"FRIENDLYDOCNAME") || !_wcsicmp(argv[2], L"FriendlyTypeName"))
324 qtype = ASSOCSTR_FRIENDLYDOCNAME;
325 if (!_wcsicmp(argv[2], L"DEFAULTICON"))
326 qtype = ASSOCSTR_DEFAULTICON;
327
328 buf[0] = UNICODE_NULL;
329 size /= sizeof(buf[0]); // Convert to number of characters
330 hr = qa->GetString(qflags, (ASSOCSTR)qtype, extra, buf, &size);
331 size *= sizeof(buf[0]); // Convert back to bytes
332 if (SUCCEEDED(hr) ||
333 hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER))
334 {
335 wprintf(L"0x%.8X: %s\n", hr, buf);
336 }
337 else
338 {
339 wprintf(size != maxSize ? L"%u " : L"", size);
340 ErrMsg(hr);
341 }
342 }
343 else if (!_wcsicmp(argv[1], L"data"))
344 {
345 if (!_wcsicmp(argv[2], L"EDITFLAGS"))
346 qtype = ASSOCDATA_EDITFLAGS;
347 if (!_wcsicmp(argv[2], L"VALUE"))
348 qtype = ASSOCDATA_VALUE;
349
350 hr = qa->GetData(qflags, (ASSOCDATA)qtype, extra, buf, &size);
351 if (SUCCEEDED(hr))
352 {
353 wprintf(L"0x%.8X: %u byte(s) ", hr, size);
354 DumpBytes(buf, min(size, maxSize));
355 }
356 else
357 {
358 wprintf(size != maxSize ? L"%u " : L"", size);
359 ErrMsg(hr);
360 }
361 }
362 else if (!_wcsicmp(argv[1], L"key"))
363 {
364 HKEY hKey = NULL;
365 hr = qa->GetKey(qflags, (ASSOCKEY)qtype, extra, &hKey);
366 if (SUCCEEDED(hr))
367 {
368 wprintf(L"0x%.8X: hKey %p\n", hr, hKey);
369 RegQueryValueExW(hKey, L"shlextdbg", 0, NULL, NULL, NULL); // Filter by this in Process Monitor
370 RegCloseKey(hKey);
371 }
372 else
373 {
374 ErrMsg(hr);
375 }
376 }
377 else
378 {
379 PrintHelp(L"Unknown query");
380 return ErrMsg(ERROR_INVALID_PARAMETER);
381 }
382 return hr;
383 }
384
385 enum WaitType
386 {
387 Wait_None,
388 Wait_Infinite,
389 Wait_OpenWindows,
390 Wait_Input,
391 Wait_ExplorerInstance,
392 };
393
394 CLSID g_CLSID = { 0 };
395 CStringW g_DLL;
396 CStringW g_ShellExtInit;
397 bool g_bIShellPropSheetExt = false;
398 CStringA g_ContextMenu;
399 WaitType g_Wait = Wait_ExplorerInstance;
400
CreateIDataObject(CComHeapPtr<ITEMIDLIST> & pidl,CComPtr<IDataObject> & dataObject,PCWSTR FileName)401 HRESULT CreateIDataObject(CComHeapPtr<ITEMIDLIST>& pidl, CComPtr<IDataObject>& dataObject, PCWSTR FileName)
402 {
403 HRESULT hr = SHParseDisplayName(FileName, NULL, &pidl, 0, NULL);
404 if (!SUCCEEDED(hr))
405 {
406 wprintf(L"Failed to create pidl from '%s': 0x%x\n", FileName, hr);
407 return hr;
408 }
409
410 CComPtr<IShellFolder> shellFolder;
411 PCUITEMID_CHILD childs;
412 hr = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &shellFolder), &childs);
413 if (!SUCCEEDED(hr))
414 {
415 wprintf(L"Failed to bind to parent: 0x%x\n", hr);
416 return hr;
417 }
418 hr = shellFolder->GetUIObjectOf(NULL, 1, &childs, IID_IDataObject, NULL, (PVOID*)&dataObject);
419 if (!SUCCEEDED(hr))
420 {
421 wprintf(L"Failed to query IDataObject: 0x%x\n", hr);
422 }
423 return hr;
424 }
425
LoadAndInitialize(REFIID riid,LPVOID * ppv)426 HRESULT LoadAndInitialize(REFIID riid, LPVOID* ppv)
427 {
428 CComPtr<IShellExtInit> spShellExtInit;
429 HRESULT hr;
430 if (g_DLL.IsEmpty())
431 {
432 hr = CoCreateInstance(g_CLSID, NULL, CLSCTX_ALL, IID_PPV_ARG(IShellExtInit, &spShellExtInit));
433 if (!SUCCEEDED(hr))
434 {
435 WCHAR Buffer[100];
436 StringFromGUID2(g_CLSID, Buffer, _countof(Buffer));
437 wprintf(L"Failed to Create %s:IShellExtInit: 0x%x\n", Buffer, hr);
438 return hr;
439 }
440 }
441 else
442 {
443 typedef HRESULT (STDAPICALLTYPE *tDllGetClassObject)(REFCLSID rclsid, REFIID riid, LPVOID *ppv);
444 HMODULE mod = LoadLibraryW(g_DLL);
445 if (!mod)
446 {
447 wprintf(L"Failed to Load %s:(0x%x)\n", g_DLL.GetString(), GetLastError());
448 return E_FAIL;
449 }
450 tDllGetClassObject DllGet = (tDllGetClassObject)GetProcAddress(mod, "DllGetClassObject");
451 if (!DllGet)
452 {
453 wprintf(L"%s does not export DllGetClassObject\n", g_DLL.GetString());
454 return E_FAIL;
455 }
456 CComPtr<IClassFactory> spClassFactory;
457 hr = DllGet(g_CLSID, IID_PPV_ARG(IClassFactory, &spClassFactory));
458 if (!SUCCEEDED(hr))
459 {
460 wprintf(L"Failed to create IClassFactory: 0x%x\n", hr);
461 return hr;
462 }
463 hr = spClassFactory->CreateInstance(NULL, IID_PPV_ARG(IShellExtInit, &spShellExtInit));
464 if (!SUCCEEDED(hr))
465 {
466 wprintf(L"Failed to Request IShellExtInit from IClassFactory: 0x%x\n", hr);
467 return hr;
468 }
469 }
470
471 CComPtr<IDataObject> spDataObject;
472 CComHeapPtr<ITEMIDLIST> pidl;
473 hr = CreateIDataObject(pidl, spDataObject, g_ShellExtInit.GetString());
474 if (!SUCCEEDED(hr))
475 return hr;
476
477 HKEY hKey = NULL;
478 GetAssocClass(g_ShellExtInit.GetString(), pidl, hKey);
479 hr = spShellExtInit->Initialize(pidl, spDataObject, hKey);
480 if (hKey)
481 RegCloseKey(hKey);
482 if (!SUCCEEDED(hr))
483 {
484 wprintf(L"IShellExtInit->Initialize failed: 0x%x\n", hr);
485 return hr;
486 }
487 hr = spShellExtInit->QueryInterface(riid, ppv);
488 if (!SUCCEEDED(hr))
489 {
490 WCHAR Buffer[100];
491 StringFromGUID2(riid, Buffer, _countof(Buffer));
492 wprintf(L"Failed to query %s from IShellExtInit: 0x%x\n", Buffer, hr);
493 }
494 return hr;
495 }
496
497
498 CSimpleArray<HWND> g_Windows;
499 HWND g_ConsoleWindow;
EnumWindowsProc(HWND hwnd,LPARAM lParam)500 BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
501 {
502 if (hwnd != g_ConsoleWindow)
503 {
504 DWORD pid = 0;
505 GetWindowThreadProcessId(hwnd, &pid);
506 if (pid == GetCurrentProcessId())
507 {
508 g_Windows.Add(hwnd);
509 }
510 }
511 return TRUE;
512 }
513
WaitWindows()514 void WaitWindows()
515 {
516 /* Give the windows some time to spawn */
517 Sleep(2000);
518 g_ConsoleWindow = GetConsoleWindow();
519 while (true)
520 {
521 g_Windows.RemoveAll();
522 EnumWindows(EnumWindowsProc, NULL);
523 if (g_Windows.GetSize() == 0)
524 break;
525 Sleep(500);
526 }
527 wprintf(L"All windows closed (ignoring console window)\n");
528 }
529
530 struct ExplorerInstance : public IUnknown
531 {
532 HWND m_hWnd;
533 volatile LONG m_rc;
534
ExplorerInstanceExplorerInstance535 ExplorerInstance() : m_hWnd(NULL), m_rc(1) {}
QueryInterfaceExplorerInstance536 virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppv)
537 {
538 static const QITAB rgqit[] = { { 0 } };
539 return QISearch(this, rgqit, riid, ppv);
540 }
AddRefExplorerInstance541 virtual ULONG STDMETHODCALLTYPE AddRef()
542 {
543 if (g_Wait == Wait_ExplorerInstance)
544 wprintf(L"INFO: SHGetInstanceExplorer\n");
545 return InterlockedIncrement(&m_rc);
546 }
ReleaseExplorerInstance547 virtual ULONG STDMETHODCALLTYPE Release()
548 {
549 if (g_Wait == Wait_ExplorerInstance)
550 wprintf(L"INFO: Release ExplorerInstance\n");
551 ULONG r = InterlockedDecrement(&m_rc);
552 if (!r)
553 PostMessage(m_hWnd, WM_CLOSE, 0, 0);
554 return r;
555 }
WaitExplorerInstance556 void Wait()
557 {
558 SHSetInstanceExplorer(NULL);
559 m_hWnd = CreateWindowEx(WS_EX_TOOLWINDOW, TEXT("STATIC"), NULL, WS_POPUP,
560 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
561 BOOL loop = InterlockedDecrement(&m_rc) != 0;
562 MSG msg;
563 while (loop && (int)GetMessage(&msg, NULL, 0, 0) > 0)
564 {
565 if (msg.hwnd == m_hWnd && msg.message == WM_CLOSE)
566 PostMessage(m_hWnd, WM_QUIT, 0, 0);
567 DispatchMessage(&msg);
568 }
569 }
570 } g_EI;
571
Wait()572 static void Wait()
573 {
574 LPCWSTR nag = L"(Please use SHGetInstanceExplorer in your code instead)";
575 switch (g_Wait)
576 {
577 case Wait_None:
578 break;
579 case Wait_Infinite:
580 _putws(nag);
581 while (true)
582 Sleep(1000);
583 break;
584 case Wait_OpenWindows:
585 _putws(nag);
586 WaitWindows();
587 break;
588 case Wait_Input:
589 wprintf(L"Press any key to continue... %s\n", nag);
590 _getch();
591 break;
592 case Wait_ExplorerInstance:
593 g_EI.Wait();
594 break;
595 }
596 }
597
598 CSimpleArray<HPROPSHEETPAGE> g_Pages;
cb_AddPage(HPROPSHEETPAGE page,LPARAM lParam)599 static BOOL CALLBACK cb_AddPage(HPROPSHEETPAGE page, LPARAM lParam)
600 {
601 g_Pages.Add(page);
602 if (lParam != (LPARAM)&g_Pages)
603 {
604 wprintf(L"Propsheet failed to pass lParam, got: 0x%Ix\n", lParam);
605 }
606 return TRUE;
607 }
608
isCmdWithArg(int argc,WCHAR ** argv,int & n,PCWSTR check,PCWSTR & arg)609 static bool isCmdWithArg(int argc, WCHAR** argv, int& n, PCWSTR check, PCWSTR &arg)
610 {
611 arg = NULL;
612 size_t len = wcslen(check);
613 if (!_wcsnicmp(argv[n] + 1, check, len))
614 {
615 PCWSTR cmd = argv[n] + len + 1;
616 if (*cmd == ':' || *cmd == '=')
617 {
618 arg = cmd + 1;
619 return true;
620 }
621 if (n + 1 < argc)
622 {
623 arg = argv[n+1];
624 n++;
625 return true;
626 }
627 wprintf(L"Command %s has no required argument!\n", check);
628 return false;
629 }
630 return false;
631 }
632
isCmd(int argc,WCHAR ** argv,int n,PCWSTR check)633 static bool isCmd(int argc, WCHAR** argv, int n, PCWSTR check)
634 {
635 return !_wcsicmp(argv[n] + 1, check);
636 }
637
638 extern "C" // and another hack for gcc
wmain(int argc,WCHAR ** argv)639 int wmain(int argc, WCHAR **argv)
640 {
641 INITCOMMONCONTROLSEX icc = { sizeof(icc), ICC_LINK_CLASS | ICC_STANDARD_CLASSES };
642 InitCommonControlsEx(&icc);
643 CoInitialize(NULL);
644 SHSetInstanceExplorer(static_cast<IUnknown*>(&g_EI));
645
646 bool failArgs = false;
647 for (int n = 1; n < argc; ++n)
648 {
649 WCHAR* cmd = argv[n];
650 if (cmd[0] == '-' || cmd[0] == '/')
651 {
652 PCWSTR arg;
653 if (isCmdWithArg(argc, argv, n, L"shgfi", arg))
654 {
655 failArgs = true;
656 if (*arg)
657 return SHGFI(arg);
658 }
659 else if (isCmd(argc, argv, n, L"assocq"))
660 {
661 failArgs = true;
662 if (argc - (n + 1) >= 6 && argc - (n + 1) <= 8)
663 return AssocQ(argc - (n + 1), &argv[(n + 1)]);
664 }
665 else if (isCmdWithArg(argc, argv, n, L"shellexec", arg))
666 {
667 PIDLIST_ABSOLUTE pidl = NULL;
668 HRESULT hr = SHParseDisplayName(arg, NULL, &pidl, 0, NULL);
669 if (FAILED(hr))
670 return ErrMsg(hr);
671 SHELLEXECUTEINFOW sei = { sizeof(sei), SEE_MASK_IDLIST | SEE_MASK_UNICODE };
672 sei.lpIDList = pidl;
673 sei.nShow = SW_SHOW;
674 while (++n < argc)
675 {
676 if (argv[n][0] != '-' && argv[n][0] != '/')
677 break;
678 else if (isCmd(argc, argv, n, L"INVOKE"))
679 sei.fMask |= SEE_MASK_INVOKEIDLIST;
680 else if (isCmd(argc, argv, n, L"NOUI"))
681 sei.fMask |= SEE_MASK_FLAG_NO_UI;
682 else if (isCmd(argc, argv, n, L"ASYNCOK"))
683 sei.fMask |= SEE_MASK_ASYNCOK ;
684 else if (isCmd(argc, argv, n, L"NOASYNC"))
685 sei.fMask |= SEE_MASK_NOASYNC;
686 else
687 wprintf(L"WARN: Ignoring switch %s\n", argv[n]);
688 }
689 if (n < argc && *argv[n++])
690 {
691 sei.lpVerb = argv[n - 1];
692 }
693 if (n < argc && *argv[n++])
694 {
695 sei.lpClass = argv[n - 1];
696 sei.fMask |= SEE_MASK_CLASSNAME;
697 }
698 UINT succ = ShellExecuteExW(&sei), gle = GetLastError();
699 SHFree(pidl);
700 if (!succ)
701 return ErrMsg(gle);
702 Wait();
703 return 0;
704 }
705 else if (isCmdWithArg(argc, argv, n, L"dumpmenu", arg))
706 {
707 HRESULT hr;
708 CComPtr<IContextMenu> cm;
709 if (CLSIDPrefix(arg, g_CLSID))
710 {
711 g_ShellExtInit = arg;
712 hr = LoadAndInitialize(IID_PPV_ARG(IContextMenu, &cm));
713 }
714 else
715 {
716 CComPtr<IShellItem> si;
717 hr = CreateShellItemFromParse(arg, &si);
718 if (SUCCEEDED(hr))
719 hr = si->BindToHandler(NULL, BHID_SFUIObject, IID_PPV_ARG(IContextMenu, &cm));
720 }
721 if (SUCCEEDED(hr))
722 {
723 UINT first = 10, last = 9000;
724 UINT cmf = 0, nosub = 0, fakeinit = 0;
725 while (++n < argc)
726 {
727 if (argv[n][0] != '-' && argv[n][0] != '/')
728 break;
729 else if (isCmd(argc, argv, n, L"DEFAULTONLY"))
730 cmf |= CMF_DEFAULTONLY;
731 else if (isCmd(argc, argv, n, L"NODEFAULT"))
732 cmf |= CMF_NODEFAULT;
733 else if (isCmd(argc, argv, n, L"DONOTPICKDEFAULT"))
734 cmf |= CMF_DONOTPICKDEFAULT;
735 else if (isCmd(argc, argv, n, L"EXTENDED") || isCmd(argc, argv, n, L"EXTENDEDVERBS"))
736 cmf |= CMF_EXTENDEDVERBS;
737 else if (isCmd(argc, argv, n, L"SYNCCASCADEMENU"))
738 cmf |= CMF_SYNCCASCADEMENU;
739 else if (isCmd(argc, argv, n, L"EXPLORE"))
740 cmf |= CMF_EXPLORE;
741 else if (isCmd(argc, argv, n, L"VERBSONLY"))
742 cmf |= CMF_VERBSONLY;
743 else if (isCmd(argc, argv, n, L"NOVERBS"))
744 cmf |= CMF_NOVERBS;
745 else if (isCmd(argc, argv, n, L"DISABLEDVERBS"))
746 cmf |= CMF_DISABLEDVERBS;
747 else if (isCmd(argc, argv, n, L"OPTIMIZEFORINVOKE"))
748 cmf |= CMF_OPTIMIZEFORINVOKE;
749 else if (isCmd(argc, argv, n, L"CANRENAME"))
750 cmf |= CMF_CANRENAME;
751 else if (isCmd(argc, argv, n, L"NOSUBMENU"))
752 nosub++;
753 else if (isCmd(argc, argv, n, L"INITMENUPOPUP"))
754 fakeinit++; // Tickle async submenus
755 else
756 wprintf(L"WARN: Ignoring switch %s\n", argv[n]);
757 }
758 HMENU hMenu = CreatePopupMenu();
759 hr = cm->QueryContextMenu(hMenu, 0, first, last, cmf);
760 if (SUCCEEDED(hr))
761 {
762 DumpMenu(hMenu, first, cm, fakeinit, nosub ? -1 : 0);
763 }
764 DestroyMenu(hMenu);
765 }
766 if (FAILED(hr))
767 return ErrMsg(hr);
768 return 0;
769 }
770 else if (isCmdWithArg(argc, argv, n, L"clsid", arg))
771 {
772 HRESULT hr = CLSIDFromString(arg, &g_CLSID);
773 if (!SUCCEEDED(hr))
774 {
775 wprintf(L"Failed to convert %s to CLSID\n", arg);
776 failArgs = true;
777 }
778 }
779 else if (isCmdWithArg(argc, argv, n, L"dll", arg))
780 {
781 g_DLL = arg;
782 }
783 else if (isCmdWithArg(argc, argv, n, L"IShellExtInit", arg))
784 {
785 g_ShellExtInit = arg;
786 }
787 else if (isCmd(argc, argv, n, L"IShellPropSheetExt"))
788 {
789 g_bIShellPropSheetExt = true;
790 }
791 else if (isCmdWithArg(argc, argv, n, L"IContextMenu", arg))
792 {
793 g_ContextMenu = arg;
794 }
795 else if (isCmd(argc, argv, n, L"infinite"))
796 {
797 g_Wait = Wait_Infinite;
798 }
799 else if (isCmd(argc, argv, n, L"openwindows"))
800 {
801 g_Wait = Wait_OpenWindows;
802 }
803 else if (isCmd(argc, argv, n, L"input"))
804 {
805 g_Wait = Wait_Input;
806 }
807 else if (isCmd(argc, argv, n, L"explorerinstance"))
808 {
809 g_Wait = Wait_ExplorerInstance;
810 }
811 else if (isCmd(argc, argv, n, L"nowait"))
812 {
813 g_Wait = Wait_None;
814 }
815 else
816 {
817 wprintf(L"Unknown argument: %s\n", cmd);
818 failArgs = true;
819 }
820 }
821 }
822
823 if (failArgs)
824 {
825 PrintHelp(NULL);
826 return E_INVALIDARG;
827 }
828
829 CLSID EmptyCLSID = { 0 };
830 if (EmptyCLSID == g_CLSID)
831 {
832 PrintHelp(L"No CLSID specified");
833 return E_INVALIDARG;
834 }
835
836 if (g_ShellExtInit.IsEmpty())
837 {
838 PrintHelp(L"No filename specified");
839 return E_INVALIDARG;
840 }
841
842 HRESULT hr;
843 if (g_bIShellPropSheetExt)
844 {
845 CComPtr<IShellPropSheetExt> spSheetExt;
846 hr = LoadAndInitialize(IID_PPV_ARG(IShellPropSheetExt, &spSheetExt));
847 if (!SUCCEEDED(hr))
848 return hr;
849
850 hr = spSheetExt->AddPages(cb_AddPage, (LPARAM)&g_Pages);
851 if (!SUCCEEDED(hr))
852 {
853 wprintf(L"IShellPropSheetExt->AddPages failed: 0x%x\n", hr);
854 return hr;
855 }
856
857 USHORT ActivePage = HRESULT_CODE(hr);
858 PROPSHEETHEADERW psh = { 0 };
859
860 psh.dwSize = sizeof(psh);
861 psh.dwFlags = PSH_PROPTITLE;
862 psh.pszCaption = L"shlextdbg";
863 psh.phpage = g_Pages.GetData();
864 psh.nPages = g_Pages.GetSize();
865 psh.nStartPage = ActivePage ? (ActivePage-1) : 0;
866 hr = PropertySheetW(&psh);
867
868 wprintf(L"PropertySheetW returned: 0x%x\n", hr);
869 }
870 if (!g_ContextMenu.IsEmpty())
871 {
872 CComPtr<IContextMenu> spContextMenu;
873 hr = LoadAndInitialize(IID_PPV_ARG(IContextMenu, &spContextMenu));
874 if (!SUCCEEDED(hr))
875 return hr;
876
877 // FIXME: Must call QueryContextMenu before InvokeCommand?
878
879 CMINVOKECOMMANDINFO cm = { sizeof(cm), 0 };
880 cm.lpVerb = g_ContextMenu.GetString();
881 cm.nShow = SW_SHOW;
882 hr = spContextMenu->InvokeCommand(&cm);
883
884 if (!SUCCEEDED(hr))
885 {
886 wprintf(L"IContextMenu->InvokeCommand failed: 0x%x\n", hr);
887 return hr;
888 }
889 wprintf(L"IContextMenu->InvokeCommand returned: 0x%x\n", hr);
890 }
891
892 Wait();
893 return 0;
894 }
895