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