Reactos
at master 3437 lines 112 kB view raw
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 &paramlen) == 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(&parameters, 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}