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 
13 #define ok_ShellExecuteEx (winetest_set_location(__FILE__, __LINE__), 0) ? (void)0 : TestShellExecuteEx
14 
15 static
16 BOOL
17 CreateAppPathRegKey(const WCHAR* Name)
18 {
19     HKEY RegistryKey;
20     LONG Result;
21     WCHAR Buffer[1024];
22     WCHAR KeyValue[1024];
23     DWORD Length = sizeof(KeyValue);
24     DWORD Disposition;
25 
26     wcscpy(Buffer, L"Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\");
27     wcscat(Buffer, L"IEXPLORE.EXE");
28     Result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, Buffer, 0, KEY_READ, &RegistryKey);
29     if (Result != ERROR_SUCCESS) trace("Could not open iexplore.exe key. Status: %lu\n", Result);
30     if (Result) goto end;
31     Result = RegQueryValueExW(RegistryKey, NULL, NULL, NULL, (LPBYTE)KeyValue, &Length);
32     if (Result != ERROR_SUCCESS) trace("Could not read iexplore.exe key. Status: %lu\n", Result);
33     if (Result) goto end;
34     RegCloseKey(RegistryKey);
35 
36     wcscpy(Buffer, L"Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\");
37     wcscat(Buffer, Name);
38     Result = RegCreateKeyExW(HKEY_LOCAL_MACHINE, Buffer, 0, NULL,
39         0, KEY_WRITE, NULL, &RegistryKey, &Disposition);
40     if (Result != ERROR_SUCCESS) trace("Could not create test key. Status: %lu\n", Result);
41     if (Result) goto end;
42     Result = RegSetValueW(RegistryKey, NULL, REG_SZ, KeyValue, 0);
43     if (Result != ERROR_SUCCESS) trace("Could not set value of the test key. Status: %lu\n", Result);
44     if (Result) goto end;
45     RegCloseKey(RegistryKey);
46 end:
47     if (RegistryKey) RegCloseKey(RegistryKey);
48     return Result == ERROR_SUCCESS;
49 }
50 
51 static
52 VOID
53 DeleteAppPathRegKey(const WCHAR* Name)
54 {
55     LONG Result;
56     WCHAR Buffer[1024];
57     wcscpy(Buffer, L"Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\");
58     wcscat(Buffer, Name);
59     Result = RegDeleteKeyW(HKEY_LOCAL_MACHINE, Buffer);
60     if (Result != ERROR_SUCCESS) trace("Could not remove the test key. Status: %lu\n", Result);
61 }
62 
63 static
64 VOID
65 TestShellExecuteEx(const WCHAR* Name, BOOL ExpectedResult)
66 {
67     SHELLEXECUTEINFOW ShellExecInfo;
68     BOOL Result;
69     ZeroMemory(&ShellExecInfo, sizeof(ShellExecInfo));
70     ShellExecInfo.cbSize = sizeof(ShellExecInfo);
71     ShellExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI;
72     ShellExecInfo.hwnd = NULL;
73     ShellExecInfo.nShow = SW_SHOWNORMAL;
74     ShellExecInfo.lpFile = Name;
75     ShellExecInfo.lpDirectory = NULL;
76     Result = ShellExecuteExW(&ShellExecInfo);
77     ok(Result == ExpectedResult, "ShellExecuteEx lpFile %s failed. Error: %lu\n", wine_dbgstr_w(Name), GetLastError());
78     if (ShellExecInfo.hProcess)
79     {
80         Result = TerminateProcess(ShellExecInfo.hProcess, 0);
81         if (!Result) trace("Terminate process failed. Error: %lu\n", GetLastError());
82         WaitForSingleObject(ShellExecInfo.hProcess, INFINITE);
83         CloseHandle(ShellExecInfo.hProcess);
84     }
85 }
86 
87 static void DoAppPathTest(void)
88 {
89     ok_ShellExecuteEx(L"iexplore", TRUE);
90     ok_ShellExecuteEx(L"iexplore.exe", TRUE);
91 
92     if (CreateAppPathRegKey(L"iexplore.bat"))
93     {
94         ok_ShellExecuteEx(L"iexplore.bat", TRUE);
95         ok_ShellExecuteEx(L"iexplore.bat.exe", FALSE);
96         DeleteAppPathRegKey(L"iexplore.bat");
97     }
98 
99     if (CreateAppPathRegKey(L"iexplore.bat.exe"))
100     {
101         ok_ShellExecuteEx(L"iexplore.bat", FALSE);
102         ok_ShellExecuteEx(L"iexplore.bat.exe", TRUE);
103         DeleteAppPathRegKey(L"iexplore.bat.exe");
104     }
105 }
106 
107 typedef struct TEST_ENTRY
108 {
109     INT lineno;
110     BOOL ret;
111     BOOL bProcessHandle;
112     LPCSTR file;
113     LPCSTR params;
114     LPCSTR curdir;
115 } TEST_ENTRY;
116 
117 static char s_sub_program[MAX_PATH];
118 static char s_win_test_exe[MAX_PATH];
119 static char s_sys_test_exe[MAX_PATH];
120 static char s_win_bat_file[MAX_PATH];
121 static char s_sys_bat_file[MAX_PATH];
122 static char s_win_txt_file[MAX_PATH];
123 static char s_sys_txt_file[MAX_PATH];
124 
125 #define DONT_CARE 0x0BADF00D
126 
127 static const TEST_ENTRY s_entries_1[] =
128 {
129     { __LINE__, TRUE, TRUE, "test program" },
130     { __LINE__, TRUE, TRUE, "test program.bat" },
131     { __LINE__, TRUE, TRUE, "test program.exe" },
132     { __LINE__, FALSE, FALSE, "  test program" },
133     { __LINE__, FALSE, FALSE, "  test program.bat" },
134     { __LINE__, FALSE, FALSE, "  test program.exe" },
135     { __LINE__, FALSE, FALSE, "test program  " },
136     { __LINE__, TRUE, TRUE, "test program.bat  " },
137     { __LINE__, TRUE, TRUE, "test program.exe  " },
138     { __LINE__, TRUE, TRUE, "test program", "TEST" },
139     { __LINE__, TRUE, TRUE, "test program.bat", "TEST" },
140     { __LINE__, TRUE, TRUE, "test program.exe", "TEST" },
141     { __LINE__, FALSE, FALSE, ".\\test program.bat" },
142     { __LINE__, FALSE, FALSE, ".\\test program.exe" },
143     { __LINE__, TRUE, TRUE, "\"test program\"" },
144     { __LINE__, TRUE, TRUE, "\"test program.bat\"" },
145     { __LINE__, TRUE, TRUE, "\"test program.exe\"" },
146     { __LINE__, FALSE, FALSE, "\"test program\" TEST" },
147     { __LINE__, FALSE, FALSE, "\"test program.bat\" TEST" },
148     { __LINE__, FALSE, FALSE, "\"test program.exe\" TEST" },
149     { __LINE__, FALSE, FALSE, "  \"test program\"" },
150     { __LINE__, FALSE, FALSE, "  \"test program.bat\"" },
151     { __LINE__, FALSE, FALSE, "  \"test program.exe\"" },
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.bat\"" },
156     { __LINE__, FALSE, FALSE, "\".\\test program.exe\"" },
157     { __LINE__, TRUE, TRUE, s_win_test_exe },
158     { __LINE__, TRUE, TRUE, s_sys_test_exe },
159     { __LINE__, TRUE, TRUE, s_win_bat_file },
160     { __LINE__, TRUE, TRUE, s_sys_bat_file },
161     { __LINE__, TRUE, TRUE, s_win_bat_file, "TEST" },
162     { __LINE__, TRUE, TRUE, s_sys_bat_file, "TEST" },
163     { __LINE__, FALSE, FALSE, "invalid program" },
164     { __LINE__, FALSE, FALSE, "invalid program.bat" },
165     { __LINE__, FALSE, FALSE, "invalid program.exe" },
166     { __LINE__, TRUE, TRUE, "test_file.txt" },
167     { __LINE__, TRUE, TRUE, "test_file.txt", "parameters parameters" },
168     { __LINE__, TRUE, TRUE, "test_file.txt", "parameters parameters", "." },
169     { __LINE__, TRUE, TRUE, "shell32_apitest_sub.exe" },
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, DONT_CARE, "https://google.com" },
174     { __LINE__, TRUE, FALSE, "::{450d8fba-ad25-11d0-98a8-0800361b1103}" },
175     { __LINE__, TRUE, FALSE, "shell:::{450d8fba-ad25-11d0-98a8-0800361b1103}" },
176     { __LINE__, TRUE, FALSE, "shell:sendto" },
177 };
178 
179 static const TEST_ENTRY s_entries_2[] =
180 {
181     { __LINE__, TRUE, TRUE, "test program" },
182     { __LINE__, TRUE, TRUE, "test program", "TEST" },
183     { __LINE__, TRUE, TRUE, "\"test program\"" },
184     { __LINE__, TRUE, TRUE, s_win_test_exe },
185     { __LINE__, TRUE, TRUE, s_sys_test_exe },
186     { __LINE__, FALSE, FALSE, s_win_bat_file },
187     { __LINE__, FALSE, FALSE, s_sys_bat_file },
188     { __LINE__, FALSE, FALSE, s_win_bat_file, "TEST" },
189     { __LINE__, FALSE, FALSE, s_sys_bat_file, "TEST" },
190 };
191 
192 typedef struct OPENWNDS
193 {
194     UINT count;
195     HWND *phwnd;
196 } OPENWNDS;
197 
198 static OPENWNDS s_wi0 = { 0 }, s_wi1 = { 0 };
199 
200 static BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
201 {
202     OPENWNDS *info = (OPENWNDS *)lParam;
203     info->phwnd = (HWND *)realloc(info->phwnd, (info->count + 1) * sizeof(HWND));
204     if (!info->phwnd)
205         return FALSE;
206     info->phwnd[info->count] = hwnd;
207     ++(info->count);
208     return TRUE;
209 }
210 
211 static VOID DoTestEntry(const TEST_ENTRY *pEntry)
212 {
213     SHELLEXECUTEINFOA info = { sizeof(info) };
214     info.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI;
215     info.nShow = SW_SHOWNORMAL;
216     info.lpFile = pEntry->file;
217     info.lpParameters = pEntry->params;
218     info.lpDirectory = pEntry->curdir;
219     BOOL ret = ShellExecuteExA(&info);
220     ok(ret == pEntry->ret, "Line %u: ret expected %d, got %d\n",
221        pEntry->lineno, pEntry->ret, ret);
222     if (!pEntry->ret)
223         return;
224 
225     if ((UINT)pEntry->bProcessHandle != DONT_CARE)
226     {
227         if (pEntry->bProcessHandle)
228         {
229             ok(!!info.hProcess, "Line %u: hProcess expected non-NULL\n", pEntry->lineno);
230         }
231         else
232         {
233             ok(!info.hProcess, "Line %u: hProcess expected NULL\n", pEntry->lineno);
234             return;
235         }
236     }
237 
238     WaitForInputIdle(info.hProcess, INFINITE);
239 
240     // close newly opened windows
241     EnumWindows(EnumWindowsProc, (LPARAM)&s_wi1);
242     for (UINT i1 = 0; i1 < s_wi1.count; ++i1)
243     {
244         BOOL bFound = FALSE;
245         for (UINT i0 = 0; i0 < s_wi0.count; ++i0)
246         {
247             if (s_wi1.phwnd[i1] == s_wi0.phwnd[i0])
248             {
249                 bFound = TRUE;
250                 break;
251             }
252         }
253         if (!bFound)
254             PostMessageW(s_wi1.phwnd[i1], WM_CLOSE, 0, 0);
255     }
256     free(s_wi1.phwnd);
257     ZeroMemory(&s_wi1, sizeof(s_wi1));
258 
259     WaitForSingleObject(info.hProcess, INFINITE);
260     CloseHandle(info.hProcess);
261 }
262 
263 static BOOL
264 GetSubProgramPath(void)
265 {
266     GetModuleFileNameA(NULL, s_sub_program, _countof(s_sub_program));
267     PathRemoveFileSpecA(s_sub_program);
268     PathAppendA(s_sub_program, "shell32_apitest_sub.exe");
269 
270     if (!PathFileExistsA(s_sub_program))
271     {
272         PathRemoveFileSpecA(s_sub_program);
273         PathAppendA(s_sub_program, "testdata\\shell32_apitest_sub.exe");
274 
275         if (!PathFileExistsA(s_sub_program))
276         {
277             return FALSE;
278         }
279     }
280 
281     return TRUE;
282 }
283 
284 static void DoTestEntries(void)
285 {
286     if (!GetSubProgramPath())
287     {
288         skip("shell32_apitest_sub.exe is not found\n");
289         return;
290     }
291 
292     // s_win_test_exe
293     GetWindowsDirectoryA(s_win_test_exe, _countof(s_win_test_exe));
294     PathAppendA(s_win_test_exe, "test program.exe");
295     BOOL ret = CopyFileA(s_sub_program, s_win_test_exe, FALSE);
296     if (!ret)
297     {
298         skip("Please retry with admin rights\n");
299         return;
300     }
301 
302     // record open windows
303     if (!EnumWindows(EnumWindowsProc, (LPARAM)&s_wi0))
304     {
305         skip("EnumWindows failed\n");
306         DeleteFileA(s_win_test_exe);
307         free(s_wi0.phwnd);
308         return;
309     }
310 
311     // s_sys_test_exe
312     GetSystemDirectoryA(s_sys_test_exe, _countof(s_sys_test_exe));
313     PathAppendA(s_sys_test_exe, "test program.exe");
314     ok_int(CopyFileA(s_sub_program, s_sys_test_exe, FALSE), TRUE);
315 
316     // s_win_bat_file
317     GetWindowsDirectoryA(s_win_bat_file, _countof(s_win_bat_file));
318     PathAppendA(s_win_bat_file, "test program.bat");
319     FILE *fp = fopen(s_win_bat_file, "wb");
320     fprintf(fp, "exit /b 3");
321     fclose(fp);
322     ok_int(PathFileExistsA(s_win_bat_file), TRUE);
323 
324     // s_sys_bat_file
325     GetSystemDirectoryA(s_sys_bat_file, _countof(s_sys_bat_file));
326     PathAppendA(s_sys_bat_file, "test program.bat");
327     fp = fopen(s_sys_bat_file, "wb");
328     fprintf(fp, "exit /b 4");
329     fclose(fp);
330     ok_int(PathFileExistsA(s_sys_bat_file), TRUE);
331 
332     // s_win_txt_file
333     GetWindowsDirectoryA(s_win_txt_file, _countof(s_win_txt_file));
334     PathAppendA(s_win_txt_file, "test_file.txt");
335     fp = fopen(s_win_txt_file, "wb");
336     fclose(fp);
337     ok_int(PathFileExistsA(s_win_txt_file), TRUE);
338 
339     // s_sys_txt_file
340     GetSystemDirectoryA(s_sys_txt_file, _countof(s_sys_txt_file));
341     PathAppendA(s_sys_txt_file, "test_file.txt");
342     fp = fopen(s_sys_txt_file, "wb");
343     fclose(fp);
344     ok_int(PathFileExistsA(s_sys_txt_file), TRUE);
345 
346     for (UINT iTest = 0; iTest < _countof(s_entries_1); ++iTest)
347     {
348         DoTestEntry(&s_entries_1[iTest]);
349     }
350 
351     DeleteFileA(s_win_bat_file);
352     DeleteFileA(s_sys_bat_file);
353 
354     for (UINT iTest = 0; iTest < _countof(s_entries_2); ++iTest)
355     {
356         DoTestEntry(&s_entries_2[iTest]);
357     }
358 
359     DeleteFileA(s_win_test_exe);
360     DeleteFileA(s_sys_test_exe);
361     DeleteFileA(s_win_txt_file);
362     DeleteFileA(s_sys_txt_file);
363 
364     free(s_wi0.phwnd);
365 }
366 
367 START_TEST(ShellExecuteEx)
368 {
369     DoAppPathTest();
370     DoTestEntries();
371 }
372