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