1 /*
2 * Shell Library Functions
3 *
4 * Copyright 1998 Marcus Meissner
5 * Copyright 2002 Eric Pouech
6 * Copyright 2018-2024 Katayama Hirofumi MZ
7 *
8 * This library 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 library 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 library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 */
22
23 #include "precomp.h"
24 #include <undocshell.h>
25
26 WINE_DEFAULT_DEBUG_CHANNEL(exec);
27
28 EXTERN_C BOOL PathIsExeW(LPCWSTR lpszPath);
29
30 #define SEE_MASK_CLASSALL (SEE_MASK_CLASSNAME | SEE_MASK_CLASSKEY)
31
32 typedef UINT_PTR (*SHELL_ExecuteW32)(const WCHAR *lpCmd, WCHAR *env, BOOL shWait,
33 const SHELLEXECUTEINFOW *sei, LPSHELLEXECUTEINFOW sei_out);
34
35 // Is the current process a rundll32.exe?
SHELL_InRunDllProcess(VOID)36 static BOOL SHELL_InRunDllProcess(VOID)
37 {
38 WCHAR szModule[MAX_PATH];
39 static INT s_bInDllProcess = -1;
40
41 if (s_bInDllProcess != -1)
42 return s_bInDllProcess;
43
44 s_bInDllProcess = GetModuleFileNameW(NULL, szModule, _countof(szModule)) &&
45 (StrStrIW(PathFindFileNameW(szModule), L"rundll") != NULL);
46 return s_bInDllProcess;
47 }
48
ParseNoTildeEffect(PWSTR & res,LPCWSTR & args,DWORD & len,DWORD & used,int argNum)49 static void ParseNoTildeEffect(PWSTR &res, LPCWSTR &args, DWORD &len, DWORD &used, int argNum)
50 {
51 bool firstCharQuote = false;
52 bool quotes_opened = false;
53 bool backslash_encountered = false;
54
55 for (int curArg = 0; curArg <= argNum && *args; ++curArg)
56 {
57 firstCharQuote = false;
58 if (*args == '"')
59 {
60 quotes_opened = true;
61 firstCharQuote = true;
62 args++;
63 }
64
65 while(*args)
66 {
67 if (*args == '\\')
68 {
69 // if we found a backslash then flip the variable
70 backslash_encountered = !backslash_encountered;
71 }
72 else if (*args == '"')
73 {
74 if (quotes_opened)
75 {
76 if (*(args + 1) != '"')
77 {
78 quotes_opened = false;
79 args++;
80 break;
81 }
82 else
83 {
84 args++;
85 }
86 }
87 else
88 {
89 quotes_opened = true;
90 }
91
92 backslash_encountered = false;
93 }
94 else
95 {
96 backslash_encountered = false;
97 if (*args == ' ' && !firstCharQuote)
98 break;
99 }
100
101 if (curArg == argNum)
102 {
103 used++;
104 if (used < len)
105 *res++ = *args;
106 }
107
108 args++;
109 }
110
111 while(*args == ' ')
112 ++args;
113 }
114 }
115
ParseTildeEffect(PWSTR & res,LPCWSTR & args,DWORD & len,DWORD & used,int argNum)116 static void ParseTildeEffect(PWSTR &res, LPCWSTR &args, DWORD &len, DWORD &used, int argNum)
117 {
118 bool quotes_opened = false;
119 bool backslash_encountered = false;
120
121 for (int curArg = 0; curArg <= argNum && *args; ++curArg)
122 {
123 while(*args)
124 {
125 if (*args == '\\')
126 {
127 // if we found a backslash then flip the variable
128 backslash_encountered = !backslash_encountered;
129 }
130 else if (*args == '"')
131 {
132 if (quotes_opened)
133 {
134 if (*(args + 1) != '"')
135 {
136 quotes_opened = false;
137 }
138 else
139 {
140 args++;
141 }
142 }
143 else
144 {
145 quotes_opened = true;
146 }
147
148 backslash_encountered = false;
149 }
150 else
151 {
152 backslash_encountered = false;
153 if (*args == ' ' && !quotes_opened && curArg != argNum)
154 break;
155 }
156
157 if (curArg == argNum)
158 {
159 used++;
160 if (used < len)
161 *res++ = *args;
162 }
163
164 args++;
165 }
166 }
167 }
168
169 /***********************************************************************
170 * SHELL_ArgifyW [Internal]
171 *
172 * this function is supposed to expand the escape sequences found in the registry
173 * some diving reported that the following were used:
174 * + %1, %2... seem to report to parameter of index N in ShellExecute pmts
175 * %1 file
176 * %2 printer
177 * %3 driver
178 * %4 port
179 * %I address of a global item ID (explorer switch /idlist)
180 * %L seems to be %1 as long filename followed by the 8+3 variation
181 * %S ???
182 * %W Working directory
183 * %V Use either %L or %W
184 * %* all following parameters (see batfile)
185 *
186 * The way we parse the command line arguments was determined through extensive
187 * testing and can be summed up by the following rules"
188 *
189 * - %2
190 * - if first letter is " break on first non literal " and include any white spaces
191 * - if first letter is NOT " break on first " or white space
192 * - if " is opened any pair of consecutive " results in ONE literal "
193 *
194 * - %~2
195 * - use rules from here http://www.autohotkey.net/~deleyd/parameters/parameters.htm
196 */
197
SHELL_ArgifyW(WCHAR * out,DWORD len,const WCHAR * fmt,const WCHAR * lpFile,LPITEMIDLIST pidl,LPCWSTR args,DWORD * out_len,const WCHAR * lpDir)198 static BOOL SHELL_ArgifyW(WCHAR* out, DWORD len, const WCHAR* fmt, const WCHAR* lpFile, LPITEMIDLIST pidl, LPCWSTR args, DWORD* out_len, const WCHAR* lpDir)
199 {
200 BOOL done = FALSE;
201 BOOL found_p1 = FALSE;
202 PWSTR res = out;
203 DWORD used = 0;
204 bool tildeEffect = false;
205
206 TRACE("Before parsing: %p, %d, %s, %s, %p, %p\n", out, len, debugstr_w(fmt),
207 debugstr_w(lpFile), pidl, args);
208
209 while (*fmt)
210 {
211 if (*fmt == '%')
212 {
213 switch (*++fmt)
214 {
215 case '\0':
216 case '%':
217 {
218 used++;
219 if (used < len)
220 *res++ = '%';
221 };
222 break;
223
224 case '*':
225 {
226 if (args)
227 {
228 if (*fmt == '*')
229 {
230 used++;
231 while(*args)
232 {
233 used++;
234 if (used < len)
235 *res++ = *args++;
236 else
237 args++;
238 }
239 used++;
240 break;
241 }
242 }
243 };
244 break;
245
246 case '~':
247
248 case '2':
249 case '3':
250 case '4':
251 case '5':
252 case '6':
253 case '7':
254 case '8':
255 case '9':
256 //case '0':
257 {
258 if (*fmt == '~')
259 {
260 fmt++;
261 tildeEffect = true;
262 }
263
264 if (args)
265 {
266 if (tildeEffect)
267 {
268 ParseTildeEffect(res, args, len, used, *fmt - '2');
269 tildeEffect = false;
270 }
271 else
272 {
273 ParseNoTildeEffect(res, args, len, used, *fmt - '2');
274 }
275 }
276 };
277 break;
278
279 case '1':
280 if ((!done || (*fmt == '1')) && lpFile)
281 {
282 SIZE_T filelen = wcslen(lpFile);
283 used += filelen;
284 if (used < len)
285 {
286 wcscpy(res, lpFile);
287 res += filelen;
288 }
289 }
290 found_p1 = TRUE;
291 break;
292
293 /*
294 * IE uses this a lot for activating things such as windows media
295 * player. This is not verified to be fully correct but it appears
296 * to work just fine.
297 */
298 case 'l':
299 case 'L':
300 if (lpFile)
301 {
302 used += wcslen(lpFile);
303 if (used < len)
304 {
305 wcscpy(res, lpFile);
306 res += wcslen(lpFile);
307 }
308 }
309 found_p1 = TRUE;
310 break;
311
312 case 'w':
313 case 'W':
314 if (lpDir)
315 {
316 used += wcslen(lpDir);
317 if (used < len)
318 {
319 wcscpy(res, lpDir);
320 res += wcslen(lpDir);
321 }
322 }
323 break;
324
325 case 'v':
326 case 'V':
327 if (lpFile)
328 {
329 used += wcslen(lpFile);
330 if (used < len)
331 {
332 wcscpy(res, lpFile);
333 res += wcslen(lpFile);
334 }
335 found_p1 = TRUE;
336 }
337 else if (lpDir)
338 {
339 used += wcslen(lpDir);
340 if (used < len)
341 {
342 wcscpy(res, lpDir);
343 res += wcslen(lpDir);
344 }
345 }
346 break;
347
348 case 'i':
349 case 'I':
350 if (pidl)
351 {
352 DWORD chars = 0;
353 /* %p should not exceed 8, maybe 16 when looking forward to 64bit.
354 * allowing a buffer of 100 should more than exceed all needs */
355 WCHAR buf[100];
356 LPVOID pv;
357 HGLOBAL hmem = SHAllocShared(pidl, ILGetSize(pidl), 0);
358 pv = SHLockShared(hmem, 0);
359 chars = swprintf(buf, L":%p", pv);
360
361 if (chars >= ARRAY_SIZE(buf))
362 ERR("pidl format buffer too small!\n");
363
364 used += chars;
365
366 if (used < len)
367 {
368 wcscpy(res, buf);
369 res += chars;
370 }
371 SHUnlockShared(pv);
372 }
373 found_p1 = TRUE;
374 break;
375
376 default:
377 /*
378 * Check if this is an env-variable here...
379 */
380
381 /* Make sure that we have at least one more %.*/
382 if (strchrW(fmt, '%'))
383 {
384 WCHAR tmpBuffer[1024];
385 PWSTR tmpB = tmpBuffer;
386 WCHAR tmpEnvBuff[MAX_PATH];
387 DWORD envRet;
388
389 while (*fmt != '%')
390 *tmpB++ = *fmt++;
391 *tmpB++ = 0;
392
393 TRACE("Checking %s to be an env-var\n", debugstr_w(tmpBuffer));
394
395 envRet = GetEnvironmentVariableW(tmpBuffer, tmpEnvBuff, MAX_PATH);
396 if (envRet == 0 || envRet > MAX_PATH)
397 {
398 used += wcslen(tmpBuffer);
399 if (used < len)
400 {
401 wcscpy( res, tmpBuffer );
402 res += wcslen(tmpBuffer);
403 }
404 }
405 else
406 {
407 used += wcslen(tmpEnvBuff);
408 if (used < len)
409 {
410 wcscpy( res, tmpEnvBuff );
411 res += wcslen(tmpEnvBuff);
412 }
413 }
414 }
415 done = TRUE;
416 break;
417 }
418 /* Don't skip past terminator (catch a single '%' at the end) */
419 if (*fmt != '\0')
420 {
421 fmt++;
422 }
423 }
424 else
425 {
426 used ++;
427 if (used < len)
428 *res++ = *fmt++;
429 else
430 fmt++;
431 }
432 }
433
434 used++;
435 if (res - out < static_cast<int>(len))
436 *res = '\0';
437 else
438 out[len-1] = '\0';
439
440 TRACE("used %i of %i space\n", used, len);
441 if (out_len)
442 *out_len = used;
443
444 TRACE("After parsing: %p, %d, %s, %s, %p, %p\n", out, len, debugstr_w(fmt),
445 debugstr_w(lpFile), pidl, args);
446
447 return found_p1;
448 }
449
SHELL_GetPathFromIDListForExecuteW(LPCITEMIDLIST pidl,LPWSTR pszPath,UINT uOutSize)450 static HRESULT SHELL_GetPathFromIDListForExecuteW(LPCITEMIDLIST pidl, LPWSTR pszPath, UINT uOutSize)
451 {
452 STRRET strret;
453 CComPtr<IShellFolder> desktop;
454
455 HRESULT hr = SHGetDesktopFolder(&desktop);
456
457 if (SUCCEEDED(hr))
458 {
459 hr = desktop->GetDisplayNameOf(pidl, SHGDN_FORPARSING, &strret);
460
461 if (SUCCEEDED(hr))
462 StrRetToStrNW(pszPath, uOutSize, &strret, pidl);
463 }
464
465 return hr;
466 }
467
468 /*************************************************************************
469 * SHELL_ExecuteW [Internal]
470 *
471 */
SHELL_ExecuteW(const WCHAR * lpCmd,WCHAR * env,BOOL shWait,const SHELLEXECUTEINFOW * psei,LPSHELLEXECUTEINFOW psei_out)472 static UINT_PTR SHELL_ExecuteW(const WCHAR *lpCmd, WCHAR *env, BOOL shWait,
473 const SHELLEXECUTEINFOW *psei, LPSHELLEXECUTEINFOW psei_out)
474 {
475 STARTUPINFOW startup;
476 PROCESS_INFORMATION info;
477 UINT_PTR retval = SE_ERR_NOASSOC;
478 UINT gcdret = 0;
479 WCHAR curdir[MAX_PATH];
480 DWORD dwCreationFlags;
481 const WCHAR *lpDirectory = NULL;
482
483 TRACE("Execute %s from directory %s\n", debugstr_w(lpCmd), debugstr_w(psei->lpDirectory));
484
485 /* make sure we don't fail the CreateProcess if the calling app passes in
486 * a bad working directory */
487 if (!StrIsNullOrEmpty(psei->lpDirectory))
488 {
489 DWORD attr = GetFileAttributesW(psei->lpDirectory);
490 if (attr != INVALID_FILE_ATTRIBUTES && attr & FILE_ATTRIBUTE_DIRECTORY)
491 lpDirectory = psei->lpDirectory;
492 }
493
494 /* ShellExecute specifies the command from psei->lpDirectory
495 * if present. Not from the current dir as CreateProcess does */
496 if (lpDirectory)
497 if ((gcdret = GetCurrentDirectoryW( MAX_PATH, curdir)))
498 if (!SetCurrentDirectoryW( lpDirectory))
499 ERR("cannot set directory %s\n", debugstr_w(lpDirectory));
500
501 ZeroMemory(&startup, sizeof(STARTUPINFOW));
502 startup.cb = sizeof(STARTUPINFOW);
503 startup.dwFlags = STARTF_USESHOWWINDOW;
504 startup.wShowWindow = psei->nShow;
505 dwCreationFlags = CREATE_UNICODE_ENVIRONMENT;
506 if (!(psei->fMask & SEE_MASK_NO_CONSOLE))
507 dwCreationFlags |= CREATE_NEW_CONSOLE;
508 if (psei->fMask & SEE_MASK_FLAG_SEPVDM)
509 dwCreationFlags |= CREATE_SEPARATE_WOW_VDM;
510 startup.lpTitle = (LPWSTR)(psei->fMask & (SEE_MASK_HASLINKNAME | SEE_MASK_HASTITLE) ? psei->lpClass : NULL);
511
512 if (psei->fMask & SEE_MASK_HASLINKNAME)
513 startup.dwFlags |= STARTF_TITLEISLINKNAME;
514
515 if (CreateProcessW(NULL, (LPWSTR)lpCmd, NULL, NULL, FALSE, dwCreationFlags, env,
516 lpDirectory, &startup, &info))
517 {
518 /* Give 30 seconds to the app to come up, if desired. Probably only needed
519 when starting app immediately before making a DDE connection. */
520 if (shWait)
521 if (WaitForInputIdle(info.hProcess, 30000) == WAIT_FAILED)
522 WARN("WaitForInputIdle failed: Error %d\n", GetLastError() );
523 retval = 33;
524
525 if (psei->fMask & SEE_MASK_NOCLOSEPROCESS)
526 psei_out->hProcess = info.hProcess;
527 else
528 CloseHandle( info.hProcess );
529 CloseHandle( info.hThread );
530 }
531 else if ((retval = GetLastError()) >= 32)
532 {
533 WARN("CreateProcess returned error %ld\n", retval);
534 retval = ERROR_BAD_FORMAT;
535 }
536
537 TRACE("returning %lu\n", retval);
538
539 psei_out->hInstApp = (HINSTANCE)retval;
540
541 if (gcdret)
542 if (!SetCurrentDirectoryW(curdir))
543 ERR("cannot return to directory %s\n", debugstr_w(curdir));
544
545 return retval;
546 }
547
548
549 /***********************************************************************
550 * SHELL_BuildEnvW [Internal]
551 *
552 * Build the environment for the new process, adding the specified
553 * path to the PATH variable. Returned pointer must be freed by caller.
554 */
SHELL_BuildEnvW(const WCHAR * path)555 static LPWSTR SHELL_BuildEnvW( const WCHAR *path )
556 {
557 CHeapPtr<WCHAR, CLocalAllocator> new_env;
558 WCHAR *strings, *p, *p2;
559 int total = wcslen(path) + 1;
560 BOOL got_path = FALSE;
561
562 if (!(strings = GetEnvironmentStringsW())) return NULL;
563 p = strings;
564 while (*p)
565 {
566 int len = wcslen(p) + 1;
567 if (!_wcsnicmp( p, L"PATH=", 5 )) got_path = TRUE;
568 total += len;
569 p += len;
570 }
571 if (!got_path) total += 5; /* we need to create PATH */
572 total++; /* terminating null */
573
574 if (!new_env.Allocate(total))
575 {
576 FreeEnvironmentStringsW(strings);
577 return NULL;
578 }
579 p = strings;
580 p2 = new_env;
581 while (*p)
582 {
583 int len = wcslen(p) + 1;
584 memcpy(p2, p, len * sizeof(WCHAR));
585 if (!_wcsnicmp( p, L"PATH=", 5 ))
586 {
587 p2[len - 1] = ';';
588 wcscpy( p2 + len, path );
589 p2 += wcslen(path) + 1;
590 }
591 p += len;
592 p2 += len;
593 }
594 if (!got_path)
595 {
596 wcscpy(p2, L"PATH=");
597 wcscat(p2, path);
598 p2 += wcslen(p2) + 1;
599 }
600 *p2 = 0;
601 FreeEnvironmentStringsW(strings);
602 return new_env.Detach();
603 }
604
605 /***********************************************************************
606 * SHELL_TryAppPathW [Internal]
607 *
608 * Helper function for SHELL_FindExecutable
609 * @param lpResult - pointer to a buffer of size MAX_PATH
610 * On entry: szName is a filename (probably without path separators).
611 * On exit: if szName found in "App Path", place full path in lpResult, and return true
612 */
SHELL_TryAppPathW(LPCWSTR szName,LPWSTR lpResult,WCHAR ** env)613 static BOOL SHELL_TryAppPathW( LPCWSTR szName, LPWSTR lpResult, WCHAR **env)
614 {
615 HKEY hkApp = NULL;
616 WCHAR buffer[1024];
617 DWORD len, dwType;
618 LONG res;
619 BOOL found = FALSE;
620
621 if (env) *env = NULL;
622 wcscpy(buffer, L"Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\");
623 wcscat(buffer, szName);
624 res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, buffer, 0, KEY_READ, &hkApp);
625 if (res)
626 {
627 // Add ".exe" extension, if extension does not exists
628 if (PathAddExtensionW(buffer, L".exe"))
629 {
630 res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, buffer, 0, KEY_READ, &hkApp);
631 }
632 if (res) goto end;
633 }
634
635 len = MAX_PATH * sizeof(WCHAR);
636 res = SHRegQueryValueExW(hkApp, NULL, NULL, &dwType, (LPBYTE)lpResult, &len);
637 if (res != ERROR_SUCCESS || dwType != REG_SZ)
638 goto end;
639
640 found = TRUE;
641
642 if (env)
643 {
644 len = sizeof(buffer);
645 res = SHRegQueryValueExW(hkApp, L"Path", NULL, &dwType, (LPBYTE)buffer, &len);
646 if (res == ERROR_SUCCESS && dwType == REG_SZ && buffer[0])
647 *env = SHELL_BuildEnvW(buffer);
648 }
649
650 end:
651 if (hkApp) RegCloseKey(hkApp);
652 return found;
653 }
654
655 /*************************************************************************
656 * SHELL_FindExecutableByVerb [Internal]
657 *
658 * called from SHELL_FindExecutable or SHELL_execute_class
659 * in/out:
660 * classname a buffer, big enough, to get the key name to do actually the
661 * command "WordPad.Document.1\\shell\\open\\command"
662 * passed as "WordPad.Document.1"
663 * in:
664 * lpVerb the operation on it (open)
665 * commandlen the size of command buffer (in bytes)
666 * out:
667 * command a buffer, to store the command to do the
668 * operation on the file
669 * key a buffer, big enough, to get the key name to do actually the
670 * command "WordPad.Document.1\\shell\\open\\command"
671 * Can be NULL
672 */
SHELL_FindExecutableByVerb(LPCWSTR lpVerb,LPWSTR key,LPWSTR classname,LPWSTR command,LONG commandlen)673 static UINT SHELL_FindExecutableByVerb(LPCWSTR lpVerb, LPWSTR key, LPWSTR classname, LPWSTR command, LONG commandlen)
674 {
675 HKEY hkeyClass;
676 WCHAR verb[MAX_PATH];
677
678 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, classname, 0, 0x02000000, &hkeyClass))
679 return SE_ERR_NOASSOC;
680 if (!HCR_GetDefaultVerbW(hkeyClass, lpVerb, verb, ARRAY_SIZE(verb)))
681 return SE_ERR_NOASSOC;
682 RegCloseKey(hkeyClass);
683
684 /* Looking for ...buffer\shell\<verb>\command */
685 wcscat(classname, L"\\shell\\");
686 wcscat(classname, verb);
687 wcscat(classname, L"\\command");
688
689 if (RegQueryValueW(HKEY_CLASSES_ROOT, classname, command,
690 &commandlen) == ERROR_SUCCESS)
691 {
692 commandlen /= sizeof(WCHAR);
693 if (key) wcscpy(key, classname);
694 #if 0
695 LPWSTR tmp;
696 WCHAR param[256];
697 LONG paramlen = sizeof(param);
698
699 /* FIXME: it seems all Windows version don't behave the same here.
700 * the doc states that this ddeexec information can be found after
701 * the exec names.
702 * on Win98, it doesn't appear, but I think it does on Win2k
703 */
704 /* Get the parameters needed by the application
705 from the associated ddeexec key */
706 tmp = strstrW(classname, L"\\command");
707 tmp[0] = '\0';
708 wcscat(classname, wDdeexec);
709 if (RegQueryValueW(HKEY_CLASSES_ROOT, classname, param,
710 ¶mlen) == ERROR_SUCCESS)
711 {
712 paramlen /= sizeof(WCHAR);
713 wcscat(command, L" ");
714 wcscat(command, param);
715 commandlen += paramlen;
716 }
717 #endif
718
719 command[commandlen] = '\0';
720
721 return 33; /* FIXME see SHELL_FindExecutable() */
722 }
723
724 return SE_ERR_NOASSOC;
725 }
726
727 /*************************************************************************
728 * SHELL_FindExecutable [Internal]
729 *
730 * Utility for code sharing between FindExecutable and ShellExecute
731 * in:
732 * lpFile the name of a file
733 * lpVerb the operation on it (open)
734 * out:
735 * lpResult a buffer, big enough :-(, to store the command to do the
736 * operation on the file
737 * key a buffer, big enough, to get the key name to do actually the
738 * command (it'll be used afterwards for more information
739 * on the operation)
740 */
SHELL_FindExecutable(LPCWSTR lpPath,LPCWSTR lpFile,LPCWSTR lpVerb,LPWSTR lpResult,DWORD resultLen,LPWSTR key,WCHAR ** env,LPITEMIDLIST pidl,LPCWSTR args)741 static UINT SHELL_FindExecutable(LPCWSTR lpPath, LPCWSTR lpFile, LPCWSTR lpVerb,
742 LPWSTR lpResult, DWORD resultLen, LPWSTR key, WCHAR **env, LPITEMIDLIST pidl, LPCWSTR args)
743 {
744 WCHAR *extension = NULL; /* pointer to file extension */
745 WCHAR classname[256]; /* registry name for this file type */
746 LONG classnamelen = sizeof(classname); /* length of above */
747 WCHAR command[1024]; /* command from registry */
748 WCHAR wBuffer[256]; /* Used to GetProfileString */
749 UINT retval = SE_ERR_NOASSOC;
750 WCHAR *tok; /* token pointer */
751 WCHAR xlpFile[MAX_PATH]; /* result of PathResolve */
752 DWORD attribs; /* file attributes */
753 WCHAR curdir[MAX_PATH];
754 const WCHAR *search_paths[3] = {0};
755
756 TRACE("%s\n", debugstr_w(lpFile));
757
758 if (!lpResult)
759 return ERROR_INVALID_PARAMETER;
760
761 xlpFile[0] = '\0';
762 lpResult[0] = '\0'; /* Start off with an empty return string */
763 if (key) *key = '\0';
764
765 /* trap NULL parameters on entry */
766 if (!lpFile)
767 {
768 WARN("(lpFile=%s,lpResult=%s): NULL parameter\n",
769 debugstr_w(lpFile), debugstr_w(lpResult));
770 return ERROR_FILE_NOT_FOUND; /* File not found. Close enough, I guess. */
771 }
772
773 if (SHELL_TryAppPathW( lpFile, lpResult, env ))
774 {
775 TRACE("found %s via App Paths\n", debugstr_w(lpResult));
776 return 33;
777 }
778
779 GetCurrentDirectoryW(ARRAY_SIZE(curdir), curdir);
780 if (lpPath && *lpPath)
781 {
782 search_paths[0] = lpPath;
783 search_paths[1] = curdir;
784 }
785 else
786 {
787 search_paths[0] = curdir;
788 }
789
790 lstrcpyW(xlpFile, lpFile);
791 if (PathResolveW(xlpFile, search_paths, PRF_TRYPROGRAMEXTENSIONS | PRF_VERIFYEXISTS) ||
792 PathFindOnPathW(xlpFile, search_paths))
793 {
794 TRACE("PathResolveW returned non-zero\n");
795 lpFile = xlpFile;
796 PathRemoveBlanksW(xlpFile);
797
798 /* Clear any trailing periods */
799 SIZE_T i = wcslen(xlpFile);
800 while (i > 0 && xlpFile[i - 1] == '.')
801 {
802 xlpFile[--i] = '\0';
803 }
804
805 lstrcpyW(lpResult, xlpFile);
806 /* The file was found in lpPath or one of the directories in the system-wide search path */
807 }
808 else
809 {
810 xlpFile[0] = '\0';
811 }
812
813 attribs = GetFileAttributesW(lpFile);
814 if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY))
815 {
816 wcscpy(classname, L"Folder");
817 }
818 else
819 {
820 /* Did we get something? Anything? */
821 if (xlpFile[0] == 0)
822 {
823 TRACE("Returning SE_ERR_FNF\n");
824 return SE_ERR_FNF;
825 }
826 /* First thing we need is the file's extension */
827 extension = wcsrchr(xlpFile, '.'); /* Assume last "." is the one; */
828 /* File->Run in progman uses */
829 /* .\FILE.EXE :( */
830 TRACE("xlpFile=%s,extension=%s\n", debugstr_w(xlpFile), debugstr_w(extension));
831
832 if (extension == NULL || extension[1] == 0)
833 {
834 WARN("Returning SE_ERR_NOASSOC\n");
835 return SE_ERR_NOASSOC;
836 }
837
838 /* Three places to check: */
839 /* 1. win.ini, [windows], programs (NB no leading '.') */
840 /* 2. Registry, HKEY_CLASS_ROOT\<classname>\shell\open\command */
841 /* 3. win.ini, [extensions], extension (NB no leading '.' */
842 /* All I know of the order is that registry is checked before */
843 /* extensions; however, it'd make sense to check the programs */
844 /* section first, so that's what happens here. */
845
846 /* See if it's a program - if GetProfileString fails, we skip this
847 * section. Actually, if GetProfileString fails, we've probably
848 * got a lot more to worry about than running a program... */
849 if (GetProfileStringW(L"windows", L"programs", L"exe pif bat cmd com", wBuffer, ARRAY_SIZE(wBuffer)) > 0)
850 {
851 CharLowerW(wBuffer);
852 tok = wBuffer;
853 while (*tok)
854 {
855 WCHAR *p = tok;
856 while (*p && *p != ' ' && *p != '\t') p++;
857 if (*p)
858 {
859 *p++ = 0;
860 while (*p == ' ' || *p == '\t') p++;
861 }
862
863 if (_wcsicmp(tok, &extension[1]) == 0) /* have to skip the leading "." */
864 {
865 wcscpy(lpResult, xlpFile);
866 /* Need to perhaps check that the file has a path
867 * attached */
868 TRACE("found %s\n", debugstr_w(lpResult));
869 return 33;
870 /* Greater than 32 to indicate success */
871 }
872 tok = p;
873 }
874 }
875
876 /* Check registry */
877 if (RegQueryValueW(HKEY_CLASSES_ROOT, extension, classname,
878 &classnamelen) == ERROR_SUCCESS)
879 {
880 classnamelen /= sizeof(WCHAR);
881 if (classnamelen == ARRAY_SIZE(classname))
882 classnamelen--;
883
884 classname[classnamelen] = '\0';
885 TRACE("File type: %s\n", debugstr_w(classname));
886 }
887 else
888 {
889 *classname = '\0';
890 }
891 }
892
893 if (*classname)
894 {
895 /* pass the verb string to SHELL_FindExecutableByVerb() */
896 retval = SHELL_FindExecutableByVerb(lpVerb, key, classname, command, sizeof(command));
897
898 if (retval > 32)
899 {
900 DWORD finishedLen;
901 SHELL_ArgifyW(lpResult, resultLen, command, xlpFile, pidl, args, &finishedLen, lpPath);
902 if (finishedLen > resultLen)
903 ERR("Argify buffer not large enough.. truncated\n");
904 /* Remove double quotation marks and command line arguments */
905 if (*lpResult == '"')
906 {
907 WCHAR *p = lpResult;
908 while (*(p + 1) != '"')
909 {
910 *p = *(p + 1);
911 p++;
912 }
913 *p = '\0';
914 }
915 else
916 {
917 /* Truncate on first space */
918 WCHAR *p = lpResult;
919 while (*p != ' ' && *p != '\0')
920 p++;
921 *p = '\0';
922 }
923 }
924 }
925 else /* Check win.ini */
926 {
927 /* Toss the leading dot */
928 extension++;
929 if (GetProfileStringW(L"extensions", extension, L"", command, ARRAY_SIZE(command)) > 0)
930 {
931 if (wcslen(command) != 0)
932 {
933 wcscpy(lpResult, command);
934 tok = wcschr(lpResult, '^'); /* should be ^.extension? */
935 if (tok != NULL)
936 {
937 tok[0] = '\0';
938 wcscat(lpResult, xlpFile); /* what if no dir in xlpFile? */
939 tok = wcschr(command, '^'); /* see above */
940 if ((tok != NULL) && (wcslen(tok) > 5))
941 {
942 wcscat(lpResult, &tok[5]);
943 }
944 }
945 retval = 33; /* FIXME - see above */
946 }
947 }
948 }
949
950 TRACE("returning path %s, retval %d\n", debugstr_w(lpResult), retval);
951 return retval;
952 }
953
954 /******************************************************************
955 * dde_cb
956 *
957 * callback for the DDE connection. not really useful
958 */
dde_cb(UINT uType,UINT uFmt,HCONV hConv,HSZ hsz1,HSZ hsz2,HDDEDATA hData,ULONG_PTR dwData1,ULONG_PTR dwData2)959 static HDDEDATA CALLBACK dde_cb(UINT uType, UINT uFmt, HCONV hConv,
960 HSZ hsz1, HSZ hsz2, HDDEDATA hData,
961 ULONG_PTR dwData1, ULONG_PTR dwData2)
962 {
963 TRACE("dde_cb: %04x, %04x, %p, %p, %p, %p, %08lx, %08lx\n",
964 uType, uFmt, hConv, hsz1, hsz2, hData, dwData1, dwData2);
965 return NULL;
966 }
967
968 /******************************************************************
969 * dde_connect
970 *
971 * ShellExecute helper. Used to do an operation with a DDE connection
972 *
973 * Handles both the direct connection (try #1), and if it fails,
974 * launching an application and trying (#2) to connect to it
975 *
976 */
dde_connect(const WCHAR * key,const WCHAR * start,WCHAR * ddeexec,const WCHAR * lpFile,WCHAR * env,LPCWSTR szCommandline,LPITEMIDLIST pidl,SHELL_ExecuteW32 execfunc,const SHELLEXECUTEINFOW * psei,LPSHELLEXECUTEINFOW psei_out)977 static unsigned dde_connect(const WCHAR* key, const WCHAR* start, WCHAR* ddeexec,
978 const WCHAR* lpFile, WCHAR *env,
979 LPCWSTR szCommandline, LPITEMIDLIST pidl, SHELL_ExecuteW32 execfunc,
980 const SHELLEXECUTEINFOW *psei, LPSHELLEXECUTEINFOW psei_out)
981 {
982 WCHAR regkey[256];
983 WCHAR * endkey = regkey + wcslen(key);
984 WCHAR app[256], topic[256], ifexec[256], static_res[256];
985 CHeapPtr<WCHAR, CLocalAllocator> dynamic_res;
986 WCHAR * res;
987 LONG applen, topiclen, ifexeclen;
988 WCHAR * exec;
989 DWORD ddeInst = 0;
990 DWORD tid;
991 DWORD resultLen, endkeyLen;
992 HSZ hszApp, hszTopic;
993 HCONV hConv;
994 HDDEDATA hDdeData;
995 unsigned ret = SE_ERR_NOASSOC;
996 BOOL unicode = !(GetVersion() & 0x80000000);
997
998 if (strlenW(key) + 1 > ARRAY_SIZE(regkey))
999 {
1000 FIXME("input parameter %s larger than buffer\n", debugstr_w(key));
1001 return 2;
1002 }
1003 wcscpy(regkey, key);
1004 endkeyLen = ARRAY_SIZE(regkey) - (endkey - regkey);
1005 if (strlenW(L"\\application") + 1 > endkeyLen)
1006 {
1007 FIXME("endkey %s overruns buffer\n", debugstr_w(L"\\application"));
1008 return 2;
1009 }
1010 wcscpy(endkey, L"\\application");
1011 applen = sizeof(app);
1012 if (RegQueryValueW(HKEY_CLASSES_ROOT, regkey, app, &applen) != ERROR_SUCCESS)
1013 {
1014 WCHAR command[1024], fullpath[MAX_PATH];
1015 LPWSTR ptr = NULL;
1016 DWORD ret = 0;
1017
1018 /* Get application command from start string and find filename of application */
1019 if (*start == '"')
1020 {
1021 if (strlenW(start + 1) + 1 > ARRAY_SIZE(command))
1022 {
1023 FIXME("size of input parameter %s larger than buffer\n",
1024 debugstr_w(start + 1));
1025 return 2;
1026 }
1027 wcscpy(command, start + 1);
1028 if ((ptr = wcschr(command, '"')))
1029 * ptr = 0;
1030 ret = SearchPathW(NULL, command, L".exe", ARRAY_SIZE(fullpath), fullpath, &ptr);
1031 }
1032 else
1033 {
1034 LPCWSTR p;
1035 LPWSTR space;
1036 for (p = start; (space = const_cast<LPWSTR>(strchrW(p, ' '))); p = space + 1)
1037 {
1038 int idx = space - start;
1039 memcpy(command, start, idx * sizeof(WCHAR));
1040 command[idx] = '\0';
1041 if ((ret = SearchPathW(NULL, command, L".exe", ARRAY_SIZE(fullpath), fullpath, &ptr)))
1042 break;
1043 }
1044 if (!ret)
1045 ret = SearchPathW(NULL, start, L".exe", ARRAY_SIZE(fullpath), fullpath, &ptr);
1046 }
1047
1048 if (!ret)
1049 {
1050 ERR("Unable to find application path for command %s\n", debugstr_w(start));
1051 return ERROR_ACCESS_DENIED;
1052 }
1053 if (strlenW(ptr) + 1 > ARRAY_SIZE(app))
1054 {
1055 FIXME("size of found path %s larger than buffer\n", debugstr_w(ptr));
1056 return 2;
1057 }
1058 wcscpy(app, ptr);
1059
1060 /* Remove extensions (including .so) */
1061 ptr = app + wcslen(app) - 3;
1062 if (ptr > app && !wcscmp(ptr, L".so"))
1063 *ptr = 0;
1064
1065 ptr = const_cast<LPWSTR>(strrchrW(app, '.'));
1066 assert(ptr);
1067 *ptr = 0;
1068 }
1069
1070 if (strlenW(L"\\topic") + 1 > endkeyLen)
1071 {
1072 FIXME("endkey %s overruns buffer\n", debugstr_w(L"\\topic"));
1073 return 2;
1074 }
1075 wcscpy(endkey, L"\\topic");
1076 topiclen = sizeof(topic);
1077 if (RegQueryValueW(HKEY_CLASSES_ROOT, regkey, topic, &topiclen) != ERROR_SUCCESS)
1078 {
1079 wcscpy(topic, L"System");
1080 }
1081
1082 if (unicode)
1083 {
1084 if (DdeInitializeW(&ddeInst, dde_cb, APPCMD_CLIENTONLY, 0L) != DMLERR_NO_ERROR)
1085 return 2;
1086 }
1087 else
1088 {
1089 if (DdeInitializeA(&ddeInst, dde_cb, APPCMD_CLIENTONLY, 0L) != DMLERR_NO_ERROR)
1090 return 2;
1091 }
1092
1093 hszApp = DdeCreateStringHandleW(ddeInst, app, CP_WINUNICODE);
1094 hszTopic = DdeCreateStringHandleW(ddeInst, topic, CP_WINUNICODE);
1095
1096 hConv = DdeConnect(ddeInst, hszApp, hszTopic, NULL);
1097 exec = ddeexec;
1098 if (!hConv)
1099 {
1100 TRACE("Launching %s\n", debugstr_w(start));
1101 ret = execfunc(start, env, TRUE, psei, psei_out);
1102 if (ret <= 32)
1103 {
1104 TRACE("Couldn't launch\n");
1105 goto error;
1106 }
1107 /* if ddeexec is NULL, then we just need to exit here */
1108 if (ddeexec == NULL)
1109 {
1110 TRACE("Exiting because ddeexec is NULL. ret=42.\n");
1111 /* See https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutew */
1112 /* for reason why we use 42 here and also "Shell32_apitest ShellExecuteW" regression test */
1113 return 42;
1114 }
1115 /* if ddeexec is 'empty string', then we just need to exit here */
1116 if (wcscmp(ddeexec, L"") == 0)
1117 {
1118 TRACE("Exiting because ddeexec is 'empty string'. ret=42.\n");
1119 /* See https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutew */
1120 /* for reason why we use 42 here and also "Shell32_apitest ShellExecuteW" regression test */
1121 return 42;
1122 }
1123 hConv = DdeConnect(ddeInst, hszApp, hszTopic, NULL);
1124 if (!hConv)
1125 {
1126 TRACE("Couldn't connect. ret=%d\n", ret);
1127 DdeUninitialize(ddeInst);
1128 SetLastError(ERROR_DDE_FAIL);
1129 return 30; /* whatever */
1130 }
1131 if (strlenW(L"\\ifexec") + 1 > endkeyLen)
1132 {
1133 FIXME("endkey %s overruns buffer\n", debugstr_w(L"\\ifexec"));
1134 return 2;
1135 }
1136 strcpyW(endkey, L"\\ifexec");
1137 ifexeclen = sizeof(ifexec);
1138 if (RegQueryValueW(HKEY_CLASSES_ROOT, regkey, ifexec, &ifexeclen) == ERROR_SUCCESS)
1139 {
1140 exec = ifexec;
1141 }
1142 }
1143
1144 SHELL_ArgifyW(static_res, ARRAY_SIZE(static_res), exec, lpFile, pidl, szCommandline, &resultLen, NULL);
1145 if (resultLen > ARRAY_SIZE(static_res))
1146 {
1147 dynamic_res.Allocate(resultLen);
1148 res = dynamic_res;
1149 SHELL_ArgifyW(dynamic_res, resultLen, exec, lpFile, pidl, szCommandline, NULL, NULL);
1150 }
1151 else
1152 res = static_res;
1153 TRACE("%s %s => %s\n", debugstr_w(exec), debugstr_w(lpFile), debugstr_w(res));
1154
1155 /* It's documented in the KB 330337 that IE has a bug and returns
1156 * error DMLERR_NOTPROCESSED on XTYP_EXECUTE request.
1157 */
1158 if (unicode)
1159 hDdeData = DdeClientTransaction((LPBYTE)res, (strlenW(res) + 1) * sizeof(WCHAR), hConv, 0L, 0, XTYP_EXECUTE, 30000, &tid);
1160 else
1161 {
1162 DWORD lenA = WideCharToMultiByte(CP_ACP, 0, res, -1, NULL, 0, NULL, NULL);
1163 CHeapPtr<char, CLocalAllocator> resA;
1164 resA.Allocate(lenA);
1165 WideCharToMultiByte(CP_ACP, 0, res, -1, resA, lenA, NULL, NULL);
1166 hDdeData = DdeClientTransaction( (LPBYTE)(LPSTR)resA, lenA, hConv, 0L, 0,
1167 XTYP_EXECUTE, 10000, &tid );
1168 }
1169 if (hDdeData)
1170 DdeFreeDataHandle(hDdeData);
1171 else
1172 WARN("DdeClientTransaction failed with error %04x\n", DdeGetLastError(ddeInst));
1173 ret = 33;
1174
1175 DdeDisconnect(hConv);
1176
1177 error:
1178 DdeUninitialize(ddeInst);
1179
1180 return ret;
1181 }
1182
1183 /*************************************************************************
1184 * execute_from_key [Internal]
1185 */
execute_from_key(LPCWSTR key,LPCWSTR lpFile,WCHAR * env,LPCWSTR szCommandline,LPCWSTR executable_name,SHELL_ExecuteW32 execfunc,LPSHELLEXECUTEINFOW psei,LPSHELLEXECUTEINFOW psei_out)1186 static UINT_PTR execute_from_key(LPCWSTR key, LPCWSTR lpFile, WCHAR *env,
1187 LPCWSTR szCommandline, LPCWSTR executable_name,
1188 SHELL_ExecuteW32 execfunc,
1189 LPSHELLEXECUTEINFOW psei, LPSHELLEXECUTEINFOW psei_out)
1190 {
1191 WCHAR cmd[256], param[1024], ddeexec[256];
1192 DWORD cmdlen = sizeof(cmd), ddeexeclen = sizeof(ddeexec);
1193 UINT_PTR retval = SE_ERR_NOASSOC;
1194 DWORD resultLen;
1195 LPWSTR tmp;
1196
1197 TRACE("%s %s %s %s %s\n", debugstr_w(key), debugstr_w(lpFile), debugstr_w(env),
1198 debugstr_w(szCommandline), debugstr_w(executable_name));
1199
1200 cmd[0] = '\0';
1201 param[0] = '\0';
1202
1203 /* Get the application from the registry */
1204 if (RegQueryValueW(HKEY_CLASSES_ROOT, key, cmd, (LONG *)&cmdlen) == ERROR_SUCCESS)
1205 {
1206 TRACE("got cmd: %s\n", debugstr_w(cmd));
1207
1208 /* Is there a replace() function anywhere? */
1209 cmdlen /= sizeof(WCHAR);
1210 if (cmdlen >= ARRAY_SIZE(cmd))
1211 cmdlen = ARRAY_SIZE(cmd) - 1;
1212 cmd[cmdlen] = '\0';
1213 SHELL_ArgifyW(param, ARRAY_SIZE(param), cmd, lpFile, (LPITEMIDLIST)psei->lpIDList, szCommandline, &resultLen,
1214 (psei->lpDirectory && *psei->lpDirectory) ? psei->lpDirectory : NULL);
1215 if (resultLen > ARRAY_SIZE(param))
1216 ERR("Argify buffer not large enough, truncating\n");
1217 }
1218
1219 /* Get the parameters needed by the application
1220 from the associated ddeexec key */
1221 tmp = const_cast<LPWSTR>(strstrW(key, L"command"));
1222 assert(tmp);
1223 wcscpy(tmp, L"ddeexec");
1224
1225 if (RegQueryValueW(HKEY_CLASSES_ROOT, key, ddeexec, (LONG *)&ddeexeclen) == ERROR_SUCCESS)
1226 {
1227 TRACE("Got ddeexec %s => %s\n", debugstr_w(key), debugstr_w(ddeexec));
1228 if (!param[0]) strcpyW(param, executable_name);
1229 retval = dde_connect(key, param, ddeexec, lpFile, env, szCommandline, (LPITEMIDLIST)psei->lpIDList, execfunc, psei, psei_out);
1230 }
1231 else if (param[0])
1232 {
1233 TRACE("executing: %s\n", debugstr_w(param));
1234 retval = execfunc(param, env, FALSE, psei, psei_out);
1235 }
1236 else
1237 WARN("Nothing appropriate found for %s\n", debugstr_w(key));
1238
1239 return retval;
1240 }
1241
1242 /*************************************************************************
1243 * FindExecutableA [SHELL32.@]
1244 */
FindExecutableA(LPCSTR lpFile,LPCSTR lpDirectory,LPSTR lpResult)1245 HINSTANCE WINAPI FindExecutableA(LPCSTR lpFile, LPCSTR lpDirectory, LPSTR lpResult)
1246 {
1247 HINSTANCE retval;
1248 WCHAR *wFile = NULL, *wDirectory = NULL;
1249 WCHAR wResult[MAX_PATH];
1250
1251 if (lpFile) __SHCloneStrAtoW(&wFile, lpFile);
1252 if (lpDirectory) __SHCloneStrAtoW(&wDirectory, lpDirectory);
1253
1254 retval = FindExecutableW(wFile, wDirectory, wResult);
1255 WideCharToMultiByte(CP_ACP, 0, wResult, -1, lpResult, MAX_PATH, NULL, NULL);
1256 SHFree(wFile);
1257 SHFree(wDirectory);
1258
1259 TRACE("returning %s\n", lpResult);
1260 return retval;
1261 }
1262
1263 /*************************************************************************
1264 * FindExecutableW [SHELL32.@]
1265 *
1266 * This function returns the executable associated with the specified file
1267 * for the default verb.
1268 *
1269 * PARAMS
1270 * lpFile [I] The file to find the association for. This must refer to
1271 * an existing file otherwise FindExecutable fails and returns
1272 * SE_ERR_FNF.
1273 * lpResult [O] Points to a buffer into which the executable path is
1274 * copied. This parameter must not be NULL otherwise
1275 * FindExecutable() segfaults. The buffer must be of size at
1276 * least MAX_PATH characters.
1277 *
1278 * RETURNS
1279 * A value greater than 32 on success, less than or equal to 32 otherwise.
1280 * See the SE_ERR_* constants.
1281 *
1282 * NOTES
1283 * On Windows XP and 2003, FindExecutable() seems to first convert the
1284 * filename into 8.3 format, thus taking into account only the first three
1285 * characters of the extension, and expects to find an association for those.
1286 * However other Windows versions behave sanely.
1287 */
FindExecutableW(LPCWSTR lpFile,LPCWSTR lpDirectory,LPWSTR lpResult)1288 HINSTANCE WINAPI FindExecutableW(LPCWSTR lpFile, LPCWSTR lpDirectory, LPWSTR lpResult)
1289 {
1290 UINT_PTR retval;
1291 WCHAR old_dir[MAX_PATH], res[MAX_PATH];
1292 DWORD cch = _countof(res);
1293 LPCWSTR dirs[2];
1294
1295 TRACE("File %s, Dir %s\n", debugstr_w(lpFile), debugstr_w(lpDirectory));
1296
1297 *lpResult = UNICODE_NULL;
1298
1299 GetCurrentDirectoryW(_countof(old_dir), old_dir);
1300
1301 if (lpDirectory && *lpDirectory)
1302 {
1303 SetCurrentDirectoryW(lpDirectory);
1304 dirs[0] = lpDirectory;
1305 }
1306 else
1307 {
1308 dirs[0] = old_dir;
1309 }
1310 dirs[1] = NULL;
1311
1312 if (!GetShortPathNameW(lpFile, res, _countof(res)))
1313 StringCchCopyW(res, _countof(res), lpFile);
1314
1315 if (PathResolveW(res, dirs, PRF_TRYPROGRAMEXTENSIONS | PRF_FIRSTDIRDEF))
1316 {
1317 // NOTE: The last parameter of this AssocQueryStringW call is "strange" in Windows.
1318 if (PathIsExeW(res) ||
1319 SUCCEEDED(AssocQueryStringW(ASSOCF_NONE, ASSOCSTR_EXECUTABLE, res, NULL, res, &cch)))
1320 {
1321 StringCchCopyW(lpResult, MAX_PATH, res);
1322 retval = 42;
1323 }
1324 else
1325 {
1326 retval = SE_ERR_NOASSOC;
1327 }
1328 }
1329 else
1330 {
1331 retval = SE_ERR_FNF;
1332 }
1333
1334 TRACE("returning %s\n", debugstr_w(lpResult));
1335 SetCurrentDirectoryW(old_dir);
1336 return (HINSTANCE)retval;
1337 }
1338
1339 /* FIXME: is this already implemented somewhere else? */
ShellExecute_GetClassKey(const SHELLEXECUTEINFOW * sei)1340 static HKEY ShellExecute_GetClassKey(const SHELLEXECUTEINFOW *sei)
1341 {
1342 LPCWSTR ext = NULL, lpClass = NULL;
1343 CHeapPtr<WCHAR, CLocalAllocator> cls;
1344 DWORD type = 0, sz = 0;
1345 HKEY hkey = 0;
1346 LONG r;
1347
1348 if (sei->fMask & SEE_MASK_CLASSALL)
1349 return sei->hkeyClass;
1350
1351 if (sei->fMask & SEE_MASK_CLASSNAME)
1352 lpClass = sei->lpClass;
1353 else
1354 {
1355 ext = PathFindExtensionW(sei->lpFile);
1356 TRACE("ext = %s\n", debugstr_w(ext));
1357 if (!ext)
1358 return hkey;
1359
1360 r = RegOpenKeyW(HKEY_CLASSES_ROOT, ext, &hkey);
1361 if (r != ERROR_SUCCESS)
1362 return hkey;
1363
1364 r = RegQueryValueExW(hkey, NULL, 0, &type, NULL, &sz);
1365 if (r == ERROR_SUCCESS && type == REG_SZ)
1366 {
1367 sz += sizeof (WCHAR);
1368 cls.Allocate(sz / sizeof(WCHAR));
1369 cls[0] = 0;
1370 RegQueryValueExW(hkey, NULL, 0, &type, (LPBYTE)(LPWSTR)cls, &sz);
1371 }
1372
1373 RegCloseKey( hkey );
1374 lpClass = cls;
1375 }
1376
1377 TRACE("class = %s\n", debugstr_w(lpClass));
1378
1379 hkey = 0;
1380 if (lpClass)
1381 RegOpenKeyW( HKEY_CLASSES_ROOT, lpClass, &hkey);
1382
1383 return hkey;
1384 }
1385
shellex_get_dataobj(LPSHELLEXECUTEINFOW sei,CComPtr<IDataObject> & dataObj)1386 static HRESULT shellex_get_dataobj( LPSHELLEXECUTEINFOW sei, CComPtr<IDataObject>& dataObj)
1387 {
1388 CComHeapPtr<ITEMIDLIST> allocatedPidl;
1389 LPITEMIDLIST pidl = NULL;
1390
1391 if (sei->fMask & SEE_MASK_CLASSALL)
1392 {
1393 pidl = (LPITEMIDLIST)sei->lpIDList;
1394 }
1395 else
1396 {
1397 WCHAR fullpath[MAX_PATH];
1398 BOOL ret;
1399
1400 fullpath[0] = 0;
1401 ret = GetFullPathNameW(sei->lpFile, MAX_PATH, fullpath, NULL);
1402 if (!ret)
1403 return HRESULT_FROM_WIN32(GetLastError());
1404
1405 pidl = ILCreateFromPathW(fullpath);
1406 allocatedPidl.Attach(pidl);
1407 }
1408 return SHELL_GetUIObjectOfAbsoluteItem(NULL, pidl, IID_PPV_ARG(IDataObject, &dataObj));
1409 }
1410
shellex_run_context_menu_default(IShellExtInit * obj,LPSHELLEXECUTEINFOW sei)1411 static HRESULT shellex_run_context_menu_default(IShellExtInit *obj,
1412 LPSHELLEXECUTEINFOW sei)
1413 {
1414 CComPtr<IContextMenu> cm = NULL;
1415 CMINVOKECOMMANDINFOEX ici;
1416 MENUITEMINFOW info;
1417 WCHAR string[0x80];
1418 INT i, n, def = -1;
1419 HMENU hmenu = 0;
1420 HRESULT r;
1421
1422 TRACE("%p %p\n", obj, sei);
1423
1424 r = obj->QueryInterface(IID_PPV_ARG(IContextMenu, &cm));
1425 if (FAILED(r))
1426 return r;
1427
1428 hmenu = CreateMenu();
1429 if (!hmenu)
1430 goto end;
1431
1432 /* the number of the last menu added is returned in r */
1433 r = cm->QueryContextMenu(hmenu, 0, 0x20, 0x7fff, CMF_DEFAULTONLY);
1434 if (FAILED(r))
1435 goto end;
1436
1437 n = GetMenuItemCount(hmenu);
1438 for (i = 0; i < n; i++)
1439 {
1440 memset(&info, 0, sizeof(info));
1441 info.cbSize = sizeof info;
1442 info.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_STATE | MIIM_DATA | MIIM_ID;
1443 info.dwTypeData = string;
1444 info.cch = sizeof string;
1445 string[0] = 0;
1446 GetMenuItemInfoW(hmenu, i, TRUE, &info);
1447
1448 TRACE("menu %d %s %08x %08lx %08x %08x\n", i, debugstr_w(string),
1449 info.fState, info.dwItemData, info.fType, info.wID);
1450 if ((!sei->lpVerb && (info.fState & MFS_DEFAULT)) ||
1451 (sei->lpVerb && !lstrcmpiW(sei->lpVerb, string)))
1452 {
1453 def = i;
1454 break;
1455 }
1456 }
1457
1458 r = E_FAIL;
1459 if (def == -1)
1460 goto end;
1461
1462 memset(&ici, 0, sizeof ici);
1463 ici.cbSize = sizeof ici;
1464 ici.fMask = CMIC_MASK_UNICODE | (sei->fMask & (SEE_MASK_NO_CONSOLE | SEE_MASK_NOASYNC | SEE_MASK_ASYNCOK | SEE_MASK_FLAG_NO_UI));
1465 ici.nShow = sei->nShow;
1466 ici.lpVerb = MAKEINTRESOURCEA(def);
1467 ici.hwnd = sei->hwnd;
1468 ici.lpParametersW = sei->lpParameters;
1469
1470 r = cm->InvokeCommand((LPCMINVOKECOMMANDINFO)&ici);
1471
1472 TRACE("invoke command returned %08x\n", r);
1473
1474 end:
1475 if (hmenu)
1476 DestroyMenu( hmenu );
1477 return r;
1478 }
1479
shellex_load_object_and_run(HKEY hkey,LPCGUID guid,LPSHELLEXECUTEINFOW sei)1480 static HRESULT shellex_load_object_and_run(HKEY hkey, LPCGUID guid, LPSHELLEXECUTEINFOW sei)
1481 {
1482 TRACE("%p %s %p\n", hkey, debugstr_guid(guid), sei);
1483
1484 CCoInit coInit;
1485
1486 if (FAILED_UNEXPECTEDLY(coInit.hr))
1487 return coInit.hr;
1488
1489 CComPtr<IShellExtInit> obj;
1490 HRESULT hr = CoCreateInstance(*guid, NULL, CLSCTX_INPROC_SERVER,
1491 IID_PPV_ARG(IShellExtInit, &obj));
1492 if (FAILED_UNEXPECTEDLY(hr))
1493 return hr;
1494
1495 CComPtr<IDataObject> dataobj;
1496 hr = shellex_get_dataobj(sei, dataobj);
1497 if (FAILED_UNEXPECTEDLY(hr))
1498 return hr;
1499
1500 hr = obj->Initialize(NULL, dataobj, hkey);
1501 if (FAILED_UNEXPECTEDLY(hr))
1502 return hr;
1503
1504 CComPtr<IObjectWithSite> ows;
1505 hr = obj->QueryInterface(IID_PPV_ARG(IObjectWithSite, &ows));
1506 if (FAILED_UNEXPECTEDLY(hr))
1507 return hr;
1508
1509 ows->SetSite(NULL);
1510
1511 return shellex_run_context_menu_default(obj, sei);
1512 }
1513
shellex_get_contextmenu(LPSHELLEXECUTEINFOW sei,CComPtr<IContextMenu> & cm)1514 static HRESULT shellex_get_contextmenu(LPSHELLEXECUTEINFOW sei, CComPtr<IContextMenu>& cm)
1515 {
1516 CComHeapPtr<ITEMIDLIST> allocatedPidl;
1517 LPITEMIDLIST pidl = NULL;
1518
1519 if (sei->lpIDList)
1520 {
1521 pidl = (LPITEMIDLIST)sei->lpIDList;
1522 }
1523 else
1524 {
1525 SFGAOF sfga = 0;
1526 HRESULT hr = SHParseDisplayName(sei->lpFile, NULL, &allocatedPidl, SFGAO_STORAGECAPMASK, &sfga);
1527 if (FAILED(hr))
1528 {
1529 WCHAR Buffer[MAX_PATH] = {};
1530 // FIXME: MAX_PATH.....
1531 UINT retval = SHELL_FindExecutable(sei->lpDirectory, sei->lpFile, sei->lpVerb, Buffer, _countof(Buffer), NULL, NULL, NULL, sei->lpParameters);
1532 if (retval <= 32)
1533 return HRESULT_FROM_WIN32(retval);
1534
1535 hr = SHParseDisplayName(Buffer, NULL, &allocatedPidl, SFGAO_STORAGECAPMASK, &sfga);
1536 // This should not happen, we found it...
1537 if (FAILED_UNEXPECTEDLY(hr))
1538 return hr;
1539 }
1540
1541 pidl = allocatedPidl;
1542 }
1543
1544 CComPtr<IShellFolder> shf;
1545 LPCITEMIDLIST pidllast = NULL;
1546 HRESULT hr = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &shf), &pidllast);
1547 if (FAILED(hr))
1548 return hr;
1549
1550 return shf->GetUIObjectOf(NULL, 1, &pidllast, IID_NULL_PPV_ARG(IContextMenu, &cm));
1551 }
1552
ShellExecute_ContextMenuVerb(LPSHELLEXECUTEINFOW sei)1553 static HRESULT ShellExecute_ContextMenuVerb(LPSHELLEXECUTEINFOW sei)
1554 {
1555 TRACE("%p\n", sei);
1556
1557 CCoInit coInit;
1558
1559 if (FAILED_UNEXPECTEDLY(coInit.hr))
1560 return coInit.hr;
1561
1562 CComPtr<IContextMenu> cm;
1563 HRESULT hr = shellex_get_contextmenu(sei, cm);
1564 if (FAILED_UNEXPECTEDLY(hr))
1565 return hr;
1566
1567 CComHeapPtr<char> verb, parameters, dir;
1568 __SHCloneStrWtoA(&verb, sei->lpVerb);
1569 __SHCloneStrWtoA(¶meters, sei->lpParameters);
1570 __SHCloneStrWtoA(&dir, sei->lpDirectory);
1571
1572 BOOL fDefault = StrIsNullOrEmpty(sei->lpVerb);
1573 CMINVOKECOMMANDINFOEX ici = { sizeof(ici) };
1574 ici.fMask = SeeFlagsToCmicFlags(sei->fMask) | CMIC_MASK_UNICODE;
1575 ici.nShow = sei->nShow;
1576 if (!fDefault)
1577 {
1578 ici.lpVerb = verb;
1579 ici.lpVerbW = sei->lpVerb;
1580 }
1581 ici.hwnd = sei->hwnd;
1582 ici.lpParameters = parameters;
1583 ici.lpParametersW = sei->lpParameters;
1584 ici.lpDirectory = dir;
1585 ici.lpDirectoryW = sei->lpDirectory;
1586 ici.dwHotKey = sei->dwHotKey;
1587 ici.hIcon = sei->hIcon;
1588 if (ici.fMask & (CMIC_MASK_HASLINKNAME | CMIC_MASK_HASTITLE))
1589 ici.lpTitleW = sei->lpClass;
1590
1591 enum { idFirst = 1, idLast = 0x7fff };
1592 HMENU hMenu = CreatePopupMenu();
1593 // Note: Windows does not pass CMF_EXTENDEDVERBS so "hidden" verbs cannot be executed
1594 hr = cm->QueryContextMenu(hMenu, 0, idFirst, idLast, fDefault ? CMF_DEFAULTONLY : 0);
1595 if (!FAILED_UNEXPECTEDLY(hr))
1596 {
1597 if (fDefault)
1598 {
1599 INT uDefault = GetMenuDefaultItem(hMenu, FALSE, 0);
1600 uDefault = (uDefault != -1) ? uDefault - idFirst : 0;
1601 ici.lpVerb = MAKEINTRESOURCEA(uDefault);
1602 ici.lpVerbW = MAKEINTRESOURCEW(uDefault);
1603 }
1604
1605 hr = cm->InvokeCommand((LPCMINVOKECOMMANDINFO)&ici);
1606 if (!FAILED_UNEXPECTEDLY(hr))
1607 hr = S_OK;
1608 }
1609
1610 DestroyMenu(hMenu);
1611
1612 return hr;
1613 }
1614
1615
1616 /*************************************************************************
1617 * ShellExecute_FromContextMenu [Internal]
1618 */
ShellExecute_FromContextMenuHandlers(LPSHELLEXECUTEINFOW sei)1619 static LONG ShellExecute_FromContextMenuHandlers( LPSHELLEXECUTEINFOW sei )
1620 {
1621 HKEY hkey, hkeycm = 0;
1622 WCHAR szguid[39];
1623 HRESULT hr;
1624 GUID guid;
1625 DWORD i;
1626 LONG r;
1627
1628 TRACE("%s\n", debugstr_w(sei->lpFile));
1629
1630 hkey = ShellExecute_GetClassKey(sei);
1631 if (!hkey)
1632 return ERROR_FUNCTION_FAILED;
1633
1634 r = RegOpenKeyW(hkey, L"shellex\\ContextMenuHandlers", &hkeycm);
1635 if (r == ERROR_SUCCESS)
1636 {
1637 i = 0;
1638 while (1)
1639 {
1640 r = RegEnumKeyW(hkeycm, i++, szguid, ARRAY_SIZE(szguid));
1641 if (r != ERROR_SUCCESS)
1642 break;
1643
1644 hr = CLSIDFromString(szguid, &guid);
1645 if (SUCCEEDED(hr))
1646 {
1647 /* stop at the first one that succeeds in running */
1648 hr = shellex_load_object_and_run(hkey, &guid, sei);
1649 if (SUCCEEDED(hr))
1650 break;
1651 }
1652 }
1653 RegCloseKey(hkeycm);
1654 }
1655
1656 if (hkey != sei->hkeyClass)
1657 RegCloseKey(hkey);
1658 return r;
1659 }
1660
1661 static UINT_PTR SHELL_quote_and_execute(LPCWSTR wcmd, LPCWSTR wszParameters, LPCWSTR lpstrProtocol, LPCWSTR wszApplicationName, LPWSTR env, LPSHELLEXECUTEINFOW psei, LPSHELLEXECUTEINFOW psei_out, SHELL_ExecuteW32 execfunc);
1662
SHELL_execute_class(LPCWSTR wszApplicationName,LPSHELLEXECUTEINFOW psei,LPSHELLEXECUTEINFOW psei_out,SHELL_ExecuteW32 execfunc)1663 static UINT_PTR SHELL_execute_class(LPCWSTR wszApplicationName, LPSHELLEXECUTEINFOW psei, LPSHELLEXECUTEINFOW psei_out, SHELL_ExecuteW32 execfunc)
1664 {
1665 WCHAR execCmd[1024], classname[1024];
1666 /* launch a document by fileclass like 'WordPad.Document.1' */
1667 /* the Commandline contains 'c:\Path\wordpad.exe "%1"' */
1668 /* FIXME: wcmd should not be of a fixed size. Fixed to 1024, MAX_PATH is way too short! */
1669 ULONG cmask = (psei->fMask & SEE_MASK_CLASSALL);
1670 DWORD resultLen;
1671 BOOL done;
1672 UINT_PTR rslt;
1673
1674 /* FIXME: remove following block when SHELL_quote_and_execute supports hkeyClass parameter */
1675 if (cmask != SEE_MASK_CLASSNAME)
1676 {
1677 WCHAR wcmd[1024];
1678 HCR_GetExecuteCommandW((cmask == SEE_MASK_CLASSKEY) ? psei->hkeyClass : NULL,
1679 (cmask == SEE_MASK_CLASSNAME) ? psei->lpClass : NULL,
1680 psei->lpVerb,
1681 execCmd, sizeof(execCmd));
1682
1683 /* FIXME: get the extension of lpFile, check if it fits to the lpClass */
1684 TRACE("SEE_MASK_CLASSNAME->%s, doc->%s\n", debugstr_w(execCmd), debugstr_w(wszApplicationName));
1685
1686 wcmd[0] = '\0';
1687 done = SHELL_ArgifyW(wcmd, ARRAY_SIZE(wcmd), execCmd, wszApplicationName, (LPITEMIDLIST)psei->lpIDList, psei->lpParameters,
1688 &resultLen, (psei->lpDirectory && *psei->lpDirectory) ? psei->lpDirectory : NULL);
1689 if (!done && wszApplicationName[0])
1690 {
1691 #if 0 // Given HKCR\.test=SZ:"test" and HKCR\test\shell\open\command=SZ:"cmd.exe /K echo.Hello", no filename is
1692 // appended on Windows when there is no %1 nor %L when executed with: shlextdbg.exe /shellexec=c:\file.test /INVOKE
1693 strcatW(wcmd, L" ");
1694 if (*wszApplicationName != '"')
1695 {
1696 strcatW(wcmd, L"\"");
1697 strcatW(wcmd, wszApplicationName);
1698 strcatW(wcmd, L"\"");
1699 }
1700 else
1701 strcatW(wcmd, wszApplicationName);
1702 #endif
1703 }
1704 if (resultLen > ARRAY_SIZE(wcmd))
1705 ERR("Argify buffer not large enough... truncating\n");
1706 return execfunc(wcmd, NULL, FALSE, psei, psei_out);
1707 }
1708
1709 strcpyW(classname, psei->lpClass);
1710 rslt = SHELL_FindExecutableByVerb(psei->lpVerb, NULL, classname, execCmd, sizeof(execCmd));
1711
1712 TRACE("SHELL_FindExecutableByVerb returned %u (%s, %s)\n", (unsigned int)rslt, debugstr_w(classname), debugstr_w(execCmd));
1713 if (33 > rslt)
1714 return rslt;
1715 rslt = SHELL_quote_and_execute( execCmd, L"", classname,
1716 wszApplicationName, NULL, psei,
1717 psei_out, execfunc );
1718 return rslt;
1719
1720 }
1721
SHELL_translate_idlist(LPSHELLEXECUTEINFOW sei,LPWSTR wszParameters,DWORD parametersLen,LPWSTR wszApplicationName,DWORD dwApplicationNameLen)1722 static BOOL SHELL_translate_idlist(LPSHELLEXECUTEINFOW sei, LPWSTR wszParameters, DWORD parametersLen, LPWSTR wszApplicationName, DWORD dwApplicationNameLen)
1723 {
1724 WCHAR buffer[MAX_PATH];
1725 BOOL appKnownSingular = FALSE;
1726
1727 /* last chance to translate IDList: now also allow CLSID paths */
1728 if (SUCCEEDED(SHELL_GetPathFromIDListForExecuteW((LPCITEMIDLIST)sei->lpIDList, buffer, ARRAY_SIZE(buffer)))) {
1729 if (buffer[0] == ':' && buffer[1] == ':') {
1730 /* open shell folder for the specified class GUID */
1731 if (strlenW(buffer) + 1 > parametersLen)
1732 ERR("parameters len exceeds buffer size (%i > %i), truncating\n",
1733 lstrlenW(buffer) + 1, parametersLen);
1734 lstrcpynW(wszParameters, buffer, parametersLen);
1735 if (strlenW(L"explorer.exe") > dwApplicationNameLen)
1736 ERR("application len exceeds buffer size (%i), truncating\n",
1737 dwApplicationNameLen);
1738 lstrcpynW(wszApplicationName, L"explorer.exe", dwApplicationNameLen);
1739 appKnownSingular = TRUE;
1740
1741 sei->fMask &= ~SEE_MASK_INVOKEIDLIST;
1742 } else {
1743 WCHAR target[max(MAX_PATH, _countof(buffer))];
1744 DWORD attribs;
1745 DWORD resultLen;
1746 /* Check if we're executing a directory and if so use the
1747 handler for the Folder class */
1748 strcpyW(target, buffer);
1749 attribs = GetFileAttributesW(buffer);
1750 if (attribs != INVALID_FILE_ATTRIBUTES &&
1751 (attribs & FILE_ATTRIBUTE_DIRECTORY) &&
1752 HCR_GetExecuteCommandW(0, L"Folder",
1753 sei->lpVerb,
1754 buffer, sizeof(buffer))) {
1755 SHELL_ArgifyW(wszApplicationName, dwApplicationNameLen,
1756 buffer, target, (LPITEMIDLIST)sei->lpIDList, NULL, &resultLen,
1757 !StrIsNullOrEmpty(sei->lpDirectory) ? sei->lpDirectory : NULL);
1758 if (resultLen > dwApplicationNameLen)
1759 ERR("Argify buffer not large enough... truncating\n"); // FIXME: Report this to the caller?
1760 appKnownSingular = FALSE;
1761 // HACKFIX: We really want the !appKnownSingular code in SHELL_execute to split the
1762 // parameters for us but we cannot guarantee that the exe in the registry is quoted.
1763 // We have now turned 'explorer.exe "%1" into 'explorer.exe "c:\path\from\pidl"' and
1764 // need to split to application and parameters.
1765 LPCWSTR params = PathGetArgsW(wszApplicationName);
1766 lstrcpynW(wszParameters, params, parametersLen);
1767 PathRemoveArgsW(wszApplicationName);
1768 PathUnquoteSpacesW(wszApplicationName);
1769 appKnownSingular = TRUE;
1770 }
1771 sei->fMask &= ~SEE_MASK_INVOKEIDLIST;
1772 }
1773 }
1774 return appKnownSingular;
1775 }
1776
1777 static BOOL
SHELL_InvokePidl(_In_ LPSHELLEXECUTEINFOW sei,_In_ LPCITEMIDLIST pidl)1778 SHELL_InvokePidl(
1779 _In_ LPSHELLEXECUTEINFOW sei,
1780 _In_ LPCITEMIDLIST pidl)
1781 {
1782 // Bind pidl
1783 CComPtr<IShellFolder> psfFolder;
1784 LPCITEMIDLIST pidlLast;
1785 HRESULT hr = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &psfFolder), &pidlLast);
1786 if (FAILED_UNEXPECTEDLY(hr))
1787 return FALSE;
1788
1789 // Get the context menu to invoke a command
1790 CComPtr<IContextMenu> pCM;
1791 hr = psfFolder->GetUIObjectOf(NULL, 1, &pidlLast, IID_NULL_PPV_ARG(IContextMenu, &pCM));
1792 if (FAILED_UNEXPECTEDLY(hr))
1793 return FALSE;
1794
1795 // Invoke a command
1796 CMINVOKECOMMANDINFO ici = { sizeof(ici) };
1797 ici.fMask = (sei->fMask & (SEE_MASK_NO_CONSOLE | SEE_MASK_ASYNCOK | SEE_MASK_FLAG_NO_UI));
1798 ici.nShow = sei->nShow;
1799 ici.hwnd = sei->hwnd;
1800 char szVerb[VERBKEY_CCHMAX];
1801 if (sei->lpVerb && sei->lpVerb[0])
1802 {
1803 WideCharToMultiByte(CP_ACP, 0, sei->lpVerb, -1, szVerb, _countof(szVerb), NULL, NULL);
1804 szVerb[_countof(szVerb) - 1] = ANSI_NULL; // Avoid buffer overrun
1805 ici.lpVerb = szVerb;
1806 }
1807 else // The default verb?
1808 {
1809 HMENU hMenu = CreatePopupMenu();
1810 const INT idCmdFirst = 1, idCmdLast = 0x7FFF;
1811 hr = pCM->QueryContextMenu(hMenu, 0, idCmdFirst, idCmdLast, CMF_DEFAULTONLY);
1812 if (FAILED_UNEXPECTEDLY(hr))
1813 {
1814 DestroyMenu(hMenu);
1815 return FALSE;
1816 }
1817
1818 INT nDefaultID = GetMenuDefaultItem(hMenu, FALSE, 0);
1819 DestroyMenu(hMenu);
1820 if (nDefaultID == -1)
1821 nDefaultID = idCmdFirst;
1822
1823 ici.lpVerb = MAKEINTRESOURCEA(nDefaultID - idCmdFirst);
1824 }
1825 hr = pCM->InvokeCommand(&ici);
1826
1827 return !FAILED_UNEXPECTEDLY(hr);
1828 }
1829
SHELL_quote_and_execute(LPCWSTR wcmd,LPCWSTR wszParameters,LPCWSTR wszKeyname,LPCWSTR wszApplicationName,LPWSTR env,LPSHELLEXECUTEINFOW psei,LPSHELLEXECUTEINFOW psei_out,SHELL_ExecuteW32 execfunc)1830 static UINT_PTR SHELL_quote_and_execute(LPCWSTR wcmd, LPCWSTR wszParameters, LPCWSTR wszKeyname, LPCWSTR wszApplicationName, LPWSTR env, LPSHELLEXECUTEINFOW psei, LPSHELLEXECUTEINFOW psei_out, SHELL_ExecuteW32 execfunc)
1831 {
1832 UINT_PTR retval;
1833 DWORD len;
1834 CHeapPtr<WCHAR, CLocalAllocator> wszQuotedCmd;
1835
1836 /* Length of quotes plus length of command plus NULL terminator */
1837 len = 2 + lstrlenW(wcmd) + 1;
1838 if (wszParameters[0])
1839 {
1840 /* Length of space plus length of parameters */
1841 len += 1 + lstrlenW(wszParameters);
1842 }
1843 wszQuotedCmd.Allocate(len);
1844 /* Must quote to handle case where cmd contains spaces,
1845 * else security hole if malicious user creates executable file "C:\\Program"
1846 */
1847 strcpyW(wszQuotedCmd, L"\"");
1848 strcatW(wszQuotedCmd, wcmd);
1849 strcatW(wszQuotedCmd, L"\"");
1850 if (wszParameters[0])
1851 {
1852 strcatW(wszQuotedCmd, L" ");
1853 strcatW(wszQuotedCmd, wszParameters);
1854 }
1855
1856 TRACE("%s/%s => %s/%s\n", debugstr_w(wszApplicationName), debugstr_w(psei->lpVerb), debugstr_w(wszQuotedCmd), debugstr_w(wszKeyname));
1857
1858 if (*wszKeyname)
1859 retval = execute_from_key(wszKeyname, wszApplicationName, env, psei->lpParameters, wcmd, execfunc, psei, psei_out);
1860 else
1861 retval = execfunc(wszQuotedCmd, env, FALSE, psei, psei_out);
1862
1863 return retval;
1864 }
1865
SHELL_execute_url(LPCWSTR lpFile,LPCWSTR wcmd,LPSHELLEXECUTEINFOW psei,LPSHELLEXECUTEINFOW psei_out,SHELL_ExecuteW32 execfunc)1866 static UINT_PTR SHELL_execute_url(LPCWSTR lpFile, LPCWSTR wcmd, LPSHELLEXECUTEINFOW psei, LPSHELLEXECUTEINFOW psei_out, SHELL_ExecuteW32 execfunc)
1867 {
1868 UINT_PTR retval;
1869 CHeapPtr<WCHAR, CLocalAllocator> lpstrProtocol;
1870 LPCWSTR lpstrRes;
1871 INT iSize;
1872 DWORD len;
1873
1874 lpstrRes = strchrW(lpFile, ':');
1875 if (lpstrRes)
1876 iSize = lpstrRes - lpFile;
1877 else
1878 iSize = strlenW(lpFile);
1879
1880 TRACE("Got URL: %s\n", debugstr_w(lpFile));
1881 /* Looking for ...<protocol>\shell\<lpVerb>\command */
1882 len = iSize + lstrlenW(L"\\shell\\") + lstrlenW(L"\\command") + 1;
1883 if (psei->lpVerb && *psei->lpVerb)
1884 len += lstrlenW(psei->lpVerb);
1885 else
1886 len += lstrlenW(L"open");
1887 lpstrProtocol.Allocate(len);
1888 memcpy(lpstrProtocol, lpFile, iSize * sizeof(WCHAR));
1889 lpstrProtocol[iSize] = '\0';
1890 strcatW(lpstrProtocol, L"\\shell\\");
1891 strcatW(lpstrProtocol, psei->lpVerb && *psei->lpVerb ? psei->lpVerb : L"open");
1892 strcatW(lpstrProtocol, L"\\command");
1893
1894 retval = execute_from_key(lpstrProtocol, lpFile, NULL, psei->lpParameters,
1895 wcmd, execfunc, psei, psei_out);
1896
1897 return retval;
1898 }
1899
do_error_dialog(UINT_PTR retval,HWND hwnd,WCHAR * filename)1900 static void do_error_dialog(UINT_PTR retval, HWND hwnd, WCHAR* filename)
1901 {
1902 WCHAR msg[2048];
1903 DWORD_PTR msgArguments[3] = { (DWORD_PTR)filename, 0, 0 };
1904 DWORD error_code;
1905
1906 error_code = GetLastError();
1907 if (retval == SE_ERR_NOASSOC)
1908 LoadStringW(shell32_hInstance, IDS_SHLEXEC_NOASSOC, msg, ARRAY_SIZE(msg));
1909 else
1910 FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY,
1911 NULL,
1912 error_code,
1913 LANG_USER_DEFAULT,
1914 msg,
1915 ARRAY_SIZE(msg),
1916 (va_list*)msgArguments);
1917
1918 MessageBoxW(hwnd, msg, NULL, MB_ICONERROR);
1919 }
1920
expand_environment(const WCHAR * str)1921 static WCHAR *expand_environment( const WCHAR *str )
1922 {
1923 CHeapPtr<WCHAR, CLocalAllocator> buf;
1924 DWORD len;
1925
1926 len = ExpandEnvironmentStringsW(str, NULL, 0);
1927 if (!len) return NULL;
1928
1929 if (!buf.Allocate(len))
1930 return NULL;
1931
1932 len = ExpandEnvironmentStringsW(str, buf, len);
1933 if (!len)
1934 return NULL;
1935
1936 return buf.Detach();
1937 }
1938
1939 /*************************************************************************
1940 * SHELL_execute [Internal]
1941 */
SHELL_execute(LPSHELLEXECUTEINFOW sei,SHELL_ExecuteW32 execfunc)1942 static BOOL SHELL_execute(LPSHELLEXECUTEINFOW sei, SHELL_ExecuteW32 execfunc)
1943 {
1944 static const DWORD unsupportedFlags =
1945 SEE_MASK_ICON | SEE_MASK_HOTKEY |
1946 SEE_MASK_CONNECTNETDRV | SEE_MASK_FLAG_DDEWAIT |
1947 SEE_MASK_ASYNCOK | SEE_MASK_HMONITOR;
1948
1949 DWORD len;
1950 UINT_PTR retval = SE_ERR_NOASSOC;
1951 BOOL appKnownSingular = FALSE;
1952
1953 /* make a local copy of the LPSHELLEXECUTEINFO structure and work with this from now on */
1954 SHELLEXECUTEINFOW sei_tmp = *sei;
1955
1956 TRACE("mask=0x%08x hwnd=%p verb=%s file=%s parm=%s dir=%s show=0x%08x class=%s\n",
1957 sei_tmp.fMask, sei_tmp.hwnd, debugstr_w(sei_tmp.lpVerb),
1958 debugstr_w(sei_tmp.lpFile), debugstr_w(sei_tmp.lpParameters),
1959 debugstr_w(sei_tmp.lpDirectory), sei_tmp.nShow,
1960 ((sei_tmp.fMask & SEE_MASK_CLASSALL) == SEE_MASK_CLASSNAME) ?
1961 debugstr_w(sei_tmp.lpClass) : "not used");
1962
1963 sei->hProcess = NULL;
1964
1965 /* make copies of all path/command strings */
1966 CHeapPtr<WCHAR, CLocalAllocator> wszApplicationName;
1967 DWORD dwApplicationNameLen = MAX_PATH + 2;
1968 if (!sei_tmp.lpFile)
1969 {
1970 wszApplicationName.Allocate(dwApplicationNameLen);
1971 *wszApplicationName = '\0';
1972 }
1973 else if (*sei_tmp.lpFile == '\"' && sei_tmp.lpFile[(len = strlenW(sei_tmp.lpFile))-1] == '\"')
1974 {
1975 if(len-1 >= dwApplicationNameLen)
1976 dwApplicationNameLen = len;
1977
1978 wszApplicationName.Allocate(dwApplicationNameLen);
1979 memcpy(wszApplicationName, sei_tmp.lpFile + 1, len * sizeof(WCHAR));
1980
1981 if(len > 2)
1982 wszApplicationName[len-2] = '\0';
1983 appKnownSingular = TRUE;
1984
1985 TRACE("wszApplicationName=%s\n", debugstr_w(wszApplicationName));
1986 }
1987 else
1988 {
1989 DWORD l = strlenW(sei_tmp.lpFile) + 1;
1990 if(l > dwApplicationNameLen) dwApplicationNameLen = l + 1;
1991 wszApplicationName.Allocate(dwApplicationNameLen);
1992 memcpy(wszApplicationName, sei_tmp.lpFile, l * sizeof(WCHAR));
1993
1994 if (wszApplicationName[2] == 0 && wszApplicationName[1] == L':' &&
1995 ((L'A' <= wszApplicationName[0] && wszApplicationName[0] <= L'Z') ||
1996 (L'a' <= wszApplicationName[0] && wszApplicationName[0] <= L'z')))
1997 {
1998 // 'C:' --> 'C:\'
1999 PathAddBackslashW(wszApplicationName);
2000 }
2001 }
2002
2003 WCHAR parametersBuffer[1024];
2004 LPWSTR wszParameters = parametersBuffer;
2005 CHeapPtr<WCHAR, CLocalAllocator> wszParamAlloc;
2006 DWORD parametersLen = _countof(parametersBuffer);
2007
2008 if (sei_tmp.lpParameters)
2009 {
2010 len = lstrlenW(sei_tmp.lpParameters) + 1;
2011 if (len > parametersLen)
2012 {
2013 wszParamAlloc.Allocate(len);
2014 wszParameters = wszParamAlloc;
2015 parametersLen = len;
2016 }
2017 strcpyW(wszParameters, sei_tmp.lpParameters);
2018 }
2019 else
2020 *wszParameters = L'\0';
2021
2022 // Get the working directory
2023 WCHAR dirBuffer[MAX_PATH];
2024 LPWSTR wszDir = dirBuffer;
2025 wszDir[0] = UNICODE_NULL;
2026 CHeapPtr<WCHAR, CLocalAllocator> wszDirAlloc;
2027 if (sei_tmp.lpDirectory && *sei_tmp.lpDirectory)
2028 {
2029 if (sei_tmp.fMask & SEE_MASK_DOENVSUBST)
2030 {
2031 LPWSTR tmp = expand_environment(sei_tmp.lpDirectory);
2032 if (tmp)
2033 {
2034 wszDirAlloc.Attach(tmp);
2035 wszDir = wszDirAlloc;
2036 }
2037 }
2038 else
2039 {
2040 __SHCloneStrW(&wszDirAlloc, sei_tmp.lpDirectory);
2041 if (wszDirAlloc)
2042 wszDir = wszDirAlloc;
2043 }
2044 }
2045 if (!wszDir[0])
2046 {
2047 ::GetCurrentDirectoryW(_countof(dirBuffer), dirBuffer);
2048 wszDir = dirBuffer;
2049 }
2050 // NOTE: ShellExecute should accept the invalid working directory for historical reason.
2051 if (!PathIsDirectoryW(wszDir))
2052 {
2053 INT iDrive = PathGetDriveNumberW(wszDir);
2054 if (iDrive >= 0)
2055 {
2056 PathStripToRootW(wszDir);
2057 if (!PathIsDirectoryW(wszDir))
2058 {
2059 ::GetWindowsDirectoryW(dirBuffer, _countof(dirBuffer));
2060 wszDir = dirBuffer;
2061 }
2062 }
2063 }
2064
2065 /* adjust string pointers to point to the new buffers */
2066 sei_tmp.lpFile = wszApplicationName;
2067 sei_tmp.lpParameters = wszParameters;
2068 sei_tmp.lpDirectory = wszDir;
2069
2070 if (sei_tmp.fMask & unsupportedFlags)
2071 {
2072 FIXME("flags ignored: 0x%08x\n", sei_tmp.fMask & unsupportedFlags);
2073 }
2074
2075 /* process the IDList */
2076 if (sei_tmp.fMask & SEE_MASK_IDLIST &&
2077 (sei_tmp.fMask & SEE_MASK_INVOKEIDLIST) != SEE_MASK_INVOKEIDLIST)
2078 {
2079 CComPtr<IShellExecuteHookW> pSEH;
2080
2081 HRESULT hr = SHBindToParent((LPCITEMIDLIST)sei_tmp.lpIDList, IID_PPV_ARG(IShellExecuteHookW, &pSEH), NULL);
2082
2083 if (SUCCEEDED(hr))
2084 {
2085 hr = pSEH->Execute(&sei_tmp);
2086 if (hr == S_OK)
2087 return TRUE;
2088 }
2089
2090 SHGetPathFromIDListW((LPCITEMIDLIST)sei_tmp.lpIDList, wszApplicationName);
2091 appKnownSingular = TRUE;
2092 TRACE("-- idlist=%p (%s)\n", sei_tmp.lpIDList, debugstr_w(wszApplicationName));
2093 }
2094
2095 if ((sei_tmp.fMask & SEE_MASK_DOENVSUBST) && !StrIsNullOrEmpty(sei_tmp.lpFile))
2096 {
2097 WCHAR *tmp = expand_environment(sei_tmp.lpFile);
2098 if (tmp)
2099 {
2100 wszApplicationName.Attach(tmp);
2101 sei_tmp.lpFile = wszApplicationName;
2102 }
2103 }
2104
2105 if ((sei_tmp.fMask & SEE_MASK_INVOKEIDLIST) == SEE_MASK_INVOKEIDLIST)
2106 {
2107 HRESULT hr = ShellExecute_ContextMenuVerb(&sei_tmp);
2108 if (SUCCEEDED(hr))
2109 {
2110 sei->hInstApp = (HINSTANCE)42;
2111 return TRUE;
2112 }
2113 }
2114
2115 if (ERROR_SUCCESS == ShellExecute_FromContextMenuHandlers(&sei_tmp))
2116 {
2117 sei->hInstApp = (HINSTANCE) 33;
2118 return TRUE;
2119 }
2120
2121 if (sei_tmp.fMask & SEE_MASK_CLASSALL)
2122 {
2123 retval = SHELL_execute_class(wszApplicationName, &sei_tmp, sei, execfunc);
2124 if (retval <= 32 && !(sei_tmp.fMask & SEE_MASK_FLAG_NO_UI))
2125 {
2126 OPENASINFO Info;
2127
2128 //FIXME
2129 // need full path
2130
2131 Info.pcszFile = wszApplicationName;
2132 Info.pcszClass = NULL;
2133 Info.oaifInFlags = OAIF_ALLOW_REGISTRATION | OAIF_EXEC;
2134
2135 //if (SHOpenWithDialog(sei_tmp.hwnd, &Info) != S_OK)
2136 DBG_UNREFERENCED_LOCAL_VARIABLE(Info);
2137 do_error_dialog(retval, sei_tmp.hwnd, wszApplicationName);
2138 }
2139 return retval > 32;
2140 }
2141
2142 if (!(sei_tmp.fMask & SEE_MASK_IDLIST) && // Not an ID List
2143 (StrCmpNIW(sei_tmp.lpFile, L"shell:", 6) == 0 ||
2144 StrCmpNW(sei_tmp.lpFile, L"::{", 3) == 0))
2145 {
2146 CComHeapPtr<ITEMIDLIST> pidlParsed;
2147 HRESULT hr = SHParseDisplayName(sei_tmp.lpFile, NULL, &pidlParsed, 0, NULL);
2148 if (SUCCEEDED(hr) && SHELL_InvokePidl(&sei_tmp, pidlParsed))
2149 {
2150 sei_tmp.hInstApp = (HINSTANCE)UlongToHandle(42);
2151 return TRUE;
2152 }
2153 }
2154
2155 /* Has the IDList not yet been translated? */
2156 if (sei_tmp.fMask & SEE_MASK_IDLIST)
2157 {
2158 appKnownSingular = SHELL_translate_idlist( &sei_tmp, wszParameters,
2159 parametersLen,
2160 wszApplicationName,
2161 dwApplicationNameLen );
2162 }
2163
2164 /* convert file URLs */
2165 if (UrlIsFileUrlW(sei_tmp.lpFile))
2166 {
2167 CHeapPtr<WCHAR, CLocalAllocator> buf;
2168 DWORD size = MAX_PATH;
2169 if (!buf.Allocate(size) || FAILED(PathCreateFromUrlW(sei_tmp.lpFile, buf, &size, 0)))
2170 return SE_ERR_OOM;
2171
2172 wszApplicationName.Attach(buf.Detach());
2173 sei_tmp.lpFile = wszApplicationName;
2174 }
2175
2176 /* Else, try to execute the filename */
2177 TRACE("execute: %s,%s,%s\n", debugstr_w(wszApplicationName), debugstr_w(wszParameters), debugstr_w(wszDir));
2178
2179 /* separate out command line arguments from executable file name */
2180 LPCWSTR lpFile = sei_tmp.lpFile;
2181 if (!*sei_tmp.lpParameters && !appKnownSingular)
2182 {
2183 /* If the executable path is quoted, handle the rest of the command line as parameters. */
2184 if (sei_tmp.lpFile[0] == L'"')
2185 {
2186 LPWSTR pszArgs = PathGetArgsW(wszApplicationName);
2187 PathRemoveArgsW(wszApplicationName);
2188 PathUnquoteSpacesW(wszApplicationName);
2189 parametersLen = lstrlenW(pszArgs);
2190 if (parametersLen < _countof(parametersBuffer))
2191 {
2192 StringCchCopyW(parametersBuffer, _countof(parametersBuffer), pszArgs);
2193 wszParameters = parametersBuffer;
2194 }
2195 else
2196 {
2197 wszParamAlloc.Attach(StrDupW(pszArgs));
2198 wszParameters = wszParamAlloc;
2199 }
2200 }
2201 /* We have to test sei instead of sei_tmp because sei_tmp had its
2202 * input fMask modified above in SHELL_translate_idlist.
2203 * This code is needed to handle the case where we only have an
2204 * lpIDList with multiple CLSID/PIDL's (not 'My Computer' only) */
2205 else if ((sei->fMask & SEE_MASK_IDLIST) == SEE_MASK_IDLIST)
2206 {
2207 WCHAR buffer[MAX_PATH], xlpFile[MAX_PATH];
2208 LPWSTR space, s;
2209
2210 LPWSTR beg = wszApplicationName;
2211 for(s = beg; (space = const_cast<LPWSTR>(strchrW(s, L' '))); s = space + 1)
2212 {
2213 int idx = space - sei_tmp.lpFile;
2214 memcpy(buffer, sei_tmp.lpFile, idx * sizeof(WCHAR));
2215 buffer[idx] = '\0';
2216
2217 if (SearchPathW(*sei_tmp.lpDirectory ? sei_tmp.lpDirectory : NULL,
2218 buffer, L".exe", _countof(xlpFile), xlpFile, NULL))
2219 {
2220 /* separate out command from parameter string */
2221 LPCWSTR p = space + 1;
2222
2223 while(isspaceW(*p))
2224 ++p;
2225
2226 strcpyW(wszParameters, p);
2227 *space = L'\0';
2228
2229 break;
2230 }
2231 }
2232 }
2233 }
2234
2235 WCHAR wcmdBuffer[1024];
2236 LPWSTR wcmd = wcmdBuffer;
2237 DWORD wcmdLen = _countof(wcmdBuffer);
2238 CHeapPtr<WCHAR, CLocalAllocator> wcmdAlloc;
2239
2240 /* Only execute if it has an executable extension */
2241 if (PathIsExeW(lpFile))
2242 {
2243 len = lstrlenW(wszApplicationName) + 3;
2244 if (sei_tmp.lpParameters[0])
2245 len += 1 + lstrlenW(wszParameters);
2246 if (len > wcmdLen)
2247 {
2248 wcmdAlloc.Allocate(len);
2249 wcmd = wcmdAlloc;
2250 wcmdLen = len;
2251 }
2252 swprintf(wcmd, L"\"%s\"", (LPWSTR)wszApplicationName);
2253 if (sei_tmp.lpParameters[0])
2254 {
2255 strcatW(wcmd, L" ");
2256 strcatW(wcmd, wszParameters);
2257 }
2258
2259 retval = execfunc(wcmd, NULL, FALSE, &sei_tmp, sei);
2260 if (retval > 32)
2261 return TRUE;
2262 }
2263
2264 /* Else, try to find the executable */
2265 WCHAR wszKeyname[256];
2266 CHeapPtr<WCHAR, CLocalAllocator> env;
2267 wcmd[0] = UNICODE_NULL;
2268 retval = SHELL_FindExecutable(sei_tmp.lpDirectory, lpFile, sei_tmp.lpVerb, wcmd, wcmdLen, wszKeyname, &env, (LPITEMIDLIST)sei_tmp.lpIDList, sei_tmp.lpParameters);
2269 if (retval > 32) /* Found */
2270 {
2271 retval = SHELL_quote_and_execute(wcmd, wszParameters, wszKeyname,
2272 wszApplicationName, env, &sei_tmp,
2273 sei, execfunc);
2274 }
2275 else if (PathIsDirectoryW(lpFile))
2276 {
2277 WCHAR wExec[MAX_PATH];
2278 CHeapPtr<WCHAR, CLocalAllocator> lpQuotedFile;
2279 if (lpQuotedFile.Allocate(strlenW(lpFile) + 3))
2280 {
2281 retval = SHELL_FindExecutable(sei_tmp.lpDirectory, L"explorer",
2282 L"open", wExec, MAX_PATH,
2283 NULL, &env, NULL, NULL);
2284 if (retval > 32)
2285 {
2286 swprintf(lpQuotedFile, L"\"%s\"", lpFile);
2287 retval = SHELL_quote_and_execute(wExec, lpQuotedFile,
2288 wszKeyname,
2289 wszApplicationName, env,
2290 &sei_tmp, sei, execfunc);
2291 }
2292 }
2293 else
2294 retval = 0; /* Out of memory */
2295 }
2296 else if (PathIsURLW(lpFile)) /* File not found, check for URL */
2297 {
2298 retval = SHELL_execute_url(lpFile, wcmd, &sei_tmp, sei, execfunc );
2299 }
2300 /* Check if file specified is in the form www.??????.*** */
2301 else if (!strncmpiW(lpFile, L"www", 3))
2302 {
2303 /* if so, prefix lpFile with http:// and call ShellExecute */
2304 WCHAR lpstrTmpFile[256];
2305 strcpyW(lpstrTmpFile, L"http://");
2306 strcatW(lpstrTmpFile, lpFile);
2307 retval = (UINT_PTR)ShellExecuteW(sei_tmp.hwnd, sei_tmp.lpVerb, lpstrTmpFile, NULL, NULL, 0);
2308 }
2309
2310 TRACE("retval %lu\n", retval);
2311
2312 if (retval <= 32 && !(sei_tmp.fMask & SEE_MASK_FLAG_NO_UI))
2313 {
2314 OPENASINFO Info;
2315
2316 //FIXME
2317 // need full path
2318
2319 Info.pcszFile = wszApplicationName;
2320 Info.pcszClass = NULL;
2321 Info.oaifInFlags = OAIF_ALLOW_REGISTRATION | OAIF_EXEC;
2322
2323 //if (SHOpenWithDialog(sei_tmp.hwnd, &Info) != S_OK)
2324 DBG_UNREFERENCED_LOCAL_VARIABLE(Info);
2325 do_error_dialog(retval, sei_tmp.hwnd, wszApplicationName);
2326 }
2327
2328 sei->hInstApp = (HINSTANCE)(retval > 32 ? 33 : retval);
2329
2330 return retval > 32;
2331 }
2332
2333 /*************************************************************************
2334 * ShellExecuteA [SHELL32.290]
2335 */
ShellExecuteA(HWND hWnd,LPCSTR lpVerb,LPCSTR lpFile,LPCSTR lpParameters,LPCSTR lpDirectory,INT iShowCmd)2336 HINSTANCE WINAPI ShellExecuteA(HWND hWnd, LPCSTR lpVerb, LPCSTR lpFile,
2337 LPCSTR lpParameters, LPCSTR lpDirectory, INT iShowCmd)
2338 {
2339 SHELLEXECUTEINFOA sei;
2340
2341 TRACE("%p,%s,%s,%s,%s,%d\n",
2342 hWnd, debugstr_a(lpVerb), debugstr_a(lpFile),
2343 debugstr_a(lpParameters), debugstr_a(lpDirectory), iShowCmd);
2344
2345 sei.cbSize = sizeof(sei);
2346 sei.fMask = SEE_MASK_FLAG_NO_UI;
2347 sei.hwnd = hWnd;
2348 sei.lpVerb = lpVerb;
2349 sei.lpFile = lpFile;
2350 sei.lpParameters = lpParameters;
2351 sei.lpDirectory = lpDirectory;
2352 sei.nShow = iShowCmd;
2353 sei.lpIDList = 0;
2354 sei.lpClass = 0;
2355 sei.hkeyClass = 0;
2356 sei.dwHotKey = 0;
2357 sei.hProcess = 0;
2358
2359 if (!(SHGetAppCompatFlags(SHACF_WIN95SHLEXEC) & SHACF_WIN95SHLEXEC))
2360 sei.fMask |= SEE_MASK_NOASYNC;
2361 ShellExecuteExA(&sei);
2362 return sei.hInstApp;
2363 }
2364
2365 static DWORD
ShellExecute_Normal(_Inout_ LPSHELLEXECUTEINFOW sei)2366 ShellExecute_Normal(_Inout_ LPSHELLEXECUTEINFOW sei)
2367 {
2368 // FIXME
2369 return SHELL_execute(sei, SHELL_ExecuteW) ? ERROR_SUCCESS : ERROR_FILE_NOT_FOUND;
2370 }
2371
2372 static VOID
ShellExecute_ShowError(_In_ const SHELLEXECUTEINFOW * ExecInfo,_In_opt_ LPCWSTR pszCaption,_In_ DWORD dwError)2373 ShellExecute_ShowError(
2374 _In_ const SHELLEXECUTEINFOW *ExecInfo,
2375 _In_opt_ LPCWSTR pszCaption,
2376 _In_ DWORD dwError)
2377 {
2378 // FIXME: Show error message
2379 }
2380
2381 /*************************************************************************
2382 * ShellExecuteExA [SHELL32.292]
2383 */
2384 BOOL
2385 WINAPI
2386 DECLSPEC_HOTPATCH
ShellExecuteExA(LPSHELLEXECUTEINFOA sei)2387 ShellExecuteExA(LPSHELLEXECUTEINFOA sei)
2388 {
2389 SHELLEXECUTEINFOW seiW;
2390 BOOL ret;
2391 WCHAR *wVerb = NULL, *wFile = NULL, *wParameters = NULL, *wDirectory = NULL, *wClass = NULL;
2392
2393 TRACE("%p\n", sei);
2394
2395 if (sei->cbSize != sizeof(SHELLEXECUTEINFOA))
2396 {
2397 sei->hInstApp = (HINSTANCE)ERROR_ACCESS_DENIED;
2398 SetLastError(ERROR_ACCESS_DENIED);
2399 return FALSE;
2400 }
2401
2402 memcpy(&seiW, sei, sizeof(SHELLEXECUTEINFOW));
2403
2404 seiW.cbSize = sizeof(SHELLEXECUTEINFOW);
2405
2406 if (sei->lpVerb)
2407 seiW.lpVerb = __SHCloneStrAtoW(&wVerb, sei->lpVerb);
2408
2409 if (sei->lpFile)
2410 seiW.lpFile = __SHCloneStrAtoW(&wFile, sei->lpFile);
2411
2412 if (sei->lpParameters)
2413 seiW.lpParameters = __SHCloneStrAtoW(&wParameters, sei->lpParameters);
2414
2415 if (sei->lpDirectory)
2416 seiW.lpDirectory = __SHCloneStrAtoW(&wDirectory, sei->lpDirectory);
2417
2418 if ((sei->fMask & SEE_MASK_CLASSALL) == SEE_MASK_CLASSNAME && sei->lpClass)
2419 seiW.lpClass = __SHCloneStrAtoW(&wClass, sei->lpClass);
2420 else
2421 seiW.lpClass = NULL;
2422
2423 ret = ShellExecuteExW(&seiW);
2424
2425 sei->hInstApp = seiW.hInstApp;
2426
2427 if (sei->fMask & SEE_MASK_NOCLOSEPROCESS)
2428 sei->hProcess = seiW.hProcess;
2429
2430 SHFree(wVerb);
2431 SHFree(wFile);
2432 SHFree(wParameters);
2433 SHFree(wDirectory);
2434 SHFree(wClass);
2435
2436 return ret;
2437 }
2438
2439 /*************************************************************************
2440 * ShellExecuteExW [SHELL32.293]
2441 */
2442 BOOL
2443 WINAPI
2444 DECLSPEC_HOTPATCH
ShellExecuteExW(LPSHELLEXECUTEINFOW sei)2445 ShellExecuteExW(LPSHELLEXECUTEINFOW sei)
2446 {
2447 HRESULT hrCoInit;
2448 DWORD dwError;
2449 ULONG fOldMask;
2450
2451 if (sei->cbSize != sizeof(SHELLEXECUTEINFOW))
2452 {
2453 sei->hInstApp = (HINSTANCE)UlongToHandle(SE_ERR_ACCESSDENIED);
2454 SetLastError(ERROR_ACCESS_DENIED);
2455 return FALSE;
2456 }
2457
2458 hrCoInit = SHCoInitializeAnyApartment();
2459
2460 if (SHRegGetBoolUSValueW(L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer",
2461 L"MaximizeApps", FALSE, FALSE))
2462 {
2463 switch (sei->nShow)
2464 {
2465 case SW_SHOW:
2466 case SW_SHOWDEFAULT:
2467 case SW_SHOWNORMAL:
2468 case SW_RESTORE:
2469 sei->nShow = SW_SHOWMAXIMIZED;
2470 break;
2471 default:
2472 break;
2473 }
2474 }
2475
2476 fOldMask = sei->fMask;
2477
2478 if (!(fOldMask & SEE_MASK_NOASYNC) && SHELL_InRunDllProcess())
2479 sei->fMask |= SEE_MASK_WAITFORINPUTIDLE | SEE_MASK_NOASYNC;
2480
2481 dwError = ShellExecute_Normal(sei);
2482
2483 if (dwError && dwError != ERROR_DLL_NOT_FOUND && dwError != ERROR_CANCELLED)
2484 ShellExecute_ShowError(sei, NULL, dwError);
2485
2486 sei->fMask = fOldMask;
2487
2488 if (SUCCEEDED(hrCoInit))
2489 CoUninitialize();
2490
2491 if (dwError)
2492 SetLastError(dwError);
2493
2494 return dwError == ERROR_SUCCESS;
2495 }
2496
2497 /*************************************************************************
2498 * ShellExecuteW [SHELL32.294]
2499 */
ShellExecuteW(HWND hwnd,LPCWSTR lpVerb,LPCWSTR lpFile,LPCWSTR lpParameters,LPCWSTR lpDirectory,INT nShowCmd)2500 HINSTANCE WINAPI ShellExecuteW(HWND hwnd, LPCWSTR lpVerb, LPCWSTR lpFile,
2501 LPCWSTR lpParameters, LPCWSTR lpDirectory, INT nShowCmd)
2502 {
2503 SHELLEXECUTEINFOW sei;
2504
2505 TRACE("\n");
2506 sei.cbSize = sizeof(sei);
2507 sei.fMask = SEE_MASK_FLAG_NO_UI;
2508 sei.hwnd = hwnd;
2509 sei.lpVerb = lpVerb;
2510 sei.lpFile = lpFile;
2511 sei.lpParameters = lpParameters;
2512 sei.lpDirectory = lpDirectory;
2513 sei.nShow = nShowCmd;
2514 sei.lpIDList = 0;
2515 sei.lpClass = 0;
2516 sei.hkeyClass = 0;
2517 sei.dwHotKey = 0;
2518 sei.hProcess = 0;
2519
2520 if (!(SHGetAppCompatFlags(SHACF_WIN95SHLEXEC) & SHACF_WIN95SHLEXEC))
2521 sei.fMask |= SEE_MASK_NOASYNC;
2522 ShellExecuteExW(&sei);
2523 return sei.hInstApp;
2524 }
2525
2526 /*************************************************************************
2527 * WOWShellExecute [SHELL32.@]
2528 *
2529 * FIXME: the callback function most likely doesn't work the same way on Windows.
2530 */
WOWShellExecute(HWND hWnd,LPCSTR lpVerb,LPCSTR lpFile,LPCSTR lpParameters,LPCSTR lpDirectory,INT iShowCmd,void * callback)2531 EXTERN_C HINSTANCE WINAPI WOWShellExecute(HWND hWnd, LPCSTR lpVerb, LPCSTR lpFile,
2532 LPCSTR lpParameters, LPCSTR lpDirectory, INT iShowCmd, void *callback)
2533 {
2534 SHELLEXECUTEINFOW seiW;
2535 WCHAR *wVerb = NULL, *wFile = NULL, *wParameters = NULL, *wDirectory = NULL;
2536 HANDLE hProcess = 0;
2537
2538 seiW.lpVerb = lpVerb ? __SHCloneStrAtoW(&wVerb, lpVerb) : NULL;
2539 seiW.lpFile = lpFile ? __SHCloneStrAtoW(&wFile, lpFile) : NULL;
2540 seiW.lpParameters = lpParameters ? __SHCloneStrAtoW(&wParameters, lpParameters) : NULL;
2541 seiW.lpDirectory = lpDirectory ? __SHCloneStrAtoW(&wDirectory, lpDirectory) : NULL;
2542
2543 seiW.cbSize = sizeof(seiW);
2544 seiW.fMask = 0;
2545 seiW.hwnd = hWnd;
2546 seiW.nShow = iShowCmd;
2547 seiW.lpIDList = 0;
2548 seiW.lpClass = 0;
2549 seiW.hkeyClass = 0;
2550 seiW.dwHotKey = 0;
2551 seiW.hProcess = hProcess;
2552
2553 SHELL_execute(&seiW, (SHELL_ExecuteW32)callback);
2554
2555 SHFree(wVerb);
2556 SHFree(wFile);
2557 SHFree(wParameters);
2558 SHFree(wDirectory);
2559 return seiW.hInstApp;
2560 }
2561
2562 /*************************************************************************
2563 * OpenAs_RunDLLW [SHELL32.@]
2564 */
2565 EXTERN_C void WINAPI
OpenAs_RunDLLW(HWND hwnd,HINSTANCE hinst,LPCWSTR cmdline,int cmdshow)2566 OpenAs_RunDLLW(HWND hwnd, HINSTANCE hinst, LPCWSTR cmdline, int cmdshow)
2567 {
2568 OPENASINFO info;
2569 TRACE("%p, %p, %s, %d\n", hwnd, hinst, debugstr_w(cmdline), cmdshow);
2570
2571 ZeroMemory(&info, sizeof(info));
2572 info.pcszFile = cmdline;
2573 info.pcszClass = NULL;
2574 info.oaifInFlags = OAIF_ALLOW_REGISTRATION | OAIF_REGISTER_EXT | OAIF_EXEC;
2575
2576 SHOpenWithDialog(hwnd, &info);
2577 }
2578
2579 /*************************************************************************
2580 * OpenAs_RunDLLA [SHELL32.@]
2581 */
2582 EXTERN_C void WINAPI
OpenAs_RunDLLA(HWND hwnd,HINSTANCE hinst,LPCSTR cmdline,int cmdshow)2583 OpenAs_RunDLLA(HWND hwnd, HINSTANCE hinst, LPCSTR cmdline, int cmdshow)
2584 {
2585 LPWSTR pszCmdLineW = NULL;
2586 TRACE("%p, %p, %s, %d\n", hwnd, hinst, debugstr_a(cmdline), cmdshow);
2587
2588 if (cmdline)
2589 __SHCloneStrAtoW(&pszCmdLineW, cmdline);
2590 OpenAs_RunDLLW(hwnd, hinst, pszCmdLineW, cmdshow);
2591 SHFree(pszCmdLineW);
2592 }
2593
2594 /*************************************************************************/
2595
2596 static LPCWSTR
SplitParams(LPCWSTR psz,LPWSTR pszArg0,size_t cchArg0)2597 SplitParams(LPCWSTR psz, LPWSTR pszArg0, size_t cchArg0)
2598 {
2599 LPCWSTR pch;
2600 size_t ich = 0;
2601 if (*psz == L'"')
2602 {
2603 // 1st argument is quoted. the string in quotes is quoted 1st argument.
2604 // [pch] --> [pszArg0+ich]
2605 for (pch = psz + 1; *pch && ich + 1 < cchArg0; ++ich, ++pch)
2606 {
2607 if (*pch == L'"' && pch[1] == L'"')
2608 {
2609 // doubled double quotations found!
2610 pszArg0[ich] = L'"';
2611 }
2612 else if (*pch == L'"')
2613 {
2614 // single double quotation found!
2615 ++pch;
2616 break;
2617 }
2618 else
2619 {
2620 // otherwise
2621 pszArg0[ich] = *pch;
2622 }
2623 }
2624 }
2625 else
2626 {
2627 // 1st argument is unquoted. non-space sequence is 1st argument.
2628 // [pch] --> [pszArg0+ich]
2629 for (pch = psz; *pch && !iswspace(*pch) && ich + 1 < cchArg0; ++ich, ++pch)
2630 {
2631 pszArg0[ich] = *pch;
2632 }
2633 }
2634 pszArg0[ich] = 0;
2635
2636 // skip space
2637 while (iswspace(*pch))
2638 ++pch;
2639
2640 return pch;
2641 }
2642
ShellExecCmdLine(HWND hwnd,LPCWSTR pwszCommand,LPCWSTR pwszStartDir,int nShow,LPVOID pUnused,DWORD dwSeclFlags)2643 HRESULT WINAPI ShellExecCmdLine(
2644 HWND hwnd,
2645 LPCWSTR pwszCommand,
2646 LPCWSTR pwszStartDir,
2647 int nShow,
2648 LPVOID pUnused,
2649 DWORD dwSeclFlags)
2650 {
2651 SHELLEXECUTEINFOW info;
2652 DWORD dwSize, dwError, dwType, dwFlags = SEE_MASK_DOENVSUBST | SEE_MASK_NOASYNC;
2653 LPCWSTR pszVerb = NULL;
2654 WCHAR szFile[MAX_PATH], szFile2[MAX_PATH];
2655 HRESULT hr;
2656 LPCWSTR pchParams;
2657 LPWSTR lpCommand = NULL;
2658
2659 if (pwszCommand == NULL)
2660 RaiseException(EXCEPTION_ACCESS_VIOLATION, EXCEPTION_NONCONTINUABLE,
2661 1, (ULONG_PTR*)pwszCommand);
2662
2663 __SHCloneStrW(&lpCommand, pwszCommand);
2664 StrTrimW(lpCommand, L" \t");
2665
2666 if (dwSeclFlags & SECL_NO_UI)
2667 dwFlags |= SEE_MASK_FLAG_NO_UI;
2668 if (dwSeclFlags & SECL_LOG_USAGE)
2669 dwFlags |= SEE_MASK_FLAG_LOG_USAGE;
2670 if (dwSeclFlags & SECL_USE_IDLIST)
2671 dwFlags |= SEE_MASK_INVOKEIDLIST;
2672
2673 if (dwSeclFlags & SECL_RUNAS)
2674 {
2675 dwSize = 0;
2676 hr = AssocQueryStringW(ASSOCF_NONE, ASSOCSTR_COMMAND, lpCommand, L"RunAs", NULL, &dwSize);
2677 if (SUCCEEDED(hr) && dwSize != 0)
2678 {
2679 pszVerb = L"runas";
2680 }
2681 }
2682
2683 if (PathIsURLW(lpCommand) || UrlIsW(lpCommand, URLIS_APPLIABLE))
2684 {
2685 StringCchCopyW(szFile, _countof(szFile), lpCommand);
2686 pchParams = NULL;
2687 }
2688 else
2689 {
2690 PCWSTR apPathList[2];
2691
2692 pchParams = SplitParams(lpCommand, szFile, _countof(szFile));
2693 if (szFile[0] != UNICODE_NULL && szFile[1] == L':' &&
2694 szFile[2] == UNICODE_NULL)
2695 {
2696 PathAddBackslashW(szFile);
2697 }
2698
2699 WCHAR szCurDir[MAX_PATH];
2700 GetCurrentDirectoryW(_countof(szCurDir), szCurDir);
2701 if (pwszStartDir)
2702 {
2703 SetCurrentDirectoryW(pwszStartDir);
2704 }
2705
2706 if ((PathIsRelativeW(szFile) &&
2707 GetFullPathNameW(szFile, _countof(szFile2), szFile2, NULL) &&
2708 PathFileExistsW(szFile2)) ||
2709 SearchPathW(NULL, szFile, NULL, _countof(szFile2), szFile2, NULL))
2710 {
2711 StringCchCopyW(szFile, _countof(szFile), szFile2);
2712 }
2713
2714 apPathList[0] = pwszStartDir;
2715 apPathList[1] = NULL;
2716 PathFindOnPathExW(szFile, apPathList, WHICH_DEFAULT);
2717
2718 if (!(dwSeclFlags & SECL_ALLOW_NONEXE))
2719 {
2720 if (!GetBinaryTypeW(szFile, &dwType))
2721 {
2722 SHFree(lpCommand);
2723
2724 if (!(dwSeclFlags & SECL_NO_UI))
2725 {
2726 WCHAR szText[128 + MAX_PATH], szFormat[128];
2727 LoadStringW(shell32_hInstance, IDS_FILE_NOT_FOUND, szFormat, _countof(szFormat));
2728 StringCchPrintfW(szText, _countof(szText), szFormat, szFile);
2729 MessageBoxW(hwnd, szText, NULL, MB_ICONERROR);
2730 }
2731 return CO_E_APPNOTFOUND;
2732 }
2733 }
2734 else
2735 {
2736 if (GetFileAttributesW(szFile) == INVALID_FILE_ATTRIBUTES)
2737 {
2738 SHFree(lpCommand);
2739
2740 if (!(dwSeclFlags & SECL_NO_UI))
2741 {
2742 WCHAR szText[128 + MAX_PATH], szFormat[128];
2743 LoadStringW(shell32_hInstance, IDS_FILE_NOT_FOUND, szFormat, _countof(szFormat));
2744 StringCchPrintfW(szText, _countof(szText), szFormat, szFile);
2745 MessageBoxW(hwnd, szText, NULL, MB_ICONERROR);
2746 }
2747 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
2748 }
2749 }
2750 }
2751
2752 ZeroMemory(&info, sizeof(info));
2753 info.cbSize = sizeof(info);
2754 info.fMask = dwFlags;
2755 info.hwnd = hwnd;
2756 info.lpVerb = pszVerb;
2757 info.lpFile = szFile;
2758 info.lpParameters = (pchParams && *pchParams) ? pchParams : NULL;
2759 info.lpDirectory = pwszStartDir;
2760 info.nShow = nShow;
2761 if (ShellExecuteExW(&info))
2762 {
2763 if (info.lpIDList)
2764 CoTaskMemFree(info.lpIDList);
2765
2766 SHFree(lpCommand);
2767
2768 return S_OK;
2769 }
2770
2771 dwError = GetLastError();
2772
2773 SHFree(lpCommand);
2774
2775 return HRESULT_FROM_WIN32(dwError);
2776 }
2777
2778 /*************************************************************************
2779 * RealShellExecuteExA (SHELL32.266)
2780 */
2781 EXTERN_C
2782 HINSTANCE WINAPI
RealShellExecuteExA(_In_opt_ HWND hwnd,_In_opt_ LPCSTR lpOperation,_In_opt_ LPCSTR lpFile,_In_opt_ LPCSTR lpParameters,_In_opt_ LPCSTR lpDirectory,_In_opt_ LPSTR lpReturn,_In_opt_ LPCSTR lpTitle,_In_opt_ LPVOID lpReserved,_In_ INT nCmdShow,_Out_opt_ PHANDLE lphProcess,_In_ DWORD dwFlags)2783 RealShellExecuteExA(
2784 _In_opt_ HWND hwnd,
2785 _In_opt_ LPCSTR lpOperation,
2786 _In_opt_ LPCSTR lpFile,
2787 _In_opt_ LPCSTR lpParameters,
2788 _In_opt_ LPCSTR lpDirectory,
2789 _In_opt_ LPSTR lpReturn,
2790 _In_opt_ LPCSTR lpTitle,
2791 _In_opt_ LPVOID lpReserved,
2792 _In_ INT nCmdShow,
2793 _Out_opt_ PHANDLE lphProcess,
2794 _In_ DWORD dwFlags)
2795 {
2796 SHELLEXECUTEINFOA ExecInfo;
2797
2798 TRACE("(%p, %s, %s, %s, %s, %p, %s, %p, %u, %p, %lu)\n",
2799 hwnd, debugstr_a(lpOperation), debugstr_a(lpFile), debugstr_a(lpParameters),
2800 debugstr_a(lpDirectory), lpReserved, debugstr_a(lpTitle),
2801 lpReserved, nCmdShow, lphProcess, dwFlags);
2802
2803 ZeroMemory(&ExecInfo, sizeof(ExecInfo));
2804 ExecInfo.cbSize = sizeof(ExecInfo);
2805 ExecInfo.fMask = SEE_MASK_FLAG_NO_UI | SEE_MASK_UNKNOWN_0x1000;
2806 ExecInfo.hwnd = hwnd;
2807 ExecInfo.lpVerb = lpOperation;
2808 ExecInfo.lpFile = lpFile;
2809 ExecInfo.lpParameters = lpParameters;
2810 ExecInfo.lpDirectory = lpDirectory;
2811 ExecInfo.nShow = (WORD)nCmdShow;
2812
2813 if (lpReserved)
2814 {
2815 ExecInfo.fMask |= SEE_MASK_USE_RESERVED;
2816 ExecInfo.hInstApp = (HINSTANCE)lpReserved;
2817 }
2818
2819 if (lpTitle)
2820 {
2821 ExecInfo.fMask |= SEE_MASK_HASTITLE;
2822 ExecInfo.lpClass = lpTitle;
2823 }
2824
2825 if (dwFlags & 1)
2826 ExecInfo.fMask |= SEE_MASK_FLAG_SEPVDM;
2827
2828 if (dwFlags & 2)
2829 ExecInfo.fMask |= SEE_MASK_NO_CONSOLE;
2830
2831 if (lphProcess == NULL)
2832 {
2833 ShellExecuteExA(&ExecInfo);
2834 }
2835 else
2836 {
2837 ExecInfo.fMask |= SEE_MASK_NOCLOSEPROCESS;
2838 ShellExecuteExA(&ExecInfo);
2839 *lphProcess = ExecInfo.hProcess;
2840 }
2841
2842 return ExecInfo.hInstApp;
2843 }
2844
2845 /*************************************************************************
2846 * RealShellExecuteExW (SHELL32.267)
2847 */
2848 EXTERN_C
2849 HINSTANCE WINAPI
RealShellExecuteExW(_In_opt_ HWND hwnd,_In_opt_ LPCWSTR lpOperation,_In_opt_ LPCWSTR lpFile,_In_opt_ LPCWSTR lpParameters,_In_opt_ LPCWSTR lpDirectory,_In_opt_ LPWSTR lpReturn,_In_opt_ LPCWSTR lpTitle,_In_opt_ LPVOID lpReserved,_In_ INT nCmdShow,_Out_opt_ PHANDLE lphProcess,_In_ DWORD dwFlags)2850 RealShellExecuteExW(
2851 _In_opt_ HWND hwnd,
2852 _In_opt_ LPCWSTR lpOperation,
2853 _In_opt_ LPCWSTR lpFile,
2854 _In_opt_ LPCWSTR lpParameters,
2855 _In_opt_ LPCWSTR lpDirectory,
2856 _In_opt_ LPWSTR lpReturn,
2857 _In_opt_ LPCWSTR lpTitle,
2858 _In_opt_ LPVOID lpReserved,
2859 _In_ INT nCmdShow,
2860 _Out_opt_ PHANDLE lphProcess,
2861 _In_ DWORD dwFlags)
2862 {
2863 SHELLEXECUTEINFOW ExecInfo;
2864
2865 TRACE("(%p, %s, %s, %s, %s, %p, %s, %p, %u, %p, %lu)\n",
2866 hwnd, debugstr_w(lpOperation), debugstr_w(lpFile), debugstr_w(lpParameters),
2867 debugstr_w(lpDirectory), lpReserved, debugstr_w(lpTitle),
2868 lpReserved, nCmdShow, lphProcess, dwFlags);
2869
2870 ZeroMemory(&ExecInfo, sizeof(ExecInfo));
2871 ExecInfo.cbSize = sizeof(ExecInfo);
2872 ExecInfo.fMask = SEE_MASK_FLAG_NO_UI | SEE_MASK_UNKNOWN_0x1000;
2873 ExecInfo.hwnd = hwnd;
2874 ExecInfo.lpVerb = lpOperation;
2875 ExecInfo.lpFile = lpFile;
2876 ExecInfo.lpParameters = lpParameters;
2877 ExecInfo.lpDirectory = lpDirectory;
2878 ExecInfo.nShow = (WORD)nCmdShow;
2879
2880 if (lpReserved)
2881 {
2882 ExecInfo.fMask |= SEE_MASK_USE_RESERVED;
2883 ExecInfo.hInstApp = (HINSTANCE)lpReserved;
2884 }
2885
2886 if (lpTitle)
2887 {
2888 ExecInfo.fMask |= SEE_MASK_HASTITLE;
2889 ExecInfo.lpClass = lpTitle;
2890 }
2891
2892 if (dwFlags & 1)
2893 ExecInfo.fMask |= SEE_MASK_FLAG_SEPVDM;
2894
2895 if (dwFlags & 2)
2896 ExecInfo.fMask |= SEE_MASK_NO_CONSOLE;
2897
2898 if (lphProcess == NULL)
2899 {
2900 ShellExecuteExW(&ExecInfo);
2901 }
2902 else
2903 {
2904 ExecInfo.fMask |= SEE_MASK_NOCLOSEPROCESS;
2905 ShellExecuteExW(&ExecInfo);
2906 *lphProcess = ExecInfo.hProcess;
2907 }
2908
2909 return ExecInfo.hInstApp;
2910 }
2911
2912 /*************************************************************************
2913 * RealShellExecuteA (SHELL32.265)
2914 */
2915 EXTERN_C
2916 HINSTANCE WINAPI
RealShellExecuteA(_In_opt_ HWND hwnd,_In_opt_ LPCSTR lpOperation,_In_opt_ LPCSTR lpFile,_In_opt_ LPCSTR lpParameters,_In_opt_ LPCSTR lpDirectory,_In_opt_ LPSTR lpReturn,_In_opt_ LPCSTR lpTitle,_In_opt_ LPVOID lpReserved,_In_ INT nCmdShow,_Out_opt_ PHANDLE lphProcess)2917 RealShellExecuteA(
2918 _In_opt_ HWND hwnd,
2919 _In_opt_ LPCSTR lpOperation,
2920 _In_opt_ LPCSTR lpFile,
2921 _In_opt_ LPCSTR lpParameters,
2922 _In_opt_ LPCSTR lpDirectory,
2923 _In_opt_ LPSTR lpReturn,
2924 _In_opt_ LPCSTR lpTitle,
2925 _In_opt_ LPVOID lpReserved,
2926 _In_ INT nCmdShow,
2927 _Out_opt_ PHANDLE lphProcess)
2928 {
2929 return RealShellExecuteExA(hwnd,
2930 lpOperation,
2931 lpFile,
2932 lpParameters,
2933 lpDirectory,
2934 lpReturn,
2935 lpTitle,
2936 lpReserved,
2937 nCmdShow,
2938 lphProcess,
2939 0);
2940 }
2941
2942 /*************************************************************************
2943 * RealShellExecuteW (SHELL32.268)
2944 */
2945 EXTERN_C
2946 HINSTANCE WINAPI
RealShellExecuteW(_In_opt_ HWND hwnd,_In_opt_ LPCWSTR lpOperation,_In_opt_ LPCWSTR lpFile,_In_opt_ LPCWSTR lpParameters,_In_opt_ LPCWSTR lpDirectory,_In_opt_ LPWSTR lpReturn,_In_opt_ LPCWSTR lpTitle,_In_opt_ LPVOID lpReserved,_In_ INT nCmdShow,_Out_opt_ PHANDLE lphProcess)2947 RealShellExecuteW(
2948 _In_opt_ HWND hwnd,
2949 _In_opt_ LPCWSTR lpOperation,
2950 _In_opt_ LPCWSTR lpFile,
2951 _In_opt_ LPCWSTR lpParameters,
2952 _In_opt_ LPCWSTR lpDirectory,
2953 _In_opt_ LPWSTR lpReturn,
2954 _In_opt_ LPCWSTR lpTitle,
2955 _In_opt_ LPVOID lpReserved,
2956 _In_ INT nCmdShow,
2957 _Out_opt_ PHANDLE lphProcess)
2958 {
2959 return RealShellExecuteExW(hwnd,
2960 lpOperation,
2961 lpFile,
2962 lpParameters,
2963 lpDirectory,
2964 lpReturn,
2965 lpTitle,
2966 lpReserved,
2967 nCmdShow,
2968 lphProcess,
2969 0);
2970 }
2971
2972 // The common helper of ShellExec_RunDLLA and ShellExec_RunDLLW
2973 static VOID
ShellExec_RunDLL_Helper(_In_opt_ HWND hwnd,_In_opt_ HINSTANCE hInstance,_In_ PCWSTR pszCmdLine,_In_ INT nCmdShow)2974 ShellExec_RunDLL_Helper(
2975 _In_opt_ HWND hwnd,
2976 _In_opt_ HINSTANCE hInstance,
2977 _In_ PCWSTR pszCmdLine,
2978 _In_ INT nCmdShow)
2979 {
2980 TRACE("(%p, %p, %s, 0x%X)\n", hwnd, hInstance, wine_dbgstr_w(pszCmdLine), nCmdShow);
2981
2982 if (!pszCmdLine || !*pszCmdLine)
2983 return;
2984
2985 // '?' enables us to specify the additional mask value
2986 ULONG fNewMask = SEE_MASK_NOASYNC;
2987 if (*pszCmdLine == L'?') // 1st question
2988 {
2989 INT MaskValue;
2990 if (StrToIntExW(pszCmdLine + 1, STIF_SUPPORT_HEX, &MaskValue))
2991 fNewMask |= MaskValue;
2992
2993 PCWSTR pch2ndQuestion = StrChrW(pszCmdLine + 1, L'?'); // 2nd question
2994 if (pch2ndQuestion)
2995 pszCmdLine = pch2ndQuestion + 1;
2996 }
2997
2998 WCHAR szPath[2 * MAX_PATH];
2999 if (PathProcessCommandAW(pszCmdLine, szPath, _countof(szPath), L'C') == -1)
3000 StrCpyNW(szPath, pszCmdLine, _countof(szPath));
3001
3002 // Split arguments from the path
3003 LPWSTR Args = PathGetArgsW(szPath);
3004 if (*Args)
3005 *(Args - 1) = UNICODE_NULL;
3006
3007 PathUnquoteSpacesW(szPath);
3008
3009 // Execute
3010 SHELLEXECUTEINFOW execInfo = { sizeof(execInfo) };
3011 execInfo.fMask = fNewMask;
3012 execInfo.hwnd = hwnd;
3013 execInfo.lpFile = szPath;
3014 execInfo.lpParameters = Args;
3015 execInfo.nShow = nCmdShow;
3016 if (!ShellExecuteExW(&execInfo))
3017 {
3018 DWORD dwError = GetLastError();
3019 if (SHELL_InRunDllProcess()) // Is it a RUNDLL process?
3020 ExitProcess(dwError); // Terminate it now
3021 }
3022 }
3023
3024 /*************************************************************************
3025 * ShellExec_RunDLLA [SHELL32.358]
3026 *
3027 * @see https://www.hexacorn.com/blog/2024/11/30/1-little-known-secret-of-shellexec_rundll/
3028 */
3029 EXTERN_C
3030 VOID WINAPI
ShellExec_RunDLLA(_In_opt_ HWND hwnd,_In_opt_ HINSTANCE hInstance,_In_ PCSTR pszCmdLine,_In_ INT nCmdShow)3031 ShellExec_RunDLLA(
3032 _In_opt_ HWND hwnd,
3033 _In_opt_ HINSTANCE hInstance,
3034 _In_ PCSTR pszCmdLine,
3035 _In_ INT nCmdShow)
3036 {
3037 CStringW strCmdLine = pszCmdLine; // Keep
3038 ShellExec_RunDLL_Helper(hwnd, hInstance, strCmdLine, nCmdShow);
3039 }
3040
3041 /*************************************************************************
3042 * ShellExec_RunDLLW [SHELL32.359]
3043 *
3044 * @see https://www.hexacorn.com/blog/2024/11/30/1-little-known-secret-of-shellexec_rundll/
3045 */
3046 EXTERN_C
3047 VOID WINAPI
ShellExec_RunDLLW(_In_opt_ HWND hwnd,_In_opt_ HINSTANCE hInstance,_In_ PCWSTR pszCmdLine,_In_ INT nCmdShow)3048 ShellExec_RunDLLW(
3049 _In_opt_ HWND hwnd,
3050 _In_opt_ HINSTANCE hInstance,
3051 _In_ PCWSTR pszCmdLine,
3052 _In_ INT nCmdShow)
3053 {
3054 ShellExec_RunDLL_Helper(hwnd, hInstance, pszCmdLine, nCmdShow);
3055 }
3056