xref: /reactos/dll/win32/shell32/shlexec.cpp (revision 960a305e)
1 /*
2  *          Shell Library Functions
3  *
4  * Copyright 1998 Marcus Meissner
5  * Copyright 2002 Eric Pouech
6  * Copyright 2018-2024 Katayama Hirofumi MZ
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21  */
22 
23 #include "precomp.h"
24 #include <undocshell.h>
25 
26 WINE_DEFAULT_DEBUG_CHANNEL(exec);
27 
28 EXTERN_C BOOL PathIsExeW(LPCWSTR lpszPath);
29 
30 #define SEE_MASK_CLASSALL (SEE_MASK_CLASSNAME | SEE_MASK_CLASSKEY)
31 
32 typedef UINT_PTR (*SHELL_ExecuteW32)(const WCHAR *lpCmd, WCHAR *env, BOOL shWait,
33                 const SHELLEXECUTEINFOW *sei, LPSHELLEXECUTEINFOW sei_out);
34 
35 // Is the current process a rundll32.exe?
36 static BOOL SHELL_InRunDllProcess(VOID)
37 {
38     WCHAR szModule[MAX_PATH];
39     static INT s_bInDllProcess = -1;
40 
41     if (s_bInDllProcess != -1)
42         return s_bInDllProcess;
43 
44     s_bInDllProcess = GetModuleFileNameW(NULL, szModule, _countof(szModule)) &&
45                       (StrStrIW(PathFindFileNameW(szModule), L"rundll") != NULL);
46     return s_bInDllProcess;
47 }
48 
49 static void ParseNoTildeEffect(PWSTR &res, LPCWSTR &args, DWORD &len, DWORD &used, int argNum)
50 {
51     bool firstCharQuote = false;
52     bool quotes_opened = false;
53     bool backslash_encountered = false;
54 
55     for (int curArg = 0; curArg <= argNum && *args; ++curArg)
56     {
57         firstCharQuote = false;
58         if (*args == '"')
59         {
60             quotes_opened = true;
61             firstCharQuote = true;
62             args++;
63         }
64 
65         while(*args)
66         {
67             if (*args == '\\')
68             {
69                 // if we found a backslash then flip the variable
70                 backslash_encountered = !backslash_encountered;
71             }
72             else if (*args == '"')
73             {
74                 if (quotes_opened)
75                 {
76                     if (*(args + 1) != '"')
77                     {
78                         quotes_opened = false;
79                         args++;
80                         break;
81                     }
82                     else
83                     {
84                         args++;
85                     }
86                 }
87                 else
88                 {
89                     quotes_opened = true;
90                 }
91 
92                 backslash_encountered = false;
93             }
94             else
95             {
96                 backslash_encountered = false;
97                 if (*args == ' ' && !firstCharQuote)
98                     break;
99             }
100 
101             if (curArg == argNum)
102             {
103                 used++;
104                 if (used < len)
105                     *res++ = *args;
106             }
107 
108             args++;
109         }
110 
111         while(*args == ' ')
112             ++args;
113     }
114 }
115 
116 static void ParseTildeEffect(PWSTR &res, LPCWSTR &args, DWORD &len, DWORD &used, int argNum)
117 {
118     bool quotes_opened = false;
119     bool backslash_encountered = false;
120 
121     for (int curArg = 0; curArg <= argNum && *args; ++curArg)
122     {
123         while(*args)
124         {
125             if (*args == '\\')
126             {
127                 // if we found a backslash then flip the variable
128                 backslash_encountered = !backslash_encountered;
129             }
130             else if (*args == '"')
131             {
132                 if (quotes_opened)
133                 {
134                     if (*(args + 1) != '"')
135                     {
136                         quotes_opened = false;
137                     }
138                     else
139                     {
140                         args++;
141                     }
142                 }
143                 else
144                 {
145                     quotes_opened = true;
146                 }
147 
148                 backslash_encountered = false;
149             }
150             else
151             {
152                 backslash_encountered = false;
153                 if (*args == ' ' && !quotes_opened && curArg != argNum)
154                     break;
155             }
156 
157             if (curArg == argNum)
158             {
159                 used++;
160                 if (used < len)
161                     *res++ = *args;
162             }
163 
164             args++;
165         }
166     }
167 }
168 
169 /***********************************************************************
170  * SHELL_ArgifyW [Internal]
171  *
172  * this function is supposed to expand the escape sequences found in the registry
173  * some diving reported that the following were used:
174  * + %1, %2...  seem to report to parameter of index N in ShellExecute pmts
175  * %1 file
176  * %2 printer
177  * %3 driver
178  * %4 port
179  * %I address of a global item ID (explorer switch /idlist)
180  * %L seems to be %1 as long filename followed by the 8+3 variation
181  * %S ???
182  * %W Working directory
183  * %V Use either %L or %W
184  * %* all following parameters (see batfile)
185  *
186  * The way we parse the command line arguments was determined through extensive
187  * testing and can be summed up by the following rules"
188  *
189  * - %2
190  *     - if first letter is " break on first non literal " and include any white spaces
191  *     - if first letter is NOT " break on first " or white space
192  *     - if " is opened any pair of consecutive " results in ONE literal "
193  *
194  * - %~2
195  *     - use rules from here http://www.autohotkey.net/~deleyd/parameters/parameters.htm
196  */
197 
198 static BOOL SHELL_ArgifyW(WCHAR* out, DWORD len, const WCHAR* fmt, const WCHAR* lpFile, LPITEMIDLIST pidl, LPCWSTR args, DWORD* out_len, const WCHAR* lpDir)
199 {
200     WCHAR   xlpFile[1024];
201     BOOL    done = FALSE;
202     BOOL    found_p1 = FALSE;
203     PWSTR   res = out;
204     PCWSTR  cmd;
205     DWORD   used = 0;
206     bool    tildeEffect = false;
207 
208     TRACE("Before parsing: %p, %d, %s, %s, %p, %p\n", out, len, debugstr_w(fmt),
209           debugstr_w(lpFile), pidl, args);
210 
211     while (*fmt)
212     {
213         if (*fmt == '%')
214         {
215             switch (*++fmt)
216             {
217                 case '\0':
218                 case '%':
219                 {
220                     used++;
221                     if (used < len)
222                         *res++ = '%';
223                 };
224                 break;
225 
226                 case '*':
227                 {
228                     if (args)
229                     {
230                         if (*fmt == '*')
231                         {
232                             used++;
233                             while(*args)
234                             {
235                                 used++;
236                                 if (used < len)
237                                     *res++ = *args++;
238                                 else
239                                     args++;
240                             }
241                             used++;
242                             break;
243                         }
244                     }
245                 };
246                 break;
247 
248                 case '~':
249 
250                 case '2':
251                 case '3':
252                 case '4':
253                 case '5':
254                 case '6':
255                 case '7':
256                 case '8':
257                 case '9':
258                     //case '0':
259                 {
260                     if (*fmt == '~')
261                     {
262                         fmt++;
263                         tildeEffect = true;
264                     }
265 
266                     if (args)
267                     {
268                         if (tildeEffect)
269                         {
270                             ParseTildeEffect(res, args, len, used, *fmt - '2');
271                             tildeEffect = false;
272                         }
273                         else
274                         {
275                             ParseNoTildeEffect(res, args, len, used, *fmt - '2');
276                         }
277                     }
278                 };
279                 break;
280 
281                 case '1':
282                     if (!done || (*fmt == '1'))
283                     {
284                         /*FIXME Is the call to SearchPathW() really needed? We already have separated out the parameter string in args. */
285                         if (SearchPathW(lpDir, lpFile, L".exe", ARRAY_SIZE(xlpFile), xlpFile, NULL))
286                             cmd = xlpFile;
287                         else
288                             cmd = lpFile;
289 
290                         used += wcslen(cmd);
291                         if (used < len)
292                         {
293                             wcscpy(res, cmd);
294                             res += wcslen(cmd);
295                         }
296                     }
297                     found_p1 = TRUE;
298                     break;
299 
300                     /*
301                      * IE uses this a lot for activating things such as windows media
302                      * player. This is not verified to be fully correct but it appears
303                      * to work just fine.
304                      */
305                 case 'l':
306                 case 'L':
307                     if (lpFile)
308                     {
309                         used += wcslen(lpFile);
310                         if (used < len)
311                         {
312                             wcscpy(res, lpFile);
313                             res += wcslen(lpFile);
314                         }
315                     }
316                     found_p1 = TRUE;
317                     break;
318 
319                 case 'w':
320                 case 'W':
321                     if (lpDir)
322                     {
323                         used += wcslen(lpDir);
324                         if (used < len)
325                         {
326                             wcscpy(res, lpDir);
327                             res += wcslen(lpDir);
328                         }
329                     }
330                     break;
331 
332                 case 'v':
333                 case 'V':
334                     if (lpFile)
335                     {
336                         used += wcslen(lpFile);
337                         if (used < len)
338                         {
339                             wcscpy(res, lpFile);
340                             res += wcslen(lpFile);
341                         }
342                         found_p1 = TRUE;
343                     }
344                     else if (lpDir)
345                     {
346                         used += wcslen(lpDir);
347                         if (used < len)
348                         {
349                             wcscpy(res, lpDir);
350                             res += wcslen(lpDir);
351                         }
352                     }
353                     break;
354 
355                 case 'i':
356                 case 'I':
357                     if (pidl)
358                     {
359                         DWORD chars = 0;
360                         /* %p should not exceed 8, maybe 16 when looking forward to 64bit.
361                             * allowing a buffer of 100 should more than exceed all needs */
362                         WCHAR buf[100];
363                         LPVOID  pv;
364                         HGLOBAL hmem = SHAllocShared(pidl, ILGetSize(pidl), 0);
365                         pv = SHLockShared(hmem, 0);
366                         chars = swprintf(buf, L":%p", pv);
367 
368                         if (chars >= ARRAY_SIZE(buf))
369                             ERR("pidl format buffer too small!\n");
370 
371                         used += chars;
372 
373                         if (used < len)
374                         {
375                             wcscpy(res, buf);
376                             res += chars;
377                         }
378                         SHUnlockShared(pv);
379                     }
380                     found_p1 = TRUE;
381                     break;
382 
383                 default:
384                     /*
385                      * Check if this is an env-variable here...
386                      */
387 
388                     /* Make sure that we have at least one more %.*/
389                     if (strchrW(fmt, '%'))
390                     {
391                         WCHAR   tmpBuffer[1024];
392                         PWSTR   tmpB = tmpBuffer;
393                         WCHAR   tmpEnvBuff[MAX_PATH];
394                         DWORD   envRet;
395 
396                         while (*fmt != '%')
397                             *tmpB++ = *fmt++;
398                         *tmpB++ = 0;
399 
400                         TRACE("Checking %s to be an env-var\n", debugstr_w(tmpBuffer));
401 
402                         envRet = GetEnvironmentVariableW(tmpBuffer, tmpEnvBuff, MAX_PATH);
403                         if (envRet == 0 || envRet > MAX_PATH)
404                         {
405                             used += wcslen(tmpBuffer);
406                             if (used < len)
407                             {
408                                 wcscpy( res, tmpBuffer );
409                                 res += wcslen(tmpBuffer);
410                             }
411                         }
412                         else
413                         {
414                             used += wcslen(tmpEnvBuff);
415                             if (used < len)
416                             {
417                                 wcscpy( res, tmpEnvBuff );
418                                 res += wcslen(tmpEnvBuff);
419                             }
420                         }
421                     }
422                     done = TRUE;
423                     break;
424             }
425             /* Don't skip past terminator (catch a single '%' at the end) */
426             if (*fmt != '\0')
427             {
428                 fmt++;
429             }
430         }
431         else
432         {
433             used ++;
434             if (used < len)
435                 *res++ = *fmt++;
436             else
437                 fmt++;
438         }
439     }
440 
441     used++;
442     if (res - out < static_cast<int>(len))
443         *res = '\0';
444     else
445         out[len-1] = '\0';
446 
447     TRACE("used %i of %i space\n", used, len);
448     if (out_len)
449         *out_len = used;
450 
451     TRACE("After parsing: %p, %d, %s, %s, %p, %p\n", out, len, debugstr_w(fmt),
452           debugstr_w(lpFile), pidl, args);
453 
454     return found_p1;
455 }
456 
457 static HRESULT SHELL_GetPathFromIDListForExecuteW(LPCITEMIDLIST pidl, LPWSTR pszPath, UINT uOutSize)
458 {
459     STRRET strret;
460     CComPtr<IShellFolder> desktop;
461 
462     HRESULT hr = SHGetDesktopFolder(&desktop);
463 
464     if (SUCCEEDED(hr))
465     {
466         hr = desktop->GetDisplayNameOf(pidl, SHGDN_FORPARSING, &strret);
467 
468         if (SUCCEEDED(hr))
469             StrRetToStrNW(pszPath, uOutSize, &strret, pidl);
470     }
471 
472     return hr;
473 }
474 
475 /*************************************************************************
476  *    SHELL_ExecuteW [Internal]
477  *
478  */
479 static UINT_PTR SHELL_ExecuteW(const WCHAR *lpCmd, WCHAR *env, BOOL shWait,
480                                const SHELLEXECUTEINFOW *psei, LPSHELLEXECUTEINFOW psei_out)
481 {
482     STARTUPINFOW  startup;
483     PROCESS_INFORMATION info;
484     UINT_PTR retval = SE_ERR_NOASSOC;
485     UINT gcdret = 0;
486     WCHAR curdir[MAX_PATH];
487     DWORD dwCreationFlags;
488     const WCHAR *lpDirectory = NULL;
489 
490     TRACE("Execute %s from directory %s\n", debugstr_w(lpCmd), debugstr_w(psei->lpDirectory));
491 
492     /* make sure we don't fail the CreateProcess if the calling app passes in
493      * a bad working directory */
494     if (psei->lpDirectory && psei->lpDirectory[0])
495     {
496         DWORD attr = GetFileAttributesW(psei->lpDirectory);
497         if (attr != INVALID_FILE_ATTRIBUTES && attr & FILE_ATTRIBUTE_DIRECTORY)
498             lpDirectory = psei->lpDirectory;
499     }
500 
501     /* ShellExecute specifies the command from psei->lpDirectory
502      * if present. Not from the current dir as CreateProcess does */
503     if (lpDirectory)
504         if ((gcdret = GetCurrentDirectoryW( MAX_PATH, curdir)))
505             if (!SetCurrentDirectoryW( lpDirectory))
506                 ERR("cannot set directory %s\n", debugstr_w(lpDirectory));
507 
508     ZeroMemory(&startup, sizeof(STARTUPINFOW));
509     startup.cb = sizeof(STARTUPINFOW);
510     startup.dwFlags = STARTF_USESHOWWINDOW;
511     startup.wShowWindow = psei->nShow;
512     dwCreationFlags = CREATE_UNICODE_ENVIRONMENT;
513     if (!(psei->fMask & SEE_MASK_NO_CONSOLE))
514         dwCreationFlags |= CREATE_NEW_CONSOLE;
515     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;
1559     __SHCloneStrWtoA(&verb, sei->lpVerb);
1560     __SHCloneStrWtoA(&parameters, sei->lpParameters);
1561 
1562     BOOL fDefault = !sei->lpVerb || !sei->lpVerb[0];
1563     CMINVOKECOMMANDINFOEX ici = { sizeof(ici) };
1564     ici.fMask = (sei->fMask & (SEE_MASK_NO_CONSOLE | SEE_MASK_ASYNCOK | SEE_MASK_FLAG_NO_UI)) | CMIC_MASK_UNICODE;
1565     ici.nShow = sei->nShow;
1566     if (!fDefault)
1567     {
1568         ici.lpVerb = verb;
1569         ici.lpVerbW = sei->lpVerb;
1570     }
1571     ici.hwnd = sei->hwnd;
1572     ici.lpParameters = parameters;
1573     ici.lpParametersW = sei->lpParameters;
1574     if ((sei->fMask & (SEE_MASK_HASLINKNAME | SEE_MASK_CLASSNAME)) == SEE_MASK_HASLINKNAME)
1575     {
1576         ici.fMask |= CMIC_MASK_HASLINKNAME;
1577         ici.lpTitleW = sei->lpClass;
1578     }
1579 
1580     HMENU hMenu = CreatePopupMenu();
1581     hr = cm->QueryContextMenu(hMenu, 0, 1, 0x7fff, fDefault ? CMF_DEFAULTONLY : 0);
1582     if (!FAILED_UNEXPECTEDLY(hr))
1583     {
1584         if (fDefault)
1585         {
1586             INT uDefault = GetMenuDefaultItem(hMenu, FALSE, 0);
1587             uDefault = (uDefault != -1) ? uDefault - 1 : 0;
1588             ici.lpVerb = MAKEINTRESOURCEA(uDefault);
1589             ici.lpVerbW = MAKEINTRESOURCEW(uDefault);
1590         }
1591 
1592         hr = cm->InvokeCommand((LPCMINVOKECOMMANDINFO)&ici);
1593         if (!FAILED_UNEXPECTEDLY(hr))
1594             hr = S_OK;
1595     }
1596 
1597     DestroyMenu(hMenu);
1598 
1599     return hr;
1600 }
1601 
1602 
1603 
1604 /*************************************************************************
1605  *    ShellExecute_FromContextMenu [Internal]
1606  */
1607 static LONG ShellExecute_FromContextMenuHandlers( LPSHELLEXECUTEINFOW sei )
1608 {
1609     HKEY hkey, hkeycm = 0;
1610     WCHAR szguid[39];
1611     HRESULT hr;
1612     GUID guid;
1613     DWORD i;
1614     LONG r;
1615 
1616     TRACE("%s\n", debugstr_w(sei->lpFile));
1617 
1618     hkey = ShellExecute_GetClassKey(sei);
1619     if (!hkey)
1620         return ERROR_FUNCTION_FAILED;
1621 
1622     r = RegOpenKeyW(hkey, L"shellex\\ContextMenuHandlers", &hkeycm);
1623     if (r == ERROR_SUCCESS)
1624     {
1625         i = 0;
1626         while (1)
1627         {
1628             r = RegEnumKeyW(hkeycm, i++, szguid, ARRAY_SIZE(szguid));
1629             if (r != ERROR_SUCCESS)
1630                 break;
1631 
1632             hr = CLSIDFromString(szguid, &guid);
1633             if (SUCCEEDED(hr))
1634             {
1635                 /* stop at the first one that succeeds in running */
1636                 hr = shellex_load_object_and_run(hkey, &guid, sei);
1637                 if (SUCCEEDED(hr))
1638                     break;
1639             }
1640         }
1641         RegCloseKey(hkeycm);
1642     }
1643 
1644     if (hkey != sei->hkeyClass)
1645         RegCloseKey(hkey);
1646     return r;
1647 }
1648 
1649 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);
1650 
1651 static UINT_PTR SHELL_execute_class(LPCWSTR wszApplicationName, LPSHELLEXECUTEINFOW psei, LPSHELLEXECUTEINFOW psei_out, SHELL_ExecuteW32 execfunc)
1652 {
1653     WCHAR execCmd[1024], classname[1024];
1654     /* launch a document by fileclass like 'WordPad.Document.1' */
1655     /* the Commandline contains 'c:\Path\wordpad.exe "%1"' */
1656     /* FIXME: wcmd should not be of a fixed size. Fixed to 1024, MAX_PATH is way too short! */
1657     ULONG cmask = (psei->fMask & SEE_MASK_CLASSALL);
1658     DWORD resultLen;
1659     BOOL done;
1660     UINT_PTR rslt;
1661 
1662     /* FIXME: remove following block when SHELL_quote_and_execute supports hkeyClass parameter */
1663     if (cmask != SEE_MASK_CLASSNAME)
1664     {
1665         WCHAR wcmd[1024];
1666         HCR_GetExecuteCommandW((cmask == SEE_MASK_CLASSKEY) ? psei->hkeyClass : NULL,
1667                                (cmask == SEE_MASK_CLASSNAME) ? psei->lpClass : NULL,
1668                                psei->lpVerb,
1669                                execCmd, sizeof(execCmd));
1670 
1671         /* FIXME: get the extension of lpFile, check if it fits to the lpClass */
1672         TRACE("SEE_MASK_CLASSNAME->%s, doc->%s\n", debugstr_w(execCmd), debugstr_w(wszApplicationName));
1673 
1674         wcmd[0] = '\0';
1675         done = SHELL_ArgifyW(wcmd, ARRAY_SIZE(wcmd), execCmd, wszApplicationName, (LPITEMIDLIST)psei->lpIDList, psei->lpParameters,
1676                              &resultLen, (psei->lpDirectory && *psei->lpDirectory) ? psei->lpDirectory : NULL);
1677         if (!done && wszApplicationName[0])
1678         {
1679             strcatW(wcmd, L" ");
1680             if (*wszApplicationName != '"')
1681             {
1682                 strcatW(wcmd, L"\"");
1683                 strcatW(wcmd, wszApplicationName);
1684                 strcatW(wcmd, L"\"");
1685             }
1686             else
1687                 strcatW(wcmd, wszApplicationName);
1688         }
1689         if (resultLen > ARRAY_SIZE(wcmd))
1690             ERR("Argify buffer not large enough... truncating\n");
1691         return execfunc(wcmd, NULL, FALSE, psei, psei_out);
1692     }
1693 
1694     strcpyW(classname, psei->lpClass);
1695     rslt = SHELL_FindExecutableByVerb(psei->lpVerb, NULL, classname, execCmd, sizeof(execCmd));
1696 
1697     TRACE("SHELL_FindExecutableByVerb returned %u (%s, %s)\n", (unsigned int)rslt, debugstr_w(classname), debugstr_w(execCmd));
1698     if (33 > rslt)
1699         return rslt;
1700     rslt = SHELL_quote_and_execute( execCmd, L"", classname,
1701                                       wszApplicationName, NULL, psei,
1702                                       psei_out, execfunc );
1703     return rslt;
1704 
1705 }
1706 
1707 static BOOL SHELL_translate_idlist(LPSHELLEXECUTEINFOW sei, LPWSTR wszParameters, DWORD parametersLen, LPWSTR wszApplicationName, DWORD dwApplicationNameLen)
1708 {
1709     WCHAR buffer[MAX_PATH];
1710     BOOL appKnownSingular = FALSE;
1711 
1712     /* last chance to translate IDList: now also allow CLSID paths */
1713     if (SUCCEEDED(SHELL_GetPathFromIDListForExecuteW((LPCITEMIDLIST)sei->lpIDList, buffer, ARRAY_SIZE(buffer)))) {
1714         if (buffer[0] == ':' && buffer[1] == ':') {
1715             /* open shell folder for the specified class GUID */
1716             if (strlenW(buffer) + 1 > parametersLen)
1717                 ERR("parameters len exceeds buffer size (%i > %i), truncating\n",
1718                     lstrlenW(buffer) + 1, parametersLen);
1719             lstrcpynW(wszParameters, buffer, parametersLen);
1720             if (strlenW(L"explorer.exe") > dwApplicationNameLen)
1721                 ERR("application len exceeds buffer size (%i), truncating\n",
1722                     dwApplicationNameLen);
1723             lstrcpynW(wszApplicationName, L"explorer.exe", dwApplicationNameLen);
1724             appKnownSingular = TRUE;
1725 
1726             sei->fMask &= ~SEE_MASK_INVOKEIDLIST;
1727         } else {
1728             WCHAR target[MAX_PATH];
1729             DWORD attribs;
1730             DWORD resultLen;
1731             /* Check if we're executing a directory and if so use the
1732                handler for the Folder class */
1733             strcpyW(target, buffer);
1734             attribs = GetFileAttributesW(buffer);
1735             if (attribs != INVALID_FILE_ATTRIBUTES &&
1736                     (attribs & FILE_ATTRIBUTE_DIRECTORY) &&
1737                     HCR_GetExecuteCommandW(0, L"Folder",
1738                                            sei->lpVerb,
1739                                            buffer, sizeof(buffer))) {
1740                 SHELL_ArgifyW(wszApplicationName, dwApplicationNameLen,
1741                               buffer, target, (LPITEMIDLIST)sei->lpIDList, NULL, &resultLen,
1742                               (sei->lpDirectory && *sei->lpDirectory) ? sei->lpDirectory : NULL);
1743                 if (resultLen > dwApplicationNameLen)
1744                     ERR("Argify buffer not large enough... truncating\n");
1745                 appKnownSingular = FALSE;
1746             }
1747             sei->fMask &= ~SEE_MASK_INVOKEIDLIST;
1748         }
1749     }
1750     return appKnownSingular;
1751 }
1752 
1753 static BOOL
1754 SHELL_InvokePidl(
1755     _In_ LPSHELLEXECUTEINFOW sei,
1756     _In_ LPCITEMIDLIST pidl)
1757 {
1758     // Bind pidl
1759     CComPtr<IShellFolder> psfFolder;
1760     LPCITEMIDLIST pidlLast;
1761     HRESULT hr = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &psfFolder), &pidlLast);
1762     if (FAILED_UNEXPECTEDLY(hr))
1763         return FALSE;
1764 
1765     // Get the context menu to invoke a command
1766     CComPtr<IContextMenu> pCM;
1767     hr = psfFolder->GetUIObjectOf(NULL, 1, &pidlLast, IID_NULL_PPV_ARG(IContextMenu, &pCM));
1768     if (FAILED_UNEXPECTEDLY(hr))
1769         return FALSE;
1770 
1771     // Invoke a command
1772     CMINVOKECOMMANDINFO ici = { sizeof(ici) };
1773     ici.fMask = (sei->fMask & (SEE_MASK_NO_CONSOLE | SEE_MASK_ASYNCOK | SEE_MASK_FLAG_NO_UI));
1774     ici.nShow = sei->nShow;
1775     ici.hwnd = sei->hwnd;
1776     char szVerb[VERBKEY_CCHMAX];
1777     if (sei->lpVerb && sei->lpVerb[0])
1778     {
1779         WideCharToMultiByte(CP_ACP, 0, sei->lpVerb, -1, szVerb, _countof(szVerb), NULL, NULL);
1780         szVerb[_countof(szVerb) - 1] = ANSI_NULL; // Avoid buffer overrun
1781         ici.lpVerb = szVerb;
1782     }
1783     else // The default verb?
1784     {
1785         HMENU hMenu = CreatePopupMenu();
1786         const INT idCmdFirst = 1, idCmdLast = 0x7FFF;
1787         hr = pCM->QueryContextMenu(hMenu, 0, idCmdFirst, idCmdLast, CMF_DEFAULTONLY);
1788         if (FAILED_UNEXPECTEDLY(hr))
1789         {
1790             DestroyMenu(hMenu);
1791             return FALSE;
1792         }
1793 
1794         INT nDefaultID = GetMenuDefaultItem(hMenu, FALSE, 0);
1795         DestroyMenu(hMenu);
1796         if (nDefaultID == -1)
1797             nDefaultID = idCmdFirst;
1798 
1799         ici.lpVerb = MAKEINTRESOURCEA(nDefaultID - idCmdFirst);
1800     }
1801     hr = pCM->InvokeCommand(&ici);
1802 
1803     return !FAILED_UNEXPECTEDLY(hr);
1804 }
1805 
1806 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)
1807 {
1808     UINT_PTR retval;
1809     DWORD len;
1810     CHeapPtr<WCHAR, CLocalAllocator> wszQuotedCmd;
1811 
1812     /* Length of quotes plus length of command plus NULL terminator */
1813     len = 2 + lstrlenW(wcmd) + 1;
1814     if (wszParameters[0])
1815     {
1816         /* Length of space plus length of parameters */
1817         len += 1 + lstrlenW(wszParameters);
1818     }
1819     wszQuotedCmd.Allocate(len);
1820     /* Must quote to handle case where cmd contains spaces,
1821      * else security hole if malicious user creates executable file "C:\\Program"
1822      */
1823     strcpyW(wszQuotedCmd, L"\"");
1824     strcatW(wszQuotedCmd, wcmd);
1825     strcatW(wszQuotedCmd, L"\"");
1826     if (wszParameters[0])
1827     {
1828         strcatW(wszQuotedCmd, L" ");
1829         strcatW(wszQuotedCmd, wszParameters);
1830     }
1831 
1832     TRACE("%s/%s => %s/%s\n", debugstr_w(wszApplicationName), debugstr_w(psei->lpVerb), debugstr_w(wszQuotedCmd), debugstr_w(wszKeyname));
1833 
1834     if (*wszKeyname)
1835         retval = execute_from_key(wszKeyname, wszApplicationName, env, psei->lpParameters, wcmd, execfunc, psei, psei_out);
1836     else
1837         retval = execfunc(wszQuotedCmd, env, FALSE, psei, psei_out);
1838 
1839     return retval;
1840 }
1841 
1842 static UINT_PTR SHELL_execute_url(LPCWSTR lpFile, LPCWSTR wcmd, LPSHELLEXECUTEINFOW psei, LPSHELLEXECUTEINFOW psei_out, SHELL_ExecuteW32 execfunc)
1843 {
1844     UINT_PTR retval;
1845     CHeapPtr<WCHAR, CLocalAllocator> lpstrProtocol;
1846     LPCWSTR lpstrRes;
1847     INT iSize;
1848     DWORD len;
1849 
1850     lpstrRes = strchrW(lpFile, ':');
1851     if (lpstrRes)
1852         iSize = lpstrRes - lpFile;
1853     else
1854         iSize = strlenW(lpFile);
1855 
1856     TRACE("Got URL: %s\n", debugstr_w(lpFile));
1857     /* Looking for ...<protocol>\shell\<lpVerb>\command */
1858     len = iSize + lstrlenW(L"\\shell\\") + lstrlenW(L"\\command") + 1;
1859     if (psei->lpVerb && *psei->lpVerb)
1860         len += lstrlenW(psei->lpVerb);
1861     else
1862         len += lstrlenW(L"open");
1863     lpstrProtocol.Allocate(len);
1864     memcpy(lpstrProtocol, lpFile, iSize * sizeof(WCHAR));
1865     lpstrProtocol[iSize] = '\0';
1866     strcatW(lpstrProtocol, L"\\shell\\");
1867     strcatW(lpstrProtocol, psei->lpVerb && *psei->lpVerb ? psei->lpVerb : L"open");
1868     strcatW(lpstrProtocol, L"\\command");
1869 
1870     retval = execute_from_key(lpstrProtocol, lpFile, NULL, psei->lpParameters,
1871                               wcmd, execfunc, psei, psei_out);
1872 
1873     return retval;
1874 }
1875 
1876 static void do_error_dialog(UINT_PTR retval, HWND hwnd, WCHAR* filename)
1877 {
1878     WCHAR msg[2048];
1879     DWORD_PTR msgArguments[3]  = { (DWORD_PTR)filename, 0, 0 };
1880     DWORD error_code;
1881 
1882     error_code = GetLastError();
1883     if (retval == SE_ERR_NOASSOC)
1884         LoadStringW(shell32_hInstance, IDS_SHLEXEC_NOASSOC, msg, ARRAY_SIZE(msg));
1885     else
1886         FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY,
1887                        NULL,
1888                        error_code,
1889                        LANG_USER_DEFAULT,
1890                        msg,
1891                        ARRAY_SIZE(msg),
1892                        (va_list*)msgArguments);
1893 
1894     MessageBoxW(hwnd, msg, NULL, MB_ICONERROR);
1895 }
1896 
1897 static WCHAR *expand_environment( const WCHAR *str )
1898 {
1899     CHeapPtr<WCHAR, CLocalAllocator> buf;
1900     DWORD len;
1901 
1902     len = ExpandEnvironmentStringsW(str, NULL, 0);
1903     if (!len) return NULL;
1904 
1905     if (!buf.Allocate(len))
1906         return NULL;
1907 
1908     len = ExpandEnvironmentStringsW(str, buf, len);
1909     if (!len)
1910         return NULL;
1911 
1912     return buf.Detach();
1913 }
1914 
1915 /*************************************************************************
1916  *    SHELL_execute [Internal]
1917  */
1918 static BOOL SHELL_execute(LPSHELLEXECUTEINFOW sei, SHELL_ExecuteW32 execfunc)
1919 {
1920     static const DWORD unsupportedFlags =
1921         SEE_MASK_ICON         | SEE_MASK_HOTKEY |
1922         SEE_MASK_CONNECTNETDRV | SEE_MASK_FLAG_DDEWAIT |
1923         SEE_MASK_ASYNCOK      | SEE_MASK_HMONITOR;
1924 
1925     DWORD len;
1926     UINT_PTR retval = SE_ERR_NOASSOC;
1927     BOOL appKnownSingular = FALSE;
1928 
1929     /* make a local copy of the LPSHELLEXECUTEINFO structure and work with this from now on */
1930     SHELLEXECUTEINFOW sei_tmp = *sei;
1931 
1932     TRACE("mask=0x%08x hwnd=%p verb=%s file=%s parm=%s dir=%s show=0x%08x class=%s\n",
1933           sei_tmp.fMask, sei_tmp.hwnd, debugstr_w(sei_tmp.lpVerb),
1934           debugstr_w(sei_tmp.lpFile), debugstr_w(sei_tmp.lpParameters),
1935           debugstr_w(sei_tmp.lpDirectory), sei_tmp.nShow,
1936           ((sei_tmp.fMask & SEE_MASK_CLASSALL) == SEE_MASK_CLASSNAME) ?
1937           debugstr_w(sei_tmp.lpClass) : "not used");
1938 
1939     sei->hProcess = NULL;
1940 
1941     /* make copies of all path/command strings */
1942     CHeapPtr<WCHAR, CLocalAllocator> wszApplicationName;
1943     DWORD dwApplicationNameLen = MAX_PATH + 2;
1944     if (!sei_tmp.lpFile)
1945     {
1946         wszApplicationName.Allocate(dwApplicationNameLen);
1947         *wszApplicationName = '\0';
1948     }
1949     else if (*sei_tmp.lpFile == '\"' && sei_tmp.lpFile[(len = strlenW(sei_tmp.lpFile))-1] == '\"')
1950     {
1951         if(len-1 >= dwApplicationNameLen)
1952             dwApplicationNameLen = len;
1953 
1954         wszApplicationName.Allocate(dwApplicationNameLen);
1955         memcpy(wszApplicationName, sei_tmp.lpFile + 1, len * sizeof(WCHAR));
1956 
1957         if(len > 2)
1958             wszApplicationName[len-2] = '\0';
1959         appKnownSingular = TRUE;
1960 
1961         TRACE("wszApplicationName=%s\n", debugstr_w(wszApplicationName));
1962     }
1963     else
1964     {
1965         DWORD l = strlenW(sei_tmp.lpFile) + 1;
1966         if(l > dwApplicationNameLen) dwApplicationNameLen = l + 1;
1967         wszApplicationName.Allocate(dwApplicationNameLen);
1968         memcpy(wszApplicationName, sei_tmp.lpFile, l * sizeof(WCHAR));
1969 
1970         if (wszApplicationName[2] == 0 && wszApplicationName[1] == L':' &&
1971             ((L'A' <= wszApplicationName[0] && wszApplicationName[0] <= L'Z') ||
1972              (L'a' <= wszApplicationName[0] && wszApplicationName[0] <= L'z')))
1973         {
1974             // 'C:' --> 'C:\'
1975             PathAddBackslashW(wszApplicationName);
1976         }
1977     }
1978 
1979     WCHAR parametersBuffer[1024];
1980     LPWSTR wszParameters = parametersBuffer;
1981     CHeapPtr<WCHAR, CLocalAllocator> wszParamAlloc;
1982     DWORD parametersLen = _countof(parametersBuffer);
1983 
1984     if (sei_tmp.lpParameters)
1985     {
1986         len = lstrlenW(sei_tmp.lpParameters) + 1;
1987         if (len > parametersLen)
1988         {
1989             wszParamAlloc.Allocate(len);
1990             wszParameters = wszParamAlloc;
1991             parametersLen = len;
1992         }
1993         strcpyW(wszParameters, sei_tmp.lpParameters);
1994     }
1995     else
1996         *wszParameters = L'\0';
1997 
1998     // Get the working directory
1999     WCHAR dirBuffer[MAX_PATH];
2000     LPWSTR wszDir = dirBuffer;
2001     wszDir[0] = UNICODE_NULL;
2002     CHeapPtr<WCHAR, CLocalAllocator> wszDirAlloc;
2003     if (sei_tmp.lpDirectory && *sei_tmp.lpDirectory)
2004     {
2005         if (sei_tmp.fMask & SEE_MASK_DOENVSUBST)
2006         {
2007             LPWSTR tmp = expand_environment(sei_tmp.lpDirectory);
2008             if (tmp)
2009             {
2010                 wszDirAlloc.Attach(tmp);
2011                 wszDir = wszDirAlloc;
2012             }
2013         }
2014         else
2015         {
2016             __SHCloneStrW(&wszDirAlloc, sei_tmp.lpDirectory);
2017             if (wszDirAlloc)
2018                 wszDir = wszDirAlloc;
2019         }
2020     }
2021     if (!wszDir[0])
2022     {
2023         ::GetCurrentDirectoryW(_countof(dirBuffer), dirBuffer);
2024         wszDir = dirBuffer;
2025     }
2026     // NOTE: ShellExecute should accept the invalid working directory for historical reason.
2027     if (!PathIsDirectoryW(wszDir))
2028     {
2029         INT iDrive = PathGetDriveNumberW(wszDir);
2030         if (iDrive >= 0)
2031         {
2032             PathStripToRootW(wszDir);
2033             if (!PathIsDirectoryW(wszDir))
2034             {
2035                 ::GetWindowsDirectoryW(dirBuffer, _countof(dirBuffer));
2036                 wszDir = dirBuffer;
2037             }
2038         }
2039     }
2040 
2041     /* adjust string pointers to point to the new buffers */
2042     sei_tmp.lpFile = wszApplicationName;
2043     sei_tmp.lpParameters = wszParameters;
2044     sei_tmp.lpDirectory = wszDir;
2045 
2046     if (sei_tmp.fMask & unsupportedFlags)
2047     {
2048         FIXME("flags ignored: 0x%08x\n", sei_tmp.fMask & unsupportedFlags);
2049     }
2050 
2051     /* process the IDList */
2052     if (sei_tmp.fMask & SEE_MASK_IDLIST &&
2053         (sei_tmp.fMask & SEE_MASK_INVOKEIDLIST) != SEE_MASK_INVOKEIDLIST)
2054     {
2055         CComPtr<IShellExecuteHookW> pSEH;
2056 
2057         HRESULT hr = SHBindToParent((LPCITEMIDLIST)sei_tmp.lpIDList, IID_PPV_ARG(IShellExecuteHookW, &pSEH), NULL);
2058 
2059         if (SUCCEEDED(hr))
2060         {
2061             hr = pSEH->Execute(&sei_tmp);
2062             if (hr == S_OK)
2063                 return TRUE;
2064         }
2065 
2066         SHGetPathFromIDListW((LPCITEMIDLIST)sei_tmp.lpIDList, wszApplicationName);
2067         appKnownSingular = TRUE;
2068         TRACE("-- idlist=%p (%s)\n", sei_tmp.lpIDList, debugstr_w(wszApplicationName));
2069     }
2070 
2071     if ((sei_tmp.fMask & SEE_MASK_DOENVSUBST) && !StrIsNullOrEmpty(sei_tmp.lpFile))
2072     {
2073         WCHAR *tmp = expand_environment(sei_tmp.lpFile);
2074         if (tmp)
2075         {
2076             wszApplicationName.Attach(tmp);
2077             sei_tmp.lpFile = wszApplicationName;
2078         }
2079     }
2080 
2081     if ((sei_tmp.fMask & SEE_MASK_INVOKEIDLIST) == SEE_MASK_INVOKEIDLIST)
2082     {
2083         HRESULT hr = ShellExecute_ContextMenuVerb(&sei_tmp);
2084         if (SUCCEEDED(hr))
2085         {
2086             sei->hInstApp = (HINSTANCE)42;
2087             return TRUE;
2088         }
2089     }
2090 
2091     if (ERROR_SUCCESS == ShellExecute_FromContextMenuHandlers(&sei_tmp))
2092     {
2093         sei->hInstApp = (HINSTANCE) 33;
2094         return TRUE;
2095     }
2096 
2097     if (sei_tmp.fMask & SEE_MASK_CLASSALL)
2098     {
2099         retval = SHELL_execute_class(wszApplicationName, &sei_tmp, sei, execfunc);
2100         if (retval <= 32 && !(sei_tmp.fMask & SEE_MASK_FLAG_NO_UI))
2101         {
2102             OPENASINFO Info;
2103 
2104             //FIXME
2105             // need full path
2106 
2107             Info.pcszFile = wszApplicationName;
2108             Info.pcszClass = NULL;
2109             Info.oaifInFlags = OAIF_ALLOW_REGISTRATION | OAIF_EXEC;
2110 
2111             //if (SHOpenWithDialog(sei_tmp.hwnd, &Info) != S_OK)
2112             DBG_UNREFERENCED_LOCAL_VARIABLE(Info);
2113             do_error_dialog(retval, sei_tmp.hwnd, wszApplicationName);
2114         }
2115         return retval > 32;
2116     }
2117 
2118     if (!(sei_tmp.fMask & SEE_MASK_IDLIST) && // Not an ID List
2119         (StrCmpNIW(sei_tmp.lpFile, L"shell:", 6) == 0 ||
2120          StrCmpNW(sei_tmp.lpFile, L"::{", 3) == 0))
2121     {
2122         CComHeapPtr<ITEMIDLIST> pidlParsed;
2123         HRESULT hr = SHParseDisplayName(sei_tmp.lpFile, NULL, &pidlParsed, 0, NULL);
2124         if (SUCCEEDED(hr) && SHELL_InvokePidl(&sei_tmp, pidlParsed))
2125         {
2126             sei_tmp.hInstApp = (HINSTANCE)UlongToHandle(42);
2127             return TRUE;
2128         }
2129     }
2130 
2131     /* Has the IDList not yet been translated? */
2132     if (sei_tmp.fMask & SEE_MASK_IDLIST)
2133     {
2134         appKnownSingular = SHELL_translate_idlist( &sei_tmp, wszParameters,
2135                            parametersLen,
2136                            wszApplicationName,
2137                            dwApplicationNameLen );
2138     }
2139 
2140     /* convert file URLs */
2141     if (UrlIsFileUrlW(sei_tmp.lpFile))
2142     {
2143         CHeapPtr<WCHAR, CLocalAllocator> buf;
2144         DWORD size = MAX_PATH;
2145         if (!buf.Allocate(size) || FAILED(PathCreateFromUrlW(sei_tmp.lpFile, buf, &size, 0)))
2146             return SE_ERR_OOM;
2147 
2148         wszApplicationName.Attach(buf.Detach());
2149         sei_tmp.lpFile = wszApplicationName;
2150     }
2151 
2152     /* Else, try to execute the filename */
2153     TRACE("execute: %s,%s,%s\n", debugstr_w(wszApplicationName), debugstr_w(wszParameters), debugstr_w(wszDir));
2154 
2155     /* separate out command line arguments from executable file name */
2156     LPCWSTR lpFile;
2157     WCHAR wfileName[MAX_PATH];
2158     if (!*sei_tmp.lpParameters && !appKnownSingular)
2159     {
2160         /* If the executable path is quoted, handle the rest of the command line as parameters. */
2161         if (sei_tmp.lpFile[0] == L'"')
2162         {
2163             LPWSTR src = wszApplicationName/*sei_tmp.lpFile*/ + 1;
2164             LPWSTR dst = wfileName;
2165             LPWSTR end;
2166 
2167             /* copy the unquoted executable path to 'wfileName' */
2168             while(*src && *src != L'"')
2169                 *dst++ = *src++;
2170 
2171             *dst = L'\0';
2172 
2173             if (*src == L'"')
2174             {
2175                 end = ++src;
2176 
2177                 while(isspaceW(*src))
2178                     ++src;
2179             }
2180             else
2181                 end = src;
2182 
2183             /* copy the parameter string to 'wszParameters' */
2184             strcpyW(wszParameters, src);
2185 
2186             /* terminate previous command string after the quote character */
2187             *end = L'\0';
2188             lpFile = wfileName;
2189         }
2190         /* We have to test sei instead of sei_tmp because sei_tmp had its
2191          * input fMask modified above in SHELL_translate_idlist.
2192          * This code is needed to handle the case where we only have an
2193          * lpIDList with multiple CLSID/PIDL's (not 'My Computer' only) */
2194         else if ((sei->fMask & SEE_MASK_IDLIST) == SEE_MASK_IDLIST)
2195         {
2196             WCHAR buffer[MAX_PATH], xlpFile[MAX_PATH];
2197             LPWSTR space, s;
2198 
2199             LPWSTR beg = wszApplicationName;
2200             for(s = beg; (space = const_cast<LPWSTR>(strchrW(s, L' '))); s = space + 1)
2201             {
2202                 int idx = space - sei_tmp.lpFile;
2203                 memcpy(buffer, sei_tmp.lpFile, idx * sizeof(WCHAR));
2204                 buffer[idx] = '\0';
2205 
2206                 if (SearchPathW(*sei_tmp.lpDirectory ? sei_tmp.lpDirectory : NULL,
2207                     buffer, L".exe", _countof(xlpFile), xlpFile, NULL))
2208                 {
2209                     /* separate out command from parameter string */
2210                     LPCWSTR p = space + 1;
2211 
2212                     while(isspaceW(*p))
2213                         ++p;
2214 
2215                     strcpyW(wszParameters, p);
2216                     *space = L'\0';
2217 
2218                     break;
2219                 }
2220             }
2221             lpFile = sei_tmp.lpFile;
2222         }
2223         else
2224         {
2225             lpFile = sei_tmp.lpFile;
2226         }
2227     }
2228     else
2229         lpFile = sei_tmp.lpFile;
2230 
2231     WCHAR wcmdBuffer[1024];
2232     LPWSTR wcmd = wcmdBuffer;
2233     DWORD wcmdLen = _countof(wcmdBuffer);
2234     CHeapPtr<WCHAR, CLocalAllocator> wcmdAlloc;
2235 
2236     /* Only execute if it has an executable extension */
2237     if (PathIsExeW(lpFile))
2238     {
2239         len = lstrlenW(wszApplicationName) + 3;
2240         if (sei_tmp.lpParameters[0])
2241             len += 1 + lstrlenW(wszParameters);
2242         if (len > wcmdLen)
2243         {
2244             wcmdAlloc.Allocate(len);
2245             wcmd = wcmdAlloc;
2246             wcmdLen = len;
2247         }
2248         swprintf(wcmd, L"\"%s\"", (LPWSTR)wszApplicationName);
2249         if (sei_tmp.lpParameters[0])
2250         {
2251             strcatW(wcmd, L" ");
2252             strcatW(wcmd, wszParameters);
2253         }
2254 
2255         retval = execfunc(wcmd, NULL, FALSE, &sei_tmp, sei);
2256         if (retval > 32)
2257             return TRUE;
2258     }
2259 
2260     /* Else, try to find the executable */
2261     WCHAR wszKeyname[256];
2262     CHeapPtr<WCHAR, CLocalAllocator> env;
2263     wcmd[0] = UNICODE_NULL;
2264     retval = SHELL_FindExecutable(sei_tmp.lpDirectory, lpFile, sei_tmp.lpVerb, wcmd, wcmdLen, wszKeyname, &env, (LPITEMIDLIST)sei_tmp.lpIDList, sei_tmp.lpParameters);
2265     if (retval > 32)  /* Found */
2266     {
2267         retval = SHELL_quote_and_execute(wcmd, wszParameters, wszKeyname,
2268                                          wszApplicationName, env, &sei_tmp,
2269                                          sei, execfunc);
2270     }
2271     else if (PathIsDirectoryW(lpFile))
2272     {
2273         WCHAR wExec[MAX_PATH];
2274         CHeapPtr<WCHAR, CLocalAllocator> lpQuotedFile;
2275         if (lpQuotedFile.Allocate(strlenW(lpFile) + 3))
2276         {
2277             retval = SHELL_FindExecutable(sei_tmp.lpDirectory, L"explorer",
2278                                           L"open", wExec, MAX_PATH,
2279                                           NULL, &env, NULL, NULL);
2280             if (retval > 32)
2281             {
2282                 swprintf(lpQuotedFile, L"\"%s\"", lpFile);
2283                 retval = SHELL_quote_and_execute(wExec, lpQuotedFile,
2284                                                  wszKeyname,
2285                                                  wszApplicationName, env,
2286                                                  &sei_tmp, sei, execfunc);
2287             }
2288         }
2289         else
2290             retval = 0; /* Out of memory */
2291     }
2292     else if (PathIsURLW(lpFile))    /* File not found, check for URL */
2293     {
2294         retval = SHELL_execute_url(lpFile, wcmd, &sei_tmp, sei, execfunc );
2295     }
2296     /* Check if file specified is in the form www.??????.*** */
2297     else if (!strncmpiW(lpFile, L"www", 3))
2298     {
2299         /* if so, prefix lpFile with http:// and call ShellExecute */
2300         WCHAR lpstrTmpFile[256];
2301         strcpyW(lpstrTmpFile, L"http://");
2302         strcatW(lpstrTmpFile, lpFile);
2303         retval = (UINT_PTR)ShellExecuteW(sei_tmp.hwnd, sei_tmp.lpVerb, lpstrTmpFile, NULL, NULL, 0);
2304     }
2305 
2306     TRACE("retval %lu\n", retval);
2307 
2308     if (retval <= 32 && !(sei_tmp.fMask & SEE_MASK_FLAG_NO_UI))
2309     {
2310         OPENASINFO Info;
2311 
2312         //FIXME
2313         // need full path
2314 
2315         Info.pcszFile = wszApplicationName;
2316         Info.pcszClass = NULL;
2317         Info.oaifInFlags = OAIF_ALLOW_REGISTRATION | OAIF_EXEC;
2318 
2319         //if (SHOpenWithDialog(sei_tmp.hwnd, &Info) != S_OK)
2320         DBG_UNREFERENCED_LOCAL_VARIABLE(Info);
2321         do_error_dialog(retval, sei_tmp.hwnd, wszApplicationName);
2322     }
2323 
2324     sei->hInstApp = (HINSTANCE)(retval > 32 ? 33 : retval);
2325 
2326     return retval > 32;
2327 }
2328 
2329 /*************************************************************************
2330  * ShellExecuteA            [SHELL32.290]
2331  */
2332 HINSTANCE WINAPI ShellExecuteA(HWND hWnd, LPCSTR lpVerb, LPCSTR lpFile,
2333                                LPCSTR lpParameters, LPCSTR lpDirectory, INT iShowCmd)
2334 {
2335     SHELLEXECUTEINFOA sei;
2336 
2337     TRACE("%p,%s,%s,%s,%s,%d\n",
2338           hWnd, debugstr_a(lpVerb), debugstr_a(lpFile),
2339           debugstr_a(lpParameters), debugstr_a(lpDirectory), iShowCmd);
2340 
2341     sei.cbSize = sizeof(sei);
2342     sei.fMask = SEE_MASK_FLAG_NO_UI;
2343     sei.hwnd = hWnd;
2344     sei.lpVerb = lpVerb;
2345     sei.lpFile = lpFile;
2346     sei.lpParameters = lpParameters;
2347     sei.lpDirectory = lpDirectory;
2348     sei.nShow = iShowCmd;
2349     sei.lpIDList = 0;
2350     sei.lpClass = 0;
2351     sei.hkeyClass = 0;
2352     sei.dwHotKey = 0;
2353     sei.hProcess = 0;
2354 
2355     ShellExecuteExA(&sei);
2356     return sei.hInstApp;
2357 }
2358 
2359 static DWORD
2360 ShellExecute_Normal(_Inout_ LPSHELLEXECUTEINFOW sei)
2361 {
2362     // FIXME
2363     return SHELL_execute(sei, SHELL_ExecuteW) ? ERROR_SUCCESS : ERROR_FILE_NOT_FOUND;
2364 }
2365 
2366 static VOID
2367 ShellExecute_ShowError(
2368     _In_ const SHELLEXECUTEINFOW *ExecInfo,
2369     _In_opt_ LPCWSTR pszCaption,
2370     _In_ DWORD dwError)
2371 {
2372     // FIXME: Show error message
2373 }
2374 
2375 /*************************************************************************
2376  * ShellExecuteExA                [SHELL32.292]
2377  */
2378 BOOL
2379 WINAPI
2380 DECLSPEC_HOTPATCH
2381 ShellExecuteExA(LPSHELLEXECUTEINFOA sei)
2382 {
2383     SHELLEXECUTEINFOW seiW;
2384     BOOL ret;
2385     WCHAR *wVerb = NULL, *wFile = NULL, *wParameters = NULL, *wDirectory = NULL, *wClass = NULL;
2386 
2387     TRACE("%p\n", sei);
2388 
2389     if (sei->cbSize != sizeof(SHELLEXECUTEINFOA))
2390     {
2391         sei->hInstApp = (HINSTANCE)ERROR_ACCESS_DENIED;
2392         SetLastError(ERROR_ACCESS_DENIED);
2393         return FALSE;
2394     }
2395 
2396     memcpy(&seiW, sei, sizeof(SHELLEXECUTEINFOW));
2397 
2398     seiW.cbSize = sizeof(SHELLEXECUTEINFOW);
2399 
2400     if (sei->lpVerb)
2401         seiW.lpVerb = __SHCloneStrAtoW(&wVerb, sei->lpVerb);
2402 
2403     if (sei->lpFile)
2404         seiW.lpFile = __SHCloneStrAtoW(&wFile, sei->lpFile);
2405 
2406     if (sei->lpParameters)
2407         seiW.lpParameters = __SHCloneStrAtoW(&wParameters, sei->lpParameters);
2408 
2409     if (sei->lpDirectory)
2410         seiW.lpDirectory = __SHCloneStrAtoW(&wDirectory, sei->lpDirectory);
2411 
2412     if ((sei->fMask & SEE_MASK_CLASSALL) == SEE_MASK_CLASSNAME && sei->lpClass)
2413         seiW.lpClass = __SHCloneStrAtoW(&wClass, sei->lpClass);
2414     else
2415         seiW.lpClass = NULL;
2416 
2417     ret = ShellExecuteExW(&seiW);
2418 
2419     sei->hInstApp = seiW.hInstApp;
2420 
2421     if (sei->fMask & SEE_MASK_NOCLOSEPROCESS)
2422         sei->hProcess = seiW.hProcess;
2423 
2424     SHFree(wVerb);
2425     SHFree(wFile);
2426     SHFree(wParameters);
2427     SHFree(wDirectory);
2428     SHFree(wClass);
2429 
2430     return ret;
2431 }
2432 
2433 /*************************************************************************
2434  * ShellExecuteExW                [SHELL32.293]
2435  */
2436 BOOL
2437 WINAPI
2438 DECLSPEC_HOTPATCH
2439 ShellExecuteExW(LPSHELLEXECUTEINFOW sei)
2440 {
2441     HRESULT hrCoInit;
2442     DWORD dwError;
2443     ULONG fOldMask;
2444 
2445     if (sei->cbSize != sizeof(SHELLEXECUTEINFOW))
2446     {
2447         sei->hInstApp = (HINSTANCE)UlongToHandle(SE_ERR_ACCESSDENIED);
2448         SetLastError(ERROR_ACCESS_DENIED);
2449         return FALSE;
2450     }
2451 
2452     hrCoInit = SHCoInitializeAnyApartment();
2453 
2454     if (SHRegGetBoolUSValueW(L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer",
2455                              L"MaximizeApps", FALSE, FALSE))
2456     {
2457         switch (sei->nShow)
2458         {
2459             case SW_SHOW:
2460             case SW_SHOWDEFAULT:
2461             case SW_SHOWNORMAL:
2462             case SW_RESTORE:
2463                 sei->nShow = SW_SHOWMAXIMIZED;
2464                 break;
2465             default:
2466                 break;
2467         }
2468     }
2469 
2470     fOldMask = sei->fMask;
2471 
2472     if (!(fOldMask & SEE_MASK_NOASYNC) && SHELL_InRunDllProcess())
2473         sei->fMask |= SEE_MASK_WAITFORINPUTIDLE | SEE_MASK_NOASYNC;
2474 
2475     dwError = ShellExecute_Normal(sei);
2476 
2477     if (dwError && dwError != ERROR_DLL_NOT_FOUND && dwError != ERROR_CANCELLED)
2478         ShellExecute_ShowError(sei, NULL, dwError);
2479 
2480     sei->fMask = fOldMask;
2481 
2482     if (SUCCEEDED(hrCoInit))
2483         CoUninitialize();
2484 
2485     if (dwError)
2486         SetLastError(dwError);
2487 
2488     return dwError == ERROR_SUCCESS;
2489 }
2490 
2491 /*************************************************************************
2492  * ShellExecuteW            [SHELL32.294]
2493  * from shellapi.h
2494  * WINSHELLAPI HINSTANCE APIENTRY ShellExecuteW(HWND hwnd, LPCWSTR lpVerb,
2495  * LPCWSTR lpFile, LPCWSTR lpParameters, LPCWSTR lpDirectory, INT nShowCmd);
2496  */
2497 HINSTANCE WINAPI ShellExecuteW(HWND hwnd, LPCWSTR lpVerb, LPCWSTR lpFile,
2498                                LPCWSTR lpParameters, LPCWSTR lpDirectory, INT nShowCmd)
2499 {
2500     SHELLEXECUTEINFOW sei;
2501 
2502     TRACE("\n");
2503     sei.cbSize = sizeof(sei);
2504     sei.fMask = SEE_MASK_FLAG_NO_UI;
2505     sei.hwnd = hwnd;
2506     sei.lpVerb = lpVerb;
2507     sei.lpFile = lpFile;
2508     sei.lpParameters = lpParameters;
2509     sei.lpDirectory = lpDirectory;
2510     sei.nShow = nShowCmd;
2511     sei.lpIDList = 0;
2512     sei.lpClass = 0;
2513     sei.hkeyClass = 0;
2514     sei.dwHotKey = 0;
2515     sei.hProcess = 0;
2516 
2517     SHELL_execute(&sei, SHELL_ExecuteW);
2518     return sei.hInstApp;
2519 }
2520 
2521 /*************************************************************************
2522  * WOWShellExecute            [SHELL32.@]
2523  *
2524  * FIXME: the callback function most likely doesn't work the same way on Windows.
2525  */
2526 EXTERN_C HINSTANCE WINAPI WOWShellExecute(HWND hWnd, LPCSTR lpVerb, LPCSTR lpFile,
2527         LPCSTR lpParameters, LPCSTR lpDirectory, INT iShowCmd, void *callback)
2528 {
2529     SHELLEXECUTEINFOW seiW;
2530     WCHAR *wVerb = NULL, *wFile = NULL, *wParameters = NULL, *wDirectory = NULL;
2531     HANDLE hProcess = 0;
2532 
2533     seiW.lpVerb = lpVerb ? __SHCloneStrAtoW(&wVerb, lpVerb) : NULL;
2534     seiW.lpFile = lpFile ? __SHCloneStrAtoW(&wFile, lpFile) : NULL;
2535     seiW.lpParameters = lpParameters ? __SHCloneStrAtoW(&wParameters, lpParameters) : NULL;
2536     seiW.lpDirectory = lpDirectory ? __SHCloneStrAtoW(&wDirectory, lpDirectory) : NULL;
2537 
2538     seiW.cbSize = sizeof(seiW);
2539     seiW.fMask = 0;
2540     seiW.hwnd = hWnd;
2541     seiW.nShow = iShowCmd;
2542     seiW.lpIDList = 0;
2543     seiW.lpClass = 0;
2544     seiW.hkeyClass = 0;
2545     seiW.dwHotKey = 0;
2546     seiW.hProcess = hProcess;
2547 
2548     SHELL_execute(&seiW, (SHELL_ExecuteW32)callback);
2549 
2550     SHFree(wVerb);
2551     SHFree(wFile);
2552     SHFree(wParameters);
2553     SHFree(wDirectory);
2554     return seiW.hInstApp;
2555 }
2556 
2557 /*************************************************************************
2558  * OpenAs_RunDLLW          [SHELL32.@]
2559  */
2560 EXTERN_C void WINAPI
2561 OpenAs_RunDLLW(HWND hwnd, HINSTANCE hinst, LPCWSTR cmdline, int cmdshow)
2562 {
2563     OPENASINFO info;
2564     TRACE("%p, %p, %s, %d\n", hwnd, hinst, debugstr_w(cmdline), cmdshow);
2565 
2566     ZeroMemory(&info, sizeof(info));
2567     info.pcszFile = cmdline;
2568     info.pcszClass = NULL;
2569     info.oaifInFlags = OAIF_ALLOW_REGISTRATION | OAIF_REGISTER_EXT | OAIF_EXEC;
2570 
2571     SHOpenWithDialog(hwnd, &info);
2572 }
2573 
2574 /*************************************************************************
2575  * OpenAs_RunDLLA          [SHELL32.@]
2576  */
2577 EXTERN_C void WINAPI
2578 OpenAs_RunDLLA(HWND hwnd, HINSTANCE hinst, LPCSTR cmdline, int cmdshow)
2579 {
2580     LPWSTR pszCmdLineW = NULL;
2581     TRACE("%p, %p, %s, %d\n", hwnd, hinst, debugstr_a(cmdline), cmdshow);
2582 
2583     if (cmdline)
2584         __SHCloneStrAtoW(&pszCmdLineW, cmdline);
2585     OpenAs_RunDLLW(hwnd, hinst, pszCmdLineW, cmdshow);
2586     SHFree(pszCmdLineW);
2587 }
2588 
2589 /*************************************************************************/
2590 
2591 static LPCWSTR
2592 SplitParams(LPCWSTR psz, LPWSTR pszArg0, size_t cchArg0)
2593 {
2594     LPCWSTR pch;
2595     size_t ich = 0;
2596     if (*psz == L'"')
2597     {
2598         // 1st argument is quoted. the string in quotes is quoted 1st argument.
2599         // [pch] --> [pszArg0+ich]
2600         for (pch = psz + 1; *pch && ich + 1 < cchArg0; ++ich, ++pch)
2601         {
2602             if (*pch == L'"' && pch[1] == L'"')
2603             {
2604                 // doubled double quotations found!
2605                 pszArg0[ich] = L'"';
2606             }
2607             else if (*pch == L'"')
2608             {
2609                 // single double quotation found!
2610                 ++pch;
2611                 break;
2612             }
2613             else
2614             {
2615                 // otherwise
2616                 pszArg0[ich] = *pch;
2617             }
2618         }
2619     }
2620     else
2621     {
2622         // 1st argument is unquoted. non-space sequence is 1st argument.
2623         // [pch] --> [pszArg0+ich]
2624         for (pch = psz; *pch && !iswspace(*pch) && ich + 1 < cchArg0; ++ich, ++pch)
2625         {
2626             pszArg0[ich] = *pch;
2627         }
2628     }
2629     pszArg0[ich] = 0;
2630 
2631     // skip space
2632     while (iswspace(*pch))
2633         ++pch;
2634 
2635     return pch;
2636 }
2637 
2638 HRESULT WINAPI ShellExecCmdLine(
2639     HWND hwnd,
2640     LPCWSTR pwszCommand,
2641     LPCWSTR pwszStartDir,
2642     int nShow,
2643     LPVOID pUnused,
2644     DWORD dwSeclFlags)
2645 {
2646     SHELLEXECUTEINFOW info;
2647     DWORD dwSize, dwError, dwType, dwFlags = SEE_MASK_DOENVSUBST | SEE_MASK_NOASYNC;
2648     LPCWSTR pszVerb = NULL;
2649     WCHAR szFile[MAX_PATH], szFile2[MAX_PATH];
2650     HRESULT hr;
2651     LPCWSTR pchParams;
2652     LPWSTR lpCommand = NULL;
2653 
2654     if (pwszCommand == NULL)
2655         RaiseException(EXCEPTION_ACCESS_VIOLATION, EXCEPTION_NONCONTINUABLE,
2656                        1, (ULONG_PTR*)pwszCommand);
2657 
2658     __SHCloneStrW(&lpCommand, pwszCommand);
2659     StrTrimW(lpCommand, L" \t");
2660 
2661     if (dwSeclFlags & SECL_NO_UI)
2662         dwFlags |= SEE_MASK_FLAG_NO_UI;
2663     if (dwSeclFlags & SECL_LOG_USAGE)
2664         dwFlags |= SEE_MASK_FLAG_LOG_USAGE;
2665     if (dwSeclFlags & SECL_USE_IDLIST)
2666         dwFlags |= SEE_MASK_INVOKEIDLIST;
2667 
2668     if (dwSeclFlags & SECL_RUNAS)
2669     {
2670         dwSize = 0;
2671         hr = AssocQueryStringW(ASSOCF_NONE, ASSOCSTR_COMMAND, lpCommand, L"RunAs", NULL, &dwSize);
2672         if (SUCCEEDED(hr) && dwSize != 0)
2673         {
2674             pszVerb = L"runas";
2675         }
2676     }
2677 
2678     if (PathIsURLW(lpCommand) || UrlIsW(lpCommand, URLIS_APPLIABLE))
2679     {
2680         StringCchCopyW(szFile, _countof(szFile), lpCommand);
2681         pchParams = NULL;
2682     }
2683     else
2684     {
2685         PCWSTR apPathList[2];
2686 
2687         pchParams = SplitParams(lpCommand, szFile, _countof(szFile));
2688         if (szFile[0] != UNICODE_NULL && szFile[1] == L':' &&
2689             szFile[2] == UNICODE_NULL)
2690         {
2691             PathAddBackslashW(szFile);
2692         }
2693 
2694         WCHAR szCurDir[MAX_PATH];
2695         GetCurrentDirectoryW(_countof(szCurDir), szCurDir);
2696         if (pwszStartDir)
2697         {
2698             SetCurrentDirectoryW(pwszStartDir);
2699         }
2700 
2701         if ((PathIsRelativeW(szFile) &&
2702              GetFullPathNameW(szFile, _countof(szFile2), szFile2, NULL) &&
2703              PathFileExistsW(szFile2)) ||
2704             SearchPathW(NULL, szFile, NULL, _countof(szFile2), szFile2, NULL))
2705         {
2706             StringCchCopyW(szFile, _countof(szFile), szFile2);
2707         }
2708 
2709         apPathList[0] = pwszStartDir;
2710         apPathList[1] = NULL;
2711         PathFindOnPathExW(szFile, apPathList, WHICH_DEFAULT);
2712 
2713         if (!(dwSeclFlags & SECL_ALLOW_NONEXE))
2714         {
2715             if (!GetBinaryTypeW(szFile, &dwType))
2716             {
2717                 SHFree(lpCommand);
2718 
2719                 if (!(dwSeclFlags & SECL_NO_UI))
2720                 {
2721                     WCHAR szText[128 + MAX_PATH], szFormat[128];
2722                     LoadStringW(shell32_hInstance, IDS_FILE_NOT_FOUND, szFormat, _countof(szFormat));
2723                     StringCchPrintfW(szText, _countof(szText), szFormat, szFile);
2724                     MessageBoxW(hwnd, szText, NULL, MB_ICONERROR);
2725                 }
2726                 return CO_E_APPNOTFOUND;
2727             }
2728         }
2729         else
2730         {
2731             if (GetFileAttributesW(szFile) == INVALID_FILE_ATTRIBUTES)
2732             {
2733                 SHFree(lpCommand);
2734 
2735                 if (!(dwSeclFlags & SECL_NO_UI))
2736                 {
2737                     WCHAR szText[128 + MAX_PATH], szFormat[128];
2738                     LoadStringW(shell32_hInstance, IDS_FILE_NOT_FOUND, szFormat, _countof(szFormat));
2739                     StringCchPrintfW(szText, _countof(szText), szFormat, szFile);
2740                     MessageBoxW(hwnd, szText, NULL, MB_ICONERROR);
2741                 }
2742                 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
2743             }
2744         }
2745     }
2746 
2747     ZeroMemory(&info, sizeof(info));
2748     info.cbSize = sizeof(info);
2749     info.fMask = dwFlags;
2750     info.hwnd = hwnd;
2751     info.lpVerb = pszVerb;
2752     info.lpFile = szFile;
2753     info.lpParameters = (pchParams && *pchParams) ? pchParams : NULL;
2754     info.lpDirectory = pwszStartDir;
2755     info.nShow = nShow;
2756     if (ShellExecuteExW(&info))
2757     {
2758         if (info.lpIDList)
2759             CoTaskMemFree(info.lpIDList);
2760 
2761         SHFree(lpCommand);
2762 
2763         return S_OK;
2764     }
2765 
2766     dwError = GetLastError();
2767 
2768     SHFree(lpCommand);
2769 
2770     return HRESULT_FROM_WIN32(dwError);
2771 }
2772 
2773 /*************************************************************************
2774  *                RealShellExecuteExA (SHELL32.266)
2775  */
2776 EXTERN_C
2777 HINSTANCE WINAPI
2778 RealShellExecuteExA(
2779     _In_opt_ HWND hwnd,
2780     _In_opt_ LPCSTR lpOperation,
2781     _In_opt_ LPCSTR lpFile,
2782     _In_opt_ LPCSTR lpParameters,
2783     _In_opt_ LPCSTR lpDirectory,
2784     _In_opt_ LPSTR lpReturn,
2785     _In_opt_ LPCSTR lpTitle,
2786     _In_opt_ LPVOID lpReserved,
2787     _In_ INT nCmdShow,
2788     _Out_opt_ PHANDLE lphProcess,
2789     _In_ DWORD dwFlags)
2790 {
2791     SHELLEXECUTEINFOA ExecInfo;
2792 
2793     TRACE("(%p, %s, %s, %s, %s, %p, %s, %p, %u, %p, %lu)\n",
2794           hwnd, debugstr_a(lpOperation), debugstr_a(lpFile), debugstr_a(lpParameters),
2795           debugstr_a(lpDirectory), lpReserved, debugstr_a(lpTitle),
2796           lpReserved, nCmdShow, lphProcess, dwFlags);
2797 
2798     ZeroMemory(&ExecInfo, sizeof(ExecInfo));
2799     ExecInfo.cbSize = sizeof(ExecInfo);
2800     ExecInfo.fMask = SEE_MASK_FLAG_NO_UI | SEE_MASK_UNKNOWN_0x1000;
2801     ExecInfo.hwnd = hwnd;
2802     ExecInfo.lpVerb = lpOperation;
2803     ExecInfo.lpFile = lpFile;
2804     ExecInfo.lpParameters = lpParameters;
2805     ExecInfo.lpDirectory = lpDirectory;
2806     ExecInfo.nShow = (WORD)nCmdShow;
2807 
2808     if (lpReserved)
2809     {
2810         ExecInfo.fMask |= SEE_MASK_USE_RESERVED;
2811         ExecInfo.hInstApp = (HINSTANCE)lpReserved;
2812     }
2813 
2814     if (lpTitle)
2815     {
2816         ExecInfo.fMask |= SEE_MASK_HASTITLE;
2817         ExecInfo.lpClass = lpTitle;
2818     }
2819 
2820     if (dwFlags & 1)
2821         ExecInfo.fMask |= SEE_MASK_FLAG_SEPVDM;
2822 
2823     if (dwFlags & 2)
2824         ExecInfo.fMask |= SEE_MASK_NO_CONSOLE;
2825 
2826     if (lphProcess == NULL)
2827     {
2828         ShellExecuteExA(&ExecInfo);
2829     }
2830     else
2831     {
2832         ExecInfo.fMask |= SEE_MASK_NOCLOSEPROCESS;
2833         ShellExecuteExA(&ExecInfo);
2834         *lphProcess = ExecInfo.hProcess;
2835     }
2836 
2837     return ExecInfo.hInstApp;
2838 }
2839 
2840 /*************************************************************************
2841  *                RealShellExecuteExW (SHELL32.267)
2842  */
2843 EXTERN_C
2844 HINSTANCE WINAPI
2845 RealShellExecuteExW(
2846     _In_opt_ HWND hwnd,
2847     _In_opt_ LPCWSTR lpOperation,
2848     _In_opt_ LPCWSTR lpFile,
2849     _In_opt_ LPCWSTR lpParameters,
2850     _In_opt_ LPCWSTR lpDirectory,
2851     _In_opt_ LPWSTR lpReturn,
2852     _In_opt_ LPCWSTR lpTitle,
2853     _In_opt_ LPVOID lpReserved,
2854     _In_ INT nCmdShow,
2855     _Out_opt_ PHANDLE lphProcess,
2856     _In_ DWORD dwFlags)
2857 {
2858     SHELLEXECUTEINFOW ExecInfo;
2859 
2860     TRACE("(%p, %s, %s, %s, %s, %p, %s, %p, %u, %p, %lu)\n",
2861           hwnd, debugstr_w(lpOperation), debugstr_w(lpFile), debugstr_w(lpParameters),
2862           debugstr_w(lpDirectory), lpReserved, debugstr_w(lpTitle),
2863           lpReserved, nCmdShow, lphProcess, dwFlags);
2864 
2865     ZeroMemory(&ExecInfo, sizeof(ExecInfo));
2866     ExecInfo.cbSize = sizeof(ExecInfo);
2867     ExecInfo.fMask = SEE_MASK_FLAG_NO_UI | SEE_MASK_UNKNOWN_0x1000;
2868     ExecInfo.hwnd = hwnd;
2869     ExecInfo.lpVerb = lpOperation;
2870     ExecInfo.lpFile = lpFile;
2871     ExecInfo.lpParameters = lpParameters;
2872     ExecInfo.lpDirectory = lpDirectory;
2873     ExecInfo.nShow = (WORD)nCmdShow;
2874 
2875     if (lpReserved)
2876     {
2877         ExecInfo.fMask |= SEE_MASK_USE_RESERVED;
2878         ExecInfo.hInstApp = (HINSTANCE)lpReserved;
2879     }
2880 
2881     if (lpTitle)
2882     {
2883         ExecInfo.fMask |= SEE_MASK_HASTITLE;
2884         ExecInfo.lpClass = lpTitle;
2885     }
2886 
2887     if (dwFlags & 1)
2888         ExecInfo.fMask |= SEE_MASK_FLAG_SEPVDM;
2889 
2890     if (dwFlags & 2)
2891         ExecInfo.fMask |= SEE_MASK_NO_CONSOLE;
2892 
2893     if (lphProcess == NULL)
2894     {
2895         ShellExecuteExW(&ExecInfo);
2896     }
2897     else
2898     {
2899         ExecInfo.fMask |= SEE_MASK_NOCLOSEPROCESS;
2900         ShellExecuteExW(&ExecInfo);
2901         *lphProcess = ExecInfo.hProcess;
2902     }
2903 
2904     return ExecInfo.hInstApp;
2905 }
2906 
2907 /*************************************************************************
2908  *                RealShellExecuteA (SHELL32.265)
2909  */
2910 EXTERN_C
2911 HINSTANCE WINAPI
2912 RealShellExecuteA(
2913     _In_opt_ HWND hwnd,
2914     _In_opt_ LPCSTR lpOperation,
2915     _In_opt_ LPCSTR lpFile,
2916     _In_opt_ LPCSTR lpParameters,
2917     _In_opt_ LPCSTR lpDirectory,
2918     _In_opt_ LPSTR lpReturn,
2919     _In_opt_ LPCSTR lpTitle,
2920     _In_opt_ LPVOID lpReserved,
2921     _In_ INT nCmdShow,
2922     _Out_opt_ PHANDLE lphProcess)
2923 {
2924     return RealShellExecuteExA(hwnd,
2925                                lpOperation,
2926                                lpFile,
2927                                lpParameters,
2928                                lpDirectory,
2929                                lpReturn,
2930                                lpTitle,
2931                                lpReserved,
2932                                nCmdShow,
2933                                lphProcess,
2934                                0);
2935 }
2936 
2937 /*************************************************************************
2938  *                RealShellExecuteW (SHELL32.268)
2939  */
2940 EXTERN_C
2941 HINSTANCE WINAPI
2942 RealShellExecuteW(
2943     _In_opt_ HWND hwnd,
2944     _In_opt_ LPCWSTR lpOperation,
2945     _In_opt_ LPCWSTR lpFile,
2946     _In_opt_ LPCWSTR lpParameters,
2947     _In_opt_ LPCWSTR lpDirectory,
2948     _In_opt_ LPWSTR lpReturn,
2949     _In_opt_ LPCWSTR lpTitle,
2950     _In_opt_ LPVOID lpReserved,
2951     _In_ INT nCmdShow,
2952     _Out_opt_ PHANDLE lphProcess)
2953 {
2954     return RealShellExecuteExW(hwnd,
2955                                lpOperation,
2956                                lpFile,
2957                                lpParameters,
2958                                lpDirectory,
2959                                lpReturn,
2960                                lpTitle,
2961                                lpReserved,
2962                                nCmdShow,
2963                                lphProcess,
2964                                0);
2965 }
2966