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