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 "closewnd.h"
11 #include <pstypes.h>
12 #include <psfuncs.h>
13 #include <stdlib.h>
14 #include <stdio.h>
15 #include <strsafe.h>
16 #include <versionhelpers.h>
17 #include <shellutils.h>
18 #include "shell32_apitest_sub.h"
19 
20 static WCHAR s_win_dir[MAX_PATH];
21 static WCHAR s_sys_dir[MAX_PATH];
22 static WCHAR s_win_notepad[MAX_PATH];
23 static WCHAR s_sys_notepad[MAX_PATH];
24 static WCHAR s_win_test_exe[MAX_PATH];
25 static WCHAR s_sys_test_exe[MAX_PATH];
26 static WCHAR s_win_bat_file[MAX_PATH];
27 static WCHAR s_sys_bat_file[MAX_PATH];
28 static WCHAR s_win_txt_file[MAX_PATH];
29 static WCHAR s_sys_txt_file[MAX_PATH];
30 static WCHAR s_win_notepad_cmdline[MAX_PATH];
31 static WCHAR s_sys_notepad_cmdline[MAX_PATH];
32 static WCHAR s_win_test_exe_cmdline[MAX_PATH];
33 static WCHAR s_sys_test_exe_cmdline[MAX_PATH];
34 static BOOL s_bWow64;
35 
36 #define REG_APPPATHS L"Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\"
37 
38 typedef enum TEST_RESULT
39 {
40     TEST_FAILED,
41     TEST_SUCCESS_NO_PROCESS,
42     TEST_SUCCESS_WITH_PROCESS,
43 } TEST_RESULT;
44 
45 typedef struct TEST_ENTRY
46 {
47     INT line;
48     TEST_RESULT result;
49     LPCWSTR lpFile;
50     LPCWSTR cmdline;
51 } TEST_ENTRY, *PTEST_ENTRY;
52 
53 static void
54 TEST_DoTestEntry(INT line, TEST_RESULT result, LPCWSTR lpFile, LPCWSTR cmdline = NULL);
55 
56 static void TEST_DoTestEntries(void)
57 {
58     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_NO_PROCESS, NULL);
59     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_NO_PROCESS, L"");
60     TEST_DoTestEntry(__LINE__, TEST_FAILED, L"This is an invalid path.");
61     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_WITH_PROCESS, s_sys_bat_file, NULL);
62     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_WITH_PROCESS, s_sys_test_exe, s_sys_test_exe_cmdline);
63     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_WITH_PROCESS, s_sys_txt_file, NULL);
64     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_WITH_PROCESS, s_win_bat_file, NULL);
65     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_WITH_PROCESS, s_win_notepad, s_win_notepad_cmdline);
66     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_WITH_PROCESS, s_win_test_exe, s_win_test_exe_cmdline);
67     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_WITH_PROCESS, s_win_txt_file, NULL);
68     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_WITH_PROCESS, L"notepad", s_sys_notepad_cmdline);
69     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_WITH_PROCESS, L"notepad.exe", s_sys_notepad_cmdline);
70     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_WITH_PROCESS, L"\"notepad.exe\"", s_sys_notepad_cmdline);
71     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_WITH_PROCESS, L"\"notepad\"", s_sys_notepad_cmdline);
72     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_WITH_PROCESS, L"test program.exe", s_sys_test_exe_cmdline);
73     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_WITH_PROCESS, L"\"test program.exe\"", s_sys_test_exe_cmdline);
74     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_NO_PROCESS, s_win_dir);
75     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_NO_PROCESS, s_sys_dir);
76     TEST_DoTestEntry(__LINE__, TEST_FAILED, L"shell:ThisIsAnInvalidName");
77     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_NO_PROCESS, L"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}"); // My Computer
78     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_NO_PROCESS, L"shell:::{20D04FE0-3AEA-1069-A2D8-08002B30309D}"); // My Computer (with shell:)
79 
80     if (!IsWindowsVistaOrGreater())
81     {
82         WCHAR szCurDir[MAX_PATH];
83         GetCurrentDirectoryW(_countof(szCurDir), szCurDir);
84         SetCurrentDirectoryW(s_sys_dir);
85         TEST_DoTestEntry(__LINE__, TEST_FAILED, L"::{21EC2020-3AEA-1069-A2DD-08002B30309D}"); // Control Panel (without path)
86         SetCurrentDirectoryW(szCurDir);
87     }
88 
89     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_NO_PROCESS, L"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\::{21EC2020-3AEA-1069-A2DD-08002B30309D}"); // Control Panel (with path)
90     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_NO_PROCESS, L"shell:::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\::{21EC2020-3AEA-1069-A2DD-08002B30309D}"); // Control Panel (with path and shell:)
91     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_NO_PROCESS, L"shell:AppData");
92     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_NO_PROCESS, L"shell:Common Desktop");
93     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_NO_PROCESS, L"shell:Common Programs");
94     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_NO_PROCESS, L"shell:Common Start Menu");
95     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_NO_PROCESS, L"shell:Common StartUp");
96     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_NO_PROCESS, L"shell:ControlPanelFolder");
97     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_NO_PROCESS, L"shell:Desktop");
98     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_NO_PROCESS, L"shell:Favorites");
99     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_NO_PROCESS, L"shell:Fonts");
100     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_NO_PROCESS, L"shell:Local AppData");
101     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_NO_PROCESS, L"shell:My Pictures");
102     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_NO_PROCESS, L"shell:Personal");
103     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_NO_PROCESS, L"shell:Programs");
104     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_NO_PROCESS, L"shell:Recent");
105     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_NO_PROCESS, L"shell:RecycleBinFolder");
106     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_NO_PROCESS, L"shell:SendTo");
107     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_NO_PROCESS, L"shell:Start Menu");
108     TEST_DoTestEntry(__LINE__, TEST_SUCCESS_NO_PROCESS, L"shell:StartUp");
109 }
110 
111 static LPWSTR
112 getCommandLineFromProcess(HANDLE hProcess)
113 {
114     PEB peb;
115     PROCESS_BASIC_INFORMATION info;
116     RTL_USER_PROCESS_PARAMETERS Params;
117     NTSTATUS Status;
118     BOOL ret;
119 
120     Status = NtQueryInformationProcess(hProcess, ProcessBasicInformation, &info, sizeof(info), NULL);
121     ok_ntstatus(Status, STATUS_SUCCESS);
122 
123     ret = ReadProcessMemory(hProcess, info.PebBaseAddress, &peb, sizeof(peb), NULL);
124     if (!ret)
125         trace("ReadProcessMemory failed (%ld)\n", GetLastError());
126 
127     ReadProcessMemory(hProcess, peb.ProcessParameters, &Params, sizeof(Params), NULL);
128     if (!ret)
129         trace("ReadProcessMemory failed (%ld)\n", GetLastError());
130 
131     LPWSTR cmdline = Params.CommandLine.Buffer;
132     if (!cmdline)
133         trace("!cmdline\n");
134 
135     SIZE_T cbCmdLine = Params.CommandLine.Length;
136     if (!cbCmdLine)
137         trace("!cbCmdLine\n");
138 
139     LPWSTR pszBuffer = (LPWSTR)calloc(cbCmdLine + sizeof(WCHAR), 1);
140     if (!pszBuffer)
141         trace("!pszBuffer\n");
142 
143     ret = ReadProcessMemory(hProcess, cmdline, pszBuffer, cbCmdLine, NULL);
144     if (!ret)
145         trace("ReadProcessMemory failed (%ld)\n", GetLastError());
146 
147     pszBuffer[cbCmdLine / sizeof(WCHAR)] = UNICODE_NULL;
148 
149     return pszBuffer; // needs free()
150 }
151 
152 static void TEST_DoTestEntryStruct(const TEST_ENTRY *pEntry)
153 {
154     SHELLEXECUTEINFOW info = { sizeof(info) };
155     info.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_WAITFORINPUTIDLE |
156                  SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC;
157     info.hwnd = NULL;
158     info.lpVerb = NULL;
159     info.lpFile = pEntry->lpFile;
160     info.nShow = SW_SHOWNORMAL;
161 
162     BOOL ret = ShellExecuteExW(&info);
163 
164     TEST_RESULT result;
165     if (ret && info.hProcess)
166         result = TEST_SUCCESS_WITH_PROCESS;
167     else if (ret && !info.hProcess)
168         result = TEST_SUCCESS_NO_PROCESS;
169     else
170         result = TEST_FAILED;
171 
172     ok(pEntry->result == result,
173        "Line %d: result: %d vs %d\n", pEntry->line, pEntry->result, result);
174 
175     if (pEntry->result == TEST_SUCCESS_WITH_PROCESS && pEntry->cmdline && !s_bWow64)
176     {
177         LPWSTR cmdline = getCommandLineFromProcess(info.hProcess);
178         if (!cmdline)
179         {
180             skip("!cmdline\n");
181         }
182         else
183         {
184             ok(lstrcmpiW(pEntry->cmdline, cmdline) == 0,
185                "Line %d: cmdline: '%ls' vs '%ls'\n", pEntry->line,
186                pEntry->cmdline, cmdline);
187         }
188 
189         TerminateProcess(info.hProcess, 0xDEADFACE);
190         free(cmdline);
191     }
192 
193     CloseHandle(info.hProcess);
194 }
195 
196 static void
197 TEST_DoTestEntry(INT line, TEST_RESULT result, LPCWSTR lpFile, LPCWSTR cmdline)
198 {
199     TEST_ENTRY entry = { line, result, lpFile, cmdline };
200     TEST_DoTestEntryStruct(&entry);
201 }
202 
203 static BOOL
204 enableTokenPrivilege(LPCWSTR pszPrivilege)
205 {
206     HANDLE hToken;
207     if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
208         return FALSE;
209 
210     TOKEN_PRIVILEGES tkp = { 0 };
211     if (!LookupPrivilegeValueW(NULL, pszPrivilege, &tkp.Privileges[0].Luid))
212         return FALSE;
213 
214     tkp.PrivilegeCount = 1;
215     tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
216     return AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, NULL);
217 }
218 
219 static WINDOW_LIST s_List1, s_List2;
220 
221 static BOOL TEST_Start(void)
222 {
223     // Check Wow64
224     s_bWow64 = FALSE;
225     IsWow64Process(GetCurrentProcess(), &s_bWow64);
226     if (s_bWow64)
227         skip("Wow64: Command Line check is skipped\n");
228 
229     // getCommandLineFromProcess needs this
230     enableTokenPrivilege(SE_DEBUG_NAME);
231 
232     // s_win_dir
233     GetWindowsDirectoryW(s_win_dir, _countof(s_win_dir));
234 
235     // s_sys_dir
236     GetSystemDirectoryW(s_sys_dir, _countof(s_sys_dir));
237 
238     // s_win_notepad
239     GetWindowsDirectoryW(s_win_notepad, _countof(s_win_notepad));
240     PathAppendW(s_win_notepad, L"notepad.exe");
241 
242     // s_sys_notepad
243     GetSystemDirectoryW(s_sys_notepad, _countof(s_sys_notepad));
244     PathAppendW(s_sys_notepad, L"notepad.exe");
245 
246     // s_win_test_exe
247     GetWindowsDirectoryW(s_win_test_exe, _countof(s_win_test_exe));
248     PathAppendW(s_win_test_exe, L"test program.exe");
249     BOOL ret = CopyFileW(s_win_notepad, s_win_test_exe, FALSE);
250     if (!ret)
251     {
252         skip("Please retry with admin rights\n");
253         return FALSE;
254     }
255 
256     // s_sys_test_exe
257     GetSystemDirectoryW(s_sys_test_exe, _countof(s_sys_test_exe));
258     PathAppendW(s_sys_test_exe, L"test program.exe");
259     ok_int(CopyFileW(s_win_notepad, s_sys_test_exe, FALSE), TRUE);
260 
261     // s_win_bat_file
262     GetWindowsDirectoryW(s_win_bat_file, _countof(s_win_bat_file));
263     PathAppendW(s_win_bat_file, L"test program.bat");
264     FILE *fp = _wfopen(s_win_bat_file, L"wb");
265     fprintf(fp, "exit /b 3");
266     fclose(fp);
267     ok_int(PathFileExistsW(s_win_bat_file), TRUE);
268 
269     // s_sys_bat_file
270     GetSystemDirectoryW(s_sys_bat_file, _countof(s_sys_bat_file));
271     PathAppendW(s_sys_bat_file, L"test program.bat");
272     fp = _wfopen(s_sys_bat_file, L"wb");
273     fprintf(fp, "exit /b 4");
274     fclose(fp);
275     ok_int(PathFileExistsW(s_sys_bat_file), TRUE);
276 
277     // s_win_txt_file
278     GetWindowsDirectoryW(s_win_txt_file, _countof(s_win_txt_file));
279     PathAppendW(s_win_txt_file, L"test_file.txt");
280     fp = _wfopen(s_win_txt_file, L"wb");
281     fclose(fp);
282     ok_int(PathFileExistsW(s_win_txt_file), TRUE);
283 
284     // s_sys_txt_file
285     GetSystemDirectoryW(s_sys_txt_file, _countof(s_sys_txt_file));
286     PathAppendW(s_sys_txt_file, L"test_file.txt");
287     fp = _wfopen(s_sys_txt_file, L"wb");
288     fclose(fp);
289     ok_int(PathFileExistsW(s_sys_txt_file), TRUE);
290 
291     // Check .txt settings
292     WCHAR szPath[MAX_PATH];
293     FindExecutableW(s_sys_txt_file, NULL, szPath);
294     if (lstrcmpiW(PathFindFileNameW(szPath), L"notepad.exe") != 0)
295     {
296         skip("Please associate .txt with notepad.exe before tests\n");
297         return FALSE;
298     }
299 
300     // command lines
301     StringCchPrintfW(s_win_notepad_cmdline, _countof(s_win_notepad_cmdline),
302                      L"\"%s\" ", s_win_notepad);
303     StringCchPrintfW(s_sys_notepad_cmdline, _countof(s_sys_notepad_cmdline),
304                      L"\"%s\" ", s_sys_notepad);
305     StringCchPrintfW(s_win_test_exe_cmdline, _countof(s_win_test_exe_cmdline),
306                      L"\"%s\" ", s_win_test_exe);
307     StringCchPrintfW(s_sys_test_exe_cmdline, _countof(s_sys_test_exe_cmdline),
308                      L"\"%s\" ", s_sys_test_exe);
309 
310     GetWindowList(&s_List1);
311 
312     return TRUE;
313 }
314 
315 static void TEST_End(void)
316 {
317     GetWindowListForClose(&s_List2);
318     CloseNewWindows(&s_List1, &s_List2);
319     FreeWindowList(&s_List1);
320     FreeWindowList(&s_List2);
321 
322     DeleteFileW(s_win_test_exe);
323     DeleteFileW(s_sys_test_exe);
324     DeleteFileW(s_win_txt_file);
325     DeleteFileW(s_sys_txt_file);
326     DeleteFileW(s_win_bat_file);
327     DeleteFileW(s_sys_bat_file);
328 }
329 
330 static void test_properties()
331 {
332     HRESULT hrCoInit = CoInitialize(NULL);
333 
334     WCHAR Buffer[MAX_PATH * 4];
335     GetModuleFileNameW(NULL, Buffer, _countof(Buffer));
336 
337     SHELLEXECUTEINFOW info = { sizeof(info) };
338     info.fMask = SEE_MASK_INVOKEIDLIST | SEE_MASK_FLAG_NO_UI;
339     info.lpVerb = L"properties";
340     info.lpFile = Buffer;
341     info.nShow = SW_SHOW;
342 
343     BOOL bRet = ShellExecuteExW(&info);
344     ok(bRet, "Failed! (GetLastError(): %d)\n", (int)GetLastError());
345     ok_ptr(info.hInstApp, (HINSTANCE)42);
346 
347     WCHAR* Extension = PathFindExtensionW(Buffer);
348     if (Extension)
349     {
350         // The inclusion of this depends on the file display settings!
351         *Extension = UNICODE_NULL;
352     }
353 
354     // Now retry it with the extension cut off
355     bRet = ShellExecuteExW(&info);
356     ok(bRet, "Failed! (GetLastError(): %d)\n", (int)GetLastError());
357     ok_ptr(info.hInstApp, (HINSTANCE)42);
358 
359     // Now retry it with complete garabage
360     info.lpFile = L"complete garbage, cannot run this!";
361     bRet = ShellExecuteExW(&info);
362     ok_int(bRet, 0);
363     ok_ptr(info.hInstApp, (HINSTANCE)2);
364 
365     if (SUCCEEDED(hrCoInit))
366         CoUninitialize();
367 }
368 
369 static void test_sei_lpIDList()
370 {
371     // Note: SEE_MASK_FLAG_NO_UI prevents the test from blocking with a MessageBox
372     WCHAR path[MAX_PATH];
373 
374     /* This tests ShellExecuteEx with lpIDList for explorer C:\ */
375     GetSystemDirectoryW(path, _countof(path));
376     PathStripToRootW(path);
377     LPITEMIDLIST pidl = ILCreateFromPathW(path);
378     if (!pidl)
379     {
380         skip("Unable to initialize test\n");
381         return;
382     }
383 
384     SHELLEXECUTEINFOW ShellExecInfo = { sizeof(ShellExecInfo) };
385     ShellExecInfo.nShow = SW_SHOWNORMAL;
386     ShellExecInfo.fMask = SEE_MASK_IDLIST | SEE_MASK_FLAG_NO_UI | SEE_MASK_FLAG_DDEWAIT;
387     ShellExecInfo.lpIDList = pidl;
388     BOOL ret = ShellExecuteExW(&ShellExecInfo);
389     ok_int(ret, TRUE);
390     ILFree(pidl);
391 
392     /* This tests ShellExecuteEx with lpIDList going through IContextMenu */
393     CCoInit ComInit;
394     pidl = SHCloneSpecialIDList(NULL, CSIDL_PROFILE, TRUE);
395     if (!pidl)
396     {
397         skip("Unable to initialize test\n");
398         return;
399     }
400     ShellExecInfo.fMask = SEE_MASK_INVOKEIDLIST | SEE_MASK_FLAG_NO_UI | SEE_MASK_FLAG_DDEWAIT;
401     ShellExecInfo.lpIDList = pidl;
402     ret = ShellExecuteExW(&ShellExecInfo);
403     ok_int(ret, TRUE);
404     ILFree(pidl);
405 }
406 
407 static BOOL
408 CreateAppPath(LPCWSTR pszName, LPCWSTR pszValue)
409 {
410     WCHAR szSubKey[MAX_PATH];
411     StringCchPrintfW(szSubKey, _countof(szSubKey), L"%s\\%s", REG_APPPATHS, pszName);
412 
413     LSTATUS error;
414     HKEY hKey;
415     error = RegCreateKeyExW(HKEY_LOCAL_MACHINE, szSubKey, 0, NULL, 0, KEY_WRITE, NULL,
416                             &hKey, NULL);
417     if (error != ERROR_SUCCESS)
418         trace("Could not create test key (%lu)\n", error);
419 
420     DWORD cbValue = (lstrlenW(pszValue) + 1) * sizeof(WCHAR);
421     error = RegSetValueExW(hKey, NULL, 0, REG_SZ, (LPBYTE)pszValue, cbValue);
422     if (error != ERROR_SUCCESS)
423         trace("Could not set value of the test key (%lu)\n", error);
424 
425     RegCloseKey(hKey);
426 
427     return error == ERROR_SUCCESS;
428 }
429 
430 static VOID
431 DeleteAppPath(LPCWSTR pszName)
432 {
433     WCHAR szSubKey[MAX_PATH];
434     StringCchPrintfW(szSubKey, _countof(szSubKey), L"%s\\%s", REG_APPPATHS, pszName);
435 
436     LSTATUS error = RegDeleteKeyW(HKEY_LOCAL_MACHINE, szSubKey);
437     if (error != ERROR_SUCCESS)
438         trace("Could not remove the test key (%lu)\n", error);
439 }
440 
441 static void TEST_AppPath(void)
442 {
443     if (CreateAppPath(L"app_path_test.bat", s_win_test_exe))
444     {
445         TEST_DoTestEntry(__LINE__, TEST_SUCCESS_WITH_PROCESS, L"app_path_test.bat");
446         TEST_DoTestEntry(__LINE__, TEST_FAILED, L"app_path_test.bat.exe");
447         DeleteAppPath(L"app_path_test.bat");
448     }
449 
450     if (CreateAppPath(L"app_path_test.bat.exe", s_sys_test_exe))
451     {
452         TEST_DoTestEntry(__LINE__, TEST_FAILED, L"app_path_test.bat");
453         TEST_DoTestEntry(__LINE__, TEST_SUCCESS_WITH_PROCESS, L"app_path_test.bat.exe");
454         DeleteAppPath(L"app_path_test.bat.exe");
455     }
456 }
457 
458 static void test_DoInvalidDir(void)
459 {
460     WCHAR szSubProgram[MAX_PATH];
461     if (!FindSubProgram(szSubProgram, _countof(szSubProgram)))
462     {
463         skip("shell32_apitest_sub.exe not found\n");
464         return;
465     }
466 
467     DWORD dwExitCode;
468     SHELLEXECUTEINFOW sei = { sizeof(sei), SEE_MASK_FLAG_NO_UI | SEE_MASK_NOCLOSEPROCESS };
469     sei.lpFile = szSubProgram;
470     sei.lpParameters = L"TEST";
471     sei.nShow = SW_SHOWNORMAL;
472 
473     // Test invalid path on sei.lpDirectory
474     WCHAR szInvalidPath[MAX_PATH] = L"M:\\This is an invalid path\n";
475     sei.lpDirectory = szInvalidPath;
476     ok_int(ShellExecuteExW(&sei), TRUE);
477     WaitForSingleObject(sei.hProcess, 20 * 1000);
478     GetExitCodeProcess(sei.hProcess, &dwExitCode);
479     ok_long(dwExitCode, 0);
480     CloseHandle(sei.hProcess);
481 }
482 
483 START_TEST(ShellExecuteEx)
484 {
485 #ifdef _WIN64
486     skip("Win64 is not supported yet\n");
487     return;
488 #endif
489 
490     if (!TEST_Start())
491         return;
492 
493     TEST_AppPath();
494     TEST_DoTestEntries();
495     test_properties();
496     test_sei_lpIDList();
497     test_DoInvalidDir();
498 
499     TEST_End();
500 }
501