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 
71     ZeroMemory(&ShellExecInfo, sizeof(ShellExecInfo));
72     ShellExecInfo.cbSize = sizeof(ShellExecInfo);
73     ShellExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI;
74     ShellExecInfo.hwnd = NULL;
75     ShellExecInfo.nShow = SW_SHOWNORMAL;
76     ShellExecInfo.lpFile = Name;
77     ShellExecInfo.lpDirectory = NULL;
78 
79     Result = ShellExecuteExW(&ShellExecInfo);
80     ok(Result == ExpectedResult, "ShellExecuteEx lpFile %s failed. Error: %lu\n", wine_dbgstr_w(Name), GetLastError());
81     if (ShellExecInfo.hProcess)
82     {
83         Result = TerminateProcess(ShellExecInfo.hProcess, 0);
84         if (!Result) trace("Terminate process failed. Error: %lu\n", GetLastError());
85         WaitForSingleObject(ShellExecInfo.hProcess, INFINITE);
86         CloseHandle(ShellExecInfo.hProcess);
87     }
88 }
89 
90 static void DoAppPathTest(void)
91 {
92     ok_ShellExecuteEx(L"iexplore", TRUE);
93     ok_ShellExecuteEx(L"iexplore.exe", TRUE);
94 
95     if (CreateAppPathRegKey(L"iexplore.bat"))
96     {
97         ok_ShellExecuteEx(L"iexplore.bat", TRUE);
98         ok_ShellExecuteEx(L"iexplore.bat.exe", FALSE);
99         DeleteAppPathRegKey(L"iexplore.bat");
100     }
101 
102     if (CreateAppPathRegKey(L"iexplore.bat.exe"))
103     {
104         ok_ShellExecuteEx(L"iexplore.bat", FALSE);
105         ok_ShellExecuteEx(L"iexplore.bat.exe", TRUE);
106         DeleteAppPathRegKey(L"iexplore.bat.exe");
107     }
108 }
109 
110 typedef struct TEST_ENTRY
111 {
112     INT lineno;
113     BOOL ret;
114     BOOL bProcessHandle;
115     LPCSTR file;
116     LPCSTR params;
117     LPCSTR curdir;
118 } TEST_ENTRY;
119 
120 static char s_sub_program[MAX_PATH];
121 static char s_win_test_exe[MAX_PATH];
122 static char s_sys_test_exe[MAX_PATH];
123 static char s_win_bat_file[MAX_PATH];
124 static char s_sys_bat_file[MAX_PATH];
125 static char s_win_txt_file[MAX_PATH];
126 static char s_sys_txt_file[MAX_PATH];
127 
128 #define DONT_CARE 0x0BADF00D
129 
130 static const TEST_ENTRY s_entries_1[] =
131 {
132     { __LINE__, TRUE, TRUE, "test program" },
133     { __LINE__, TRUE, TRUE, "test program.bat" },
134     { __LINE__, TRUE, TRUE, "test program.exe" },
135     { __LINE__, FALSE, FALSE, "  test program" },
136     { __LINE__, FALSE, FALSE, "  test program.bat" },
137     { __LINE__, FALSE, FALSE, "  test program.exe" },
138     { __LINE__, FALSE, FALSE, "test program  " },
139     { __LINE__, TRUE, TRUE, "test program.bat  " },
140     { __LINE__, TRUE, TRUE, "test program.exe  " },
141     { __LINE__, TRUE, TRUE, "test program", "TEST" },
142     { __LINE__, TRUE, TRUE, "test program.bat", "TEST" },
143     { __LINE__, TRUE, TRUE, "test program.exe", "TEST" },
144     { __LINE__, FALSE, FALSE, ".\\test program.bat" },
145     { __LINE__, FALSE, FALSE, ".\\test program.exe" },
146     { __LINE__, TRUE, TRUE, "\"test program\"" },
147     { __LINE__, TRUE, TRUE, "\"test program.bat\"" },
148     { __LINE__, TRUE, TRUE, "\"test program.exe\"" },
149     { __LINE__, FALSE, FALSE, "\"test program\" TEST" },
150     { __LINE__, FALSE, FALSE, "\"test program.bat\" TEST" },
151     { __LINE__, FALSE, FALSE, "\"test program.exe\" TEST" },
152     { __LINE__, FALSE, FALSE, "  \"test program\"" },
153     { __LINE__, FALSE, FALSE, "  \"test program.bat\"" },
154     { __LINE__, FALSE, FALSE, "  \"test program.exe\"" },
155     { __LINE__, FALSE, FALSE, "\"test program\"  " },
156     { __LINE__, FALSE, FALSE, "\"test program.bat\"  " },
157     { __LINE__, FALSE, FALSE, "\"test program.exe\"  " },
158     { __LINE__, FALSE, FALSE, "\".\\test program.bat\"" },
159     { __LINE__, FALSE, FALSE, "\".\\test program.exe\"" },
160     { __LINE__, TRUE, TRUE, s_win_test_exe },
161     { __LINE__, TRUE, TRUE, s_sys_test_exe },
162     { __LINE__, TRUE, TRUE, s_win_bat_file },
163     { __LINE__, TRUE, TRUE, s_sys_bat_file },
164     { __LINE__, TRUE, TRUE, s_win_bat_file, "TEST" },
165     { __LINE__, TRUE, TRUE, s_sys_bat_file, "TEST" },
166     { __LINE__, FALSE, FALSE, "invalid program" },
167     { __LINE__, FALSE, FALSE, "invalid program.bat" },
168     { __LINE__, FALSE, FALSE, "invalid program.exe" },
169     { __LINE__, TRUE, TRUE, "test_file.txt" },
170     { __LINE__, TRUE, TRUE, "test_file.txt", "parameters parameters" },
171     { __LINE__, TRUE, TRUE, "test_file.txt", "parameters parameters", "." },
172     { __LINE__, TRUE, TRUE, "shell32_apitest_sub.exe" },
173     { __LINE__, TRUE, TRUE, ".\\shell32_apitest_sub.exe" },
174     { __LINE__, TRUE, TRUE, "\"shell32_apitest_sub.exe\"" },
175     { __LINE__, TRUE, TRUE, "\".\\shell32_apitest_sub.exe\"" },
176     { __LINE__, TRUE, DONT_CARE, "https://google.com" },
177     { __LINE__, TRUE, FALSE, "::{450d8fba-ad25-11d0-98a8-0800361b1103}" },
178     { __LINE__, TRUE, FALSE, "shell:::{450d8fba-ad25-11d0-98a8-0800361b1103}" },
179     { __LINE__, TRUE, FALSE, "shell:sendto" },
180 };
181 
182 static const TEST_ENTRY s_entries_2[] =
183 {
184     { __LINE__, TRUE, TRUE, "test program" },
185     { __LINE__, TRUE, TRUE, "test program", "TEST" },
186     { __LINE__, TRUE, TRUE, "\"test program\"" },
187     { __LINE__, TRUE, TRUE, s_win_test_exe },
188     { __LINE__, TRUE, TRUE, s_sys_test_exe },
189     { __LINE__, FALSE, FALSE, s_win_bat_file },
190     { __LINE__, FALSE, FALSE, s_sys_bat_file },
191     { __LINE__, FALSE, FALSE, s_win_bat_file, "TEST" },
192     { __LINE__, FALSE, FALSE, s_sys_bat_file, "TEST" },
193 };
194 
195 typedef struct OPENWNDS
196 {
197     UINT count;
198     HWND *phwnd;
199 } OPENWNDS;
200 
201 static OPENWNDS s_wi0 = { 0 }, s_wi1 = { 0 };
202 
203 static BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
204 {
205     OPENWNDS *info = (OPENWNDS *)lParam;
206     info->phwnd = (HWND *)realloc(info->phwnd, (info->count + 1) * sizeof(HWND));
207     if (!info->phwnd)
208         return FALSE;
209     info->phwnd[info->count] = hwnd;
210     ++(info->count);
211     return TRUE;
212 }
213 
214 static void CleanupNewlyCreatedWindows(void)
215 {
216     EnumWindows(EnumWindowsProc, (LPARAM)&s_wi1);
217     for (UINT i1 = 0; i1 < s_wi1.count; ++i1)
218     {
219         BOOL bFound = FALSE;
220         for (UINT i0 = 0; i0 < s_wi0.count; ++i0)
221         {
222             if (s_wi1.phwnd[i1] == s_wi0.phwnd[i0])
223             {
224                 bFound = TRUE;
225                 break;
226             }
227         }
228         if (!bFound)
229             PostMessageW(s_wi1.phwnd[i1], WM_CLOSE, 0, 0);
230     }
231     free(s_wi1.phwnd);
232     ZeroMemory(&s_wi1, sizeof(s_wi1));
233 }
234 
235 static VOID DoTestEntry(const TEST_ENTRY *pEntry)
236 {
237     SHELLEXECUTEINFOA info = { sizeof(info) };
238     info.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI;
239     info.nShow = SW_SHOWNORMAL;
240     info.lpFile = pEntry->file;
241     info.lpParameters = pEntry->params;
242     info.lpDirectory = pEntry->curdir;
243     BOOL ret = ShellExecuteExA(&info);
244     ok(ret == pEntry->ret, "Line %u: ret expected %d, got %d\n",
245        pEntry->lineno, pEntry->ret, ret);
246     if (!pEntry->ret)
247         return;
248 
249     if ((UINT)pEntry->bProcessHandle != DONT_CARE)
250     {
251         if (pEntry->bProcessHandle)
252         {
253             ok(!!info.hProcess, "Line %u: hProcess expected non-NULL\n", pEntry->lineno);
254         }
255         else
256         {
257             ok(!info.hProcess, "Line %u: hProcess expected NULL\n", pEntry->lineno);
258             return;
259         }
260     }
261 
262     WaitForInputIdle(info.hProcess, INFINITE);
263 
264     CleanupNewlyCreatedWindows();
265 
266     if (WaitForSingleObject(info.hProcess, 10 * 1000) == WAIT_TIMEOUT)
267     {
268         TerminateProcess(info.hProcess, 11);
269         ok(0, "Process %s did not quit!\n", pEntry->file);
270     }
271     CloseHandle(info.hProcess);
272 }
273 
274 static BOOL
275 GetSubProgramPath(void)
276 {
277     GetModuleFileNameA(NULL, s_sub_program, _countof(s_sub_program));
278     PathRemoveFileSpecA(s_sub_program);
279     PathAppendA(s_sub_program, "shell32_apitest_sub.exe");
280 
281     if (!PathFileExistsA(s_sub_program))
282     {
283         PathRemoveFileSpecA(s_sub_program);
284         PathAppendA(s_sub_program, "testdata\\shell32_apitest_sub.exe");
285 
286         if (!PathFileExistsA(s_sub_program))
287         {
288             return FALSE;
289         }
290     }
291 
292     return TRUE;
293 }
294 
295 static void DoTestEntries(void)
296 {
297     if (!GetSubProgramPath())
298     {
299         skip("shell32_apitest_sub.exe is not found\n");
300         return;
301     }
302 
303     // s_win_test_exe
304     GetWindowsDirectoryA(s_win_test_exe, _countof(s_win_test_exe));
305     PathAppendA(s_win_test_exe, "test program.exe");
306     BOOL ret = CopyFileA(s_sub_program, s_win_test_exe, FALSE);
307     if (!ret)
308     {
309         skip("Please retry with admin rights\n");
310         return;
311     }
312 
313     // record open windows
314     if (!EnumWindows(EnumWindowsProc, (LPARAM)&s_wi0))
315     {
316         skip("EnumWindows failed\n");
317         DeleteFileA(s_win_test_exe);
318         free(s_wi0.phwnd);
319         return;
320     }
321 
322     // s_sys_test_exe
323     GetSystemDirectoryA(s_sys_test_exe, _countof(s_sys_test_exe));
324     PathAppendA(s_sys_test_exe, "test program.exe");
325     ok_int(CopyFileA(s_sub_program, s_sys_test_exe, FALSE), TRUE);
326 
327     // s_win_bat_file
328     GetWindowsDirectoryA(s_win_bat_file, _countof(s_win_bat_file));
329     PathAppendA(s_win_bat_file, "test program.bat");
330     FILE *fp = fopen(s_win_bat_file, "wb");
331     fprintf(fp, "exit /b 3");
332     fclose(fp);
333     ok_int(PathFileExistsA(s_win_bat_file), TRUE);
334 
335     // s_sys_bat_file
336     GetSystemDirectoryA(s_sys_bat_file, _countof(s_sys_bat_file));
337     PathAppendA(s_sys_bat_file, "test program.bat");
338     fp = fopen(s_sys_bat_file, "wb");
339     fprintf(fp, "exit /b 4");
340     fclose(fp);
341     ok_int(PathFileExistsA(s_sys_bat_file), TRUE);
342 
343     // s_win_txt_file
344     GetWindowsDirectoryA(s_win_txt_file, _countof(s_win_txt_file));
345     PathAppendA(s_win_txt_file, "test_file.txt");
346     fp = fopen(s_win_txt_file, "wb");
347     fclose(fp);
348     ok_int(PathFileExistsA(s_win_txt_file), TRUE);
349 
350     // s_sys_txt_file
351     GetSystemDirectoryA(s_sys_txt_file, _countof(s_sys_txt_file));
352     PathAppendA(s_sys_txt_file, "test_file.txt");
353     fp = fopen(s_sys_txt_file, "wb");
354     fclose(fp);
355     ok_int(PathFileExistsA(s_sys_txt_file), TRUE);
356 
357     for (UINT iTest = 0; iTest < _countof(s_entries_1); ++iTest)
358     {
359         DoTestEntry(&s_entries_1[iTest]);
360     }
361 
362     DeleteFileA(s_win_bat_file);
363     DeleteFileA(s_sys_bat_file);
364 
365     for (UINT iTest = 0; iTest < _countof(s_entries_2); ++iTest)
366     {
367         DoTestEntry(&s_entries_2[iTest]);
368     }
369 
370     DeleteFileA(s_win_test_exe);
371     DeleteFileA(s_sys_test_exe);
372     DeleteFileA(s_win_txt_file);
373     DeleteFileA(s_sys_txt_file);
374 
375     free(s_wi0.phwnd);
376 }
377 
378 WCHAR* ExeName = NULL;
379 
380 BOOL CALLBACK EnumProc(_In_ HWND hwnd, _In_ LPARAM lParam)
381 {
382     DWORD pid = 0;
383     GetWindowThreadProcessId(hwnd, &pid);
384     if (pid == GetCurrentProcessId() &&
385         IsWindowVisible(hwnd))
386     {
387         WCHAR Buffer[512] = {0};
388 
389         GetWindowTextW(hwnd, Buffer, _countof(Buffer) - 1);
390         if (Buffer[0] && StrStrIW(Buffer, ExeName))
391         {
392             HWND* pHwnd = (HWND*)lParam;
393             *pHwnd = hwnd;
394             return FALSE;
395         }
396     }
397     return TRUE;
398 }
399 
400 BOOL WaitAndCloseWindow()
401 {
402     HWND hWnd = NULL;
403     for (int n = 0; n < 100; ++n)
404     {
405         Sleep(50);
406 
407         EnumWindows(EnumProc, (LPARAM)&hWnd);
408 
409         if (hWnd)
410         {
411             SendMessageW(hWnd, WM_SYSCOMMAND, SC_CLOSE, 0);
412             return TRUE;
413             break;
414         }
415     }
416     return FALSE;
417 }
418 
419 static void test_properties()
420 {
421     WCHAR Buffer[MAX_PATH * 4];
422 
423     CoInitialize(NULL);
424 
425     GetModuleFileNameW(NULL, Buffer, _countof(Buffer));
426     SHELLEXECUTEINFOW info = { 0 };
427 
428     info.cbSize = sizeof(SHELLEXECUTEINFOW);
429     info.fMask = SEE_MASK_INVOKEIDLIST | SEE_MASK_FLAG_NO_UI;
430     info.lpVerb = L"properties";
431     info.lpFile = Buffer;
432     info.lpParameters = L"";
433     info.nShow = SW_SHOW;
434 
435     BOOL bRet = ShellExecuteExW(&info);
436     ok(bRet, "Failed! (GetLastError(): %d)\n", (int)GetLastError());
437     ok_ptr(info.hInstApp, (HINSTANCE)42);
438 
439     ExeName = PathFindFileNameW(Buffer);
440     WCHAR* Extension = PathFindExtensionW(Buffer);
441     if (Extension)
442     {
443         // The inclusion of this depends on the file display settings!
444         *Extension = UNICODE_NULL;
445     }
446 
447     if (bRet)
448     {
449         ok(WaitAndCloseWindow(), "Could not find properties window!\n");
450     }
451 
452     // Now retry it with the extension cut off
453     bRet = ShellExecuteExW(&info);
454     ok(bRet, "Failed! (GetLastError(): %d)\n", (int)GetLastError());
455     ok_ptr(info.hInstApp, (HINSTANCE)42);
456 
457     if (bRet)
458     {
459         ok(WaitAndCloseWindow(), "Could not find properties window!\n");
460     }
461 
462     info.lpFile = L"complete garbage, cannot run this!";
463 
464     // Now retry it with complete garabage
465     bRet = ShellExecuteExW(&info);
466     ok(bRet == 0, "Succeeded!\n");
467     ok_ptr(info.hInstApp, (HINSTANCE)2);
468 }
469 
470 static void test_sei_lpIDList()
471 {
472     /* This tests ShellExecuteEx with lpIDList for explorer C:\ */
473 
474     /* ITEMIDLIST for CLSID of 'My Computer' followed by PIDL for 'C:\' */
475     BYTE lpitemidlist[30] = { 0x14, 0, 0x1f, 0, 0xe0, 0x4f, 0xd0, 0x20, 0xea,
476     0x3a, 0x69, 0x10, 0xa2, 0xd8, 0x08, 0, 0x2b, 0x30, 0x30, 0x9d, // My Computer
477     0x8, 0, 0x23, 0x43, 0x3a, 0x5c, 0x5c, 0, 0, 0,}; // C:\\ + NUL-NUL ending
478     BYTE *lpBytes;
479     lpBytes = lpitemidlist;
480 
481     SHELLEXECUTEINFOW ShellExecInfo;
482     BOOL Result;
483     STARTUPINFOW si;
484     PROCESS_INFORMATION pi;
485     HWND hWnd;
486 
487     ZeroMemory( &si, sizeof(si) );
488     si.cb = sizeof(si);
489     ZeroMemory( &pi, sizeof(pi) );
490 
491     ZeroMemory(&ShellExecInfo, sizeof(ShellExecInfo));
492     ShellExecInfo.cbSize = sizeof(ShellExecInfo);
493     ShellExecInfo.fMask = SEE_MASK_IDLIST;
494     ShellExecInfo.hwnd = NULL;
495     ShellExecInfo.nShow = SW_SHOWNORMAL;
496     ShellExecInfo.lpFile = NULL;
497     ShellExecInfo.lpDirectory = NULL;
498     ShellExecInfo.lpIDList = lpBytes;
499 
500     Result = ShellExecuteExW(&ShellExecInfo);
501     ok(Result == TRUE, "ShellExecuteEx lpIDList 'C:\\' failed\n");
502     trace("sei_lpIDList returned: %s\n", Result ? "SUCCESS" : "FAILURE");
503     if (Result)
504     {
505         Sleep(700);
506         // Terminate Window
507         hWnd = FindWindowW(L"CabinetWClass", L"Local Disk (C:)");
508         PostMessage(hWnd, WM_SYSCOMMAND, SC_CLOSE, 0);
509     }
510 }
511 
512 START_TEST(ShellExecuteEx)
513 {
514     DoAppPathTest();
515     DoTestEntries();
516     test_properties();
517 
518     DoWaitForWindow(CLASSNAME, CLASSNAME, TRUE, TRUE);
519     Sleep(100);
520 
521     test_sei_lpIDList();
522 
523 }
524