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