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 SearchPath */
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 (!PathIsFileSpecW(lpFile))
781 {
782 BOOL found = FALSE;
783 if (lpPath && *lpPath)
784 {
785 TRACE("lpPath %s\n", debugstr_w(lpPath));
786 PathCombineW(xlpFile, lpPath, lpFile);
787 if (PathFileExistsDefExtW(xlpFile, WHICH_DEFAULT | WHICH_OPTIONAL) || PathFileExistsW(xlpFile))
788 {
789 GetFullPathNameW(xlpFile, ARRAY_SIZE(xlpFile), xlpFile, NULL);
790 found = TRUE;
791 }
792 }
793 if (!found)
794 {
795 lstrcpyW(xlpFile, lpFile);
796 if (PathFileExistsDefExtW(xlpFile, WHICH_DEFAULT | WHICH_OPTIONAL) || PathFileExistsW(xlpFile))
797 {
798 GetFullPathNameW(xlpFile, ARRAY_SIZE(xlpFile), xlpFile, NULL);
799 found = TRUE;
800 }
801 }
802 if (found)
803 {
804 lpFile = xlpFile;
805 lstrcpyW(lpResult, xlpFile);
806 }
807 else
808 xlpFile[0] = '\0';
809 }
810 else
811 {
812 if (lpPath && *lpPath)
813 {
814 search_paths[0] = lpPath;
815 search_paths[1] = curdir;
816 }
817 else
818 search_paths[0] = curdir;
819 lstrcpyW(xlpFile, lpFile);
820 if (PathResolveW(xlpFile, search_paths, PRF_TRYPROGRAMEXTENSIONS | PRF_VERIFYEXISTS))
821 {
822 TRACE("PathResolveW returned non-zero\n");
823 lpFile = xlpFile;
824 lstrcpyW(lpResult, xlpFile);
825 /* The file was found in lpPath or one of the directories in the system-wide search path */
826 }
827 else
828 xlpFile[0] = '\0';
829 }
830
831 attribs = GetFileAttributesW(lpFile);
832 if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY))
833 {
834 wcscpy(classname, L"Folder");
835 }
836 else
837 {
838 /* Did we get something? Anything? */
839 if (xlpFile[0] == 0)
840 {
841 TRACE("Returning SE_ERR_FNF\n");
842 return SE_ERR_FNF;
843 }
844 /* First thing we need is the file's extension */
845 extension = wcsrchr(xlpFile, '.'); /* Assume last "." is the one; */
846 /* File->Run in progman uses */
847 /* .\FILE.EXE :( */
848 TRACE("xlpFile=%s,extension=%s\n", debugstr_w(xlpFile), debugstr_w(extension));
849
850 if (extension == NULL || extension[1] == 0)
851 {
852 WARN("Returning SE_ERR_NOASSOC\n");
853 return SE_ERR_NOASSOC;
854 }
855
856 /* Three places to check: */
857 /* 1. win.ini, [windows], programs (NB no leading '.') */
858 /* 2. Registry, HKEY_CLASS_ROOT\<classname>\shell\open\command */
859 /* 3. win.ini, [extensions], extension (NB no leading '.' */
860 /* All I know of the order is that registry is checked before */
861 /* extensions; however, it'd make sense to check the programs */
862 /* section first, so that's what happens here. */
863
864 /* See if it's a program - if GetProfileString fails, we skip this
865 * section. Actually, if GetProfileString fails, we've probably
866 * got a lot more to worry about than running a program... */
867 if (GetProfileStringW(L"windows", L"programs", L"exe pif bat cmd com", wBuffer, ARRAY_SIZE(wBuffer)) > 0)
868 {
869 CharLowerW(wBuffer);
870 tok = wBuffer;
871 while (*tok)
872 {
873 WCHAR *p = tok;
874 while (*p && *p != ' ' && *p != '\t') p++;
875 if (*p)
876 {
877 *p++ = 0;
878 while (*p == ' ' || *p == '\t') p++;
879 }
880
881 if (_wcsicmp(tok, &extension[1]) == 0) /* have to skip the leading "." */
882 {
883 wcscpy(lpResult, xlpFile);
884 /* Need to perhaps check that the file has a path
885 * attached */
886 TRACE("found %s\n", debugstr_w(lpResult));
887 return 33;
888 /* Greater than 32 to indicate success */
889 }
890 tok = p;
891 }
892 }
893
894 /* Check registry */
895 if (RegQueryValueW(HKEY_CLASSES_ROOT, extension, classname,
896 &classnamelen) == ERROR_SUCCESS)
897 {
898 classnamelen /= sizeof(WCHAR);
899 if (classnamelen == ARRAY_SIZE(classname))
900 classnamelen--;
901
902 classname[classnamelen] = '\0';
903 TRACE("File type: %s\n", debugstr_w(classname));
904 }
905 else
906 {
907 *classname = '\0';
908 }
909 }
910
911 if (*classname)
912 {
913 /* pass the verb string to SHELL_FindExecutableByVerb() */
914 retval = SHELL_FindExecutableByVerb(lpVerb, key, classname, command, sizeof(command));
915
916 if (retval > 32)
917 {
918 DWORD finishedLen;
919 SHELL_ArgifyW(lpResult, resultLen, command, xlpFile, pidl, args, &finishedLen, lpPath);
920 if (finishedLen > resultLen)
921 ERR("Argify buffer not large enough.. truncated\n");
922 /* Remove double quotation marks and command line arguments */
923 if (*lpResult == '"')
924 {
925 WCHAR *p = lpResult;
926 while (*(p + 1) != '"')
927 {
928 *p = *(p + 1);
929 p++;
930 }
931 *p = '\0';
932 }
933 else
934 {
935 /* Truncate on first space */
936 WCHAR *p = lpResult;
937 while (*p != ' ' && *p != '\0')
938 p++;
939 *p = '\0';
940 }
941 }
942 }
943 else /* Check win.ini */
944 {
945 /* Toss the leading dot */
946 extension++;
947 if (GetProfileStringW(L"extensions", extension, L"", command, ARRAY_SIZE(command)) > 0)
948 {
949 if (wcslen(command) != 0)
950 {
951 wcscpy(lpResult, command);
952 tok = wcschr(lpResult, '^'); /* should be ^.extension? */
953 if (tok != NULL)
954 {
955 tok[0] = '\0';
956 wcscat(lpResult, xlpFile); /* what if no dir in xlpFile? */
957 tok = wcschr(command, '^'); /* see above */
958 if ((tok != NULL) && (wcslen(tok) > 5))
959 {
960 wcscat(lpResult, &tok[5]);
961 }
962 }
963 retval = 33; /* FIXME - see above */
964 }
965 }
966 }
967
968 TRACE("returning path %s, retval %d\n", debugstr_w(lpResult), retval);
969 return retval;
970 }
971
972 /******************************************************************
973 * dde_cb
974 *
975 * callback for the DDE connection. not really useful
976 */
dde_cb(UINT uType,UINT uFmt,HCONV hConv,HSZ hsz1,HSZ hsz2,HDDEDATA hData,ULONG_PTR dwData1,ULONG_PTR dwData2)977 static HDDEDATA CALLBACK dde_cb(UINT uType, UINT uFmt, HCONV hConv,
978 HSZ hsz1, HSZ hsz2, HDDEDATA hData,
979 ULONG_PTR dwData1, ULONG_PTR dwData2)
980 {
981 TRACE("dde_cb: %04x, %04x, %p, %p, %p, %p, %08lx, %08lx\n",
982 uType, uFmt, hConv, hsz1, hsz2, hData, dwData1, dwData2);
983 return NULL;
984 }
985
986 /******************************************************************
987 * dde_connect
988 *
989 * ShellExecute helper. Used to do an operation with a DDE connection
990 *
991 * Handles both the direct connection (try #1), and if it fails,
992 * launching an application and trying (#2) to connect to it
993 *
994 */
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)995 static unsigned dde_connect(const WCHAR* key, const WCHAR* start, WCHAR* ddeexec,
996 const WCHAR* lpFile, WCHAR *env,
997 LPCWSTR szCommandline, LPITEMIDLIST pidl, SHELL_ExecuteW32 execfunc,
998 const SHELLEXECUTEINFOW *psei, LPSHELLEXECUTEINFOW psei_out)
999 {
1000 WCHAR regkey[256];
1001 WCHAR * endkey = regkey + wcslen(key);
1002 WCHAR app[256], topic[256], ifexec[256], static_res[256];
1003 CHeapPtr<WCHAR, CLocalAllocator> dynamic_res;
1004 WCHAR * res;
1005 LONG applen, topiclen, ifexeclen;
1006 WCHAR * exec;
1007 DWORD ddeInst = 0;
1008 DWORD tid;
1009 DWORD resultLen, endkeyLen;
1010 HSZ hszApp, hszTopic;
1011 HCONV hConv;
1012 HDDEDATA hDdeData;
1013 unsigned ret = SE_ERR_NOASSOC;
1014 BOOL unicode = !(GetVersion() & 0x80000000);
1015
1016 if (strlenW(key) + 1 > ARRAY_SIZE(regkey))
1017 {
1018 FIXME("input parameter %s larger than buffer\n", debugstr_w(key));
1019 return 2;
1020 }
1021 wcscpy(regkey, key);
1022 endkeyLen = ARRAY_SIZE(regkey) - (endkey - regkey);
1023 if (strlenW(L"\\application") + 1 > endkeyLen)
1024 {
1025 FIXME("endkey %s overruns buffer\n", debugstr_w(L"\\application"));
1026 return 2;
1027 }
1028 wcscpy(endkey, L"\\application");
1029 applen = sizeof(app);
1030 if (RegQueryValueW(HKEY_CLASSES_ROOT, regkey, app, &applen) != ERROR_SUCCESS)
1031 {
1032 WCHAR command[1024], fullpath[MAX_PATH];
1033 LPWSTR ptr = NULL;
1034 DWORD ret = 0;
1035
1036 /* Get application command from start string and find filename of application */
1037 if (*start == '"')
1038 {
1039 if (strlenW(start + 1) + 1 > ARRAY_SIZE(command))
1040 {
1041 FIXME("size of input parameter %s larger than buffer\n",
1042 debugstr_w(start + 1));
1043 return 2;
1044 }
1045 wcscpy(command, start + 1);
1046 if ((ptr = wcschr(command, '"')))
1047 * ptr = 0;
1048 ret = SearchPathW(NULL, command, L".exe", ARRAY_SIZE(fullpath), fullpath, &ptr);
1049 }
1050 else
1051 {
1052 LPCWSTR p;
1053 LPWSTR space;
1054 for (p = start; (space = const_cast<LPWSTR>(strchrW(p, ' '))); p = space + 1)
1055 {
1056 int idx = space - start;
1057 memcpy(command, start, idx * sizeof(WCHAR));
1058 command[idx] = '\0';
1059 if ((ret = SearchPathW(NULL, command, L".exe", ARRAY_SIZE(fullpath), fullpath, &ptr)))
1060 break;
1061 }
1062 if (!ret)
1063 ret = SearchPathW(NULL, start, L".exe", ARRAY_SIZE(fullpath), fullpath, &ptr);
1064 }
1065
1066 if (!ret)
1067 {
1068 ERR("Unable to find application path for command %s\n", debugstr_w(start));
1069 return ERROR_ACCESS_DENIED;
1070 }
1071 if (strlenW(ptr) + 1 > ARRAY_SIZE(app))
1072 {
1073 FIXME("size of found path %s larger than buffer\n", debugstr_w(ptr));
1074 return 2;
1075 }
1076 wcscpy(app, ptr);
1077
1078 /* Remove extensions (including .so) */
1079 ptr = app + wcslen(app) - 3;
1080 if (ptr > app && !wcscmp(ptr, L".so"))
1081 *ptr = 0;
1082
1083 ptr = const_cast<LPWSTR>(strrchrW(app, '.'));
1084 assert(ptr);
1085 *ptr = 0;
1086 }
1087
1088 if (strlenW(L"\\topic") + 1 > endkeyLen)
1089 {
1090 FIXME("endkey %s overruns buffer\n", debugstr_w(L"\\topic"));
1091 return 2;
1092 }
1093 wcscpy(endkey, L"\\topic");
1094 topiclen = sizeof(topic);
1095 if (RegQueryValueW(HKEY_CLASSES_ROOT, regkey, topic, &topiclen) != ERROR_SUCCESS)
1096 {
1097 wcscpy(topic, L"System");
1098 }
1099
1100 if (unicode)
1101 {
1102 if (DdeInitializeW(&ddeInst, dde_cb, APPCMD_CLIENTONLY, 0L) != DMLERR_NO_ERROR)
1103 return 2;
1104 }
1105 else
1106 {
1107 if (DdeInitializeA(&ddeInst, dde_cb, APPCMD_CLIENTONLY, 0L) != DMLERR_NO_ERROR)
1108 return 2;
1109 }
1110
1111 hszApp = DdeCreateStringHandleW(ddeInst, app, CP_WINUNICODE);
1112 hszTopic = DdeCreateStringHandleW(ddeInst, topic, CP_WINUNICODE);
1113
1114 hConv = DdeConnect(ddeInst, hszApp, hszTopic, NULL);
1115 exec = ddeexec;
1116 if (!hConv)
1117 {
1118 TRACE("Launching %s\n", debugstr_w(start));
1119 ret = execfunc(start, env, TRUE, psei, psei_out);
1120 if (ret <= 32)
1121 {
1122 TRACE("Couldn't launch\n");
1123 goto error;
1124 }
1125 /* if ddeexec is NULL, then we just need to exit here */
1126 if (ddeexec == NULL)
1127 {
1128 TRACE("Exiting because ddeexec is NULL. ret=42.\n");
1129 /* See https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutew */
1130 /* for reason why we use 42 here and also "Shell32_apitest ShellExecuteW" regression test */
1131 return 42;
1132 }
1133 /* if ddeexec is 'empty string', then we just need to exit here */
1134 if (wcscmp(ddeexec, L"") == 0)
1135 {
1136 TRACE("Exiting because ddeexec is 'empty string'. ret=42.\n");
1137 /* See https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutew */
1138 /* for reason why we use 42 here and also "Shell32_apitest ShellExecuteW" regression test */
1139 return 42;
1140 }
1141 hConv = DdeConnect(ddeInst, hszApp, hszTopic, NULL);
1142 if (!hConv)
1143 {
1144 TRACE("Couldn't connect. ret=%d\n", ret);
1145 DdeUninitialize(ddeInst);
1146 SetLastError(ERROR_DDE_FAIL);
1147 return 30; /* whatever */
1148 }
1149 if (strlenW(L"\\ifexec") + 1 > endkeyLen)
1150 {
1151 FIXME("endkey %s overruns buffer\n", debugstr_w(L"\\ifexec"));
1152 return 2;
1153 }
1154 strcpyW(endkey, L"\\ifexec");
1155 ifexeclen = sizeof(ifexec);
1156 if (RegQueryValueW(HKEY_CLASSES_ROOT, regkey, ifexec, &ifexeclen) == ERROR_SUCCESS)
1157 {
1158 exec = ifexec;
1159 }
1160 }
1161
1162 SHELL_ArgifyW(static_res, ARRAY_SIZE(static_res), exec, lpFile, pidl, szCommandline, &resultLen, NULL);
1163 if (resultLen > ARRAY_SIZE(static_res))
1164 {
1165 dynamic_res.Allocate(resultLen);
1166 res = dynamic_res;
1167 SHELL_ArgifyW(dynamic_res, resultLen, exec, lpFile, pidl, szCommandline, NULL, NULL);
1168 }
1169 else
1170 res = static_res;
1171 TRACE("%s %s => %s\n", debugstr_w(exec), debugstr_w(lpFile), debugstr_w(res));
1172
1173 /* It's documented in the KB 330337 that IE has a bug and returns
1174 * error DMLERR_NOTPROCESSED on XTYP_EXECUTE request.
1175 */
1176 if (unicode)
1177 hDdeData = DdeClientTransaction((LPBYTE)res, (strlenW(res) + 1) * sizeof(WCHAR), hConv, 0L, 0, XTYP_EXECUTE, 30000, &tid);
1178 else
1179 {
1180 DWORD lenA = WideCharToMultiByte(CP_ACP, 0, res, -1, NULL, 0, NULL, NULL);
1181 CHeapPtr<char, CLocalAllocator> resA;
1182 resA.Allocate(lenA);
1183 WideCharToMultiByte(CP_ACP, 0, res, -1, resA, lenA, NULL, NULL);
1184 hDdeData = DdeClientTransaction( (LPBYTE)(LPSTR)resA, lenA, hConv, 0L, 0,
1185 XTYP_EXECUTE, 10000, &tid );
1186 }
1187 if (hDdeData)
1188 DdeFreeDataHandle(hDdeData);
1189 else
1190 WARN("DdeClientTransaction failed with error %04x\n", DdeGetLastError(ddeInst));
1191 ret = 33;
1192
1193 DdeDisconnect(hConv);
1194
1195 error:
1196 DdeUninitialize(ddeInst);
1197
1198 return ret;
1199 }
1200
1201 /*************************************************************************
1202 * execute_from_key [Internal]
1203 */
execute_from_key(LPCWSTR key,LPCWSTR lpFile,WCHAR * env,LPCWSTR szCommandline,LPCWSTR executable_name,SHELL_ExecuteW32 execfunc,LPSHELLEXECUTEINFOW psei,LPSHELLEXECUTEINFOW psei_out)1204 static UINT_PTR execute_from_key(LPCWSTR key, LPCWSTR lpFile, WCHAR *env,
1205 LPCWSTR szCommandline, LPCWSTR executable_name,
1206 SHELL_ExecuteW32 execfunc,
1207 LPSHELLEXECUTEINFOW psei, LPSHELLEXECUTEINFOW psei_out)
1208 {
1209 WCHAR cmd[256], param[1024], ddeexec[256];
1210 DWORD cmdlen = sizeof(cmd), ddeexeclen = sizeof(ddeexec);
1211 UINT_PTR retval = SE_ERR_NOASSOC;
1212 DWORD resultLen;
1213 LPWSTR tmp;
1214
1215 TRACE("%s %s %s %s %s\n", debugstr_w(key), debugstr_w(lpFile), debugstr_w(env),
1216 debugstr_w(szCommandline), debugstr_w(executable_name));
1217
1218 cmd[0] = '\0';
1219 param[0] = '\0';
1220
1221 /* Get the application from the registry */
1222 if (RegQueryValueW(HKEY_CLASSES_ROOT, key, cmd, (LONG *)&cmdlen) == ERROR_SUCCESS)
1223 {
1224 TRACE("got cmd: %s\n", debugstr_w(cmd));
1225
1226 /* Is there a replace() function anywhere? */
1227 cmdlen /= sizeof(WCHAR);
1228 if (cmdlen >= ARRAY_SIZE(cmd))
1229 cmdlen = ARRAY_SIZE(cmd) - 1;
1230 cmd[cmdlen] = '\0';
1231 SHELL_ArgifyW(param, ARRAY_SIZE(param), cmd, lpFile, (LPITEMIDLIST)psei->lpIDList, szCommandline, &resultLen,
1232 (psei->lpDirectory && *psei->lpDirectory) ? psei->lpDirectory : NULL);
1233 if (resultLen > ARRAY_SIZE(param))
1234 ERR("Argify buffer not large enough, truncating\n");
1235 }
1236
1237 /* Get the parameters needed by the application
1238 from the associated ddeexec key */
1239 tmp = const_cast<LPWSTR>(strstrW(key, L"command"));
1240 assert(tmp);
1241 wcscpy(tmp, L"ddeexec");
1242
1243 if (RegQueryValueW(HKEY_CLASSES_ROOT, key, ddeexec, (LONG *)&ddeexeclen) == ERROR_SUCCESS)
1244 {
1245 TRACE("Got ddeexec %s => %s\n", debugstr_w(key), debugstr_w(ddeexec));
1246 if (!param[0]) strcpyW(param, executable_name);
1247 retval = dde_connect(key, param, ddeexec, lpFile, env, szCommandline, (LPITEMIDLIST)psei->lpIDList, execfunc, psei, psei_out);
1248 }
1249 else if (param[0])
1250 {
1251 TRACE("executing: %s\n", debugstr_w(param));
1252 retval = execfunc(param, env, FALSE, psei, psei_out);
1253 }
1254 else
1255 WARN("Nothing appropriate found for %s\n", debugstr_w(key));
1256
1257 return retval;
1258 }
1259
1260 /*************************************************************************
1261 * FindExecutableA [SHELL32.@]
1262 */
FindExecutableA(LPCSTR lpFile,LPCSTR lpDirectory,LPSTR lpResult)1263 HINSTANCE WINAPI FindExecutableA(LPCSTR lpFile, LPCSTR lpDirectory, LPSTR lpResult)
1264 {
1265 HINSTANCE retval;
1266 WCHAR *wFile = NULL, *wDirectory = NULL;
1267 WCHAR wResult[MAX_PATH];
1268
1269 if (lpFile) __SHCloneStrAtoW(&wFile, lpFile);
1270 if (lpDirectory) __SHCloneStrAtoW(&wDirectory, lpDirectory);
1271
1272 retval = FindExecutableW(wFile, wDirectory, wResult);
1273 WideCharToMultiByte(CP_ACP, 0, wResult, -1, lpResult, MAX_PATH, NULL, NULL);
1274 SHFree(wFile);
1275 SHFree(wDirectory);
1276
1277 TRACE("returning %s\n", lpResult);
1278 return retval;
1279 }
1280
1281 /*************************************************************************
1282 * FindExecutableW [SHELL32.@]
1283 *
1284 * This function returns the executable associated with the specified file
1285 * for the default verb.
1286 *
1287 * PARAMS
1288 * lpFile [I] The file to find the association for. This must refer to
1289 * an existing file otherwise FindExecutable fails and returns
1290 * SE_ERR_FNF.
1291 * lpResult [O] Points to a buffer into which the executable path is
1292 * copied. This parameter must not be NULL otherwise
1293 * FindExecutable() segfaults. The buffer must be of size at
1294 * least MAX_PATH characters.
1295 *
1296 * RETURNS
1297 * A value greater than 32 on success, less than or equal to 32 otherwise.
1298 * See the SE_ERR_* constants.
1299 *
1300 * NOTES
1301 * On Windows XP and 2003, FindExecutable() seems to first convert the
1302 * filename into 8.3 format, thus taking into account only the first three
1303 * characters of the extension, and expects to find an association for those.
1304 * However other Windows versions behave sanely.
1305 */
FindExecutableW(LPCWSTR lpFile,LPCWSTR lpDirectory,LPWSTR lpResult)1306 HINSTANCE WINAPI FindExecutableW(LPCWSTR lpFile, LPCWSTR lpDirectory, LPWSTR lpResult)
1307 {
1308 UINT_PTR retval;
1309 WCHAR old_dir[MAX_PATH], res[MAX_PATH];
1310 DWORD cch = _countof(res);
1311 LPCWSTR dirs[2];
1312
1313 TRACE("File %s, Dir %s\n", debugstr_w(lpFile), debugstr_w(lpDirectory));
1314
1315 *lpResult = UNICODE_NULL;
1316
1317 GetCurrentDirectoryW(_countof(old_dir), old_dir);
1318
1319 if (lpDirectory && *lpDirectory)
1320 {
1321 SetCurrentDirectoryW(lpDirectory);
1322 dirs[0] = lpDirectory;
1323 }
1324 else
1325 {
1326 dirs[0] = old_dir;
1327 }
1328 dirs[1] = NULL;
1329
1330 if (!GetShortPathNameW(lpFile, res, _countof(res)))
1331 StringCchCopyW(res, _countof(res), lpFile);
1332
1333 if (PathResolveW(res, dirs, PRF_TRYPROGRAMEXTENSIONS | PRF_FIRSTDIRDEF))
1334 {
1335 // NOTE: The last parameter of this AssocQueryStringW call is "strange" in Windows.
1336 if (PathIsExeW(res) ||
1337 SUCCEEDED(AssocQueryStringW(ASSOCF_NONE, ASSOCSTR_EXECUTABLE, res, NULL, res, &cch)))
1338 {
1339 StringCchCopyW(lpResult, MAX_PATH, res);
1340 retval = 42;
1341 }
1342 else
1343 {
1344 retval = SE_ERR_NOASSOC;
1345 }
1346 }
1347 else
1348 {
1349 retval = SE_ERR_FNF;
1350 }
1351
1352 TRACE("returning %s\n", debugstr_w(lpResult));
1353 SetCurrentDirectoryW(old_dir);
1354 return (HINSTANCE)retval;
1355 }
1356
1357 /* FIXME: is this already implemented somewhere else? */
ShellExecute_GetClassKey(const SHELLEXECUTEINFOW * sei)1358 static HKEY ShellExecute_GetClassKey(const SHELLEXECUTEINFOW *sei)
1359 {
1360 LPCWSTR ext = NULL, lpClass = NULL;
1361 CHeapPtr<WCHAR, CLocalAllocator> cls;
1362 DWORD type = 0, sz = 0;
1363 HKEY hkey = 0;
1364 LONG r;
1365
1366 if (sei->fMask & SEE_MASK_CLASSALL)
1367 return sei->hkeyClass;
1368
1369 if (sei->fMask & SEE_MASK_CLASSNAME)
1370 lpClass = sei->lpClass;
1371 else
1372 {
1373 ext = PathFindExtensionW(sei->lpFile);
1374 TRACE("ext = %s\n", debugstr_w(ext));
1375 if (!ext)
1376 return hkey;
1377
1378 r = RegOpenKeyW(HKEY_CLASSES_ROOT, ext, &hkey);
1379 if (r != ERROR_SUCCESS)
1380 return hkey;
1381
1382 r = RegQueryValueExW(hkey, NULL, 0, &type, NULL, &sz);
1383 if (r == ERROR_SUCCESS && type == REG_SZ)
1384 {
1385 sz += sizeof (WCHAR);
1386 cls.Allocate(sz / sizeof(WCHAR));
1387 cls[0] = 0;
1388 RegQueryValueExW(hkey, NULL, 0, &type, (LPBYTE)(LPWSTR)cls, &sz);
1389 }
1390
1391 RegCloseKey( hkey );
1392 lpClass = cls;
1393 }
1394
1395 TRACE("class = %s\n", debugstr_w(lpClass));
1396
1397 hkey = 0;
1398 if (lpClass)
1399 RegOpenKeyW( HKEY_CLASSES_ROOT, lpClass, &hkey);
1400
1401 return hkey;
1402 }
1403
shellex_get_dataobj(LPSHELLEXECUTEINFOW sei,CComPtr<IDataObject> & dataObj)1404 static HRESULT shellex_get_dataobj( LPSHELLEXECUTEINFOW sei, CComPtr<IDataObject>& dataObj)
1405 {
1406 CComHeapPtr<ITEMIDLIST> allocatedPidl;
1407 LPITEMIDLIST pidl = NULL;
1408
1409 if (sei->fMask & SEE_MASK_CLASSALL)
1410 {
1411 pidl = (LPITEMIDLIST)sei->lpIDList;
1412 }
1413 else
1414 {
1415 WCHAR fullpath[MAX_PATH];
1416 BOOL ret;
1417
1418 fullpath[0] = 0;
1419 ret = GetFullPathNameW(sei->lpFile, MAX_PATH, fullpath, NULL);
1420 if (!ret)
1421 return HRESULT_FROM_WIN32(GetLastError());
1422
1423 pidl = ILCreateFromPathW(fullpath);
1424 allocatedPidl.Attach(pidl);
1425 }
1426 return SHELL_GetUIObjectOfAbsoluteItem(NULL, pidl, IID_PPV_ARG(IDataObject, &dataObj));
1427 }
1428
shellex_run_context_menu_default(IShellExtInit * obj,LPSHELLEXECUTEINFOW sei)1429 static HRESULT shellex_run_context_menu_default(IShellExtInit *obj,
1430 LPSHELLEXECUTEINFOW sei)
1431 {
1432 CComPtr<IContextMenu> cm = NULL;
1433 CMINVOKECOMMANDINFOEX ici;
1434 MENUITEMINFOW info;
1435 WCHAR string[0x80];
1436 INT i, n, def = -1;
1437 HMENU hmenu = 0;
1438 HRESULT r;
1439
1440 TRACE("%p %p\n", obj, sei);
1441
1442 r = obj->QueryInterface(IID_PPV_ARG(IContextMenu, &cm));
1443 if (FAILED(r))
1444 return r;
1445
1446 hmenu = CreateMenu();
1447 if (!hmenu)
1448 goto end;
1449
1450 /* the number of the last menu added is returned in r */
1451 r = cm->QueryContextMenu(hmenu, 0, 0x20, 0x7fff, CMF_DEFAULTONLY);
1452 if (FAILED(r))
1453 goto end;
1454
1455 n = GetMenuItemCount(hmenu);
1456 for (i = 0; i < n; i++)
1457 {
1458 memset(&info, 0, sizeof(info));
1459 info.cbSize = sizeof info;
1460 info.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_STATE | MIIM_DATA | MIIM_ID;
1461 info.dwTypeData = string;
1462 info.cch = sizeof string;
1463 string[0] = 0;
1464 GetMenuItemInfoW(hmenu, i, TRUE, &info);
1465
1466 TRACE("menu %d %s %08x %08lx %08x %08x\n", i, debugstr_w(string),
1467 info.fState, info.dwItemData, info.fType, info.wID);
1468 if ((!sei->lpVerb && (info.fState & MFS_DEFAULT)) ||
1469 (sei->lpVerb && !lstrcmpiW(sei->lpVerb, string)))
1470 {
1471 def = i;
1472 break;
1473 }
1474 }
1475
1476 r = E_FAIL;
1477 if (def == -1)
1478 goto end;
1479
1480 memset(&ici, 0, sizeof ici);
1481 ici.cbSize = sizeof ici;
1482 ici.fMask = CMIC_MASK_UNICODE | (sei->fMask & (SEE_MASK_NO_CONSOLE | SEE_MASK_NOASYNC | SEE_MASK_ASYNCOK | SEE_MASK_FLAG_NO_UI));
1483 ici.nShow = sei->nShow;
1484 ici.lpVerb = MAKEINTRESOURCEA(def);
1485 ici.hwnd = sei->hwnd;
1486 ici.lpParametersW = sei->lpParameters;
1487
1488 r = cm->InvokeCommand((LPCMINVOKECOMMANDINFO)&ici);
1489
1490 TRACE("invoke command returned %08x\n", r);
1491
1492 end:
1493 if (hmenu)
1494 DestroyMenu( hmenu );
1495 return r;
1496 }
1497
shellex_load_object_and_run(HKEY hkey,LPCGUID guid,LPSHELLEXECUTEINFOW sei)1498 static HRESULT shellex_load_object_and_run(HKEY hkey, LPCGUID guid, LPSHELLEXECUTEINFOW sei)
1499 {
1500 TRACE("%p %s %p\n", hkey, debugstr_guid(guid), sei);
1501
1502 CCoInit coInit;
1503
1504 if (FAILED_UNEXPECTEDLY(coInit.hr))
1505 return coInit.hr;
1506
1507 CComPtr<IShellExtInit> obj;
1508 HRESULT hr = CoCreateInstance(*guid, NULL, CLSCTX_INPROC_SERVER,
1509 IID_PPV_ARG(IShellExtInit, &obj));
1510 if (FAILED_UNEXPECTEDLY(hr))
1511 return hr;
1512
1513 CComPtr<IDataObject> dataobj;
1514 hr = shellex_get_dataobj(sei, dataobj);
1515 if (FAILED_UNEXPECTEDLY(hr))
1516 return hr;
1517
1518 hr = obj->Initialize(NULL, dataobj, hkey);
1519 if (FAILED_UNEXPECTEDLY(hr))
1520 return hr;
1521
1522 CComPtr<IObjectWithSite> ows;
1523 hr = obj->QueryInterface(IID_PPV_ARG(IObjectWithSite, &ows));
1524 if (FAILED_UNEXPECTEDLY(hr))
1525 return hr;
1526
1527 ows->SetSite(NULL);
1528
1529 return shellex_run_context_menu_default(obj, sei);
1530 }
1531
shellex_get_contextmenu(LPSHELLEXECUTEINFOW sei,CComPtr<IContextMenu> & cm)1532 static HRESULT shellex_get_contextmenu(LPSHELLEXECUTEINFOW sei, CComPtr<IContextMenu>& cm)
1533 {
1534 CComHeapPtr<ITEMIDLIST> allocatedPidl;
1535 LPITEMIDLIST pidl = NULL;
1536
1537 if (sei->lpIDList)
1538 {
1539 pidl = (LPITEMIDLIST)sei->lpIDList;
1540 }
1541 else
1542 {
1543 SFGAOF sfga = 0;
1544 HRESULT hr = SHParseDisplayName(sei->lpFile, NULL, &allocatedPidl, SFGAO_STORAGECAPMASK, &sfga);
1545 if (FAILED(hr))
1546 {
1547 WCHAR Buffer[MAX_PATH] = {};
1548 // FIXME: MAX_PATH.....
1549 UINT retval = SHELL_FindExecutable(sei->lpDirectory, sei->lpFile, sei->lpVerb, Buffer, _countof(Buffer), NULL, NULL, NULL, sei->lpParameters);
1550 if (retval <= 32)
1551 return HRESULT_FROM_WIN32(retval);
1552
1553 hr = SHParseDisplayName(Buffer, NULL, &allocatedPidl, SFGAO_STORAGECAPMASK, &sfga);
1554 // This should not happen, we found it...
1555 if (FAILED_UNEXPECTEDLY(hr))
1556 return hr;
1557 }
1558
1559 pidl = allocatedPidl;
1560 }
1561
1562 CComPtr<IShellFolder> shf;
1563 LPCITEMIDLIST pidllast = NULL;
1564 HRESULT hr = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &shf), &pidllast);
1565 if (FAILED(hr))
1566 return hr;
1567
1568 return shf->GetUIObjectOf(NULL, 1, &pidllast, IID_NULL_PPV_ARG(IContextMenu, &cm));
1569 }
1570
ShellExecute_ContextMenuVerb(LPSHELLEXECUTEINFOW sei)1571 static HRESULT ShellExecute_ContextMenuVerb(LPSHELLEXECUTEINFOW sei)
1572 {
1573 TRACE("%p\n", sei);
1574
1575 CCoInit coInit;
1576
1577 if (FAILED_UNEXPECTEDLY(coInit.hr))
1578 return coInit.hr;
1579
1580 CComPtr<IContextMenu> cm;
1581 HRESULT hr = shellex_get_contextmenu(sei, cm);
1582 if (FAILED_UNEXPECTEDLY(hr))
1583 return hr;
1584
1585 CComHeapPtr<char> verb, parameters, dir;
1586 __SHCloneStrWtoA(&verb, sei->lpVerb);
1587 __SHCloneStrWtoA(¶meters, sei->lpParameters);
1588 __SHCloneStrWtoA(&dir, sei->lpDirectory);
1589
1590 BOOL fDefault = StrIsNullOrEmpty(sei->lpVerb);
1591 CMINVOKECOMMANDINFOEX ici = { sizeof(ici) };
1592 ici.fMask = SeeFlagsToCmicFlags(sei->fMask) | CMIC_MASK_UNICODE;
1593 ici.nShow = sei->nShow;
1594 if (!fDefault)
1595 {
1596 ici.lpVerb = verb;
1597 ici.lpVerbW = sei->lpVerb;
1598 }
1599 ici.hwnd = sei->hwnd;
1600 ici.lpParameters = parameters;
1601 ici.lpParametersW = sei->lpParameters;
1602 ici.lpDirectory = dir;
1603 ici.lpDirectoryW = sei->lpDirectory;
1604 ici.dwHotKey = sei->dwHotKey;
1605 ici.hIcon = sei->hIcon;
1606 if (ici.fMask & (CMIC_MASK_HASLINKNAME | CMIC_MASK_HASTITLE))
1607 ici.lpTitleW = sei->lpClass;
1608
1609 enum { idFirst = 1, idLast = 0x7fff };
1610 HMENU hMenu = CreatePopupMenu();
1611 // Note: Windows does not pass CMF_EXTENDEDVERBS so "hidden" verbs cannot be executed
1612 hr = cm->QueryContextMenu(hMenu, 0, idFirst, idLast, fDefault ? CMF_DEFAULTONLY : 0);
1613 if (!FAILED_UNEXPECTEDLY(hr))
1614 {
1615 if (fDefault)
1616 {
1617 INT uDefault = GetMenuDefaultItem(hMenu, FALSE, 0);
1618 uDefault = (uDefault != -1) ? uDefault - idFirst : 0;
1619 ici.lpVerb = MAKEINTRESOURCEA(uDefault);
1620 ici.lpVerbW = MAKEINTRESOURCEW(uDefault);
1621 }
1622
1623 hr = cm->InvokeCommand((LPCMINVOKECOMMANDINFO)&ici);
1624 if (!FAILED_UNEXPECTEDLY(hr))
1625 hr = S_OK;
1626 }
1627
1628 DestroyMenu(hMenu);
1629
1630 return hr;
1631 }
1632
1633
1634 /*************************************************************************
1635 * ShellExecute_FromContextMenu [Internal]
1636 */
ShellExecute_FromContextMenuHandlers(LPSHELLEXECUTEINFOW sei)1637 static LONG ShellExecute_FromContextMenuHandlers( LPSHELLEXECUTEINFOW sei )
1638 {
1639 HKEY hkey, hkeycm = 0;
1640 WCHAR szguid[39];
1641 HRESULT hr;
1642 GUID guid;
1643 DWORD i;
1644 LONG r;
1645
1646 TRACE("%s\n", debugstr_w(sei->lpFile));
1647
1648 hkey = ShellExecute_GetClassKey(sei);
1649 if (!hkey)
1650 return ERROR_FUNCTION_FAILED;
1651
1652 r = RegOpenKeyW(hkey, L"shellex\\ContextMenuHandlers", &hkeycm);
1653 if (r == ERROR_SUCCESS)
1654 {
1655 i = 0;
1656 while (1)
1657 {
1658 r = RegEnumKeyW(hkeycm, i++, szguid, ARRAY_SIZE(szguid));
1659 if (r != ERROR_SUCCESS)
1660 break;
1661
1662 hr = CLSIDFromString(szguid, &guid);
1663 if (SUCCEEDED(hr))
1664 {
1665 /* stop at the first one that succeeds in running */
1666 hr = shellex_load_object_and_run(hkey, &guid, sei);
1667 if (SUCCEEDED(hr))
1668 break;
1669 }
1670 }
1671 RegCloseKey(hkeycm);
1672 }
1673
1674 if (hkey != sei->hkeyClass)
1675 RegCloseKey(hkey);
1676 return r;
1677 }
1678
1679 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);
1680
SHELL_execute_class(LPCWSTR wszApplicationName,LPSHELLEXECUTEINFOW psei,LPSHELLEXECUTEINFOW psei_out,SHELL_ExecuteW32 execfunc)1681 static UINT_PTR SHELL_execute_class(LPCWSTR wszApplicationName, LPSHELLEXECUTEINFOW psei, LPSHELLEXECUTEINFOW psei_out, SHELL_ExecuteW32 execfunc)
1682 {
1683 WCHAR execCmd[1024], classname[1024];
1684 /* launch a document by fileclass like 'WordPad.Document.1' */
1685 /* the Commandline contains 'c:\Path\wordpad.exe "%1"' */
1686 /* FIXME: wcmd should not be of a fixed size. Fixed to 1024, MAX_PATH is way too short! */
1687 ULONG cmask = (psei->fMask & SEE_MASK_CLASSALL);
1688 DWORD resultLen;
1689 BOOL done;
1690 UINT_PTR rslt;
1691
1692 /* FIXME: remove following block when SHELL_quote_and_execute supports hkeyClass parameter */
1693 if (cmask != SEE_MASK_CLASSNAME)
1694 {
1695 WCHAR wcmd[1024];
1696 HCR_GetExecuteCommandW((cmask == SEE_MASK_CLASSKEY) ? psei->hkeyClass : NULL,
1697 (cmask == SEE_MASK_CLASSNAME) ? psei->lpClass : NULL,
1698 psei->lpVerb,
1699 execCmd, sizeof(execCmd));
1700
1701 /* FIXME: get the extension of lpFile, check if it fits to the lpClass */
1702 TRACE("SEE_MASK_CLASSNAME->%s, doc->%s\n", debugstr_w(execCmd), debugstr_w(wszApplicationName));
1703
1704 wcmd[0] = '\0';
1705 done = SHELL_ArgifyW(wcmd, ARRAY_SIZE(wcmd), execCmd, wszApplicationName, (LPITEMIDLIST)psei->lpIDList, psei->lpParameters,
1706 &resultLen, (psei->lpDirectory && *psei->lpDirectory) ? psei->lpDirectory : NULL);
1707 if (!done && wszApplicationName[0])
1708 {
1709 #if 0 // Given HKCR\.test=SZ:"test" and HKCR\test\shell\open\command=SZ:"cmd.exe /K echo.Hello", no filename is
1710 // appended on Windows when there is no %1 nor %L when executed with: shlextdbg.exe /shellexec=c:\file.test /INVOKE
1711 strcatW(wcmd, L" ");
1712 if (*wszApplicationName != '"')
1713 {
1714 strcatW(wcmd, L"\"");
1715 strcatW(wcmd, wszApplicationName);
1716 strcatW(wcmd, L"\"");
1717 }
1718 else
1719 strcatW(wcmd, wszApplicationName);
1720 #endif
1721 }
1722 if (resultLen > ARRAY_SIZE(wcmd))
1723 ERR("Argify buffer not large enough... truncating\n");
1724 return execfunc(wcmd, NULL, FALSE, psei, psei_out);
1725 }
1726
1727 strcpyW(classname, psei->lpClass);
1728 rslt = SHELL_FindExecutableByVerb(psei->lpVerb, NULL, classname, execCmd, sizeof(execCmd));
1729
1730 TRACE("SHELL_FindExecutableByVerb returned %u (%s, %s)\n", (unsigned int)rslt, debugstr_w(classname), debugstr_w(execCmd));
1731 if (33 > rslt)
1732 return rslt;
1733 rslt = SHELL_quote_and_execute( execCmd, L"", classname,
1734 wszApplicationName, NULL, psei,
1735 psei_out, execfunc );
1736 return rslt;
1737
1738 }
1739
SHELL_translate_idlist(LPSHELLEXECUTEINFOW sei,LPWSTR wszParameters,DWORD parametersLen,LPWSTR wszApplicationName,DWORD dwApplicationNameLen)1740 static BOOL SHELL_translate_idlist(LPSHELLEXECUTEINFOW sei, LPWSTR wszParameters, DWORD parametersLen, LPWSTR wszApplicationName, DWORD dwApplicationNameLen)
1741 {
1742 WCHAR buffer[MAX_PATH];
1743 BOOL appKnownSingular = FALSE;
1744
1745 /* last chance to translate IDList: now also allow CLSID paths */
1746 if (SUCCEEDED(SHELL_GetPathFromIDListForExecuteW((LPCITEMIDLIST)sei->lpIDList, buffer, ARRAY_SIZE(buffer)))) {
1747 if (buffer[0] == ':' && buffer[1] == ':') {
1748 /* open shell folder for the specified class GUID */
1749 if (strlenW(buffer) + 1 > parametersLen)
1750 ERR("parameters len exceeds buffer size (%i > %i), truncating\n",
1751 lstrlenW(buffer) + 1, parametersLen);
1752 lstrcpynW(wszParameters, buffer, parametersLen);
1753 if (strlenW(L"explorer.exe") > dwApplicationNameLen)
1754 ERR("application len exceeds buffer size (%i), truncating\n",
1755 dwApplicationNameLen);
1756 lstrcpynW(wszApplicationName, L"explorer.exe", dwApplicationNameLen);
1757 appKnownSingular = TRUE;
1758
1759 sei->fMask &= ~SEE_MASK_INVOKEIDLIST;
1760 } else {
1761 WCHAR target[max(MAX_PATH, _countof(buffer))];
1762 DWORD attribs;
1763 DWORD resultLen;
1764 /* Check if we're executing a directory and if so use the
1765 handler for the Folder class */
1766 strcpyW(target, buffer);
1767 attribs = GetFileAttributesW(buffer);
1768 if (attribs != INVALID_FILE_ATTRIBUTES &&
1769 (attribs & FILE_ATTRIBUTE_DIRECTORY) &&
1770 HCR_GetExecuteCommandW(0, L"Folder",
1771 sei->lpVerb,
1772 buffer, sizeof(buffer))) {
1773 SHELL_ArgifyW(wszApplicationName, dwApplicationNameLen,
1774 buffer, target, (LPITEMIDLIST)sei->lpIDList, NULL, &resultLen,
1775 !StrIsNullOrEmpty(sei->lpDirectory) ? sei->lpDirectory : NULL);
1776 if (resultLen > dwApplicationNameLen)
1777 ERR("Argify buffer not large enough... truncating\n"); // FIXME: Report this to the caller?
1778 appKnownSingular = FALSE;
1779 // HACKFIX: We really want the !appKnownSingular code in SHELL_execute to split the
1780 // parameters for us but we cannot guarantee that the exe in the registry is quoted.
1781 // We have now turned 'explorer.exe "%1" into 'explorer.exe "c:\path\from\pidl"' and
1782 // need to split to application and parameters.
1783 LPCWSTR params = PathGetArgsW(wszApplicationName);
1784 lstrcpynW(wszParameters, params, parametersLen);
1785 PathRemoveArgsW(wszApplicationName);
1786 PathUnquoteSpacesW(wszApplicationName);
1787 appKnownSingular = TRUE;
1788 }
1789 sei->fMask &= ~SEE_MASK_INVOKEIDLIST;
1790 }
1791 }
1792 return appKnownSingular;
1793 }
1794
1795 static BOOL
SHELL_InvokePidl(_In_ LPSHELLEXECUTEINFOW sei,_In_ LPCITEMIDLIST pidl)1796 SHELL_InvokePidl(
1797 _In_ LPSHELLEXECUTEINFOW sei,
1798 _In_ LPCITEMIDLIST pidl)
1799 {
1800 // Bind pidl
1801 CComPtr<IShellFolder> psfFolder;
1802 LPCITEMIDLIST pidlLast;
1803 HRESULT hr = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &psfFolder), &pidlLast);
1804 if (FAILED_UNEXPECTEDLY(hr))
1805 return FALSE;
1806
1807 // Get the context menu to invoke a command
1808 CComPtr<IContextMenu> pCM;
1809 hr = psfFolder->GetUIObjectOf(NULL, 1, &pidlLast, IID_NULL_PPV_ARG(IContextMenu, &pCM));
1810 if (FAILED_UNEXPECTEDLY(hr))
1811 return FALSE;
1812
1813 // Invoke a command
1814 CMINVOKECOMMANDINFO ici = { sizeof(ici) };
1815 ici.fMask = (sei->fMask & (SEE_MASK_NO_CONSOLE | SEE_MASK_ASYNCOK | SEE_MASK_FLAG_NO_UI));
1816 ici.nShow = sei->nShow;
1817 ici.hwnd = sei->hwnd;
1818 char szVerb[VERBKEY_CCHMAX];
1819 if (sei->lpVerb && sei->lpVerb[0])
1820 {
1821 WideCharToMultiByte(CP_ACP, 0, sei->lpVerb, -1, szVerb, _countof(szVerb), NULL, NULL);
1822 szVerb[_countof(szVerb) - 1] = ANSI_NULL; // Avoid buffer overrun
1823 ici.lpVerb = szVerb;
1824 }
1825 else // The default verb?
1826 {
1827 HMENU hMenu = CreatePopupMenu();
1828 const INT idCmdFirst = 1, idCmdLast = 0x7FFF;
1829 hr = pCM->QueryContextMenu(hMenu, 0, idCmdFirst, idCmdLast, CMF_DEFAULTONLY);
1830 if (FAILED_UNEXPECTEDLY(hr))
1831 {
1832 DestroyMenu(hMenu);
1833 return FALSE;
1834 }
1835
1836 INT nDefaultID = GetMenuDefaultItem(hMenu, FALSE, 0);
1837 DestroyMenu(hMenu);
1838 if (nDefaultID == -1)
1839 nDefaultID = idCmdFirst;
1840
1841 ici.lpVerb = MAKEINTRESOURCEA(nDefaultID - idCmdFirst);
1842 }
1843 hr = pCM->InvokeCommand(&ici);
1844
1845 return !FAILED_UNEXPECTEDLY(hr);
1846 }
1847
SHELL_quote_and_execute(LPCWSTR wcmd,LPCWSTR wszParameters,LPCWSTR wszKeyname,LPCWSTR wszApplicationName,LPWSTR env,LPSHELLEXECUTEINFOW psei,LPSHELLEXECUTEINFOW psei_out,SHELL_ExecuteW32 execfunc)1848 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)
1849 {
1850 UINT_PTR retval;
1851 DWORD len;
1852 CHeapPtr<WCHAR, CLocalAllocator> wszQuotedCmd;
1853
1854 /* Length of quotes plus length of command plus NULL terminator */
1855 len = 2 + lstrlenW(wcmd) + 1;
1856 if (wszParameters[0])
1857 {
1858 /* Length of space plus length of parameters */
1859 len += 1 + lstrlenW(wszParameters);
1860 }
1861 wszQuotedCmd.Allocate(len);
1862 /* Must quote to handle case where cmd contains spaces,
1863 * else security hole if malicious user creates executable file "C:\\Program"
1864 */
1865 strcpyW(wszQuotedCmd, L"\"");
1866 strcatW(wszQuotedCmd, wcmd);
1867 strcatW(wszQuotedCmd, L"\"");
1868 if (wszParameters[0])
1869 {
1870 strcatW(wszQuotedCmd, L" ");
1871 strcatW(wszQuotedCmd, wszParameters);
1872 }
1873
1874 TRACE("%s/%s => %s/%s\n", debugstr_w(wszApplicationName), debugstr_w(psei->lpVerb), debugstr_w(wszQuotedCmd), debugstr_w(wszKeyname));
1875
1876 if (*wszKeyname)
1877 retval = execute_from_key(wszKeyname, wszApplicationName, env, psei->lpParameters, wcmd, execfunc, psei, psei_out);
1878 else
1879 retval = execfunc(wszQuotedCmd, env, FALSE, psei, psei_out);
1880
1881 return retval;
1882 }
1883
SHELL_execute_url(LPCWSTR lpFile,LPCWSTR wcmd,LPSHELLEXECUTEINFOW psei,LPSHELLEXECUTEINFOW psei_out,SHELL_ExecuteW32 execfunc)1884 static UINT_PTR SHELL_execute_url(LPCWSTR lpFile, LPCWSTR wcmd, LPSHELLEXECUTEINFOW psei, LPSHELLEXECUTEINFOW psei_out, SHELL_ExecuteW32 execfunc)
1885 {
1886 UINT_PTR retval;
1887 CHeapPtr<WCHAR, CLocalAllocator> lpstrProtocol;
1888 LPCWSTR lpstrRes;
1889 INT iSize;
1890 DWORD len;
1891
1892 lpstrRes = strchrW(lpFile, ':');
1893 if (lpstrRes)
1894 iSize = lpstrRes - lpFile;
1895 else
1896 iSize = strlenW(lpFile);
1897
1898 TRACE("Got URL: %s\n", debugstr_w(lpFile));
1899 /* Looking for ...<protocol>\shell\<lpVerb>\command */
1900 len = iSize + lstrlenW(L"\\shell\\") + lstrlenW(L"\\command") + 1;
1901 if (psei->lpVerb && *psei->lpVerb)
1902 len += lstrlenW(psei->lpVerb);
1903 else
1904 len += lstrlenW(L"open");
1905 lpstrProtocol.Allocate(len);
1906 memcpy(lpstrProtocol, lpFile, iSize * sizeof(WCHAR));
1907 lpstrProtocol[iSize] = '\0';
1908 strcatW(lpstrProtocol, L"\\shell\\");
1909 strcatW(lpstrProtocol, psei->lpVerb && *psei->lpVerb ? psei->lpVerb : L"open");
1910 strcatW(lpstrProtocol, L"\\command");
1911
1912 retval = execute_from_key(lpstrProtocol, lpFile, NULL, psei->lpParameters,
1913 wcmd, execfunc, psei, psei_out);
1914
1915 return retval;
1916 }
1917
do_error_dialog(UINT_PTR retval,HWND hwnd,WCHAR * filename)1918 static void do_error_dialog(UINT_PTR retval, HWND hwnd, WCHAR* filename)
1919 {
1920 WCHAR msg[2048];
1921 DWORD_PTR msgArguments[3] = { (DWORD_PTR)filename, 0, 0 };
1922 DWORD error_code;
1923
1924 error_code = GetLastError();
1925 if (retval == SE_ERR_NOASSOC)
1926 LoadStringW(shell32_hInstance, IDS_SHLEXEC_NOASSOC, msg, ARRAY_SIZE(msg));
1927 else
1928 FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY,
1929 NULL,
1930 error_code,
1931 LANG_USER_DEFAULT,
1932 msg,
1933 ARRAY_SIZE(msg),
1934 (va_list*)msgArguments);
1935
1936 MessageBoxW(hwnd, msg, NULL, MB_ICONERROR);
1937 }
1938
expand_environment(const WCHAR * str)1939 static WCHAR *expand_environment( const WCHAR *str )
1940 {
1941 CHeapPtr<WCHAR, CLocalAllocator> buf;
1942 DWORD len;
1943
1944 len = ExpandEnvironmentStringsW(str, NULL, 0);
1945 if (!len) return NULL;
1946
1947 if (!buf.Allocate(len))
1948 return NULL;
1949
1950 len = ExpandEnvironmentStringsW(str, buf, len);
1951 if (!len)
1952 return NULL;
1953
1954 return buf.Detach();
1955 }
1956
1957 /*************************************************************************
1958 * SHELL_execute [Internal]
1959 */
SHELL_execute(LPSHELLEXECUTEINFOW sei,SHELL_ExecuteW32 execfunc)1960 static BOOL SHELL_execute(LPSHELLEXECUTEINFOW sei, SHELL_ExecuteW32 execfunc)
1961 {
1962 static const DWORD unsupportedFlags =
1963 SEE_MASK_ICON | SEE_MASK_HOTKEY |
1964 SEE_MASK_CONNECTNETDRV | SEE_MASK_FLAG_DDEWAIT |
1965 SEE_MASK_ASYNCOK | SEE_MASK_HMONITOR;
1966
1967 DWORD len;
1968 UINT_PTR retval = SE_ERR_NOASSOC;
1969 BOOL appKnownSingular = FALSE;
1970
1971 /* make a local copy of the LPSHELLEXECUTEINFO structure and work with this from now on */
1972 SHELLEXECUTEINFOW sei_tmp = *sei;
1973
1974 TRACE("mask=0x%08x hwnd=%p verb=%s file=%s parm=%s dir=%s show=0x%08x class=%s\n",
1975 sei_tmp.fMask, sei_tmp.hwnd, debugstr_w(sei_tmp.lpVerb),
1976 debugstr_w(sei_tmp.lpFile), debugstr_w(sei_tmp.lpParameters),
1977 debugstr_w(sei_tmp.lpDirectory), sei_tmp.nShow,
1978 ((sei_tmp.fMask & SEE_MASK_CLASSALL) == SEE_MASK_CLASSNAME) ?
1979 debugstr_w(sei_tmp.lpClass) : "not used");
1980
1981 sei->hProcess = NULL;
1982
1983 /* make copies of all path/command strings */
1984 CHeapPtr<WCHAR, CLocalAllocator> wszApplicationName;
1985 DWORD dwApplicationNameLen = MAX_PATH + 2;
1986 if (!sei_tmp.lpFile)
1987 {
1988 wszApplicationName.Allocate(dwApplicationNameLen);
1989 *wszApplicationName = '\0';
1990 }
1991 else if (*sei_tmp.lpFile == '\"' && sei_tmp.lpFile[(len = strlenW(sei_tmp.lpFile))-1] == '\"')
1992 {
1993 if(len-1 >= dwApplicationNameLen)
1994 dwApplicationNameLen = len;
1995
1996 wszApplicationName.Allocate(dwApplicationNameLen);
1997 memcpy(wszApplicationName, sei_tmp.lpFile + 1, len * sizeof(WCHAR));
1998
1999 if(len > 2)
2000 wszApplicationName[len-2] = '\0';
2001 appKnownSingular = TRUE;
2002
2003 TRACE("wszApplicationName=%s\n", debugstr_w(wszApplicationName));
2004 }
2005 else
2006 {
2007 DWORD l = strlenW(sei_tmp.lpFile) + 1;
2008 if(l > dwApplicationNameLen) dwApplicationNameLen = l + 1;
2009 wszApplicationName.Allocate(dwApplicationNameLen);
2010 memcpy(wszApplicationName, sei_tmp.lpFile, l * sizeof(WCHAR));
2011
2012 if (wszApplicationName[2] == 0 && wszApplicationName[1] == L':' &&
2013 ((L'A' <= wszApplicationName[0] && wszApplicationName[0] <= L'Z') ||
2014 (L'a' <= wszApplicationName[0] && wszApplicationName[0] <= L'z')))
2015 {
2016 // 'C:' --> 'C:\'
2017 PathAddBackslashW(wszApplicationName);
2018 }
2019 }
2020
2021 WCHAR parametersBuffer[1024];
2022 LPWSTR wszParameters = parametersBuffer;
2023 CHeapPtr<WCHAR, CLocalAllocator> wszParamAlloc;
2024 DWORD parametersLen = _countof(parametersBuffer);
2025
2026 if (sei_tmp.lpParameters)
2027 {
2028 len = lstrlenW(sei_tmp.lpParameters) + 1;
2029 if (len > parametersLen)
2030 {
2031 wszParamAlloc.Allocate(len);
2032 wszParameters = wszParamAlloc;
2033 parametersLen = len;
2034 }
2035 strcpyW(wszParameters, sei_tmp.lpParameters);
2036 }
2037 else
2038 *wszParameters = L'\0';
2039
2040 // Get the working directory
2041 WCHAR dirBuffer[MAX_PATH];
2042 LPWSTR wszDir = dirBuffer;
2043 wszDir[0] = UNICODE_NULL;
2044 CHeapPtr<WCHAR, CLocalAllocator> wszDirAlloc;
2045 if (sei_tmp.lpDirectory && *sei_tmp.lpDirectory)
2046 {
2047 if (sei_tmp.fMask & SEE_MASK_DOENVSUBST)
2048 {
2049 LPWSTR tmp = expand_environment(sei_tmp.lpDirectory);
2050 if (tmp)
2051 {
2052 wszDirAlloc.Attach(tmp);
2053 wszDir = wszDirAlloc;
2054 }
2055 }
2056 else
2057 {
2058 __SHCloneStrW(&wszDirAlloc, sei_tmp.lpDirectory);
2059 if (wszDirAlloc)
2060 wszDir = wszDirAlloc;
2061 }
2062 }
2063 if (!wszDir[0])
2064 {
2065 ::GetCurrentDirectoryW(_countof(dirBuffer), dirBuffer);
2066 wszDir = dirBuffer;
2067 }
2068 // NOTE: ShellExecute should accept the invalid working directory for historical reason.
2069 if (!PathIsDirectoryW(wszDir))
2070 {
2071 INT iDrive = PathGetDriveNumberW(wszDir);
2072 if (iDrive >= 0)
2073 {
2074 PathStripToRootW(wszDir);
2075 if (!PathIsDirectoryW(wszDir))
2076 {
2077 ::GetWindowsDirectoryW(dirBuffer, _countof(dirBuffer));
2078 wszDir = dirBuffer;
2079 }
2080 }
2081 }
2082
2083 /* adjust string pointers to point to the new buffers */
2084 sei_tmp.lpFile = wszApplicationName;
2085 sei_tmp.lpParameters = wszParameters;
2086 sei_tmp.lpDirectory = wszDir;
2087
2088 if (sei_tmp.fMask & unsupportedFlags)
2089 {
2090 FIXME("flags ignored: 0x%08x\n", sei_tmp.fMask & unsupportedFlags);
2091 }
2092
2093 /* process the IDList */
2094 if (sei_tmp.fMask & SEE_MASK_IDLIST &&
2095 (sei_tmp.fMask & SEE_MASK_INVOKEIDLIST) != SEE_MASK_INVOKEIDLIST)
2096 {
2097 CComPtr<IShellExecuteHookW> pSEH;
2098
2099 HRESULT hr = SHBindToParent((LPCITEMIDLIST)sei_tmp.lpIDList, IID_PPV_ARG(IShellExecuteHookW, &pSEH), NULL);
2100
2101 if (SUCCEEDED(hr))
2102 {
2103 hr = pSEH->Execute(&sei_tmp);
2104 if (hr == S_OK)
2105 return TRUE;
2106 }
2107
2108 SHGetPathFromIDListW((LPCITEMIDLIST)sei_tmp.lpIDList, wszApplicationName);
2109 appKnownSingular = TRUE;
2110 TRACE("-- idlist=%p (%s)\n", sei_tmp.lpIDList, debugstr_w(wszApplicationName));
2111 }
2112
2113 if ((sei_tmp.fMask & SEE_MASK_DOENVSUBST) && !StrIsNullOrEmpty(sei_tmp.lpFile))
2114 {
2115 WCHAR *tmp = expand_environment(sei_tmp.lpFile);
2116 if (tmp)
2117 {
2118 wszApplicationName.Attach(tmp);
2119 sei_tmp.lpFile = wszApplicationName;
2120 }
2121 }
2122
2123 if ((sei_tmp.fMask & SEE_MASK_INVOKEIDLIST) == SEE_MASK_INVOKEIDLIST)
2124 {
2125 HRESULT hr = ShellExecute_ContextMenuVerb(&sei_tmp);
2126 if (SUCCEEDED(hr))
2127 {
2128 sei->hInstApp = (HINSTANCE)42;
2129 return TRUE;
2130 }
2131 }
2132
2133 if (ERROR_SUCCESS == ShellExecute_FromContextMenuHandlers(&sei_tmp))
2134 {
2135 sei->hInstApp = (HINSTANCE) 33;
2136 return TRUE;
2137 }
2138
2139 if (sei_tmp.fMask & SEE_MASK_CLASSALL)
2140 {
2141 retval = SHELL_execute_class(wszApplicationName, &sei_tmp, sei, execfunc);
2142 if (retval <= 32 && !(sei_tmp.fMask & SEE_MASK_FLAG_NO_UI))
2143 {
2144 OPENASINFO Info;
2145
2146 //FIXME
2147 // need full path
2148
2149 Info.pcszFile = wszApplicationName;
2150 Info.pcszClass = NULL;
2151 Info.oaifInFlags = OAIF_ALLOW_REGISTRATION | OAIF_EXEC;
2152
2153 //if (SHOpenWithDialog(sei_tmp.hwnd, &Info) != S_OK)
2154 DBG_UNREFERENCED_LOCAL_VARIABLE(Info);
2155 do_error_dialog(retval, sei_tmp.hwnd, wszApplicationName);
2156 }
2157 return retval > 32;
2158 }
2159
2160 if (!(sei_tmp.fMask & SEE_MASK_IDLIST) && // Not an ID List
2161 (StrCmpNIW(sei_tmp.lpFile, L"shell:", 6) == 0 ||
2162 StrCmpNW(sei_tmp.lpFile, L"::{", 3) == 0))
2163 {
2164 CComHeapPtr<ITEMIDLIST> pidlParsed;
2165 HRESULT hr = SHParseDisplayName(sei_tmp.lpFile, NULL, &pidlParsed, 0, NULL);
2166 if (SUCCEEDED(hr) && SHELL_InvokePidl(&sei_tmp, pidlParsed))
2167 {
2168 sei_tmp.hInstApp = (HINSTANCE)UlongToHandle(42);
2169 return TRUE;
2170 }
2171 }
2172
2173 /* Has the IDList not yet been translated? */
2174 if (sei_tmp.fMask & SEE_MASK_IDLIST)
2175 {
2176 appKnownSingular = SHELL_translate_idlist( &sei_tmp, wszParameters,
2177 parametersLen,
2178 wszApplicationName,
2179 dwApplicationNameLen );
2180 }
2181
2182 /* convert file URLs */
2183 if (UrlIsFileUrlW(sei_tmp.lpFile))
2184 {
2185 CHeapPtr<WCHAR, CLocalAllocator> buf;
2186 DWORD size = MAX_PATH;
2187 if (!buf.Allocate(size) || FAILED(PathCreateFromUrlW(sei_tmp.lpFile, buf, &size, 0)))
2188 return SE_ERR_OOM;
2189
2190 wszApplicationName.Attach(buf.Detach());
2191 sei_tmp.lpFile = wszApplicationName;
2192 }
2193
2194 /* Else, try to execute the filename */
2195 TRACE("execute: %s,%s,%s\n", debugstr_w(wszApplicationName), debugstr_w(wszParameters), debugstr_w(wszDir));
2196
2197 /* separate out command line arguments from executable file name */
2198 LPCWSTR lpFile = sei_tmp.lpFile;
2199 if (!*sei_tmp.lpParameters && !appKnownSingular)
2200 {
2201 /* If the executable path is quoted, handle the rest of the command line as parameters. */
2202 if (sei_tmp.lpFile[0] == L'"')
2203 {
2204 LPWSTR pszArgs = PathGetArgsW(wszApplicationName);
2205 PathRemoveArgsW(wszApplicationName);
2206 PathUnquoteSpacesW(wszApplicationName);
2207 parametersLen = lstrlenW(pszArgs);
2208 if (parametersLen < _countof(parametersBuffer))
2209 {
2210 StringCchCopyW(parametersBuffer, _countof(parametersBuffer), pszArgs);
2211 wszParameters = parametersBuffer;
2212 }
2213 else
2214 {
2215 wszParamAlloc.Attach(StrDupW(pszArgs));
2216 wszParameters = wszParamAlloc;
2217 }
2218 }
2219 /* We have to test sei instead of sei_tmp because sei_tmp had its
2220 * input fMask modified above in SHELL_translate_idlist.
2221 * This code is needed to handle the case where we only have an
2222 * lpIDList with multiple CLSID/PIDL's (not 'My Computer' only) */
2223 else if ((sei->fMask & SEE_MASK_IDLIST) == SEE_MASK_IDLIST)
2224 {
2225 WCHAR buffer[MAX_PATH], xlpFile[MAX_PATH];
2226 LPWSTR space, s;
2227
2228 LPWSTR beg = wszApplicationName;
2229 for(s = beg; (space = const_cast<LPWSTR>(strchrW(s, L' '))); s = space + 1)
2230 {
2231 int idx = space - sei_tmp.lpFile;
2232 memcpy(buffer, sei_tmp.lpFile, idx * sizeof(WCHAR));
2233 buffer[idx] = '\0';
2234
2235 if (SearchPathW(*sei_tmp.lpDirectory ? sei_tmp.lpDirectory : NULL,
2236 buffer, L".exe", _countof(xlpFile), xlpFile, NULL))
2237 {
2238 /* separate out command from parameter string */
2239 LPCWSTR p = space + 1;
2240
2241 while(isspaceW(*p))
2242 ++p;
2243
2244 strcpyW(wszParameters, p);
2245 *space = L'\0';
2246
2247 break;
2248 }
2249 }
2250 }
2251 }
2252
2253 WCHAR wcmdBuffer[1024];
2254 LPWSTR wcmd = wcmdBuffer;
2255 DWORD wcmdLen = _countof(wcmdBuffer);
2256 CHeapPtr<WCHAR, CLocalAllocator> wcmdAlloc;
2257
2258 /* Only execute if it has an executable extension */
2259 if (PathIsExeW(lpFile))
2260 {
2261 len = lstrlenW(wszApplicationName) + 3;
2262 if (sei_tmp.lpParameters[0])
2263 len += 1 + lstrlenW(wszParameters);
2264 if (len > wcmdLen)
2265 {
2266 wcmdAlloc.Allocate(len);
2267 wcmd = wcmdAlloc;
2268 wcmdLen = len;
2269 }
2270 swprintf(wcmd, L"\"%s\"", (LPWSTR)wszApplicationName);
2271 if (sei_tmp.lpParameters[0])
2272 {
2273 strcatW(wcmd, L" ");
2274 strcatW(wcmd, wszParameters);
2275 }
2276
2277 retval = execfunc(wcmd, NULL, FALSE, &sei_tmp, sei);
2278 if (retval > 32)
2279 return TRUE;
2280 }
2281
2282 /* Else, try to find the executable */
2283 WCHAR wszKeyname[256];
2284 CHeapPtr<WCHAR, CLocalAllocator> env;
2285 wcmd[0] = UNICODE_NULL;
2286 retval = SHELL_FindExecutable(sei_tmp.lpDirectory, lpFile, sei_tmp.lpVerb, wcmd, wcmdLen, wszKeyname, &env, (LPITEMIDLIST)sei_tmp.lpIDList, sei_tmp.lpParameters);
2287 if (retval > 32) /* Found */
2288 {
2289 retval = SHELL_quote_and_execute(wcmd, wszParameters, wszKeyname,
2290 wszApplicationName, env, &sei_tmp,
2291 sei, execfunc);
2292 }
2293 else if (PathIsDirectoryW(lpFile))
2294 {
2295 WCHAR wExec[MAX_PATH];
2296 CHeapPtr<WCHAR, CLocalAllocator> lpQuotedFile;
2297 if (lpQuotedFile.Allocate(strlenW(lpFile) + 3))
2298 {
2299 retval = SHELL_FindExecutable(sei_tmp.lpDirectory, L"explorer",
2300 L"open", wExec, MAX_PATH,
2301 NULL, &env, NULL, NULL);
2302 if (retval > 32)
2303 {
2304 swprintf(lpQuotedFile, L"\"%s\"", lpFile);
2305 retval = SHELL_quote_and_execute(wExec, lpQuotedFile,
2306 wszKeyname,
2307 wszApplicationName, env,
2308 &sei_tmp, sei, execfunc);
2309 }
2310 }
2311 else
2312 retval = 0; /* Out of memory */
2313 }
2314 else if (PathIsURLW(lpFile)) /* File not found, check for URL */
2315 {
2316 retval = SHELL_execute_url(lpFile, wcmd, &sei_tmp, sei, execfunc );
2317 }
2318 /* Check if file specified is in the form www.??????.*** */
2319 else if (!strncmpiW(lpFile, L"www", 3))
2320 {
2321 /* if so, prefix lpFile with http:// and call ShellExecute */
2322 WCHAR lpstrTmpFile[256];
2323 strcpyW(lpstrTmpFile, L"http://");
2324 strcatW(lpstrTmpFile, lpFile);
2325 retval = (UINT_PTR)ShellExecuteW(sei_tmp.hwnd, sei_tmp.lpVerb, lpstrTmpFile, NULL, NULL, 0);
2326 }
2327
2328 TRACE("retval %lu\n", retval);
2329
2330 if (retval <= 32 && !(sei_tmp.fMask & SEE_MASK_FLAG_NO_UI))
2331 {
2332 OPENASINFO Info;
2333
2334 //FIXME
2335 // need full path
2336
2337 Info.pcszFile = wszApplicationName;
2338 Info.pcszClass = NULL;
2339 Info.oaifInFlags = OAIF_ALLOW_REGISTRATION | OAIF_EXEC;
2340
2341 //if (SHOpenWithDialog(sei_tmp.hwnd, &Info) != S_OK)
2342 DBG_UNREFERENCED_LOCAL_VARIABLE(Info);
2343 do_error_dialog(retval, sei_tmp.hwnd, wszApplicationName);
2344 }
2345
2346 sei->hInstApp = (HINSTANCE)(retval > 32 ? 33 : retval);
2347
2348 return retval > 32;
2349 }
2350
2351 /*************************************************************************
2352 * ShellExecuteA [SHELL32.290]
2353 */
ShellExecuteA(HWND hWnd,LPCSTR lpVerb,LPCSTR lpFile,LPCSTR lpParameters,LPCSTR lpDirectory,INT iShowCmd)2354 HINSTANCE WINAPI ShellExecuteA(HWND hWnd, LPCSTR lpVerb, LPCSTR lpFile,
2355 LPCSTR lpParameters, LPCSTR lpDirectory, INT iShowCmd)
2356 {
2357 SHELLEXECUTEINFOA sei;
2358
2359 TRACE("%p,%s,%s,%s,%s,%d\n",
2360 hWnd, debugstr_a(lpVerb), debugstr_a(lpFile),
2361 debugstr_a(lpParameters), debugstr_a(lpDirectory), iShowCmd);
2362
2363 sei.cbSize = sizeof(sei);
2364 sei.fMask = SEE_MASK_FLAG_NO_UI;
2365 sei.hwnd = hWnd;
2366 sei.lpVerb = lpVerb;
2367 sei.lpFile = lpFile;
2368 sei.lpParameters = lpParameters;
2369 sei.lpDirectory = lpDirectory;
2370 sei.nShow = iShowCmd;
2371 sei.lpIDList = 0;
2372 sei.lpClass = 0;
2373 sei.hkeyClass = 0;
2374 sei.dwHotKey = 0;
2375 sei.hProcess = 0;
2376
2377 if (!(SHGetAppCompatFlags(SHACF_WIN95SHLEXEC) & SHACF_WIN95SHLEXEC))
2378 sei.fMask |= SEE_MASK_NOASYNC;
2379 ShellExecuteExA(&sei);
2380 return sei.hInstApp;
2381 }
2382
2383 static DWORD
ShellExecute_Normal(_Inout_ LPSHELLEXECUTEINFOW sei)2384 ShellExecute_Normal(_Inout_ LPSHELLEXECUTEINFOW sei)
2385 {
2386 // FIXME
2387 return SHELL_execute(sei, SHELL_ExecuteW) ? ERROR_SUCCESS : ERROR_FILE_NOT_FOUND;
2388 }
2389
2390 static VOID
ShellExecute_ShowError(_In_ const SHELLEXECUTEINFOW * ExecInfo,_In_opt_ LPCWSTR pszCaption,_In_ DWORD dwError)2391 ShellExecute_ShowError(
2392 _In_ const SHELLEXECUTEINFOW *ExecInfo,
2393 _In_opt_ LPCWSTR pszCaption,
2394 _In_ DWORD dwError)
2395 {
2396 // FIXME: Show error message
2397 }
2398
2399 /*************************************************************************
2400 * ShellExecuteExA [SHELL32.292]
2401 */
2402 BOOL
2403 WINAPI
2404 DECLSPEC_HOTPATCH
ShellExecuteExA(LPSHELLEXECUTEINFOA sei)2405 ShellExecuteExA(LPSHELLEXECUTEINFOA sei)
2406 {
2407 SHELLEXECUTEINFOW seiW;
2408 BOOL ret;
2409 WCHAR *wVerb = NULL, *wFile = NULL, *wParameters = NULL, *wDirectory = NULL, *wClass = NULL;
2410
2411 TRACE("%p\n", sei);
2412
2413 if (sei->cbSize != sizeof(SHELLEXECUTEINFOA))
2414 {
2415 sei->hInstApp = (HINSTANCE)ERROR_ACCESS_DENIED;
2416 SetLastError(ERROR_ACCESS_DENIED);
2417 return FALSE;
2418 }
2419
2420 memcpy(&seiW, sei, sizeof(SHELLEXECUTEINFOW));
2421
2422 seiW.cbSize = sizeof(SHELLEXECUTEINFOW);
2423
2424 if (sei->lpVerb)
2425 seiW.lpVerb = __SHCloneStrAtoW(&wVerb, sei->lpVerb);
2426
2427 if (sei->lpFile)
2428 seiW.lpFile = __SHCloneStrAtoW(&wFile, sei->lpFile);
2429
2430 if (sei->lpParameters)
2431 seiW.lpParameters = __SHCloneStrAtoW(&wParameters, sei->lpParameters);
2432
2433 if (sei->lpDirectory)
2434 seiW.lpDirectory = __SHCloneStrAtoW(&wDirectory, sei->lpDirectory);
2435
2436 if ((sei->fMask & SEE_MASK_CLASSALL) == SEE_MASK_CLASSNAME && sei->lpClass)
2437 seiW.lpClass = __SHCloneStrAtoW(&wClass, sei->lpClass);
2438 else
2439 seiW.lpClass = NULL;
2440
2441 ret = ShellExecuteExW(&seiW);
2442
2443 sei->hInstApp = seiW.hInstApp;
2444
2445 if (sei->fMask & SEE_MASK_NOCLOSEPROCESS)
2446 sei->hProcess = seiW.hProcess;
2447
2448 SHFree(wVerb);
2449 SHFree(wFile);
2450 SHFree(wParameters);
2451 SHFree(wDirectory);
2452 SHFree(wClass);
2453
2454 return ret;
2455 }
2456
2457 /*************************************************************************
2458 * ShellExecuteExW [SHELL32.293]
2459 */
2460 BOOL
2461 WINAPI
2462 DECLSPEC_HOTPATCH
ShellExecuteExW(LPSHELLEXECUTEINFOW sei)2463 ShellExecuteExW(LPSHELLEXECUTEINFOW sei)
2464 {
2465 HRESULT hrCoInit;
2466 DWORD dwError;
2467 ULONG fOldMask;
2468
2469 if (sei->cbSize != sizeof(SHELLEXECUTEINFOW))
2470 {
2471 sei->hInstApp = (HINSTANCE)UlongToHandle(SE_ERR_ACCESSDENIED);
2472 SetLastError(ERROR_ACCESS_DENIED);
2473 return FALSE;
2474 }
2475
2476 hrCoInit = SHCoInitializeAnyApartment();
2477
2478 if (SHRegGetBoolUSValueW(L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer",
2479 L"MaximizeApps", FALSE, FALSE))
2480 {
2481 switch (sei->nShow)
2482 {
2483 case SW_SHOW:
2484 case SW_SHOWDEFAULT:
2485 case SW_SHOWNORMAL:
2486 case SW_RESTORE:
2487 sei->nShow = SW_SHOWMAXIMIZED;
2488 break;
2489 default:
2490 break;
2491 }
2492 }
2493
2494 fOldMask = sei->fMask;
2495
2496 if (!(fOldMask & SEE_MASK_NOASYNC) && SHELL_InRunDllProcess())
2497 sei->fMask |= SEE_MASK_WAITFORINPUTIDLE | SEE_MASK_NOASYNC;
2498
2499 dwError = ShellExecute_Normal(sei);
2500
2501 if (dwError && dwError != ERROR_DLL_NOT_FOUND && dwError != ERROR_CANCELLED)
2502 ShellExecute_ShowError(sei, NULL, dwError);
2503
2504 sei->fMask = fOldMask;
2505
2506 if (SUCCEEDED(hrCoInit))
2507 CoUninitialize();
2508
2509 if (dwError)
2510 SetLastError(dwError);
2511
2512 return dwError == ERROR_SUCCESS;
2513 }
2514
2515 /*************************************************************************
2516 * ShellExecuteW [SHELL32.294]
2517 */
ShellExecuteW(HWND hwnd,LPCWSTR lpVerb,LPCWSTR lpFile,LPCWSTR lpParameters,LPCWSTR lpDirectory,INT nShowCmd)2518 HINSTANCE WINAPI ShellExecuteW(HWND hwnd, LPCWSTR lpVerb, LPCWSTR lpFile,
2519 LPCWSTR lpParameters, LPCWSTR lpDirectory, INT nShowCmd)
2520 {
2521 SHELLEXECUTEINFOW sei;
2522
2523 TRACE("\n");
2524 sei.cbSize = sizeof(sei);
2525 sei.fMask = SEE_MASK_FLAG_NO_UI;
2526 sei.hwnd = hwnd;
2527 sei.lpVerb = lpVerb;
2528 sei.lpFile = lpFile;
2529 sei.lpParameters = lpParameters;
2530 sei.lpDirectory = lpDirectory;
2531 sei.nShow = nShowCmd;
2532 sei.lpIDList = 0;
2533 sei.lpClass = 0;
2534 sei.hkeyClass = 0;
2535 sei.dwHotKey = 0;
2536 sei.hProcess = 0;
2537
2538 if (!(SHGetAppCompatFlags(SHACF_WIN95SHLEXEC) & SHACF_WIN95SHLEXEC))
2539 sei.fMask |= SEE_MASK_NOASYNC;
2540 ShellExecuteExW(&sei);
2541 return sei.hInstApp;
2542 }
2543
2544 /*************************************************************************
2545 * WOWShellExecute [SHELL32.@]
2546 *
2547 * FIXME: the callback function most likely doesn't work the same way on Windows.
2548 */
WOWShellExecute(HWND hWnd,LPCSTR lpVerb,LPCSTR lpFile,LPCSTR lpParameters,LPCSTR lpDirectory,INT iShowCmd,void * callback)2549 EXTERN_C HINSTANCE WINAPI WOWShellExecute(HWND hWnd, LPCSTR lpVerb, LPCSTR lpFile,
2550 LPCSTR lpParameters, LPCSTR lpDirectory, INT iShowCmd, void *callback)
2551 {
2552 SHELLEXECUTEINFOW seiW;
2553 WCHAR *wVerb = NULL, *wFile = NULL, *wParameters = NULL, *wDirectory = NULL;
2554 HANDLE hProcess = 0;
2555
2556 seiW.lpVerb = lpVerb ? __SHCloneStrAtoW(&wVerb, lpVerb) : NULL;
2557 seiW.lpFile = lpFile ? __SHCloneStrAtoW(&wFile, lpFile) : NULL;
2558 seiW.lpParameters = lpParameters ? __SHCloneStrAtoW(&wParameters, lpParameters) : NULL;
2559 seiW.lpDirectory = lpDirectory ? __SHCloneStrAtoW(&wDirectory, lpDirectory) : NULL;
2560
2561 seiW.cbSize = sizeof(seiW);
2562 seiW.fMask = 0;
2563 seiW.hwnd = hWnd;
2564 seiW.nShow = iShowCmd;
2565 seiW.lpIDList = 0;
2566 seiW.lpClass = 0;
2567 seiW.hkeyClass = 0;
2568 seiW.dwHotKey = 0;
2569 seiW.hProcess = hProcess;
2570
2571 SHELL_execute(&seiW, (SHELL_ExecuteW32)callback);
2572
2573 SHFree(wVerb);
2574 SHFree(wFile);
2575 SHFree(wParameters);
2576 SHFree(wDirectory);
2577 return seiW.hInstApp;
2578 }
2579
2580 /*************************************************************************
2581 * OpenAs_RunDLLW [SHELL32.@]
2582 */
2583 EXTERN_C void WINAPI
OpenAs_RunDLLW(HWND hwnd,HINSTANCE hinst,LPCWSTR cmdline,int cmdshow)2584 OpenAs_RunDLLW(HWND hwnd, HINSTANCE hinst, LPCWSTR cmdline, int cmdshow)
2585 {
2586 OPENASINFO info;
2587 TRACE("%p, %p, %s, %d\n", hwnd, hinst, debugstr_w(cmdline), cmdshow);
2588
2589 ZeroMemory(&info, sizeof(info));
2590 info.pcszFile = cmdline;
2591 info.pcszClass = NULL;
2592 info.oaifInFlags = OAIF_ALLOW_REGISTRATION | OAIF_REGISTER_EXT | OAIF_EXEC;
2593
2594 SHOpenWithDialog(hwnd, &info);
2595 }
2596
2597 /*************************************************************************
2598 * OpenAs_RunDLLA [SHELL32.@]
2599 */
2600 EXTERN_C void WINAPI
OpenAs_RunDLLA(HWND hwnd,HINSTANCE hinst,LPCSTR cmdline,int cmdshow)2601 OpenAs_RunDLLA(HWND hwnd, HINSTANCE hinst, LPCSTR cmdline, int cmdshow)
2602 {
2603 LPWSTR pszCmdLineW = NULL;
2604 TRACE("%p, %p, %s, %d\n", hwnd, hinst, debugstr_a(cmdline), cmdshow);
2605
2606 if (cmdline)
2607 __SHCloneStrAtoW(&pszCmdLineW, cmdline);
2608 OpenAs_RunDLLW(hwnd, hinst, pszCmdLineW, cmdshow);
2609 SHFree(pszCmdLineW);
2610 }
2611
2612 /*************************************************************************/
2613
2614 static LPCWSTR
SplitParams(LPCWSTR psz,LPWSTR pszArg0,size_t cchArg0)2615 SplitParams(LPCWSTR psz, LPWSTR pszArg0, size_t cchArg0)
2616 {
2617 LPCWSTR pch;
2618 size_t ich = 0;
2619 if (*psz == L'"')
2620 {
2621 // 1st argument is quoted. the string in quotes is quoted 1st argument.
2622 // [pch] --> [pszArg0+ich]
2623 for (pch = psz + 1; *pch && ich + 1 < cchArg0; ++ich, ++pch)
2624 {
2625 if (*pch == L'"' && pch[1] == L'"')
2626 {
2627 // doubled double quotations found!
2628 pszArg0[ich] = L'"';
2629 }
2630 else if (*pch == L'"')
2631 {
2632 // single double quotation found!
2633 ++pch;
2634 break;
2635 }
2636 else
2637 {
2638 // otherwise
2639 pszArg0[ich] = *pch;
2640 }
2641 }
2642 }
2643 else
2644 {
2645 // 1st argument is unquoted. non-space sequence is 1st argument.
2646 // [pch] --> [pszArg0+ich]
2647 for (pch = psz; *pch && !iswspace(*pch) && ich + 1 < cchArg0; ++ich, ++pch)
2648 {
2649 pszArg0[ich] = *pch;
2650 }
2651 }
2652 pszArg0[ich] = 0;
2653
2654 // skip space
2655 while (iswspace(*pch))
2656 ++pch;
2657
2658 return pch;
2659 }
2660
ShellExecCmdLine(HWND hwnd,LPCWSTR pwszCommand,LPCWSTR pwszStartDir,int nShow,LPVOID pUnused,DWORD dwSeclFlags)2661 HRESULT WINAPI ShellExecCmdLine(
2662 HWND hwnd,
2663 LPCWSTR pwszCommand,
2664 LPCWSTR pwszStartDir,
2665 int nShow,
2666 LPVOID pUnused,
2667 DWORD dwSeclFlags)
2668 {
2669 SHELLEXECUTEINFOW info;
2670 DWORD dwSize, dwError, dwType, dwFlags = SEE_MASK_DOENVSUBST | SEE_MASK_NOASYNC;
2671 LPCWSTR pszVerb = NULL;
2672 WCHAR szFile[MAX_PATH], szFile2[MAX_PATH];
2673 HRESULT hr;
2674 LPCWSTR pchParams;
2675 LPWSTR lpCommand = NULL;
2676
2677 if (pwszCommand == NULL)
2678 RaiseException(EXCEPTION_ACCESS_VIOLATION, EXCEPTION_NONCONTINUABLE,
2679 1, (ULONG_PTR*)pwszCommand);
2680
2681 __SHCloneStrW(&lpCommand, pwszCommand);
2682 StrTrimW(lpCommand, L" \t");
2683
2684 if (dwSeclFlags & SECL_NO_UI)
2685 dwFlags |= SEE_MASK_FLAG_NO_UI;
2686 if (dwSeclFlags & SECL_LOG_USAGE)
2687 dwFlags |= SEE_MASK_FLAG_LOG_USAGE;
2688 if (dwSeclFlags & SECL_USE_IDLIST)
2689 dwFlags |= SEE_MASK_INVOKEIDLIST;
2690
2691 if (dwSeclFlags & SECL_RUNAS)
2692 {
2693 dwSize = 0;
2694 hr = AssocQueryStringW(ASSOCF_NONE, ASSOCSTR_COMMAND, lpCommand, L"RunAs", NULL, &dwSize);
2695 if (SUCCEEDED(hr) && dwSize != 0)
2696 {
2697 pszVerb = L"runas";
2698 }
2699 }
2700
2701 if (PathIsURLW(lpCommand) || UrlIsW(lpCommand, URLIS_APPLIABLE))
2702 {
2703 StringCchCopyW(szFile, _countof(szFile), lpCommand);
2704 pchParams = NULL;
2705 }
2706 else
2707 {
2708 PCWSTR apPathList[2];
2709
2710 pchParams = SplitParams(lpCommand, szFile, _countof(szFile));
2711 if (szFile[0] != UNICODE_NULL && szFile[1] == L':' &&
2712 szFile[2] == UNICODE_NULL)
2713 {
2714 PathAddBackslashW(szFile);
2715 }
2716
2717 WCHAR szCurDir[MAX_PATH];
2718 GetCurrentDirectoryW(_countof(szCurDir), szCurDir);
2719 if (pwszStartDir)
2720 {
2721 SetCurrentDirectoryW(pwszStartDir);
2722 }
2723
2724 if ((PathIsRelativeW(szFile) &&
2725 GetFullPathNameW(szFile, _countof(szFile2), szFile2, NULL) &&
2726 PathFileExistsW(szFile2)) ||
2727 SearchPathW(NULL, szFile, NULL, _countof(szFile2), szFile2, NULL))
2728 {
2729 StringCchCopyW(szFile, _countof(szFile), szFile2);
2730 }
2731
2732 apPathList[0] = pwszStartDir;
2733 apPathList[1] = NULL;
2734 PathFindOnPathExW(szFile, apPathList, WHICH_DEFAULT);
2735
2736 if (!(dwSeclFlags & SECL_ALLOW_NONEXE))
2737 {
2738 if (!GetBinaryTypeW(szFile, &dwType))
2739 {
2740 SHFree(lpCommand);
2741
2742 if (!(dwSeclFlags & SECL_NO_UI))
2743 {
2744 WCHAR szText[128 + MAX_PATH], szFormat[128];
2745 LoadStringW(shell32_hInstance, IDS_FILE_NOT_FOUND, szFormat, _countof(szFormat));
2746 StringCchPrintfW(szText, _countof(szText), szFormat, szFile);
2747 MessageBoxW(hwnd, szText, NULL, MB_ICONERROR);
2748 }
2749 return CO_E_APPNOTFOUND;
2750 }
2751 }
2752 else
2753 {
2754 if (GetFileAttributesW(szFile) == INVALID_FILE_ATTRIBUTES)
2755 {
2756 SHFree(lpCommand);
2757
2758 if (!(dwSeclFlags & SECL_NO_UI))
2759 {
2760 WCHAR szText[128 + MAX_PATH], szFormat[128];
2761 LoadStringW(shell32_hInstance, IDS_FILE_NOT_FOUND, szFormat, _countof(szFormat));
2762 StringCchPrintfW(szText, _countof(szText), szFormat, szFile);
2763 MessageBoxW(hwnd, szText, NULL, MB_ICONERROR);
2764 }
2765 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
2766 }
2767 }
2768 }
2769
2770 ZeroMemory(&info, sizeof(info));
2771 info.cbSize = sizeof(info);
2772 info.fMask = dwFlags;
2773 info.hwnd = hwnd;
2774 info.lpVerb = pszVerb;
2775 info.lpFile = szFile;
2776 info.lpParameters = (pchParams && *pchParams) ? pchParams : NULL;
2777 info.lpDirectory = pwszStartDir;
2778 info.nShow = nShow;
2779 if (ShellExecuteExW(&info))
2780 {
2781 if (info.lpIDList)
2782 CoTaskMemFree(info.lpIDList);
2783
2784 SHFree(lpCommand);
2785
2786 return S_OK;
2787 }
2788
2789 dwError = GetLastError();
2790
2791 SHFree(lpCommand);
2792
2793 return HRESULT_FROM_WIN32(dwError);
2794 }
2795
2796 /*************************************************************************
2797 * RealShellExecuteExA (SHELL32.266)
2798 */
2799 EXTERN_C
2800 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)2801 RealShellExecuteExA(
2802 _In_opt_ HWND hwnd,
2803 _In_opt_ LPCSTR lpOperation,
2804 _In_opt_ LPCSTR lpFile,
2805 _In_opt_ LPCSTR lpParameters,
2806 _In_opt_ LPCSTR lpDirectory,
2807 _In_opt_ LPSTR lpReturn,
2808 _In_opt_ LPCSTR lpTitle,
2809 _In_opt_ LPVOID lpReserved,
2810 _In_ INT nCmdShow,
2811 _Out_opt_ PHANDLE lphProcess,
2812 _In_ DWORD dwFlags)
2813 {
2814 SHELLEXECUTEINFOA ExecInfo;
2815
2816 TRACE("(%p, %s, %s, %s, %s, %p, %s, %p, %u, %p, %lu)\n",
2817 hwnd, debugstr_a(lpOperation), debugstr_a(lpFile), debugstr_a(lpParameters),
2818 debugstr_a(lpDirectory), lpReserved, debugstr_a(lpTitle),
2819 lpReserved, nCmdShow, lphProcess, dwFlags);
2820
2821 ZeroMemory(&ExecInfo, sizeof(ExecInfo));
2822 ExecInfo.cbSize = sizeof(ExecInfo);
2823 ExecInfo.fMask = SEE_MASK_FLAG_NO_UI | SEE_MASK_UNKNOWN_0x1000;
2824 ExecInfo.hwnd = hwnd;
2825 ExecInfo.lpVerb = lpOperation;
2826 ExecInfo.lpFile = lpFile;
2827 ExecInfo.lpParameters = lpParameters;
2828 ExecInfo.lpDirectory = lpDirectory;
2829 ExecInfo.nShow = (WORD)nCmdShow;
2830
2831 if (lpReserved)
2832 {
2833 ExecInfo.fMask |= SEE_MASK_USE_RESERVED;
2834 ExecInfo.hInstApp = (HINSTANCE)lpReserved;
2835 }
2836
2837 if (lpTitle)
2838 {
2839 ExecInfo.fMask |= SEE_MASK_HASTITLE;
2840 ExecInfo.lpClass = lpTitle;
2841 }
2842
2843 if (dwFlags & 1)
2844 ExecInfo.fMask |= SEE_MASK_FLAG_SEPVDM;
2845
2846 if (dwFlags & 2)
2847 ExecInfo.fMask |= SEE_MASK_NO_CONSOLE;
2848
2849 if (lphProcess == NULL)
2850 {
2851 ShellExecuteExA(&ExecInfo);
2852 }
2853 else
2854 {
2855 ExecInfo.fMask |= SEE_MASK_NOCLOSEPROCESS;
2856 ShellExecuteExA(&ExecInfo);
2857 *lphProcess = ExecInfo.hProcess;
2858 }
2859
2860 return ExecInfo.hInstApp;
2861 }
2862
2863 /*************************************************************************
2864 * RealShellExecuteExW (SHELL32.267)
2865 */
2866 EXTERN_C
2867 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)2868 RealShellExecuteExW(
2869 _In_opt_ HWND hwnd,
2870 _In_opt_ LPCWSTR lpOperation,
2871 _In_opt_ LPCWSTR lpFile,
2872 _In_opt_ LPCWSTR lpParameters,
2873 _In_opt_ LPCWSTR lpDirectory,
2874 _In_opt_ LPWSTR lpReturn,
2875 _In_opt_ LPCWSTR lpTitle,
2876 _In_opt_ LPVOID lpReserved,
2877 _In_ INT nCmdShow,
2878 _Out_opt_ PHANDLE lphProcess,
2879 _In_ DWORD dwFlags)
2880 {
2881 SHELLEXECUTEINFOW ExecInfo;
2882
2883 TRACE("(%p, %s, %s, %s, %s, %p, %s, %p, %u, %p, %lu)\n",
2884 hwnd, debugstr_w(lpOperation), debugstr_w(lpFile), debugstr_w(lpParameters),
2885 debugstr_w(lpDirectory), lpReserved, debugstr_w(lpTitle),
2886 lpReserved, nCmdShow, lphProcess, dwFlags);
2887
2888 ZeroMemory(&ExecInfo, sizeof(ExecInfo));
2889 ExecInfo.cbSize = sizeof(ExecInfo);
2890 ExecInfo.fMask = SEE_MASK_FLAG_NO_UI | SEE_MASK_UNKNOWN_0x1000;
2891 ExecInfo.hwnd = hwnd;
2892 ExecInfo.lpVerb = lpOperation;
2893 ExecInfo.lpFile = lpFile;
2894 ExecInfo.lpParameters = lpParameters;
2895 ExecInfo.lpDirectory = lpDirectory;
2896 ExecInfo.nShow = (WORD)nCmdShow;
2897
2898 if (lpReserved)
2899 {
2900 ExecInfo.fMask |= SEE_MASK_USE_RESERVED;
2901 ExecInfo.hInstApp = (HINSTANCE)lpReserved;
2902 }
2903
2904 if (lpTitle)
2905 {
2906 ExecInfo.fMask |= SEE_MASK_HASTITLE;
2907 ExecInfo.lpClass = lpTitle;
2908 }
2909
2910 if (dwFlags & 1)
2911 ExecInfo.fMask |= SEE_MASK_FLAG_SEPVDM;
2912
2913 if (dwFlags & 2)
2914 ExecInfo.fMask |= SEE_MASK_NO_CONSOLE;
2915
2916 if (lphProcess == NULL)
2917 {
2918 ShellExecuteExW(&ExecInfo);
2919 }
2920 else
2921 {
2922 ExecInfo.fMask |= SEE_MASK_NOCLOSEPROCESS;
2923 ShellExecuteExW(&ExecInfo);
2924 *lphProcess = ExecInfo.hProcess;
2925 }
2926
2927 return ExecInfo.hInstApp;
2928 }
2929
2930 /*************************************************************************
2931 * RealShellExecuteA (SHELL32.265)
2932 */
2933 EXTERN_C
2934 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)2935 RealShellExecuteA(
2936 _In_opt_ HWND hwnd,
2937 _In_opt_ LPCSTR lpOperation,
2938 _In_opt_ LPCSTR lpFile,
2939 _In_opt_ LPCSTR lpParameters,
2940 _In_opt_ LPCSTR lpDirectory,
2941 _In_opt_ LPSTR lpReturn,
2942 _In_opt_ LPCSTR lpTitle,
2943 _In_opt_ LPVOID lpReserved,
2944 _In_ INT nCmdShow,
2945 _Out_opt_ PHANDLE lphProcess)
2946 {
2947 return RealShellExecuteExA(hwnd,
2948 lpOperation,
2949 lpFile,
2950 lpParameters,
2951 lpDirectory,
2952 lpReturn,
2953 lpTitle,
2954 lpReserved,
2955 nCmdShow,
2956 lphProcess,
2957 0);
2958 }
2959
2960 /*************************************************************************
2961 * RealShellExecuteW (SHELL32.268)
2962 */
2963 EXTERN_C
2964 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)2965 RealShellExecuteW(
2966 _In_opt_ HWND hwnd,
2967 _In_opt_ LPCWSTR lpOperation,
2968 _In_opt_ LPCWSTR lpFile,
2969 _In_opt_ LPCWSTR lpParameters,
2970 _In_opt_ LPCWSTR lpDirectory,
2971 _In_opt_ LPWSTR lpReturn,
2972 _In_opt_ LPCWSTR lpTitle,
2973 _In_opt_ LPVOID lpReserved,
2974 _In_ INT nCmdShow,
2975 _Out_opt_ PHANDLE lphProcess)
2976 {
2977 return RealShellExecuteExW(hwnd,
2978 lpOperation,
2979 lpFile,
2980 lpParameters,
2981 lpDirectory,
2982 lpReturn,
2983 lpTitle,
2984 lpReserved,
2985 nCmdShow,
2986 lphProcess,
2987 0);
2988 }
2989
2990 // The common helper of ShellExec_RunDLLA and ShellExec_RunDLLW
2991 static VOID
ShellExec_RunDLL_Helper(_In_opt_ HWND hwnd,_In_opt_ HINSTANCE hInstance,_In_ PCWSTR pszCmdLine,_In_ INT nCmdShow)2992 ShellExec_RunDLL_Helper(
2993 _In_opt_ HWND hwnd,
2994 _In_opt_ HINSTANCE hInstance,
2995 _In_ PCWSTR pszCmdLine,
2996 _In_ INT nCmdShow)
2997 {
2998 TRACE("(%p, %p, %s, 0x%X)\n", hwnd, hInstance, wine_dbgstr_w(pszCmdLine), nCmdShow);
2999
3000 if (!pszCmdLine || !*pszCmdLine)
3001 return;
3002
3003 // '?' enables us to specify the additional mask value
3004 ULONG fNewMask = SEE_MASK_NOASYNC;
3005 if (*pszCmdLine == L'?') // 1st question
3006 {
3007 INT MaskValue;
3008 if (StrToIntExW(pszCmdLine + 1, STIF_SUPPORT_HEX, &MaskValue))
3009 fNewMask |= MaskValue;
3010
3011 PCWSTR pch2ndQuestion = StrChrW(pszCmdLine + 1, L'?'); // 2nd question
3012 if (pch2ndQuestion)
3013 pszCmdLine = pch2ndQuestion + 1;
3014 }
3015
3016 WCHAR szPath[2 * MAX_PATH];
3017 if (PathProcessCommandAW(pszCmdLine, szPath, _countof(szPath), L'C') == -1)
3018 StrCpyNW(szPath, pszCmdLine, _countof(szPath));
3019
3020 // Split arguments from the path
3021 LPWSTR Args = PathGetArgsW(szPath);
3022 if (*Args)
3023 *(Args - 1) = UNICODE_NULL;
3024
3025 PathUnquoteSpacesW(szPath);
3026
3027 // Execute
3028 SHELLEXECUTEINFOW execInfo = { sizeof(execInfo) };
3029 execInfo.fMask = fNewMask;
3030 execInfo.hwnd = hwnd;
3031 execInfo.lpFile = szPath;
3032 execInfo.lpParameters = Args;
3033 execInfo.nShow = nCmdShow;
3034 if (!ShellExecuteExW(&execInfo))
3035 {
3036 DWORD dwError = GetLastError();
3037 if (SHELL_InRunDllProcess()) // Is it a RUNDLL process?
3038 ExitProcess(dwError); // Terminate it now
3039 }
3040 }
3041
3042 /*************************************************************************
3043 * ShellExec_RunDLLA [SHELL32.358]
3044 *
3045 * @see https://www.hexacorn.com/blog/2024/11/30/1-little-known-secret-of-shellexec_rundll/
3046 */
3047 EXTERN_C
3048 VOID WINAPI
ShellExec_RunDLLA(_In_opt_ HWND hwnd,_In_opt_ HINSTANCE hInstance,_In_ PCSTR pszCmdLine,_In_ INT nCmdShow)3049 ShellExec_RunDLLA(
3050 _In_opt_ HWND hwnd,
3051 _In_opt_ HINSTANCE hInstance,
3052 _In_ PCSTR pszCmdLine,
3053 _In_ INT nCmdShow)
3054 {
3055 CStringW strCmdLine = pszCmdLine; // Keep
3056 ShellExec_RunDLL_Helper(hwnd, hInstance, strCmdLine, nCmdShow);
3057 }
3058
3059 /*************************************************************************
3060 * ShellExec_RunDLLW [SHELL32.359]
3061 *
3062 * @see https://www.hexacorn.com/blog/2024/11/30/1-little-known-secret-of-shellexec_rundll/
3063 */
3064 EXTERN_C
3065 VOID WINAPI
ShellExec_RunDLLW(_In_opt_ HWND hwnd,_In_opt_ HINSTANCE hInstance,_In_ PCWSTR pszCmdLine,_In_ INT nCmdShow)3066 ShellExec_RunDLLW(
3067 _In_opt_ HWND hwnd,
3068 _In_opt_ HINSTANCE hInstance,
3069 _In_ PCWSTR pszCmdLine,
3070 _In_ INT nCmdShow)
3071 {
3072 ShellExec_RunDLL_Helper(hwnd, hInstance, pszCmdLine, nCmdShow);
3073 }
3074