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