1 /*
2  * PROJECT:         ReactOS api tests
3  * LICENSE:         GPLv2+ - See COPYING in the top level directory
4  * PURPOSE:         Testing ShellExecuteEx
5  * PROGRAMMER:      Yaroslav Veremenko <yaroslav@veremenko.info>
6  *                  Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
7  */
8 
9 #include "shelltest.h"
10 #include <shlwapi.h>
11 #include <stdio.h>
12 #include "shell32_apitest_sub.h"
13 
14 #define ok_ShellExecuteEx (winetest_set_location(__FILE__, __LINE__), 0) ? (void)0 : TestShellExecuteEx
15 
16 static
17 BOOL
18 CreateAppPathRegKey(const WCHAR* Name)
19 {
20     HKEY RegistryKey;
21     LONG Result;
22     WCHAR Buffer[1024];
23     WCHAR KeyValue[1024];
24     DWORD Length = sizeof(KeyValue);
25     DWORD Disposition;
26 
27     wcscpy(Buffer, L"Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\");
28     wcscat(Buffer, L"IEXPLORE.EXE");
29     Result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, Buffer, 0, KEY_READ, &RegistryKey);
30     if (Result != ERROR_SUCCESS) trace("Could not open iexplore.exe key. Status: %lu\n", Result);
31     if (Result) goto end;
32     Result = RegQueryValueExW(RegistryKey, NULL, NULL, NULL, (LPBYTE)KeyValue, &Length);
33     if (Result != ERROR_SUCCESS) trace("Could not read iexplore.exe key. Status: %lu\n", Result);
34     if (Result) goto end;
35     RegCloseKey(RegistryKey);
36 
37     wcscpy(Buffer, L"Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\");
38     wcscat(Buffer, Name);
39     Result = RegCreateKeyExW(HKEY_LOCAL_MACHINE, Buffer, 0, NULL,
40         0, KEY_WRITE, NULL, &RegistryKey, &Disposition);
41     if (Result != ERROR_SUCCESS) trace("Could not create test key. Status: %lu\n", Result);
42     if (Result) goto end;
43     Result = RegSetValueW(RegistryKey, NULL, REG_SZ, KeyValue, 0);
44     if (Result != ERROR_SUCCESS) trace("Could not set value of the test key. Status: %lu\n", Result);
45     if (Result) goto end;
46     RegCloseKey(RegistryKey);
47 end:
48     if (RegistryKey) RegCloseKey(RegistryKey);
49     return Result == ERROR_SUCCESS;
50 }
51 
52 static
53 VOID
54 DeleteAppPathRegKey(const WCHAR* Name)
55 {
56     LONG Result;
57     WCHAR Buffer[1024];
58     wcscpy(Buffer, L"Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\");
59     wcscat(Buffer, Name);
60     Result = RegDeleteKeyW(HKEY_LOCAL_MACHINE, Buffer);
61     if (Result != ERROR_SUCCESS) trace("Could not remove the test key. Status: %lu\n", Result);
62 }
63 
64 static
65 VOID
66 TestShellExecuteEx(const WCHAR* Name, BOOL ExpectedResult)
67 {
68     SHELLEXECUTEINFOW ShellExecInfo;
69     BOOL Result;
70     ZeroMemory(&ShellExecInfo, sizeof(ShellExecInfo));
71     ShellExecInfo.cbSize = sizeof(ShellExecInfo);
72     ShellExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI;
73     ShellExecInfo.hwnd = NULL;
74     ShellExecInfo.nShow = SW_SHOWNORMAL;
75     ShellExecInfo.lpFile = Name;
76     ShellExecInfo.lpDirectory = NULL;
77     Result = ShellExecuteExW(&ShellExecInfo);
78     ok(Result == ExpectedResult, "ShellExecuteEx lpFile %s failed. Error: %lu\n", wine_dbgstr_w(Name), GetLastError());
79     if (ShellExecInfo.hProcess)
80     {
81         Result = TerminateProcess(ShellExecInfo.hProcess, 0);
82         if (!Result) trace("Terminate process failed. Error: %lu\n", GetLastError());
83         WaitForSingleObject(ShellExecInfo.hProcess, INFINITE);
84         CloseHandle(ShellExecInfo.hProcess);
85     }
86 }
87 
88 static void DoAppPathTest(void)
89 {
90     ok_ShellExecuteEx(L"iexplore", TRUE);
91     ok_ShellExecuteEx(L"iexplore.exe", TRUE);
92 
93     if (CreateAppPathRegKey(L"iexplore.bat"))
94     {
95         ok_ShellExecuteEx(L"iexplore.bat", TRUE);
96         ok_ShellExecuteEx(L"iexplore.bat.exe", FALSE);
97         DeleteAppPathRegKey(L"iexplore.bat");
98     }
99 
100     if (CreateAppPathRegKey(L"iexplore.bat.exe"))
101     {
102         ok_ShellExecuteEx(L"iexplore.bat", FALSE);
103         ok_ShellExecuteEx(L"iexplore.bat.exe", TRUE);
104         DeleteAppPathRegKey(L"iexplore.bat.exe");
105     }
106 }
107 
108 typedef struct TEST_ENTRY
109 {
110     INT lineno;
111     BOOL ret;
112     BOOL bProcessHandle;
113     LPCSTR file;
114     LPCSTR params;
115     LPCSTR curdir;
116 } TEST_ENTRY;
117 
118 static char s_sub_program[MAX_PATH];
119 static char s_win_test_exe[MAX_PATH];
120 static char s_sys_test_exe[MAX_PATH];
121 static char s_win_bat_file[MAX_PATH];
122 static char s_sys_bat_file[MAX_PATH];
123 static char s_win_txt_file[MAX_PATH];
124 static char s_sys_txt_file[MAX_PATH];
125 
126 #define DONT_CARE 0x0BADF00D
127 
128 static const TEST_ENTRY s_entries_1[] =
129 {
130     { __LINE__, TRUE, TRUE, "test program" },
131     { __LINE__, TRUE, TRUE, "test program.bat" },
132     { __LINE__, TRUE, TRUE, "test program.exe" },
133     { __LINE__, FALSE, FALSE, "  test program" },
134     { __LINE__, FALSE, FALSE, "  test program.bat" },
135     { __LINE__, FALSE, FALSE, "  test program.exe" },
136     { __LINE__, FALSE, FALSE, "test program  " },
137     { __LINE__, TRUE, TRUE, "test program.bat  " },
138     { __LINE__, TRUE, TRUE, "test program.exe  " },
139     { __LINE__, TRUE, TRUE, "test program", "TEST" },
140     { __LINE__, TRUE, TRUE, "test program.bat", "TEST" },
141     { __LINE__, TRUE, TRUE, "test program.exe", "TEST" },
142     { __LINE__, FALSE, FALSE, ".\\test program.bat" },
143     { __LINE__, FALSE, FALSE, ".\\test program.exe" },
144     { __LINE__, TRUE, TRUE, "\"test program\"" },
145     { __LINE__, TRUE, TRUE, "\"test program.bat\"" },
146     { __LINE__, TRUE, TRUE, "\"test program.exe\"" },
147     { __LINE__, FALSE, FALSE, "\"test program\" TEST" },
148     { __LINE__, FALSE, FALSE, "\"test program.bat\" TEST" },
149     { __LINE__, FALSE, FALSE, "\"test program.exe\" TEST" },
150     { __LINE__, FALSE, FALSE, "  \"test program\"" },
151     { __LINE__, FALSE, FALSE, "  \"test program.bat\"" },
152     { __LINE__, FALSE, FALSE, "  \"test program.exe\"" },
153     { __LINE__, FALSE, FALSE, "\"test program\"  " },
154     { __LINE__, FALSE, FALSE, "\"test program.bat\"  " },
155     { __LINE__, FALSE, FALSE, "\"test program.exe\"  " },
156     { __LINE__, FALSE, FALSE, "\".\\test program.bat\"" },
157     { __LINE__, FALSE, FALSE, "\".\\test program.exe\"" },
158     { __LINE__, TRUE, TRUE, s_win_test_exe },
159     { __LINE__, TRUE, TRUE, s_sys_test_exe },
160     { __LINE__, TRUE, TRUE, s_win_bat_file },
161     { __LINE__, TRUE, TRUE, s_sys_bat_file },
162     { __LINE__, TRUE, TRUE, s_win_bat_file, "TEST" },
163     { __LINE__, TRUE, TRUE, s_sys_bat_file, "TEST" },
164     { __LINE__, FALSE, FALSE, "invalid program" },
165     { __LINE__, FALSE, FALSE, "invalid program.bat" },
166     { __LINE__, FALSE, FALSE, "invalid program.exe" },
167     { __LINE__, TRUE, TRUE, "test_file.txt" },
168     { __LINE__, TRUE, TRUE, "test_file.txt", "parameters parameters" },
169     { __LINE__, TRUE, TRUE, "test_file.txt", "parameters parameters", "." },
170     { __LINE__, TRUE, TRUE, "shell32_apitest_sub.exe" },
171     { __LINE__, TRUE, TRUE, ".\\shell32_apitest_sub.exe" },
172     { __LINE__, TRUE, TRUE, "\"shell32_apitest_sub.exe\"" },
173     { __LINE__, TRUE, TRUE, "\".\\shell32_apitest_sub.exe\"" },
174     { __LINE__, TRUE, DONT_CARE, "https://google.com" },
175     { __LINE__, TRUE, FALSE, "::{450d8fba-ad25-11d0-98a8-0800361b1103}" },
176     { __LINE__, TRUE, FALSE, "shell:::{450d8fba-ad25-11d0-98a8-0800361b1103}" },
177     { __LINE__, TRUE, FALSE, "shell:sendto" },
178 };
179 
180 static const TEST_ENTRY s_entries_2[] =
181 {
182     { __LINE__, TRUE, TRUE, "test program" },
183     { __LINE__, TRUE, TRUE, "test program", "TEST" },
184     { __LINE__, TRUE, TRUE, "\"test program\"" },
185     { __LINE__, TRUE, TRUE, s_win_test_exe },
186     { __LINE__, TRUE, TRUE, s_sys_test_exe },
187     { __LINE__, FALSE, FALSE, s_win_bat_file },
188     { __LINE__, FALSE, FALSE, s_sys_bat_file },
189     { __LINE__, FALSE, FALSE, s_win_bat_file, "TEST" },
190     { __LINE__, FALSE, FALSE, s_sys_bat_file, "TEST" },
191 };
192 
193 typedef struct OPENWNDS
194 {
195     UINT count;
196     HWND *phwnd;
197 } OPENWNDS;
198 
199 static OPENWNDS s_wi0 = { 0 }, s_wi1 = { 0 };
200 
201 static BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
202 {
203     OPENWNDS *info = (OPENWNDS *)lParam;
204     info->phwnd = (HWND *)realloc(info->phwnd, (info->count + 1) * sizeof(HWND));
205     if (!info->phwnd)
206         return FALSE;
207     info->phwnd[info->count] = hwnd;
208     ++(info->count);
209     return TRUE;
210 }
211 
212 static VOID DoTestEntry(const TEST_ENTRY *pEntry)
213 {
214     SHELLEXECUTEINFOA info = { sizeof(info) };
215     info.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI;
216     info.nShow = SW_SHOWNORMAL;
217     info.lpFile = pEntry->file;
218     info.lpParameters = pEntry->params;
219     info.lpDirectory = pEntry->curdir;
220     BOOL ret = ShellExecuteExA(&info);
221     ok(ret == pEntry->ret, "Line %u: ret expected %d, got %d\n",
222        pEntry->lineno, pEntry->ret, ret);
223     if (!pEntry->ret)
224         return;
225 
226     if ((UINT)pEntry->bProcessHandle != DONT_CARE)
227     {
228         if (pEntry->bProcessHandle)
229         {
230             ok(!!info.hProcess, "Line %u: hProcess expected non-NULL\n", pEntry->lineno);
231         }
232         else
233         {
234             ok(!info.hProcess, "Line %u: hProcess expected NULL\n", pEntry->lineno);
235             return;
236         }
237     }
238 
239     WaitForInputIdle(info.hProcess, INFINITE);
240 
241     // close newly opened windows
242     EnumWindows(EnumWindowsProc, (LPARAM)&s_wi1);
243     for (UINT i1 = 0; i1 < s_wi1.count; ++i1)
244     {
245         BOOL bFound = FALSE;
246         for (UINT i0 = 0; i0 < s_wi0.count; ++i0)
247         {
248             if (s_wi1.phwnd[i1] == s_wi0.phwnd[i0])
249             {
250                 bFound = TRUE;
251                 break;
252             }
253         }
254         if (!bFound)
255             PostMessageW(s_wi1.phwnd[i1], WM_CLOSE, 0, 0);
256     }
257     free(s_wi1.phwnd);
258     ZeroMemory(&s_wi1, sizeof(s_wi1));
259 
260     if (WaitForSingleObject(info.hProcess, 10 * 1000) == WAIT_TIMEOUT)
261     {
262         TerminateProcess(info.hProcess, 11);
263         ok(0, "Process %s did not quit!\n", pEntry->file);
264     }
265     CloseHandle(info.hProcess);
266 }
267 
268 static BOOL
269 GetSubProgramPath(void)
270 {
271     GetModuleFileNameA(NULL, s_sub_program, _countof(s_sub_program));
272     PathRemoveFileSpecA(s_sub_program);
273     PathAppendA(s_sub_program, "shell32_apitest_sub.exe");
274 
275     if (!PathFileExistsA(s_sub_program))
276     {
277         PathRemoveFileSpecA(s_sub_program);
278         PathAppendA(s_sub_program, "testdata\\shell32_apitest_sub.exe");
279 
280         if (!PathFileExistsA(s_sub_program))
281         {
282             return FALSE;
283         }
284     }
285 
286     return TRUE;
287 }
288 
289 static void DoTestEntries(void)
290 {
291     if (!GetSubProgramPath())
292     {
293         skip("shell32_apitest_sub.exe is not found\n");
294         return;
295     }
296 
297     // s_win_test_exe
298     GetWindowsDirectoryA(s_win_test_exe, _countof(s_win_test_exe));
299     PathAppendA(s_win_test_exe, "test program.exe");
300     BOOL ret = CopyFileA(s_sub_program, s_win_test_exe, FALSE);
301     if (!ret)
302     {
303         skip("Please retry with admin rights\n");
304         return;
305     }
306 
307     // record open windows
308     if (!EnumWindows(EnumWindowsProc, (LPARAM)&s_wi0))
309     {
310         skip("EnumWindows failed\n");
311         DeleteFileA(s_win_test_exe);
312         free(s_wi0.phwnd);
313         return;
314     }
315 
316     // s_sys_test_exe
317     GetSystemDirectoryA(s_sys_test_exe, _countof(s_sys_test_exe));
318     PathAppendA(s_sys_test_exe, "test program.exe");
319     ok_int(CopyFileA(s_sub_program, s_sys_test_exe, FALSE), TRUE);
320 
321     // s_win_bat_file
322     GetWindowsDirectoryA(s_win_bat_file, _countof(s_win_bat_file));
323     PathAppendA(s_win_bat_file, "test program.bat");
324     FILE *fp = fopen(s_win_bat_file, "wb");
325     fprintf(fp, "exit /b 3");
326     fclose(fp);
327     ok_int(PathFileExistsA(s_win_bat_file), TRUE);
328 
329     // s_sys_bat_file
330     GetSystemDirectoryA(s_sys_bat_file, _countof(s_sys_bat_file));
331     PathAppendA(s_sys_bat_file, "test program.bat");
332     fp = fopen(s_sys_bat_file, "wb");
333     fprintf(fp, "exit /b 4");
334     fclose(fp);
335     ok_int(PathFileExistsA(s_sys_bat_file), TRUE);
336 
337     // s_win_txt_file
338     GetWindowsDirectoryA(s_win_txt_file, _countof(s_win_txt_file));
339     PathAppendA(s_win_txt_file, "test_file.txt");
340     fp = fopen(s_win_txt_file, "wb");
341     fclose(fp);
342     ok_int(PathFileExistsA(s_win_txt_file), TRUE);
343 
344     // s_sys_txt_file
345     GetSystemDirectoryA(s_sys_txt_file, _countof(s_sys_txt_file));
346     PathAppendA(s_sys_txt_file, "test_file.txt");
347     fp = fopen(s_sys_txt_file, "wb");
348     fclose(fp);
349     ok_int(PathFileExistsA(s_sys_txt_file), TRUE);
350 
351     for (UINT iTest = 0; iTest < _countof(s_entries_1); ++iTest)
352     {
353         DoTestEntry(&s_entries_1[iTest]);
354     }
355 
356     DeleteFileA(s_win_bat_file);
357     DeleteFileA(s_sys_bat_file);
358 
359     for (UINT iTest = 0; iTest < _countof(s_entries_2); ++iTest)
360     {
361         DoTestEntry(&s_entries_2[iTest]);
362     }
363 
364     DeleteFileA(s_win_test_exe);
365     DeleteFileA(s_sys_test_exe);
366     DeleteFileA(s_win_txt_file);
367     DeleteFileA(s_sys_txt_file);
368 
369     free(s_wi0.phwnd);
370 }
371 
372 WCHAR* ExeName = NULL;
373 
374 BOOL CALLBACK EnumProc(_In_ HWND hwnd, _In_ LPARAM lParam)
375 {
376     DWORD pid = 0;
377     GetWindowThreadProcessId(hwnd, &pid);
378     if (pid == GetCurrentProcessId() &&
379         IsWindowVisible(hwnd))
380     {
381         WCHAR Buffer[512] = {0};
382 
383         GetWindowTextW(hwnd, Buffer, _countof(Buffer) - 1);
384         if (Buffer[0] && StrStrIW(Buffer, ExeName))
385         {
386             HWND* pHwnd = (HWND*)lParam;
387             *pHwnd = hwnd;
388             return FALSE;
389         }
390     }
391     return TRUE;
392 }
393 
394 BOOL WaitAndCloseWindow()
395 {
396     HWND hWnd = NULL;
397     for (int n = 0; n < 100; ++n)
398     {
399         Sleep(50);
400 
401         EnumWindows(EnumProc, (LPARAM)&hWnd);
402 
403         if (hWnd)
404         {
405             SendMessageW(hWnd, WM_SYSCOMMAND, SC_CLOSE, 0);
406             return TRUE;
407             break;
408         }
409     }
410     return FALSE;
411 }
412 
413 static void test_properties()
414 {
415     WCHAR Buffer[MAX_PATH * 4];
416 
417     CoInitialize(NULL);
418 
419     GetModuleFileNameW(NULL, Buffer, _countof(Buffer));
420     SHELLEXECUTEINFOW info = { 0 };
421 
422     info.cbSize = sizeof(SHELLEXECUTEINFOW);
423     info.fMask = SEE_MASK_INVOKEIDLIST | SEE_MASK_FLAG_NO_UI;
424     info.lpVerb = L"properties";
425     info.lpFile = Buffer;
426     info.lpParameters = L"";
427     info.nShow = SW_SHOW;
428 
429     BOOL bRet = ShellExecuteExW(&info);
430     ok(bRet, "Failed! (GetLastError(): %d)\n", (int)GetLastError());
431     ok_ptr(info.hInstApp, (HINSTANCE)42);
432 
433     ExeName = PathFindFileNameW(Buffer);
434     WCHAR* Extension = PathFindExtensionW(Buffer);
435     if (Extension)
436     {
437         // The inclusion of this depends on the file display settings!
438         *Extension = UNICODE_NULL;
439     }
440 
441     if (bRet)
442     {
443         ok(WaitAndCloseWindow(), "Could not find properties window!\n");
444     }
445 
446     // Now retry it with the extension cut off
447     bRet = ShellExecuteExW(&info);
448     ok(bRet, "Failed! (GetLastError(): %d)\n", (int)GetLastError());
449     ok_ptr(info.hInstApp, (HINSTANCE)42);
450 
451     if (bRet)
452     {
453         ok(WaitAndCloseWindow(), "Could not find properties window!\n");
454     }
455 
456     info.lpFile = L"complete garbage, cannot run this!";
457 
458     // Now retry it with complete garabage
459     bRet = ShellExecuteExW(&info);
460     ok(bRet == 0, "Succeeded!\n");
461     ok_ptr(info.hInstApp, (HINSTANCE)2);
462 }
463 
464 START_TEST(ShellExecuteEx)
465 {
466     DoAppPathTest();
467     DoTestEntries();
468     test_properties();
469 
470     DoWaitForWindow(CLASSNAME, CLASSNAME, TRUE, TRUE);
471     Sleep(100);
472 }
473