[REACTOS] Fix MSVC printf format warnings
[reactos.git] / modules / rosapps / applications / devutils / shlextdbg / shlextdbg.cpp
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%Ix\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 }