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 <atlbase.h> // thanks gcc 11 #include <atlcom.h> // thanks gcc 12 #include <atlstr.h> 13 #include <atlsimpcoll.h> 14 #include <conio.h> 15 // hack for gcc: 16 #define DbgPrint(...) printf(__VA_ARGS__) 17 #include <shellutils.h> 18 19 enum WaitType 20 { 21 Wait_None, 22 Wait_Infinite, 23 Wait_OpenWindows, 24 Wait_Input, 25 }; 26 27 CLSID g_CLSID = { 0 }; 28 CStringW g_DLL; 29 CStringW g_ShellExtInit; 30 bool g_bIShellPropSheetExt = false; 31 CStringA g_ContextMenu; 32 WaitType g_Wait = Wait_None; 33 34 HRESULT CreateIDataObject(CComHeapPtr<ITEMIDLIST>& pidl, CComPtr<IDataObject>& dataObject, PCWSTR FileName) 35 { 36 HRESULT hr = SHParseDisplayName(FileName, NULL, &pidl, 0, NULL); 37 if (!SUCCEEDED(hr)) 38 { 39 wprintf(L"Failed to create pidl from '%s': 0x%x\n", FileName, hr); 40 return hr; 41 } 42 43 CComPtr<IShellFolder> shellFolder; 44 PCUITEMID_CHILD childs; 45 hr = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &shellFolder), &childs); 46 if (!SUCCEEDED(hr)) 47 { 48 wprintf(L"Failed to bind to parent: 0x%x\n", hr); 49 return hr; 50 } 51 hr = shellFolder->GetUIObjectOf(NULL, 1, &childs, IID_IDataObject, NULL, (PVOID*)&dataObject); 52 if (!SUCCEEDED(hr)) 53 { 54 wprintf(L"Failed to query IDataObject: 0x%x\n", hr); 55 } 56 return hr; 57 } 58 59 HRESULT LoadAndInitialize(REFIID riid, LPVOID* ppv) 60 { 61 CComPtr<IShellExtInit> spShellExtInit; 62 HRESULT hr; 63 if (g_DLL.IsEmpty()) 64 { 65 hr = CoCreateInstance(g_CLSID, NULL, CLSCTX_ALL, IID_PPV_ARG(IShellExtInit, &spShellExtInit)); 66 if (!SUCCEEDED(hr)) 67 { 68 WCHAR Buffer[100]; 69 StringFromGUID2(g_CLSID, Buffer, _countof(Buffer)); 70 wprintf(L"Failed to Create %s:IShellExtInit: 0x%x\n", Buffer, hr); 71 return hr; 72 } 73 } 74 else 75 { 76 typedef HRESULT (STDAPICALLTYPE *tDllGetClassObject)(REFCLSID rclsid, REFIID riid, LPVOID *ppv); 77 HMODULE mod = LoadLibraryW(g_DLL); 78 if (!mod) 79 { 80 wprintf(L"Failed to Load %s:(0x%x)\n", g_DLL.GetString(), GetLastError()); 81 return E_FAIL; 82 } 83 tDllGetClassObject DllGet = (tDllGetClassObject)GetProcAddress(mod, "DllGetClassObject"); 84 if (!DllGet) 85 { 86 wprintf(L"%s does not export DllGetClassObject\n", g_DLL.GetString()); 87 return E_FAIL; 88 } 89 CComPtr<IClassFactory> spClassFactory; 90 hr = DllGet(g_CLSID, IID_PPV_ARG(IClassFactory, &spClassFactory)); 91 if (!SUCCEEDED(hr)) 92 { 93 wprintf(L"Failed to create IClassFactory: 0x%x\n", hr); 94 return hr; 95 } 96 hr = spClassFactory->CreateInstance(NULL, IID_PPV_ARG(IShellExtInit, &spShellExtInit)); 97 if (!SUCCEEDED(hr)) 98 { 99 wprintf(L"Failed to Request IShellExtInit from IClassFactory: 0x%x\n", hr); 100 return hr; 101 } 102 } 103 104 CComPtr<IDataObject> spDataObject; 105 CComHeapPtr<ITEMIDLIST> pidl; 106 hr = CreateIDataObject(pidl, spDataObject, g_ShellExtInit.GetString()); 107 if (!SUCCEEDED(hr)) 108 return hr; 109 110 hr = spShellExtInit->Initialize(pidl, spDataObject, NULL); 111 if (!SUCCEEDED(hr)) 112 { 113 wprintf(L"IShellExtInit->Initialize failed: 0x%x\n", hr); 114 return hr; 115 } 116 hr = spShellExtInit->QueryInterface(riid, ppv); 117 if (!SUCCEEDED(hr)) 118 { 119 WCHAR Buffer[100]; 120 StringFromGUID2(riid, Buffer, _countof(Buffer)); 121 wprintf(L"Failed to query %s from IShellExtInit: 0x%x\n", Buffer, hr); 122 } 123 return hr; 124 } 125 126 127 CSimpleArray<HWND> g_Windows; 128 HWND g_ConsoleWindow; 129 BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) 130 { 131 if (hwnd != g_ConsoleWindow) 132 { 133 DWORD pid = 0; 134 GetWindowThreadProcessId(hwnd, &pid); 135 if (pid == GetCurrentProcessId()) 136 { 137 g_Windows.Add(hwnd); 138 } 139 } 140 return TRUE; 141 } 142 143 void WaitWindows() 144 { 145 /* Give the windows some time to spawn */ 146 Sleep(2000); 147 g_ConsoleWindow = GetConsoleWindow(); 148 while (true) 149 { 150 g_Windows.RemoveAll(); 151 EnumWindows(EnumWindowsProc, NULL); 152 if (g_Windows.GetSize() == 0) 153 break; 154 Sleep(500); 155 } 156 wprintf(L"All windows closed (ignoring console window)\n"); 157 } 158 159 160 161 CSimpleArray<HPROPSHEETPAGE> g_Pages; 162 static BOOL CALLBACK cb_AddPage(HPROPSHEETPAGE page, LPARAM lParam) 163 { 164 g_Pages.Add(page); 165 if (lParam != (LPARAM)&g_Pages) 166 { 167 wprintf(L"Propsheet failed to pass lParam, got: 0x%x\n", lParam); 168 } 169 return TRUE; 170 } 171 172 static bool isCmdWithArg(int argc, WCHAR** argv, int& n, PCWSTR check, PCWSTR &arg) 173 { 174 arg = NULL; 175 size_t len = wcslen(check); 176 if (!_wcsnicmp(argv[n] + 1, check, len)) 177 { 178 PCWSTR cmd = argv[n] + len + 1; 179 if (*cmd == ':' || *cmd == '=') 180 { 181 arg = cmd + 1; 182 return true; 183 } 184 if (n + 1 < argc) 185 { 186 arg = argv[n+1]; 187 n++; 188 return true; 189 } 190 wprintf(L"Command %s has no required argument!\n", check); 191 return false; 192 } 193 return false; 194 } 195 196 static bool isCmd(int argc, WCHAR** argv, int n, PCWSTR check) 197 { 198 return !wcsicmp(argv[n] + 1, check); 199 } 200 201 static void PrintHelp(PCWSTR ExtraLine) 202 { 203 if (ExtraLine) 204 wprintf(L"%s\n", ExtraLine); 205 206 wprintf(L"shlextdbg /clsid={clsid} [/dll=dllname] /IShellExtInit=filename |shlextype| |waitoptions|\n"); 207 wprintf(L" {clsid}: The CLSID or ProgID of the object to create\n"); 208 wprintf(L" dll: Optional dllname to create the object from, instead of CoCreateInstance\n"); 209 wprintf(L" filename: The filename to pass to IShellExtInit->Initialze\n"); 210 wprintf(L" shlextype: The type of shell extention to run:\n"); 211 wprintf(L" /IShellPropSheetExt to create a property sheet\n"); 212 wprintf(L" /IContextMenu=verb to activate the specified verb\n"); 213 wprintf(L" waitoptions: Specify how to wait:\n"); 214 wprintf(L" /infinite: Keep on waiting infinitely\n"); 215 wprintf(L" /openwindows: Wait for all windows from the current application to close\n"); 216 wprintf(L" /input: Wait for input\n"); 217 wprintf(L"\n"); 218 } 219 220 /* 221 Examples: 222 223 /clsid={513D916F-2A8E-4F51-AEAB-0CBC76FB1AF8} /IShellExtInit=C:\RosBE\Uninstall.exe /IShellPropSheetExt 224 /clsid=CompressedFolder /IShellExtInit=e:\test.zip /IContextMenu=extract /openwindows 225 /clsid=CompressedFolder /IShellExtInit=e:\test.zip /IContextMenu=extract /openwindows /dll=R:\build\dev\devenv\dll\shellext\zipfldr\Debug\zipfldr.dll 226 227 */ 228 extern "C" // and another hack for gcc 229 int wmain(int argc, WCHAR **argv) 230 { 231 bool failArgs = false; 232 for (int n = 1; n < argc; ++n) 233 { 234 WCHAR* cmd = argv[n]; 235 if (cmd[0] == '-' || cmd[0] == '/') 236 { 237 PCWSTR arg; 238 if (isCmdWithArg(argc, argv, n, L"clsid", arg)) 239 { 240 HRESULT hr = CLSIDFromString(arg, &g_CLSID); 241 if (!SUCCEEDED(hr)) 242 { 243 wprintf(L"Failed to convert %s to CLSID\n", arg); 244 failArgs = true; 245 } 246 } 247 else if (isCmdWithArg(argc, argv, n, L"dll", arg)) 248 { 249 g_DLL = arg; 250 } 251 else if (isCmdWithArg(argc, argv, n, L"IShellExtInit", arg)) 252 { 253 g_ShellExtInit = arg; 254 } 255 else if (isCmd(argc, argv, n, L"IShellPropSheetExt")) 256 { 257 g_bIShellPropSheetExt = true; 258 } 259 else if (isCmdWithArg(argc, argv, n, L"IContextMenu", arg)) 260 { 261 g_ContextMenu = arg; 262 } 263 else if (isCmd(argc, argv, n, L"infinite")) 264 { 265 g_Wait = Wait_Infinite; 266 } 267 else if (isCmd(argc, argv, n, L"openwindows")) 268 { 269 g_Wait = Wait_OpenWindows; 270 } 271 else if (isCmd(argc, argv, n, L"input")) 272 { 273 g_Wait = Wait_Input; 274 } 275 else 276 { 277 wprintf(L"Unknown argument: %s\n", cmd); 278 failArgs = true; 279 } 280 } 281 } 282 283 if (failArgs) 284 { 285 PrintHelp(NULL); 286 return E_INVALIDARG; 287 } 288 289 290 CLSID EmptyCLSID = { 0 }; 291 if (EmptyCLSID == g_CLSID) 292 { 293 PrintHelp(L"No CLSID specified"); 294 return E_INVALIDARG; 295 } 296 297 if (g_ShellExtInit.IsEmpty()) 298 { 299 PrintHelp(L"No filename specified"); 300 return E_INVALIDARG; 301 } 302 303 INITCOMMONCONTROLSEX icc = { sizeof(icc), ICC_LINK_CLASS | ICC_STANDARD_CLASSES }; 304 InitCommonControlsEx(&icc); 305 CoInitialize(NULL); 306 307 HRESULT hr; 308 if (g_bIShellPropSheetExt) 309 { 310 CComPtr<IShellPropSheetExt> spSheetExt; 311 hr = LoadAndInitialize(IID_PPV_ARG(IShellPropSheetExt, &spSheetExt)); 312 if (!SUCCEEDED(hr)) 313 return hr; 314 315 hr = spSheetExt->AddPages(cb_AddPage, (LPARAM)&g_Pages); 316 if (!SUCCEEDED(hr)) 317 { 318 wprintf(L"IShellPropSheetExt->AddPages failed: 0x%x\n", hr); 319 return hr; 320 } 321 322 USHORT ActivePage = HRESULT_CODE(hr); 323 PROPSHEETHEADERW psh = { 0 }; 324 325 psh.dwSize = sizeof(psh); 326 psh.dwFlags = PSH_PROPTITLE; 327 psh.pszCaption = L"shlextdbg"; 328 psh.phpage = g_Pages.GetData(); 329 psh.nPages = g_Pages.GetSize(); 330 psh.nStartPage = ActivePage ? (ActivePage-1) : 0; 331 hr = PropertySheetW(&psh); 332 333 wprintf(L"PropertySheetW returned: 0x%x\n", hr); 334 } 335 if (!g_ContextMenu.IsEmpty()) 336 { 337 CComPtr<IContextMenu> spContextMenu; 338 hr = LoadAndInitialize(IID_PPV_ARG(IContextMenu, &spContextMenu)); 339 if (!SUCCEEDED(hr)) 340 return hr; 341 342 CMINVOKECOMMANDINFO cm = { sizeof(cm), 0 }; 343 cm.lpVerb = g_ContextMenu.GetString(); 344 cm.nShow = SW_SHOW; 345 hr = spContextMenu->InvokeCommand(&cm); 346 347 if (!SUCCEEDED(hr)) 348 { 349 wprintf(L"IContextMenu->InvokeCommand failed: 0x%x\n", hr); 350 return hr; 351 } 352 wprintf(L"IContextMenu->InvokeCommand returned: 0x%x\n", hr); 353 } 354 355 switch (g_Wait) 356 { 357 case Wait_None: 358 break; 359 case Wait_Infinite: 360 while (true) { 361 Sleep(1000); 362 } 363 break; 364 case Wait_OpenWindows: 365 WaitWindows(); 366 break; 367 case Wait_Input: 368 wprintf(L"Press any key to continue...\n"); 369 _getch(); 370 break; 371 372 } 373 return 0; 374 } 375