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