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