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