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 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 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 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> 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 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 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 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 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 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 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 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 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 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 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; 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 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 535 ExplorerInstance() : m_hWnd(NULL), m_rc(1) {} 536 virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppv) 537 { 538 static const QITAB rgqit[] = { { 0 } }; 539 return QISearch(this, rgqit, riid, ppv); 540 } 541 virtual ULONG STDMETHODCALLTYPE AddRef() 542 { 543 if (g_Wait == Wait_ExplorerInstance) 544 wprintf(L"INFO: SHGetInstanceExplorer\n"); 545 return InterlockedIncrement(&m_rc); 546 } 547 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 } 556 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 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; 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 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 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 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