start.c 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  1. /*
  2. * Start a program using ShellExecuteEx, optionally wait for it to finish
  3. * Compatible with Microsoft's "c:\windows\command\start.exe"
  4. *
  5. * Copyright 2003 Dan Kegel
  6. * Copyright 2007 Lyutin Anatoly (Etersoft)
  7. *
  8. * This program is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU Lesser General Public
  10. * License as published by the Free Software Foundation; either
  11. * version 2.1 of the License, or (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  16. * Lesser General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU Lesser General Public
  19. * License along with this program; if not, write to the Free Software
  20. * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  21. */
  22. #include <stdio.h>
  23. #include <stdlib.h>
  24. #include <windows.h>
  25. #include <shlobj.h>
  26. #include <shellapi.h>
  27. #include <wine/debug.h>
  28. #include "resources.h"
  29. WINE_DEFAULT_DEBUG_CHANNEL(start);
  30. /**
  31. Output given message to stdout without formatting.
  32. */
  33. static void output(const WCHAR *message)
  34. {
  35. DWORD count;
  36. DWORD res;
  37. int wlen = lstrlenW(message);
  38. if (!wlen) return;
  39. res = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), message, wlen, &count, NULL);
  40. /* If writing to console fails, assume it's file
  41. * i/o so convert to OEM codepage and output
  42. */
  43. if (!res)
  44. {
  45. DWORD len;
  46. char *mesA;
  47. /* Convert to OEM, then output */
  48. len = WideCharToMultiByte( GetConsoleOutputCP(), 0, message, wlen, NULL, 0, NULL, NULL );
  49. mesA = HeapAlloc(GetProcessHeap(), 0, len*sizeof(char));
  50. if (!mesA) return;
  51. WideCharToMultiByte( GetConsoleOutputCP(), 0, message, wlen, mesA, len, NULL, NULL );
  52. WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), mesA, len, &count, FALSE);
  53. HeapFree(GetProcessHeap(), 0, mesA);
  54. }
  55. }
  56. /**
  57. Output given message from string table,
  58. followed by ": ",
  59. followed by description of given GetLastError() value to stdout,
  60. followed by a trailing newline,
  61. then terminate.
  62. */
  63. static void fatal_error(const WCHAR *msg, DWORD error_code, const WCHAR *filename)
  64. {
  65. DWORD_PTR args[1];
  66. LPVOID lpMsgBuf;
  67. int status;
  68. output(msg);
  69. output(L": ");
  70. args[0] = (DWORD_PTR)filename;
  71. status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY,
  72. NULL, error_code, 0, (LPWSTR)&lpMsgBuf, 0, (__ms_va_list *)args );
  73. if (!status)
  74. {
  75. WINE_ERR("FormatMessage failed\n");
  76. } else
  77. {
  78. output(lpMsgBuf);
  79. LocalFree((HLOCAL) lpMsgBuf);
  80. output(L"\n");
  81. }
  82. ExitProcess(1);
  83. }
  84. static void fatal_string_error(int which, DWORD error_code, const WCHAR *filename)
  85. {
  86. WCHAR msg[2048];
  87. if (!LoadStringW(GetModuleHandleW(NULL), which, msg, ARRAY_SIZE(msg)))
  88. WINE_ERR("LoadString failed, error %d\n", GetLastError());
  89. fatal_error(msg, error_code, filename);
  90. }
  91. static void fatal_string(int which)
  92. {
  93. WCHAR msg[2048];
  94. if (!LoadStringW(GetModuleHandleW(NULL), which, msg, ARRAY_SIZE(msg)))
  95. WINE_ERR("LoadString failed, error %d\n", GetLastError());
  96. output(msg);
  97. ExitProcess(1);
  98. }
  99. static void usage(void)
  100. {
  101. fatal_string(STRING_USAGE);
  102. }
  103. /***********************************************************************
  104. * build_command_line
  105. *
  106. * Build the command line of a process from the argv array.
  107. *
  108. * We must quote and escape characters so that the argv array can be rebuilt
  109. * from the command line:
  110. * - spaces and tabs must be quoted
  111. * 'a b' -> '"a b"'
  112. * - quotes must be escaped
  113. * '"' -> '\"'
  114. * - if '\'s are followed by a '"', they must be doubled and followed by '\"',
  115. * resulting in an odd number of '\' followed by a '"'
  116. * '\"' -> '\\\"'
  117. * '\\"' -> '\\\\\"'
  118. * - '\'s are followed by the closing '"' must be doubled,
  119. * resulting in an even number of '\' followed by a '"'
  120. * ' \' -> '" \\"'
  121. * ' \\' -> '" \\\\"'
  122. * - '\'s that are not followed by a '"' can be left as is
  123. * 'a\b' == 'a\b'
  124. * 'a\\b' == 'a\\b'
  125. */
  126. static WCHAR *build_command_line( WCHAR **wargv )
  127. {
  128. int len;
  129. WCHAR **arg, *ret;
  130. LPWSTR p;
  131. len = 1;
  132. for (arg = wargv; *arg; arg++) len += 3 + 2 * wcslen( *arg );
  133. if (!(ret = malloc( len * sizeof(WCHAR) ))) return NULL;
  134. p = ret;
  135. for (arg = wargv; *arg; arg++)
  136. {
  137. BOOL has_space, has_quote;
  138. int i, bcount;
  139. WCHAR *a;
  140. /* check for quotes and spaces in this argument */
  141. has_space = !**arg || wcschr( *arg, ' ' ) || wcschr( *arg, '\t' );
  142. has_quote = wcschr( *arg, '"' ) != NULL;
  143. /* now transfer it to the command line */
  144. if (has_space) *p++ = '"';
  145. if (has_quote || has_space)
  146. {
  147. bcount = 0;
  148. for (a = *arg; *a; a++)
  149. {
  150. if (*a == '\\') bcount++;
  151. else
  152. {
  153. if (*a == '"') /* double all the '\\' preceding this '"', plus one */
  154. for (i = 0; i <= bcount; i++) *p++ = '\\';
  155. bcount = 0;
  156. }
  157. *p++ = *a;
  158. }
  159. }
  160. else
  161. {
  162. wcscpy( p, *arg );
  163. p += wcslen( p );
  164. }
  165. if (has_space)
  166. {
  167. /* Double all the '\' preceding the closing quote */
  168. for (i = 0; i < bcount; i++) *p++ = '\\';
  169. *p++ = '"';
  170. }
  171. *p++ = ' ';
  172. }
  173. if (p > ret) p--; /* remove last space */
  174. *p = 0;
  175. return ret;
  176. }
  177. static WCHAR *get_parent_dir(WCHAR* path)
  178. {
  179. WCHAR *last_slash;
  180. WCHAR *result;
  181. int len;
  182. last_slash = wcsrchr( path, '\\' );
  183. if (last_slash == NULL)
  184. len = 1;
  185. else
  186. len = last_slash - path + 1;
  187. result = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
  188. CopyMemory(result, path, (len-1)*sizeof(WCHAR));
  189. result[len-1] = '\0';
  190. return result;
  191. }
  192. static BOOL is_option(const WCHAR* arg, const WCHAR* opt)
  193. {
  194. return CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE,
  195. arg, -1, opt, -1) == CSTR_EQUAL;
  196. }
  197. static void parse_title(const WCHAR *arg, WCHAR *title, int size)
  198. {
  199. /* See:
  200. * WCMD_start() in programs/cmd/builtins.c
  201. * WCMD_parameter_with_delims() in programs/cmd/batch.c
  202. * The shell has already tokenized the command line for us.
  203. * All we need to do is filter out all the quotes.
  204. */
  205. int next;
  206. const WCHAR *p = arg;
  207. for (next = 0; next < (size-1) && *p; p++) {
  208. if (*p != '"')
  209. title[next++] = *p;
  210. }
  211. title[next] = '\0';
  212. }
  213. static BOOL search_path(const WCHAR *firstParam, WCHAR **full_path)
  214. {
  215. /* Copied from WCMD_run_program() in programs/cmd/wcmdmain.c */
  216. #define MAXSTRING 8192
  217. WCHAR temp[MAX_PATH];
  218. WCHAR pathtosearch[MAXSTRING];
  219. WCHAR *pathposn;
  220. WCHAR stemofsearch[MAX_PATH]; /* maximum allowed executable name is
  221. MAX_PATH, including null character */
  222. WCHAR *lastSlash;
  223. WCHAR pathext[MAXSTRING];
  224. BOOL extensionsupplied = FALSE;
  225. DWORD len;
  226. /* Calculate the search path and stem to search for */
  227. if (wcspbrk (firstParam, L"/\\:") == NULL) { /* No explicit path given, search path */
  228. lstrcpyW(pathtosearch, L".;");
  229. len = GetEnvironmentVariableW(L"PATH", &pathtosearch[2], ARRAY_SIZE(pathtosearch)-2);
  230. if ((len == 0) || (len >= ARRAY_SIZE(pathtosearch) - 2)) {
  231. lstrcpyW (pathtosearch, L".");
  232. }
  233. if (wcschr(firstParam, '.') != NULL) extensionsupplied = TRUE;
  234. if (lstrlenW(firstParam) >= MAX_PATH) {
  235. return FALSE;
  236. }
  237. lstrcpyW(stemofsearch, firstParam);
  238. } else {
  239. /* Convert eg. ..\fred to include a directory by removing file part */
  240. GetFullPathNameW(firstParam, ARRAY_SIZE(pathtosearch), pathtosearch, NULL);
  241. lastSlash = wcsrchr(pathtosearch, '\\');
  242. if (lastSlash && wcschr(lastSlash, '.') != NULL) extensionsupplied = TRUE;
  243. lstrcpyW(stemofsearch, lastSlash+1);
  244. /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
  245. c:\windows\a.bat syntax */
  246. if (lastSlash) *(lastSlash + 1) = 0x00;
  247. }
  248. /* Now extract PATHEXT */
  249. len = GetEnvironmentVariableW(L"PATHEXT", pathext, ARRAY_SIZE(pathext));
  250. if ((len == 0) || (len >= ARRAY_SIZE(pathext))) {
  251. lstrcpyW (pathext, L".bat;.com;.cmd;.exe");
  252. }
  253. /* Loop through the search path, dir by dir */
  254. pathposn = pathtosearch;
  255. WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch),
  256. wine_dbgstr_w(stemofsearch));
  257. while (pathposn) {
  258. WCHAR thisDir[MAX_PATH] = {'\0'};
  259. int length = 0;
  260. WCHAR *pos = NULL;
  261. BOOL found = FALSE;
  262. BOOL inside_quotes = FALSE;
  263. /* Work on the first directory on the search path */
  264. pos = pathposn;
  265. while ((inside_quotes || *pos != ';') && *pos != 0)
  266. {
  267. if (*pos == '"')
  268. inside_quotes = !inside_quotes;
  269. pos++;
  270. }
  271. if (*pos) { /* Reached semicolon */
  272. memcpy(thisDir, pathposn, (pos-pathposn) * sizeof(WCHAR));
  273. thisDir[(pos-pathposn)] = 0x00;
  274. pathposn = pos+1;
  275. } else { /* Reached string end */
  276. lstrcpyW(thisDir, pathposn);
  277. pathposn = NULL;
  278. }
  279. /* Remove quotes */
  280. length = lstrlenW(thisDir);
  281. if (thisDir[length - 1] == '"')
  282. thisDir[length - 1] = 0;
  283. if (*thisDir != '"')
  284. lstrcpyW(temp, thisDir);
  285. else
  286. lstrcpyW(temp, thisDir + 1);
  287. /* Since you can have eg. ..\.. on the path, need to expand
  288. to full information */
  289. GetFullPathNameW(temp, MAX_PATH, thisDir, NULL);
  290. /* 1. If extension supplied, see if that file exists */
  291. if (thisDir[lstrlenW(thisDir) - 1] != '\\') lstrcatW(thisDir, L"\\");
  292. lstrcatW(thisDir, stemofsearch);
  293. pos = &thisDir[lstrlenW(thisDir)]; /* Pos = end of name */
  294. /* 1. If extension supplied, see if that file exists */
  295. if (extensionsupplied) {
  296. if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
  297. found = TRUE;
  298. }
  299. }
  300. /* 2. Any .* matches? */
  301. if (!found) {
  302. HANDLE h;
  303. WIN32_FIND_DATAW finddata;
  304. lstrcatW(thisDir, L".*");
  305. h = FindFirstFileW(thisDir, &finddata);
  306. FindClose(h);
  307. if (h != INVALID_HANDLE_VALUE) {
  308. WCHAR *thisExt = pathext;
  309. /* 3. Yes - Try each path ext */
  310. while (thisExt) {
  311. WCHAR *nextExt = wcschr(thisExt, ';');
  312. if (nextExt) {
  313. memcpy(pos, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
  314. pos[(nextExt-thisExt)] = 0x00;
  315. thisExt = nextExt+1;
  316. } else {
  317. lstrcpyW(pos, thisExt);
  318. thisExt = NULL;
  319. }
  320. if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
  321. found = TRUE;
  322. thisExt = NULL;
  323. }
  324. }
  325. }
  326. }
  327. if (found) {
  328. int needed_size = lstrlenW(thisDir) + 1;
  329. *full_path = HeapAlloc(GetProcessHeap(), 0, needed_size * sizeof(WCHAR));
  330. if (*full_path)
  331. lstrcpyW(*full_path, thisDir);
  332. return TRUE;
  333. }
  334. }
  335. return FALSE;
  336. }
  337. int __cdecl wmain (int argc, WCHAR *argv[])
  338. {
  339. SHELLEXECUTEINFOW sei;
  340. DWORD creation_flags;
  341. int i;
  342. BOOL unix_mode = FALSE;
  343. BOOL progid_open = FALSE;
  344. WCHAR *title = NULL;
  345. const WCHAR *file;
  346. WCHAR *dos_filename = NULL;
  347. WCHAR *fullpath = NULL;
  348. WCHAR *parent_directory = NULL;
  349. DWORD binary_type;
  350. memset(&sei, 0, sizeof(sei));
  351. sei.cbSize = sizeof(sei);
  352. sei.lpVerb = L"open";
  353. sei.nShow = SW_SHOWNORMAL;
  354. /* Dunno what these mean, but it looks like winMe's start uses them */
  355. sei.fMask = SEE_MASK_FLAG_DDEWAIT|
  356. SEE_MASK_FLAG_NO_UI;
  357. sei.lpDirectory = NULL;
  358. creation_flags = CREATE_NEW_CONSOLE;
  359. /* Canonical Microsoft commandline flag processing:
  360. * flags start with / and are case insensitive.
  361. */
  362. for (i=1; i<argc; i++) {
  363. /* parse first quoted argument as console title */
  364. if (!title && argv[i][0] == '"') {
  365. /* it will remove at least 1 quote */
  366. int maxChars = lstrlenW(argv[1]);
  367. title = HeapAlloc(GetProcessHeap(), 0, maxChars*sizeof(WCHAR));
  368. if (title)
  369. parse_title(argv[i], title, maxChars);
  370. else {
  371. WINE_ERR("out of memory\n");
  372. ExitProcess(1);
  373. }
  374. continue;
  375. }
  376. if (argv[i][0] != '/')
  377. break;
  378. if (argv[i][1] == 'd' || argv[i][1] == 'D') {
  379. if (argv[i][2])
  380. /* The start directory was concatenated to the option */
  381. sei.lpDirectory = argv[i]+2;
  382. else if (i+1 == argc) {
  383. WINE_ERR("you must specify a directory path for the /d option\n");
  384. usage();
  385. } else
  386. sei.lpDirectory = argv[++i];
  387. }
  388. else if (is_option(argv[i], L"/b")) {
  389. creation_flags &= ~CREATE_NEW_CONSOLE;
  390. }
  391. else if (argv[i][0] == '/' && (argv[i][1] == 'i' || argv[i][1] == 'I')) {
  392. TRACE("/i is ignored\n"); /* FIXME */
  393. }
  394. else if (is_option(argv[i], L"/min")) {
  395. sei.nShow = SW_SHOWMINIMIZED;
  396. }
  397. else if (is_option(argv[i], L"/max")) {
  398. sei.nShow = SW_SHOWMAXIMIZED;
  399. }
  400. else if (is_option(argv[i], L"/low")) {
  401. creation_flags |= IDLE_PRIORITY_CLASS;
  402. }
  403. else if (is_option(argv[i], L"/normal")) {
  404. creation_flags |= NORMAL_PRIORITY_CLASS;
  405. }
  406. else if (is_option(argv[i], L"/high")) {
  407. creation_flags |= HIGH_PRIORITY_CLASS;
  408. }
  409. else if (is_option(argv[i], L"/realtime")) {
  410. creation_flags |= REALTIME_PRIORITY_CLASS;
  411. }
  412. else if (is_option(argv[i], L"/abovenormal")) {
  413. creation_flags |= ABOVE_NORMAL_PRIORITY_CLASS;
  414. }
  415. else if (is_option(argv[i], L"/belownormal")) {
  416. creation_flags |= BELOW_NORMAL_PRIORITY_CLASS;
  417. }
  418. else if (is_option(argv[i], L"/separate")) {
  419. TRACE("/separate is ignored\n"); /* FIXME */
  420. }
  421. else if (is_option(argv[i], L"/shared")) {
  422. TRACE("/shared is ignored\n"); /* FIXME */
  423. }
  424. else if (is_option(argv[i], L"/node")) {
  425. if (i+1 == argc) {
  426. WINE_ERR("you must specify a numa node for the /node option\n");
  427. usage();
  428. } else
  429. {
  430. TRACE("/node is ignored\n"); /* FIXME */
  431. i++;
  432. }
  433. }
  434. else if (is_option(argv[i], L"/affinity"))
  435. {
  436. if (i+1 == argc) {
  437. WINE_ERR("you must specify a numa node for the /node option\n");
  438. usage();
  439. } else
  440. {
  441. TRACE("/affinity is ignored\n"); /* FIXME */
  442. i++;
  443. }
  444. }
  445. else if (is_option(argv[i], L"/w") || is_option(argv[i], L"/wait")) {
  446. sei.fMask |= SEE_MASK_NOCLOSEPROCESS;
  447. }
  448. else if (is_option(argv[i], L"/?")) {
  449. usage();
  450. }
  451. /* Wine extensions */
  452. else if (is_option(argv[i], L"/unix")) {
  453. unix_mode = TRUE;
  454. i++;
  455. break;
  456. }
  457. else if (is_option(argv[i], L"/exec")) {
  458. creation_flags = 0;
  459. sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE | SEE_MASK_FLAG_NO_UI;
  460. i++;
  461. break;
  462. }
  463. else if (is_option(argv[i], L"/progIDOpen")) {
  464. progid_open = TRUE;
  465. if (++i == argc) usage();
  466. sei.lpClass = argv[i++];
  467. sei.fMask |= SEE_MASK_CLASSNAME;
  468. break;
  469. } else
  470. {
  471. WINE_ERR("Unknown option '%s'\n", wine_dbgstr_w(argv[i]));
  472. usage();
  473. }
  474. }
  475. if (i == argc) {
  476. if (progid_open || unix_mode)
  477. usage();
  478. file = L"cmd.exe";
  479. }
  480. else
  481. file = argv[i++];
  482. sei.lpParameters = build_command_line( &argv[i] );
  483. if (unix_mode || progid_open) {
  484. LPWSTR (*CDECL wine_get_dos_file_name_ptr)(LPCSTR);
  485. char* multibyte_unixpath;
  486. int multibyte_unixpath_len;
  487. wine_get_dos_file_name_ptr = (void*)GetProcAddress(GetModuleHandleA("KERNEL32"), "wine_get_dos_file_name");
  488. if (!wine_get_dos_file_name_ptr)
  489. fatal_string(STRING_UNIXFAIL);
  490. multibyte_unixpath_len = WideCharToMultiByte(CP_UNIXCP, 0, file, -1, NULL, 0, NULL, NULL);
  491. multibyte_unixpath = HeapAlloc(GetProcessHeap(), 0, multibyte_unixpath_len);
  492. WideCharToMultiByte(CP_UNIXCP, 0, file, -1, multibyte_unixpath, multibyte_unixpath_len, NULL, NULL);
  493. dos_filename = wine_get_dos_file_name_ptr(multibyte_unixpath);
  494. HeapFree(GetProcessHeap(), 0, multibyte_unixpath);
  495. if (!dos_filename)
  496. fatal_string(STRING_UNIXFAIL);
  497. sei.lpFile = dos_filename;
  498. if (!sei.lpDirectory)
  499. sei.lpDirectory = parent_directory = get_parent_dir(dos_filename);
  500. sei.fMask &= ~SEE_MASK_FLAG_NO_UI;
  501. } else {
  502. if (search_path(file, &fullpath)) {
  503. if (fullpath != NULL) {
  504. sei.lpFile = fullpath;
  505. } else {
  506. fatal_string_error(STRING_EXECFAIL, ERROR_OUTOFMEMORY, file);
  507. }
  508. } else {
  509. sei.lpFile = file;
  510. }
  511. }
  512. if (GetBinaryTypeW(sei.lpFile, &binary_type)) {
  513. WCHAR *commandline;
  514. STARTUPINFOW startup_info;
  515. PROCESS_INFORMATION process_information;
  516. /* explorer on windows always quotes the filename when running a binary on windows (see bug 5224) so we have to use CreateProcessW in this case */
  517. commandline = HeapAlloc(GetProcessHeap(), 0, (lstrlenW(sei.lpFile)+4+lstrlenW(sei.lpParameters))*sizeof(WCHAR));
  518. swprintf(commandline, lstrlenW(sei.lpFile) + 4 + lstrlenW(sei.lpParameters),
  519. L"\"%s\" %s", sei.lpFile, sei.lpParameters);
  520. ZeroMemory(&startup_info, sizeof(startup_info));
  521. startup_info.cb = sizeof(startup_info);
  522. startup_info.wShowWindow = sei.nShow;
  523. startup_info.dwFlags |= STARTF_USESHOWWINDOW;
  524. startup_info.lpTitle = title;
  525. if (!CreateProcessW(
  526. sei.lpFile, /* lpApplicationName */
  527. commandline, /* lpCommandLine */
  528. NULL, /* lpProcessAttributes */
  529. NULL, /* lpThreadAttributes */
  530. FALSE, /* bInheritHandles */
  531. creation_flags, /* dwCreationFlags */
  532. NULL, /* lpEnvironment */
  533. sei.lpDirectory, /* lpCurrentDirectory */
  534. &startup_info, /* lpStartupInfo */
  535. &process_information /* lpProcessInformation */ ))
  536. {
  537. fatal_string_error(STRING_EXECFAIL, GetLastError(), sei.lpFile);
  538. }
  539. sei.hProcess = process_information.hProcess;
  540. goto done;
  541. }
  542. if (!ShellExecuteExW(&sei))
  543. {
  544. const WCHAR *filename = sei.lpFile;
  545. DWORD size, filename_len;
  546. WCHAR *name, *env;
  547. size = GetEnvironmentVariableW(L"PATHEXT", NULL, 0);
  548. if (size)
  549. {
  550. WCHAR *start, *ptr;
  551. env = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
  552. if (!env)
  553. fatal_string_error(STRING_EXECFAIL, ERROR_OUTOFMEMORY, sei.lpFile);
  554. GetEnvironmentVariableW(L"PATHEXT", env, size);
  555. filename_len = lstrlenW(filename);
  556. name = HeapAlloc(GetProcessHeap(), 0, (filename_len + size) * sizeof(WCHAR));
  557. if (!name)
  558. fatal_string_error(STRING_EXECFAIL, ERROR_OUTOFMEMORY, sei.lpFile);
  559. sei.lpFile = name;
  560. start = env;
  561. while ((ptr = wcschr(start, ';')))
  562. {
  563. if (start == ptr)
  564. {
  565. start = ptr + 1;
  566. continue;
  567. }
  568. lstrcpyW(name, filename);
  569. memcpy(&name[filename_len], start, (ptr - start) * sizeof(WCHAR));
  570. name[filename_len + (ptr - start)] = 0;
  571. if (ShellExecuteExW(&sei))
  572. {
  573. HeapFree(GetProcessHeap(), 0, name);
  574. HeapFree(GetProcessHeap(), 0, env);
  575. goto done;
  576. }
  577. start = ptr + 1;
  578. }
  579. }
  580. fatal_string_error(STRING_EXECFAIL, GetLastError(), filename);
  581. }
  582. done:
  583. HeapFree( GetProcessHeap(), 0, dos_filename );
  584. HeapFree( GetProcessHeap(), 0, fullpath );
  585. HeapFree( GetProcessHeap(), 0, parent_directory );
  586. HeapFree( GetProcessHeap(), 0, title );
  587. if (sei.fMask & SEE_MASK_NOCLOSEPROCESS) {
  588. DWORD exitcode;
  589. SetConsoleCtrlHandler(NULL, TRUE);
  590. WaitForSingleObject(sei.hProcess, INFINITE);
  591. GetExitCodeProcess(sei.hProcess, &exitcode);
  592. ExitProcess(exitcode);
  593. }
  594. ExitProcess(0);
  595. }